# tests/integration/test_aider_event_repository.py | 2026-04-22 @ Asia/Taipei """AiderEventRepository 整合測試 — 使用真實 awoooi_dev PostgreSQL 替換 tests/test_aider_event_processor.py 中違反 feedback_no_mock_testing.md 的 FakeRepo / FakeSession mock。 原測試 (test_aider_event_processor.py) 驗證的是 AiderEventProcessor._process_one() 的整體流程(parse → incident → DB write → ACK),其中: - FakeRepo / FakeSession → 此整合測試改用真實 DB 驗證 insert 行為 - fake_r.xack (Redis) → Redis 屬外部 broker,仍可在 unit test 中 mock(符合「外部 API」例外) - fake_engine (IncidentEngine) → AI 推斷服務,屬外部呼叫,仍可 mock 此檔案: 只測「DB 層」— AiderEventRepository.insert() + model_stats_since() 其餘路由邏輯已在 test_aider_event_processor.py 的 parser/ACK 部份覆蓋。 規則: 每個測試後 rollback(由 integration/conftest.py db_session fixture 保證) 禁止 Mock — 直接使用真實 DB 連線。 """ from __future__ import annotations from datetime import datetime, timezone, timedelta import pytest from src.repositories.aider_event_repository import AiderEventRepository TAIPEI = timezone(timedelta(hours=8)) def _ts() -> datetime: return datetime.now(TAIPEI) # ============================================================================= # insert() 基本寫入 # ============================================================================= class TestAiderEventRepositoryInsert: """驗證 insert() 正確寫入 aider_events 表。""" @pytest.mark.asyncio async def test_insert_error_event_returns_id(self, db_session): """error event 插入後應回傳有效 BIGSERIAL id。""" repo = AiderEventRepository(db_session) row_id = await repo.insert( session_id="s-test-001", ts=_ts(), type_="error", host="ogt-mac", payload={"cwd": "/r", "model": "elephant-alpha", "kind": "api_rate_limit", "message": "429", "context_50chars": ""}, ) assert isinstance(row_id, int) assert row_id > 0 @pytest.mark.asyncio async def test_insert_session_start_with_incident_id(self, db_session): """insert 可附帶 incident_id(nullable FK)。""" repo = AiderEventRepository(db_session) row_id = await repo.insert( session_id="s-test-002", ts=_ts(), type_="session_start", host="ogt-mac", payload={"cwd": "/r", "model": "elephant-alpha", "aider_args": [], "aider_pid": 1, "cli_version": "0.86"}, incident_id="INC-20260422-0001", ) assert row_id > 0 @pytest.mark.asyncio async def test_insert_without_incident_id(self, db_session): """incident_id 可為 None(常見情境)。""" repo = AiderEventRepository(db_session) row_id = await repo.insert( session_id="s-test-003", ts=_ts(), type_="commit", host="ogt-mac", payload={"cwd": "/r", "model": "gemini-pro", "commit_hash": "abc123", "message": "fix: something"}, ) assert row_id > 0 # ============================================================================= # count_by_session() # ============================================================================= class TestAiderEventRepositoryCount: @pytest.mark.asyncio async def test_count_returns_correct_value(self, db_session): """插入 3 筆相同 session_id,count 應回傳 3。""" repo = AiderEventRepository(db_session) sid = "s-count-test-001" for i in range(3): await repo.insert( session_id=sid, ts=_ts(), type_="error", host="ogt-mac", payload={"kind": "test", "message": f"err-{i}", "model": "m", "cwd": "/r", "context_50chars": ""}, ) count = await repo.count_by_session(sid) assert count == 3 @pytest.mark.asyncio async def test_count_unknown_session_is_zero(self, db_session): """不存在的 session_id 應回傳 0。""" repo = AiderEventRepository(db_session) count = await repo.count_by_session("nonexistent-session-xyz") assert count == 0 # ============================================================================= # model_stats_since() — AI Router feedback 聚合查詢 # ============================================================================= class TestAiderEventRepositoryModelStats: @pytest.mark.asyncio async def test_model_stats_returns_list(self, db_session): """model_stats_since() 應回傳 list(即使空)。""" repo = AiderEventRepository(db_session) result = await repo.model_stats_since(days=1) assert isinstance(result, list) @pytest.mark.asyncio async def test_model_stats_aggregates_correctly(self, db_session): """插入 session_start + error,stats 應正確統計 error_rate。 注意:model_stats_since 聚合邏輯依賴 session_start/session_end payload.model 欄位, 且需要多筆 session 才能統計。此測試驗證:不崩潰、回傳正確型別。 """ repo = AiderEventRepository(db_session) ts_now = _ts() # 插入一個 session 含 session_start(提供 model 資訊)+ 一筆 error sid = "s-stats-test-001" await repo.insert( session_id=sid, ts=ts_now, type_="session_start", host="ogt-mac", payload={"cwd": "/awoooi", "model": "elephant-alpha", "aider_args": [], "aider_pid": 1, "cli_version": "0.86"}, ) await repo.insert( session_id=sid, ts=ts_now, type_="error", host="ogt-mac", payload={"cwd": "/awoooi", "model": "elephant-alpha", "kind": "api_rate_limit", "message": "429", "context_50chars": ""}, ) await db_session.flush() # flush 使 SQL CTE 能看到資料(不 commit) result = await repo.model_stats_since(days=1) assert isinstance(result, list) # 若有回傳結果,驗證欄位格式正確 for row in result: assert "model" in row assert "total" in row assert "errors" in row assert "success_rate" in row