fix(core): serialize planning reports outside the control event loop

This commit is contained in:
DCCONSTRUCTIONS
2026-09-25 16:37:57 +03:00
parent 6d772b29fe
commit bc55901d4d
2 changed files with 91 additions and 3 deletions
+69
View File
@@ -0,0 +1,69 @@
"""Periodic planning reports must not serialize/compress on the ASGI loop."""
import threading
from types import SimpleNamespace
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from k1link.web import planning_live_api
from k1link.web.response_compression import ResponseCompressionMiddleware
@pytest.mark.parametrize("encoding", ["gzip", "identity"])
def test_active_report_keeps_content_and_encodes_off_loop(monkeypatch, encoding):
document = {"state": "completed", "evidence": [{"label": "Проверка", "x": 1.25}] * 50}
calls = []
original_json = planning_live_api.JSONResponse
original_compress = planning_live_api.gzip.compress
class RecordedJSON(original_json):
def render(self, content):
calls.append(("json", threading.get_ident()))
return super().render(content)
def compress(*args, **kwargs):
calls.append(("gzip", threading.get_ident()))
return original_compress(*args, **kwargs)
monkeypatch.setattr(planning_live_api, "JSONResponse", RecordedJSON)
monkeypatch.setattr(planning_live_api.gzip, "compress", compress)
app = FastAPI()
app.include_router(planning_live_api.build_planning_live_router(SimpleNamespace(get=lambda: document)))
app.add_middleware(ResponseCompressionMiddleware)
@app.get("/loop-thread")
async def loop_thread():
return threading.get_ident()
with TestClient(app) as client:
loop_id = client.get("/loop-thread").json()
result = client.get("/api/v1/mission-planner/live-tests/active", headers={"Accept-Encoding": encoding})
assert result.status_code == 200
assert result.json() == document # Includes HTTP decompression; no double gzip.
assert result.headers["cache-control"] == "no-store"
assert result.headers["content-type"] == "application/json"
assert [name for name, _ in calls] == (["json", "gzip"] if encoding == "gzip" else ["json"])
assert all(thread != loop_id for _, thread in calls)
if encoding == "gzip":
assert result.headers["content-encoding"] == "gzip"
assert result.headers["vary"] == "Accept-Encoding"
else:
assert "content-encoding" not in result.headers
def test_active_report_retains_empty_and_failure_contract():
service = SimpleNamespace(get=lambda: None)
app = FastAPI()
app.include_router(planning_live_api.build_planning_live_router(service))
with TestClient(app) as client:
result = client.get("/api/v1/mission-planner/live-tests/active")
assert result.status_code == 200 and result.json() is None
def fail():
raise RuntimeError("synthetic unavailable report")
service.get = fail
result = client.get("/api/v1/mission-planner/live-tests/active")
assert result.status_code == 409
assert result.json() == {"detail": "synthetic unavailable report"}