# Test aider_event_service | 2026-04-20 @ Asia/Taipei import pytest from datetime import datetime, timezone, timedelta from src.services.aider_event_service import ( classify_severity, should_create_incident, build_signal_data, ) from src.models.aider import AiderEventIn TAIPEI = timezone(timedelta(hours=8)) def _ev(t, p=None, sid="s1"): return AiderEventIn( ts=datetime.now(TAIPEI), session_id=sid, host="ogt-mac", type=t, payload=p or {}, ) def test_classify_error_is_warning(): # aider "error" event → signal severity "warning" → Severity.P2 assert classify_severity(_ev("error")) == "warning" def test_classify_silent_timeout_is_info(): # silent_timeout → "info" → P3 assert classify_severity(_ev("silent_timeout")) == "info" def test_classify_session_end_nonzero_exit_is_warning(): assert classify_severity(_ev("session_end", {"exit_code": 1})) == "warning" def test_classify_session_end_multi_error_is_high(): # 3+ errors + nonzero exit → "high" → P1 assert classify_severity(_ev("session_end", {"exit_code": 1, "error_count": 5})) == "high" def test_classify_session_end_clean_is_none(): ev = _ev("session_end", {"exit_code": 0, "error_count": 0}) assert classify_severity(ev) is None assert should_create_incident(ev) is False def test_classify_session_start_is_none(): assert should_create_incident(_ev("session_start")) is False def test_classify_error_triggers_incident(): assert should_create_incident(_ev("error")) is True def test_build_signal_data_fingerprint_per_session_type(): # 同 session + 同 type → 同 fingerprint(讓既有 3min debounce 生效) sd1 = build_signal_data(_ev("error", {"cwd": "/a", "model": "m"}, sid="sx")) sd2 = build_signal_data(_ev("error", {"cwd": "/a", "model": "m"}, sid="sx")) assert sd1["fingerprint"] == sd2["fingerprint"] assert sd1["fingerprint"].startswith("aider:sx:error") def test_build_signal_data_different_sessions_different_fp(): sd_a = build_signal_data(_ev("error", sid="sA")) sd_b = build_signal_data(_ev("error", sid="sB")) assert sd_a["fingerprint"] != sd_b["fingerprint"] def test_build_signal_data_shape(): ev = _ev("error", {"cwd": "/Users/ogt/awoooi", "model": "elephant-alpha", "message": "api timeout", "kind": "api_rate_limit"}) sd = build_signal_data(ev) # 必要 keys for k in ("alert_name", "severity", "source", "fingerprint", "target", "labels", "annotations"): assert k in sd, f"missing {k}" assert sd["source"] == "manual" assert sd["target"] == "awoooi" # 從 cwd basename assert sd["labels"]["session_id"] == "s1" assert sd["labels"]["model"] == "elephant-alpha" def test_build_signal_data_redacts_secrets_in_annotations(): """secret 不可進 annotations/labels""" ev = _ev("error", {"cwd": "/r", "model": "m", "message": "key sk-or-v1-abcdef0123456789ABCDEFghijklmnopqrstuv fail"}) sd = build_signal_data(ev) # annotations 可能含 message — 需已遮罩 annot_str = str(sd["annotations"]) assert "sk-or-v1-abcdef" not in annot_str assert "