168 lines
5.9 KiB
Python
168 lines
5.9 KiB
Python
"""실시간 내담자 공급자 준비상태의 API health 계약."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from . import main
|
|
from .upload_storage import UploadWriteFreezeStatus
|
|
|
|
|
|
class EngineHealthContractTest(unittest.IsolatedAsyncioTestCase):
|
|
async def test_health_requires_and_exposes_lifespan_database_proof(self) -> None:
|
|
target_sha256 = "a" * 64
|
|
manifest = SimpleNamespace(
|
|
manifest_sha256="b" * 64,
|
|
database_target_sha256=target_sha256,
|
|
preserved_object_count=93,
|
|
preserved_total_size_bytes=52973,
|
|
preserved_decode_valid_count=3,
|
|
preserved_decode_invalid_count=90,
|
|
required_decode_invalid_object_count=0,
|
|
required_decode_invalid_reference_count=0,
|
|
preserved_object_set_sha256="c" * 64,
|
|
required_object_count=8,
|
|
reference_set_sha256="d" * 64,
|
|
)
|
|
runtime_proof = SimpleNamespace(
|
|
database_target_sha256=target_sha256,
|
|
current_references=SimpleNamespace(
|
|
object_count=8,
|
|
reference_count=8,
|
|
reference_set_sha256="e" * 64,
|
|
decode_invalid_object_count=6,
|
|
decode_invalid_reference_count=6,
|
|
),
|
|
)
|
|
with (
|
|
patch.object(main, "upload_manifest_proof", manifest),
|
|
patch.object(main, "healthcheck", AsyncMock(return_value=True)),
|
|
patch.object(
|
|
main.engine_client,
|
|
"health_detail",
|
|
AsyncMock(return_value={"ok": True}),
|
|
),
|
|
patch.object(
|
|
main.app.state,
|
|
"upload_database_proof",
|
|
runtime_proof,
|
|
create=True,
|
|
),
|
|
):
|
|
response = await main.health()
|
|
|
|
self.assertEqual("ok", response["status"])
|
|
self.assertTrue(response["upload_manifest"]["runtime_database_validated"])
|
|
self.assertEqual(8, response["upload_manifest"]["current_object_count"])
|
|
self.assertEqual(
|
|
52973,
|
|
response["upload_manifest"]["preserved_total_size_bytes"],
|
|
)
|
|
self.assertEqual(8, response["upload_manifest"]["current_reference_count"])
|
|
self.assertTrue(response["upload_manifest"]["forensic_fallback_active"])
|
|
self.assertEqual(
|
|
6,
|
|
response["upload_manifest"]["current_decode_invalid_reference_count"],
|
|
)
|
|
self.assertEqual(
|
|
"e" * 64,
|
|
response["upload_manifest"]["current_reference_set_sha256"],
|
|
)
|
|
|
|
async def test_health_is_degraded_for_invalid_active_upload_freeze(self) -> None:
|
|
engine_detail = {"ok": True, "detail": "ready"}
|
|
invalid_freeze = UploadWriteFreezeStatus(
|
|
capable=True,
|
|
active=True,
|
|
valid=False,
|
|
in_flight=0,
|
|
path_sha256="a" * 64,
|
|
token_sha256=None,
|
|
)
|
|
with (
|
|
patch.object(main, "healthcheck", AsyncMock(return_value=True)),
|
|
patch.object(
|
|
main.engine_client,
|
|
"health_detail",
|
|
AsyncMock(return_value=engine_detail),
|
|
),
|
|
patch.object(
|
|
main.upload_write_freeze_gate,
|
|
"status",
|
|
return_value=invalid_freeze,
|
|
),
|
|
):
|
|
response = await main.health()
|
|
|
|
self.assertEqual("degraded", response["status"])
|
|
self.assertFalse(response["upload_write_freeze"]["valid"])
|
|
|
|
async def test_health_stays_ok_for_valid_active_maintenance_freeze(self) -> None:
|
|
engine_detail = {"ok": True, "detail": "ready"}
|
|
valid_freeze = UploadWriteFreezeStatus(
|
|
capable=True,
|
|
active=True,
|
|
valid=True,
|
|
in_flight=0,
|
|
path_sha256="a" * 64,
|
|
token_sha256="b" * 64,
|
|
)
|
|
with (
|
|
patch.object(main, "healthcheck", AsyncMock(return_value=True)),
|
|
patch.object(
|
|
main.engine_client,
|
|
"health_detail",
|
|
AsyncMock(return_value=engine_detail),
|
|
),
|
|
patch.object(
|
|
main.upload_write_freeze_gate,
|
|
"status",
|
|
return_value=valid_freeze,
|
|
),
|
|
):
|
|
response = await main.health()
|
|
|
|
self.assertEqual("ok", response["status"])
|
|
self.assertTrue(response["upload_write_freeze"]["active"])
|
|
|
|
async def test_health_is_degraded_when_live_client_lane_is_unready(self) -> None:
|
|
engine_detail = {
|
|
"ok": False,
|
|
"detail": "실시간 내담자 공급자 claude_cli: login required",
|
|
"default_engine": {
|
|
"provider": "agy_cli",
|
|
"ok": True,
|
|
"detail": "ready",
|
|
},
|
|
"live_client_engine": {
|
|
"provider": "claude_cli",
|
|
"ok": False,
|
|
"detail": "login required",
|
|
},
|
|
}
|
|
with (
|
|
patch.object(main, "healthcheck", AsyncMock(return_value=True)),
|
|
patch.object(
|
|
main.engine_client,
|
|
"health_detail",
|
|
AsyncMock(return_value=engine_detail),
|
|
),
|
|
patch.object(main.engine_client, "engine_mode", "agy_cli"),
|
|
patch.object(main.engine_client, "live_client_provider", "claude_cli"),
|
|
):
|
|
response = await main.health()
|
|
|
|
self.assertEqual(response["status"], "degraded")
|
|
self.assertFalse(response["engine"])
|
|
self.assertEqual(response["engine_mode"], "agy_cli")
|
|
self.assertEqual(response["live_client_provider"], "claude_cli")
|
|
self.assertEqual(
|
|
response["live_client_engine"],
|
|
engine_detail["live_client_engine"],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|