Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 35s
## ai_router.py - 抽取 _aggregate_feedback_stats() 純函數,feedback_from_aider_events 呼叫它 ## aider_event_processor.py - _process_one 加 _session_factory=None DI 參數(預設 get_session_factory()) - 可注入測試 factory,不改既有生產邏輯 ## test_ai_router_feedback.py(完全重寫) - 移除 FakeRepo/FakeSession,改為直接測試 _aggregate_feedback_stats 純函數 - 新增 test_feedback_skips_missing_model 邊界條件 - DB 失敗降級行為 test 保留(只 patch get_session_factory,無 FakeRepo) ## test_aider_event_processor.py(完全重寫) - 移除 FakeRepo/FakeSession,改用真實 PostgreSQL(real_factory fixture) - Redis xack + IncidentEngine 保留 mock(外部 broker/AI 服務,符合例外) - 每個測試後 rollback,不污染 dev DB ## setup_test_schema.sql - 補入 aider_events_payload_gin GIN index(與 adr091 生產 migration 一致) ## integration/conftest.py - 補注解說明密碼名稱 awoooi_prod_2026 的歷史混淆 - 修正 assert 邏輯:檢查 DB 名稱而非 URL 字串,避免密碼含 prod 觸發誤判 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
# apps/api/tests/test_ai_router_feedback.py | 2026-04-20 @ Asia/Taipei
|
||
# 2026-04-22 @ Asia/Taipei: 重構移除 FakeRepo/FakeSession(違反 feedback_no_mock_testing.md)
|
||
# 方案:抽取 AIRouter._aggregate_feedback_stats 純函數,直接單元測試,零 DB 依賴。
|
||
# DB 聚合查詢行為已由 integration/test_ai_router_feedback_integration.py 覆蓋。
|
||
"""Unit tests for AIRouter._aggregate_feedback_stats — 純邏輯,無 DB。"""
|
||
import pytest
|
||
from src.services.ai_router import AIRouter
|
||
|
||
|
||
# =============================================================================
|
||
# _aggregate_feedback_stats 純函數測試(無 DB 依賴)
|
||
# =============================================================================
|
||
|
||
def test_feedback_aggregates_by_model():
|
||
stats = [
|
||
{"repo": "awoooi", "model": "elephant-alpha", "total": 10,
|
||
"errors": 2, "success_rate": 0.8},
|
||
{"repo": "awoooi", "model": "gemini-pro", "total": 5,
|
||
"errors": 0, "success_rate": 1.0},
|
||
]
|
||
out = AIRouter._aggregate_feedback_stats(stats)
|
||
assert out["elephant-alpha"] == 0.8
|
||
assert out["gemini-pro"] == 1.0
|
||
|
||
|
||
def test_feedback_filters_by_repo():
|
||
stats = [
|
||
{"repo": "awoooi", "model": "elephant-alpha", "total": 5,
|
||
"errors": 1, "success_rate": 0.8},
|
||
{"repo": "other-repo", "model": "elephant-alpha", "total": 3,
|
||
"errors": 3, "success_rate": 0.0},
|
||
]
|
||
out = AIRouter._aggregate_feedback_stats(stats, repo="awoooi")
|
||
assert out == {"elephant-alpha": 0.8}
|
||
|
||
|
||
def test_feedback_handles_empty_stats():
|
||
out = AIRouter._aggregate_feedback_stats([])
|
||
assert out == {}
|
||
|
||
|
||
def test_feedback_skips_missing_model():
|
||
stats = [
|
||
{"repo": "awoooi", "model": None, "success_rate": 0.9},
|
||
{"repo": "awoooi", "model": "gemini-pro", "success_rate": 0.7},
|
||
]
|
||
out = AIRouter._aggregate_feedback_stats(stats)
|
||
assert list(out.keys()) == ["gemini-pro"]
|
||
|
||
|
||
# =============================================================================
|
||
# feedback_from_aider_events — DB 失敗降級行為(error path,無 FakeRepo)
|
||
# =============================================================================
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_feedback_returns_empty_on_db_failure(monkeypatch):
|
||
monkeypatch.setattr(
|
||
"src.services.ai_router.get_session_factory",
|
||
lambda: (_ for _ in ()).throw(RuntimeError("DB unavailable")),
|
||
raising=False,
|
||
)
|
||
|
||
r = AIRouter()
|
||
out = await r.feedback_from_aider_events(days=7)
|
||
assert out == {}
|