Files
awoooi/apps/api/tests/test_ai_router_feedback.py
Your Name 479f8d8971
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 35s
refactor(tests): 技術債清零 — 移除 FakeRepo/FakeSession Mock DB 違規
## 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>
2026-04-22 01:33:30 +08:00

66 lines
2.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 == {}