Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 35s
## Critical 修復 (C1-C5) - C1: git rm --cached 03-secrets.yaml(CHANGE_ME 模板不再追蹤) - C2: git rm --cached awoooi.db + .gitignore 加 *.db(SQLite HARD_RULES 違規) - C3: sentry-tunnel SENTRY_HOST 改為 process.env fallback - C4: config.py DATABASE_URL 移除 changeme default,改為必填 - C5: run_migration.py 改為 os.environ["DATABASE_URL"] ## Major 修復 (M1-M4) - M1: auto_repair /execute 加 CSRF 保護 + AutoRepairPanel.tsx 同步 - M2: drift /rollback /adopt 加 CSRF 保護(/internal/scan 保持無 CSRF) - M3: terminal /intent 加 CSRF 保護 + terminal.store.ts 同步 - M4: live-dashboard HOST_IPS + host-grid VIP 改為 env var ## 其他 - 新增 apps/web/.env.example(6 個 env var 說明) - K8s deployment-web 補入 3 個新 env var - 整合測試:新增 aider_event_repository + ai_router_feedback 真實 DB 測試 - test_terminal.py CSRF dependency override 修復 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
166 lines
6.4 KiB
Python
166 lines
6.4 KiB
Python
# 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
|