"""Admin operations persistence model tests.""" from __future__ import annotations 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 class _Acquire: def __init__(self, conn): self.conn = conn async def __aenter__(self): return self.conn async def __aexit__(self, exc_type, exc, tb): return None class CohortSerializationTest(unittest.TestCase): """2026-07-27 전수 순회 결함 #20: 서버가 cohort_ids 첫 항목만 저장하던 유실 회귀 가드.""" def test_multi_cohort_round_trip_preserves_every_id(self) -> None: from .auth_sessions import _cohort_ids, _cohort_value self.assertEqual(_cohort_value(["co-a", "co-b"]), "co-a,co-b") self.assertEqual(_cohort_ids("co-a,co-b"), ["co-a", "co-b"]) self.assertEqual(_cohort_ids(_cohort_value(["x-1", "x-2", "x-3"])), ["x-1", "x-2", "x-3"]) def test_cohort_value_trims_and_drops_empty_entries(self) -> None: from .auth_sessions import _cohort_ids, _cohort_value self.assertEqual(_cohort_value([" co-a ", "", " ", "co-b"]), "co-a,co-b") self.assertIsNone(_cohort_value([])) self.assertIsNone(_cohort_value(None)) self.assertEqual(_cohort_ids(None), []) self.assertEqual(_cohort_ids(" co-a , ,co-b "), ["co-a", "co-b"]) class AdminOpsTest(unittest.IsolatedAsyncioTestCase): async def test_usage_from_database_orders_by_aggregated_token_sum(self) -> None: case = self class Conn: def __init__(self) -> None: self.fetch_calls = 0 async def fetchrow(self, query, *args, **kwargs): return { "total_turns": 2, "metered_turns": 1, "token_metered_turns": 1, "token_unmetered_turns": 0, "tokens_in": 11, "tokens_out": 13, "cost_usd": 0.0042, } async def fetch(self, query, *args, **kwargs): self.fetch_calls += 1 if self.fetch_calls == 2: case.assertIn("date_trunc('day', created_at)", query) return [ { "day": "2026-06-28", "turns": 1, "token_metered_turns": 1, "token_unmetered_turns": 0, "tokens_in": 11, "tokens_out": 13, "cost_usd": 0.0042, "unpriced_turns": 0, "unpriced_tokens_in": 0, "unpriced_tokens_out": 0, "provider": "claude_cli", "model": "gateway-default", } ] case.assertIn("unpriced_tokens_in", query) case.assertNotIn("LIMIT 12", query) return [ { "provider": "claude_cli", "model": "gateway-default", "turns": 1, "token_metered_turns": 1, "token_unmetered_turns": 0, "tokens_in": 11, "tokens_out": 13, "cost_usd": 0.0042, "unpriced_turns": 0, "unpriced_tokens_in": 0, "unpriced_tokens_out": 0, } ] with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())), patch.object( admin_routes.evaluator, "evaluator_semantic_cache_stats", return_value={"hits": 3, "misses": 1, "stores": 2, "evictions": 0, "entries": 2}, ): usage = await admin_routes._usage_from_database(window_days=7) self.assertTrue(usage.durable) self.assertEqual(usage.source, "database") self.assertEqual(usage.total_turns, 2) self.assertEqual(usage.metered_turns, 1) self.assertEqual(usage.token_metered_turns, 1) self.assertEqual(usage.token_unmetered_turns, 0) self.assertEqual(usage.by_provider[0].provider, "claude_cli") self.assertEqual(usage.by_provider[0].cost_basis, "provider_estimate") self.assertEqual(usage.recorded_cost_usd, 0.0042) self.assertEqual(usage.estimated_cost_usd, 0) self.assertTrue(usage.evaluator_cache.enabled) self.assertEqual(usage.evaluator_cache.requests, 4) self.assertEqual(usage.evaluator_cache.hit_rate, 0.75) self.assertEqual(usage.daily_cost[0].day, "2026-06-28") self.assertEqual(usage.daily_cost[0].cost_usd, 0.0042) async def test_usage_from_database_backfills_agy_zero_cost_with_reference_rate(self) -> None: class Conn: def __init__(self) -> None: self.fetch_calls = 0 async def fetchrow(self, query, *args, **kwargs): return { "total_turns": 5, "metered_turns": 5, "token_metered_turns": 5, "token_unmetered_turns": 0, "tokens_in": 35_703, "tokens_out": 1_129, "cost_usd": 0, } async def fetch(self, query, *args, **kwargs): self.fetch_calls += 1 row = { "provider": "agy_cli", "model": "gemini-3.6-flash-high", "turns": 5, "token_metered_turns": 5, "token_unmetered_turns": 0, "tokens_in": 35_703, "tokens_out": 1_129, "cost_usd": 0, "unpriced_turns": 5, "unpriced_tokens_in": 35_703, "unpriced_tokens_out": 1_129, } if self.fetch_calls == 2: return [{"day": "2026-07-31", **row}] return [row] with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())): usage = await admin_routes._usage_from_database(window_days=30) self.assertEqual(usage.cost_usd, 0.062022) self.assertEqual(usage.recorded_cost_usd, 0) self.assertEqual(usage.estimated_cost_usd, 0.062022) self.assertEqual(usage.daily_cost[0].cost_usd, 0.062022) breakdown = usage.by_provider[0] self.assertEqual(breakdown.cost_usd, 0.062022) self.assertEqual(breakdown.cost_basis, "reference_rate") self.assertIn("Gemini 3.6 Flash", breakdown.rate_label or "") async def test_uptime_from_database_aggregates_health_samples(self) -> None: now = datetime.now(timezone.utc) class Conn: def __init__(self): self.calls = 0 async def fetch(self, *args, **kwargs): self.calls += 1 if self.calls == 2: return [] return [ { "id": 3, "observed_at": now, "overall_status": "ok", "service_key": "db", "service_name": "영구 저장소", "service_status": "ok", "detail": "사용자, 세션, 리뷰 저장", "metric": "풀 1/10", "load": 0.1, }, { "id": 2, "observed_at": now - timedelta(minutes=5), "overall_status": "down", "service_key": "engine", "service_name": "응답 생성", "service_status": "down", "detail": "Engine readiness failed", "metric": "로그인/설정 필요", "load": 0.0, }, { "id": 1, "observed_at": now - timedelta(minutes=10), "overall_status": "degraded", "service_key": "voice", "service_name": "음성 입력", "service_status": "degraded", "detail": "음성 입력과 재생", "metric": "설정 필요", "load": 0.0, }, ] with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())): uptime = await admin_routes._uptime_from_database(window_hours=24) self.assertTrue(uptime.durable) self.assertEqual(uptime.sample_count, 3) self.assertEqual(uptime.down_events, 1) self.assertEqual(uptime.degraded_events, 1) self.assertAlmostEqual(uptime.ok_ratio, 1 / 3, places=4) self.assertIsNotNone(uptime.last_down_at) self.assertEqual({item.service_key for item in uptime.services}, {"db", "engine", "voice"}) async def test_uptime_from_database_combines_raw_events_and_daily_rollups(self) -> None: now = datetime.now(timezone.utc) class Conn: def __init__(self): self.calls = 0 async def fetch(self, *args, **kwargs): self.calls += 1 if self.calls == 1: return [ { "id": 10, "observed_at": now, "overall_status": "ok", "service_key": "engine", "service_name": "응답 생성", "service_status": "ok", "detail": "ready", "metric": "21ms", "load": 0.02, } ] return [ { "service_key": "engine", "service_name": "응답 생성", "sample_count": 3, "ok_samples": 1, "degraded_samples": 1, "down_samples": 1, "latest_status": "down", "last_observed_at": now - timedelta(days=2), "last_down_at": now - timedelta(days=2, hours=1), } ] with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())): uptime = await admin_routes._uptime_from_database(window_hours=168) self.assertEqual(uptime.sample_count, 4) self.assertEqual(uptime.down_events, 1) self.assertEqual(uptime.degraded_events, 1) self.assertAlmostEqual(uptime.ok_ratio, 0.5, places=4) self.assertIsNotNone(uptime.last_down_at) engine = next(service for service in uptime.services if service.service_key == "engine") self.assertEqual(engine.samples, 4) self.assertEqual(engine.latest_status, "ok") self.assertEqual(len(uptime.events), 1) async def test_record_health_events_allows_synthetic_capture_without_actor(self) -> None: calls = [] class Conn: async def executemany(self, query, rows, **kwargs): calls.append((query, rows)) service = admin_routes.AdminServiceHealth( key="engine", name="응답 생성", status="ok", detail="ready", metric="12ms", load=0.01, ) with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())): recorded = await admin_routes._record_health_events( principal=None, overall_status="ok", environment="dev", engine_mode="claude_cli", services=[service], ) self.assertEqual(recorded, 1) query, rows = calls[0] self.assertIn("INSERT INTO app.admin_health_event", query) self.assertIn("captured_by", query) self.assertIsNone(rows[0][-1]) async def test_record_admin_health_sample_reuses_health_contract(self) -> None: engine_config = admin_routes.AdminEngineConfigResponse( engine_mode="claude_cli", engine_url="http://127.0.0.1:9099", model="gateway-default", durable=True, source="database", ) recorder = AsyncMock(return_value=5) with ( patch.object(admin_routes, "_current_engine_config", AsyncMock(return_value=engine_config)), patch.object(admin_routes, "healthcheck", AsyncMock(return_value=True)), patch.object(admin_routes.engine_client, "health_detail", AsyncMock(return_value={"ok": True})), patch.object(admin_routes.voice_service, "is_available", return_value=True), patch.object(admin_routes, "_runtime_health_metrics", AsyncMock(return_value=admin_routes.RuntimeHealthMetrics())), patch.object(admin_routes, "_record_health_events", recorder), ): health, recorded = await admin_routes.record_admin_health_sample(principal=None) self.assertEqual(recorded, 5) self.assertEqual(health.status, "ok") self.assertEqual(health.engine_mode, "claude_cli") recorder.assert_awaited_once() self.assertIsNone(recorder.await_args.kwargs["principal"]) self.assertEqual(len(recorder.await_args.kwargs["services"]), 5) async def test_tickets_from_database_returns_summary_without_synthetic_rows(self) -> None: now = datetime.now(timezone.utc) class Conn: async def fetch(self, *args, **kwargs): return [ { "id": "00000000-0000-0000-0000-000000000101", "reporter_id": "00000000-0000-0000-0000-000000000201", "reporter_email": "learner@hs.ac.kr", "reporter_name": "Learner", "reporter_role": "learner", "category": "session_review", "priority": "high", "status": "open", "subject": "리뷰 지연", "body": "회기 리뷰가 생성되지 않습니다.", "source_path": "/learn/session/1/review", "assigned_group": "", "resolution_note": "", "created_at": now - timedelta(days=2), "updated_at": now - timedelta(days=2), "resolved_at": None, }, { "id": "00000000-0000-0000-0000-000000000102", "reporter_id": None, "reporter_email": "teacher@hs.ac.kr", "reporter_name": "Teacher", "reporter_role": "teacher", "category": "voice_browser", "priority": "high", "status": "resolved", "subject": "마이크 권한", "body": "브라우저 권한 안내가 필요합니다.", "source_path": "/learn/session/2", "assigned_group": "서비스 운영자", "resolution_note": "안내 완료", "created_at": now, "updated_at": now, "resolved_at": now, }, ] with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())): tickets = await admin_routes._tickets_from_database( ticket_status=None, category=None, priority=None, assigned_group=None, source_path=None, stale_only=False, search="", window_days=30, ) self.assertTrue(tickets.durable) self.assertEqual(tickets.source, "database") self.assertEqual(len(tickets.tickets), 2) self.assertEqual(tickets.summary.total, 2) self.assertEqual(tickets.summary.open_count, 1) self.assertEqual(tickets.summary.high_priority_count, 1) self.assertEqual(tickets.summary.stale_count, 1) self.assertEqual(tickets.summary.by_category["session_review"], 1) async def test_tickets_from_database_applies_queue_filters(self) -> None: class Conn: async def fetch(self, query, *args, **kwargs): self.query = query self.args = args return [] conn = Conn() with patch.object(admin_routes, "acquire", return_value=_Acquire(conn)): tickets = await admin_routes._tickets_from_database( ticket_status="open", category="voice_browser", priority="urgent", assigned_group="서비스 운영자", source_path="/learn/session/1", stale_only=True, search="마이크", window_days=7, ) self.assertTrue(tickets.durable) self.assertIn("assigned_group = $4", conn.query) self.assertIn("lower(subject)", conn.query) self.assertEqual( conn.args, ( "open", "voice_browser", "urgent", "서비스 운영자", "/learn/session/1", True, "마이크", 7, ), ) async def test_patch_ticket_records_metadata_only_audit(self) -> None: now = datetime.now(timezone.utc) ticket_id = "00000000-0000-0000-0000-000000000101" audit_calls: list[tuple[str, tuple[object, ...]]] = [] class Conn: def __init__(self): self.fetchrow_calls = 0 async def fetchrow(self, query, *args, **kwargs): self.fetchrow_calls += 1 if self.fetchrow_calls == 1: return { "id": ticket_id, "category": "session_review", "priority": "high", "status": "open", "source_path": "/learn/session/1/review", "assigned_group": "", "resolution_note": "", } return { "id": ticket_id, "reporter_id": "00000000-0000-0000-0000-000000000201", "reporter_email": "learner@hs.ac.kr", "reporter_name": "Learner", "reporter_role": "learner", "category": "session_review", "priority": "urgent", "status": "in_progress", "subject": "리뷰 지연", "body": "회기 리뷰가 생성되지 않습니다.", "source_path": "/learn/session/1/review", "assigned_group": "서비스 운영자", "resolution_note": "", "created_at": now, "updated_at": now, "resolved_at": None, "event_count": 1, "last_event_at": now, } async def execute(self, query, *args, **kwargs): audit_calls.append((query, args)) principal = Principal( user_id="00000000-0000-0000-0000-000000000301", role=Role.ADMIN, admin_access=True, email="admin@hs.ac.kr", display_name="Admin", ) with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())): updated = await admin_routes.patch_ticket( ticket_id, admin_routes.AdminTicketPatch( status="in_progress", priority="urgent", assigned_group="서비스 운영자", ), principal, ) self.assertEqual(updated.status, "in_progress") self.assertEqual(updated.event_count, 1) self.assertEqual(len(audit_calls), 1) query, args = audit_calls[0] self.assertIn("INSERT INTO audit.audit_log", query) self.assertEqual(args[1], "support_ticket_update") detail = args[4] self.assertEqual(set(detail["changed_fields"]), {"status", "priority", "assigned_group"}) self.assertNotIn("body", detail) self.assertNotIn("subject", detail) async def test_patch_ticket_links_manual_duplicate_parent(self) -> None: now = datetime.now(timezone.utc) ticket_id = "00000000-0000-0000-0000-000000000101" parent_id = "00000000-0000-0000-0000-000000000102" audit_calls: list[tuple[str, tuple[object, ...]]] = [] class Conn: def __init__(self): self.fetchrow_calls = 0 async def fetchrow(self, query, *args, **kwargs): self.fetchrow_calls += 1 if self.fetchrow_calls == 1: return { "id": ticket_id, "category": "voice_browser", "priority": "normal", "status": "open", "source_path": "/learn/session/voice", "assigned_group": "", "resolution_note": "", "parent_ticket_id": None, } if self.fetchrow_calls == 2: case.assertIn("WITH RECURSIVE ancestors", query) return {"parent_exists": True, "creates_cycle": False} if self.fetchrow_calls == 3: case.assertIn("parent_ticket_id = CASE", query) return { "id": ticket_id, "reporter_id": "00000000-0000-0000-0000-000000000201", "reporter_email": "learner@hs.ac.kr", "reporter_name": "Learner", "reporter_role": "learner", "category": "voice_browser", "priority": "normal", "status": "open", "subject": "마이크 권한 오류", "body": "브라우저 마이크 권한 실패", "source_path": "/learn/session/voice", "fingerprint": "dup-fingerprint", "parent_ticket_id": parent_id, "assigned_group": "", "resolution_note": "", "created_at": now, "updated_at": now, "resolved_at": None, } return { "id": ticket_id, "reporter_id": "00000000-0000-0000-0000-000000000201", "reporter_email": "learner@hs.ac.kr", "reporter_name": "Learner", "reporter_role": "learner", "category": "voice_browser", "priority": "normal", "status": "open", "subject": "마이크 권한 오류", "body": "브라우저 마이크 권한 실패", "source_path": "/learn/session/voice", "fingerprint": "dup-fingerprint", "parent_ticket_id": parent_id, "duplicate_count": 1, "duplicate_parent_candidate_id": parent_id, "child_ticket_count": 0, "assigned_group": "", "resolution_note": "", "created_at": now, "updated_at": now, "resolved_at": None, "event_count": 1, "last_event_at": now, } async def execute(self, query, *args, **kwargs): audit_calls.append((query, args)) case = self principal = Principal( user_id="00000000-0000-0000-0000-000000000301", role=Role.ADMIN, admin_access=True, email="admin@hs.ac.kr", display_name="Admin", ) with patch.object(admin_routes, "acquire", return_value=_Acquire(Conn())): updated = await admin_routes.patch_ticket( ticket_id, admin_routes.AdminTicketPatch(parent_ticket_id=parent_id), principal, ) self.assertEqual(updated.parent_ticket_id, parent_id) self.assertEqual(updated.duplicate_count, 1) self.assertEqual(updated.event_count, 1) self.assertEqual(len(audit_calls), 1) detail = audit_calls[0][1][4] self.assertEqual(detail["changed_fields"], ["parent_ticket_id"]) self.assertEqual(detail["parent_ticket_id"]["to"], parent_id) self.assertNotIn("body", detail) self.assertNotIn("subject", detail) def test_admin_ops_schema_enables_rls(self) -> None: from pathlib import Path root = Path(__file__).resolve().parents[3] schema = (root / "infra" / "db" / "init" / "05_runtime_auth.sql").read_text( encoding="utf-8" ) self.assertIn("ALTER TABLE app.admin_health_event ENABLE ROW LEVEL SECURITY", schema) self.assertIn("CREATE TABLE IF NOT EXISTS app.admin_health_daily_rollup", schema) self.assertIn("last_down_at TIMESTAMPTZ", schema) self.assertIn("ALTER TABLE app.support_ticket ENABLE ROW LEVEL SECURITY", schema) self.assertIn("CREATE POLICY p_admin_health_event_delete", schema) self.assertIn("CREATE POLICY p_admin_health_daily_rollup_select", schema) self.assertIn("CREATE POLICY p_admin_health_daily_rollup_update", schema) self.assertNotIn("CREATE POLICY p_admin_health_daily_rollup_delete", schema) self.assertIn("CREATE POLICY p_support_ticket_insert", schema) self.assertIn("reporter_id = app.current_uid()", schema) self.assertIn("fingerprint TEXT NOT NULL DEFAULT ''", schema) self.assertIn("parent_ticket_id UUID REFERENCES app.support_ticket(id)", schema) self.assertIn("idx_support_ticket_fingerprint", schema) self.assertIn("CREATE TABLE IF NOT EXISTS app.learner_prepost_measure", schema) 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)