feat: 운영 안정성과 세션 음성 경험 개선

This commit is contained in:
Yun Chan 2026-07-31 00:13:08 +09:00
parent facc4ad2d9
commit c788343467
95 changed files with 8431 additions and 1785 deletions

View file

@ -6,6 +6,9 @@ import unittest
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, patch
from fastapi import HTTPException
from .contracts.engine_gateway import EngineCapabilitiesResponse, EngineModelOption
from .deps import Principal, Role
from .routes import admin as admin_routes
@ -572,3 +575,224 @@ class AdminOpsTest(unittest.IsolatedAsyncioTestCase):
self.assertIn("measure_name IN ('self_efficacy','skill_proficiency','training_satisfaction')", schema)
self.assertIn("ALTER TABLE app.learner_prepost_measure ENABLE ROW LEVEL SECURITY", schema)
self.assertIn("CREATE POLICY p_learner_prepost_measure_insert", schema)
class AdminEngineConfigTest(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self) -> None:
self.previous_config = admin_routes._ENGINE_CONFIG
admin_routes._ENGINE_CONFIG = admin_routes.AdminEngineConfigResponse(
engine_mode="claude_cli",
engine_url="http://127.0.0.1:9099",
model="gateway-default",
reasoning_effort="high",
durable=True,
source="database",
)
self.principal = Principal(
user_id="00000000-0000-0000-0000-000000000099",
role=Role.ADMIN,
cohort_ids=[],
email="admin@twentyoz.kr",
display_name="Admin",
)
async def asyncTearDown(self) -> None:
admin_routes._ENGINE_CONFIG = self.previous_config
async def test_capabilities_endpoint_uses_requested_gateway_url(self) -> None:
capabilities = EngineCapabilitiesResponse(
provider="codex_cli",
available=True,
source="live_cli",
models=[
EngineModelOption(
id="gpt-5.6-terra",
label="GPT-5.6-Terra",
reasoning_efforts=["medium"],
default_reasoning_effort="medium",
)
],
default_model="gpt-5.6-terra",
default_reasoning_effort="medium",
fetched_at=1,
)
with patch.object(
admin_routes.engine_client,
"capabilities",
AsyncMock(return_value=capabilities),
) as lookup:
result = await admin_routes.get_engine_capabilities(
self.principal,
engine_mode="codex_cli",
engine_url="http://127.0.0.1:9199/",
force=True,
)
self.assertEqual(result.default_model, "gpt-5.6-terra")
lookup.assert_awaited_once_with(
provider="codex_cli",
base_url="http://127.0.0.1:9199",
force=True,
)
with patch.object(
admin_routes.engine_client,
"capabilities",
AsyncMock(return_value=capabilities),
) as current_lookup:
await admin_routes.get_engine_capabilities(
self.principal,
engine_mode="codex_cli",
engine_url=None,
force=False,
)
current_lookup.assert_awaited_once_with(
provider="codex_cli",
base_url="http://127.0.0.1:9099",
force=False,
)
async def test_patch_rejects_model_missing_from_live_catalog(self) -> None:
capabilities = EngineCapabilitiesResponse(
provider="codex_cli",
available=True,
source="live_cli",
models=[],
fetched_at=1,
)
with patch.object(
admin_routes.engine_client,
"capabilities",
AsyncMock(return_value=capabilities),
):
with self.assertRaises(HTTPException) as caught:
await admin_routes.patch_engine_config(
admin_routes.AdminEngineConfigPatch(
engine_mode="codex_cli",
model="made-up-model",
reasoning_effort="medium",
),
self.principal,
)
self.assertEqual(caught.exception.status_code, 422)
self.assertIn("사용할 수 없는 모델", caught.exception.detail)
async def test_patch_persists_and_applies_reasoning_effort(self) -> None:
capabilities = EngineCapabilitiesResponse(
provider="codex_cli",
available=True,
source="live_cli",
models=[
EngineModelOption(
id="gpt-5.6-terra",
label="GPT-5.6-Terra",
reasoning_efforts=["low", "medium", "high"],
default_reasoning_effort="medium",
)
],
default_model="gpt-5.6-terra",
default_reasoning_effort="medium",
fetched_at=1,
)
class ConfigConn:
def __init__(self):
self.query = ""
self.args = ()
async def fetchrow(self, query, *args):
self.query = query
self.args = args
return {
"engine_mode": args[0],
"engine_url": args[1],
"model": args[2],
"reasoning_effort": args[3],
"updated_by": args[4],
"updated_at": None,
}
conn = ConfigConn()
class ConfigPool:
def acquire(self):
return _Acquire(conn)
with (
patch.object(
admin_routes.engine_client,
"capabilities",
AsyncMock(return_value=capabilities),
),
patch.object(admin_routes, "get_pool", return_value=ConfigPool()),
patch.object(
admin_routes.engine_client,
"configure",
AsyncMock(),
) as configure,
):
result = await admin_routes.patch_engine_config(
admin_routes.AdminEngineConfigPatch(
engine_mode="codex_cli",
engine_url="http://127.0.0.1:9099",
model="gpt-5.6-terra",
reasoning_effort="medium",
),
self.principal,
)
self.assertEqual(result.reasoning_effort, "medium")
self.assertIn("reasoning_effort", conn.query)
self.assertEqual(conn.args[3], "medium")
configure.assert_awaited_once_with(
base_url="http://127.0.0.1:9099",
engine_mode="codex_cli",
default_model="gpt-5.6-terra",
default_reasoning_effort="medium",
)
async def test_patch_can_explicitly_clear_reasoning_effort(self) -> None:
capabilities = EngineCapabilitiesResponse(
provider="claude_api",
available=True,
source="live_api",
models=[EngineModelOption(id="claude-legacy", label="Claude Legacy")],
default_model="claude-legacy",
fetched_at=1,
)
class ConfigConn:
async def fetchrow(self, _query, *args):
return {
"engine_mode": args[0],
"engine_url": args[1],
"model": args[2],
"reasoning_effort": args[3],
"updated_by": args[4],
"updated_at": None,
}
class ConfigPool:
def acquire(self):
return _Acquire(ConfigConn())
with (
patch.object(
admin_routes.engine_client,
"capabilities",
AsyncMock(return_value=capabilities),
),
patch.object(admin_routes, "get_pool", return_value=ConfigPool()),
patch.object(admin_routes.engine_client, "configure", AsyncMock()),
):
result = await admin_routes.patch_engine_config(
admin_routes.AdminEngineConfigPatch(
engine_mode="claude_api",
model="claude-legacy",
reasoning_effort=None,
),
self.principal,
)
self.assertIsNone(result.reasoning_effort)