P1-A: ingest 联赛级 inserted/updated 读 r["leagues"][code] 而非顶层 r.get("inserted");
抽 _accumulate_ingest_result 纯函数 + 合约测试(4/4)。
P1-B: 非法 cursor 不再静默忽略,返回 400 + detail.code=INVALID_CURSOR;
抽 _parse_cursor 纯函数 + 测试(8/8)。
全量 307 通过。
76 lines
2.7 KiB
Python
76 lines
2.7 KiB
Python
"""P1-B 回归测试: 非法 cursor → 400 + code=INVALID_CURSOR。
|
|
|
|
运行: pytest tests/test_p1_b_invalid_cursor.py -v
|
|
(_parse_cursor 为纯函数,无 DB/网络依赖;HTTP 层仅测非法格式。)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from fastapi.testclient import TestClient
|
|
|
|
from src.api.app import app
|
|
from src.api.deps import require_admin
|
|
from src.api.routes.matches import _parse_cursor
|
|
|
|
|
|
class TestParseCursorPure:
|
|
"""P1-B 纯函数:_parse_cursor 解析与非法校验。"""
|
|
|
|
def test_valid_cursor(self):
|
|
d, mid = _parse_cursor("2026-01-01T15:00:00+00:00|42")
|
|
assert d == datetime(2026, 1, 1, 15, 0, tzinfo=timezone.utc)
|
|
assert mid == 42
|
|
|
|
def test_missing_pipe_raises_400(self):
|
|
with pytest.raises(HTTPException) as ei:
|
|
_parse_cursor("no-pipe-here")
|
|
assert ei.value.status_code == 400
|
|
assert ei.value.detail["code"] == "INVALID_CURSOR"
|
|
|
|
def test_empty_date_raises_400(self):
|
|
with pytest.raises(HTTPException) as ei:
|
|
_parse_cursor("|5")
|
|
assert ei.value.status_code == 400
|
|
assert ei.value.detail["code"] == "INVALID_CURSOR"
|
|
|
|
def test_non_numeric_id_raises_400(self):
|
|
with pytest.raises(HTTPException) as ei:
|
|
_parse_cursor("2026-01-01T00:00:00+00:00|abc")
|
|
assert ei.value.status_code == 400
|
|
assert ei.value.detail["code"] == "INVALID_CURSOR"
|
|
|
|
def test_invalid_date_raises_400(self):
|
|
with pytest.raises(HTTPException) as ei:
|
|
_parse_cursor("not-a-date|1")
|
|
assert ei.value.status_code == 400
|
|
assert ei.value.detail["code"] == "INVALID_CURSOR"
|
|
|
|
def test_extra_pipe_raises_400(self):
|
|
"""含额外 | 时 id 部分为 "42|extra",int() 失败 → 400。"""
|
|
with pytest.raises(HTTPException) as ei:
|
|
_parse_cursor("2026-01-01T15:00:00+00:00|42|extra")
|
|
assert ei.value.status_code == 400
|
|
assert ei.value.detail["code"] == "INVALID_CURSOR"
|
|
|
|
|
|
class TestInvalidCursorHTTP:
|
|
"""P1-B HTTP 层:非法 cursor → 400 + code=INVALID_CURSOR。"""
|
|
|
|
@pytest.fixture
|
|
def client(self):
|
|
app.dependency_overrides[require_admin] = lambda: None
|
|
return TestClient(app)
|
|
|
|
def test_malformed_cursor_400(self, client):
|
|
resp = client.get("/api/v1/matches?cursor=garbage")
|
|
assert resp.status_code == 400
|
|
assert resp.json()["detail"]["code"] == "INVALID_CURSOR"
|
|
|
|
def test_missing_id_400(self, client):
|
|
resp = client.get("/api/v1/matches?cursor=2026-01-01T00:00:00|")
|
|
assert resp.status_code == 400
|
|
assert resp.json()["detail"]["code"] == "INVALID_CURSOR"
|