[feat] add test race condition & cleanup resilience

This commit is contained in:
Azamat 2026-04-02 21:47:55 +03:00
parent 539f019f39
commit 44f1549d80
3 changed files with 359 additions and 10 deletions

View file

@ -142,10 +142,13 @@ def build_container(
)
async def post_json(
app: FastAPI, path: str, payload: dict[str, str]
async def request_json(
app: FastAPI,
method: str,
path: str,
payload: dict[str, str] | None = None,
) -> tuple[int, dict[str, object]]:
body = json.dumps(payload).encode()
body = b'' if payload is None else json.dumps(payload).encode()
messages: list[Message] = []
request_sent = False
@ -169,17 +172,13 @@ async def post_json(
'type': 'http',
'asgi': {'version': '3.0'},
'http_version': '1.1',
'method': 'POST',
'method': method,
'scheme': 'http',
'path': path,
'raw_path': path.encode(),
'query_string': b'',
'root_path': '',
'headers': [
(b'host', b'testserver'),
(b'content-type', b'application/json'),
(b'content-length', str(len(body)).encode()),
],
'headers': _build_headers(body, payload is not None),
'client': ('testclient', 50000),
'server': ('testserver', 80),
'state': {},
@ -195,9 +194,32 @@ async def post_json(
if message['type'] == 'http.response.body':
response_body += bytes(message.get('body', b''))
if not response_body:
return status, {}
return status, json.loads(response_body.decode())
def _build_headers(body: bytes, has_json_body: bool) -> list[tuple[bytes, bytes]]:
headers = [
(b'host', b'testserver'),
(b'content-length', str(len(body)).encode()),
]
if has_json_body:
headers.append((b'content-type', b'application/json'))
return headers
async def post_json(
app: FastAPI, path: str, payload: dict[str, str]
) -> tuple[int, dict[str, object]]:
return await request_json(app, 'POST', path, payload)
async def get_json(app: FastAPI, path: str) -> tuple[int, dict[str, object]]:
return await request_json(app, 'GET', path)
async def exercise_create_request(
app: FastAPI,
payload: dict[str, str],
@ -211,6 +233,19 @@ async def exercise_create_request(
await app.router.shutdown()
async def exercise_get_request(
app: FastAPI,
path: str,
) -> tuple[int, dict[str, object]]:
await app.router.startup()
try:
status, response = await get_json(app, path)
await asyncio.sleep(0)
return status, response
finally:
await app.router.shutdown()
def test_post_create_returns_session(monkeypatch) -> None:
config = build_config()
expires_at = datetime(2026, 4, 2, 12, 5, tzinfo=UTC)
@ -320,3 +355,41 @@ def test_post_create_maps_generic_sandbox_errors_to_internal_error(monkeypatch)
assert status_code == 500
assert response == {'detail': 'sandbox_broken'}
assert docker_client.close_calls == 1
def test_removed_user_endpoint_returns_not_found(monkeypatch) -> None:
config = build_config()
expires_at = datetime(2026, 4, 2, 12, 5, tzinfo=UTC)
session = SandboxSession(
session_id='session-123',
chat_id='chat-123',
container_id='container-123',
status=SandboxStatus.RUNNING,
created_at=expires_at - timedelta(minutes=5),
expires_at=expires_at,
)
logger = FakeLogger()
create_usecase = FakeCreateSandboxUsecase(session=session)
cleanup_usecase = FakeCleanupExpiredSandboxes()
docker_client = FakeDockerClient()
container = build_container(
config,
create_usecase,
cleanup_usecase,
logger,
docker_client,
)
monkeypatch.setattr(app_module, 'build_container', lambda **kwargs: container)
monkeypatch.setattr(
app_module.FastAPIInstrumentor, 'instrument_app', lambda *args, **kwargs: None
)
app = app_module.create_app(config=config)
status_code, response = asyncio.run(
exercise_get_request(app, '/api/v1/users/user-123')
)
assert status_code == 404
assert response == {'detail': 'Not Found'}
assert docker_client.close_calls == 1