from __future__ import annotations import os import sys import tempfile from pathlib import Path from fastapi.testclient import TestClient _repo_root = Path(__file__).resolve().parents[2] if str(_repo_root) not in sys.path: sys.path.insert(0, str(_repo_root)) from Spider_XHS.service.app import create_app from Spider_XHS.service.errors import ERROR_CODE_SUCCESS from Spider_XHS.service.tasks import TaskRecord, TaskStatus def _assert(cond: bool, msg: str) -> None: if not cond: raise AssertionError(msg) def _extract(body: dict) -> dict: _assert(body.get("code") == ERROR_CODE_SUCCESS, f"unexpected code={body.get('code')}, body={body}") data = body.get("data") or {} _assert(isinstance(data, dict), "data_not_dict") return data def verify_errors_summary_aggregate_and_filters() -> None: os.environ["ENABLE_LEGACY_ROUTES"] = "0" with tempfile.TemporaryDirectory() as td: os.environ["STORAGE_ROOT"] = td app = create_app() with TestClient(app) as client: storage = app.state.storage t1 = TaskRecord( id="t1", status=TaskStatus.failed, task_type="note_url", target="u1", payload={"operator": "tester"}, engine="api", created=100.0, error={"kind": "auth", "message": "m"}, ) t2 = TaskRecord( id="t2", status=TaskStatus.failed, task_type="search", target="k", payload={"operator": "tester"}, engine="browser", created=101.0, error={"kind": "rate", "message": "m"}, ) t3 = TaskRecord( id="t3", status=TaskStatus.succeeded, task_type="selftest", target="", payload={"operator": "tester"}, engine="selftest", created=102.0, ) t4 = TaskRecord( id="t4", status=TaskStatus.rpa_failed, task_type="note_url", target="u4", payload={"operator": "tester"}, engine="api", created=103.0, error={"kind": "risk", "message": "m"}, ) t5 = TaskRecord( id="t5", status=TaskStatus.failed, task_type="note_url", target="u5", payload={"operator": "tester"}, engine="api", created=104.0, error={"kind": "auth", "message": "m"}, ) for t in (t1, t2, t3, t4, t5): storage.create_task(t) mtimes = {"t1": 50.0, "t2": 51.0, "t3": 52.0, "t4": 53.0, "t5": 40.0} for tid, ts in mtimes.items(): p = storage.tasks_dir / f"{tid}.json" os.utime(p, (ts, ts)) r0 = client.get("/api/v1/errors/summary", params={"scan_limit": 3}) _assert(r0.status_code == 200, f"expected 200, got={r0.status_code}, body={r0.text}") d0 = _extract(r0.json()) _assert(d0.get("scan_limit") == 3, f"scan_limit_mismatch={d0.get('scan_limit')}") _assert(d0.get("scanned") == 3, f"scanned_mismatch={d0.get('scanned')}") _assert(d0.get("total") == 2, f"total_mismatch={d0.get('total')}") _assert( d0.get("error_kind_counts") == {"risk": 1, "rate": 1}, f"counts_mismatch={d0.get('error_kind_counts')}", ) tasks0 = d0.get("tasks") or [] _assert([t.get("id") for t in tasks0] == ["t4", "t2"], f"tasks_mismatch={tasks0}") r1 = client.get("/api/v1/errors/summary", params={"scan_limit": 5}) d1 = _extract(r1.json()) _assert(d1.get("total") == 4, f"total_mismatch={d1.get('total')}") counts1 = d1.get("error_kind_counts") or {} _assert(counts1.get("auth") == 2, f"auth_count_mismatch={counts1}") _assert(counts1.get("rate") == 1, f"rate_count_mismatch={counts1}") _assert(counts1.get("risk") == 1, f"risk_count_mismatch={counts1}") r2 = client.get("/api/v1/errors/summary", params={"scan_limit": 5, "error_kind": "auth"}) d2 = _extract(r2.json()) _assert(d2.get("total") == 2, f"auth_total_mismatch={d2.get('total')}") _assert(d2.get("error_kind_counts") == {"auth": 2}, f"auth_counts_mismatch={d2.get('error_kind_counts')}") r3 = client.get("/api/v1/errors/summary", params={"scan_limit": 5, "error_kind": "auth", "limit": 1, "offset": 1}) d3 = _extract(r3.json()) tasks3 = d3.get("tasks") or [] _assert([t.get("id") for t in tasks3] == ["t5"], f"auth_page_mismatch={tasks3}") r4 = client.get("/api/v1/errors/summary", params={"scan_limit": 5, "status": "succeeded"}) d4 = _extract(r4.json()) _assert(d4.get("total") == 1, f"succeeded_total_mismatch={d4.get('total')}") _assert(d4.get("error_kind_counts") == {"unknown": 1}, f"succeeded_counts_mismatch={d4.get('error_kind_counts')}") tasks4 = d4.get("tasks") or [] _assert([t.get("id") for t in tasks4] == ["t3"], f"succeeded_tasks_mismatch={tasks4}") def main() -> None: verify_errors_summary_aggregate_and_filters() print("OK") if __name__ == "__main__": main()