Files
ss-tools/backend/tests/core/test_async_client.py
root 632b730fff chore: migrate GRACE-Poly anchors to hierarchical dotted naming
Systematic rename of all semantic anchors (#region, [DEF], @RELATION)
across 1400+ files — backend Python, frontend Svelte/TS, specs, docs:
- Flat anchors become Namespace.Module.Entity
- @RELATION references updated to match new anchor paths
- Zero business logic changes
2026-07-22 11:48:15 +03:00

93 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# #region Test.AsyncClient.TestAsyncAPIClient [C:3] [TYPE Module] [SEMANTICS test, async, client, semaphore]
# @BRIEF Tests for AsyncAPIClient — contract tests, semaphore limits, timeout behavior.
# @RELATION BINDS_TO -> [Core.AsyncNetwork.AsyncAPIClient]
# @TEST_CONTRACT: AsyncAPIClient.request() -> Response dict
# @TEST_INVARIANT: Semaphore limits concurrent requests — VERIFIED_BY: test_semaphore_limits_concurrent_requests
# @TEST_INVARIANT: Semaphore timeout returns error — VERIFIED_BY: test_semaphore_timeout_returns_503
import asyncio
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from src.core.utils.async_network import AsyncAPIClient
from src.core.utils.network import SupersetAPIError
_BASE_CONFIG = {
"base_url": "http://superset.local",
"auth": {"username": "demo", "password": "secret"},
}
# #region Test.AsyncClient.TestAsyncClientRequest [C:2] [TYPE Function]
# @BRIEF Verify AsyncAPIClient.request() returns parsed JSON on success.
@pytest.mark.asyncio
async def test_async_client_request():
client = AsyncAPIClient(config=_BASE_CONFIG, timeout=30)
# Mock the internal httpx client
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json = MagicMock(return_value={"result": "ok"})
client._client = AsyncMock()
client._client.request = AsyncMock(return_value=mock_response)
# Mock auth
client._authenticated = True
client._tokens = {"access_token": "x", "csrf_token": "y"}
result = await client.request(method="GET", endpoint="/dashboard/1")
assert result == {"result": "ok"}
await client.aclose()
# #endregion Test.AsyncClient.TestAsyncClientRequest
# #region Test.AsyncClient.TestSemaphoreLimitsConcurrentRequests [C:2] [TYPE Function]
# @BRIEF Semaphore limits concurrent requests to pool_size. Verify total runtime ≈ max(batch_time).
@pytest.mark.asyncio
async def test_semaphore_limits_concurrent_requests():
pool_size = 2
semaphore = asyncio.Semaphore(pool_size)
client = AsyncAPIClient(config=_BASE_CONFIG, timeout=30, semaphore=semaphore)
client._authenticated = True
client._tokens = {"access_token": "x", "csrf_token": "y"}
# Mock slow response — 0.1s each
mock_resp = AsyncMock()
mock_resp.status_code = 200
mock_resp.json = AsyncMock(return_value={"result": "ok"})
client._client = AsyncMock()
client._client.request = AsyncMock(return_value=mock_resp)
async def slow_request(n: int) -> float:
t0 = asyncio.get_event_loop().time()
await client.request(method="GET", endpoint=f"/dashboard/{n}")
return asyncio.get_event_loop().time() - t0
# Launch 4 concurrent requests with pool_size=2
t0 = asyncio.get_event_loop().time()
results = await asyncio.gather(*[slow_request(i) for i in range(4)])
elapsed = asyncio.get_event_loop().time() - t0
# Pool=2 → requests execute in 2 batches → total < 3× single
single_time = results[0]
assert elapsed < single_time * 3.5, f"Semaphore not limiting: {elapsed:.3f}s vs {single_time*2:.3f}s expected"
await client.aclose()
# #endregion Test.AsyncClient.TestSemaphoreLimitsConcurrentRequests
# #region Test.AsyncClient.TestSemaphoreTimeoutReturns503 [C:2] [TYPE Function]
# @BRIEF When semaphore is exhausted, acquire with timeout returns TimeoutError.
@pytest.mark.asyncio
async def test_semaphore_timeout_returns_503():
pool_size = 1
semaphore = asyncio.Semaphore(pool_size)
# Acquire the only slot
await semaphore.acquire()
# Try to acquire with very short timeout
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(semaphore.acquire(), timeout=0.05)
semaphore.release()
# #endregion Test.AsyncClient.TestSemaphoreTimeoutReturns503
# #endregion Test.AsyncClient.TestAsyncAPIClient