Merge pull request #278 from kevin9327/fix/alerts-query-bounds

fix(alerts): 触发记录查询的 days/limit 补上范围约束 (limit=-1 静默丢记录)
This commit is contained in:
wshy
2026-09-09 09:04:40 +08:00
committed by GitHub
2 changed files with 74 additions and 3 deletions
+5 -3
View File
@@ -5,7 +5,7 @@ import random
import time import time
from pathlib import Path from pathlib import Path
from fastapi import APIRouter, HTTPException, Request from fastapi import APIRouter, HTTPException, Query, Request
from app.services import alert_store from app.services import alert_store
@@ -19,8 +19,10 @@ def _data_dir(request: Request) -> Path:
@router.get("") @router.get("")
def list_alerts( def list_alerts(
request: Request, request: Request,
days: int = 7, # 上限取存储侧保留策略 (alert_store.MAX_DAYS / MAX_RECORDS): 超出也没有可返回的记录。
limit: int = 5000, # 无下限时 days=-1 会把 cutoff 推到未来直接返回空, limit=-1 会走负数切片静默丢掉最旧一条。
days: int = Query(alert_store.MAX_DAYS, ge=1, le=alert_store.MAX_DAYS),
limit: int = Query(alert_store.MAX_RECORDS, ge=1, le=alert_store.MAX_RECORDS),
source: str | None = None, source: str | None = None,
type: str | None = None, type: str | None = None,
ext_columns: str | None = None, ext_columns: str | None = None,
+69
View File
@@ -0,0 +1,69 @@
"""告警记录查询入参边界 — days/limit 越界应被拦下, 合法值照常返回。
同类列表端点 (abnormal.py 的 limit、rps.py 的 days) 已用 Query(ge=..., le=...)
约束同名参数; 本文件锁住 /api/alerts 的同一口径。
"""
from __future__ import annotations
import time
from types import SimpleNamespace
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from app.api.alerts import router
from app.services import alert_store
def _client(tmp_path) -> TestClient:
app = FastAPI()
app.include_router(router)
app.state.repo = SimpleNamespace(store=SimpleNamespace(data_dir=tmp_path))
return TestClient(app)
def _seed(tmp_path, count: int = 3) -> None:
now_ms = int(time.time() * 1000)
alert_store.append_many(
tmp_path,
[
{"ts": now_ms - i * 1000, "rule_id": f"r{i}", "source": "monitor", "type": "price"}
for i in range(count)
],
)
@pytest.mark.parametrize("params", [{"days": -1}, {"days": 0}, {"limit": -1}, {"limit": 0}])
def test_out_of_range_params_are_rejected(tmp_path, params):
_seed(tmp_path)
resp = _client(tmp_path).get("/api/alerts", params=params)
assert resp.status_code == 422, resp.text
def test_days_and_limit_above_store_retention_are_rejected(tmp_path):
_seed(tmp_path)
client = _client(tmp_path)
# 存储侧只保留 MAX_DAYS 天 / MAX_RECORDS 条, 超出上限的请求没有可返回的数据
assert client.get("/api/alerts", params={"days": alert_store.MAX_DAYS + 1}).status_code == 422
assert client.get(
"/api/alerts", params={"limit": alert_store.MAX_RECORDS + 1}
).status_code == 422
def test_valid_params_still_return_all_records(tmp_path):
_seed(tmp_path, 3)
client = _client(tmp_path)
# 默认值
body = client.get("/api/alerts").json()
assert len(body["alerts"]) == 3
assert body["total"] == 3
# 显式边界值 (前端实际用的 days=7 / limit=1、10、500 均在内)
for params in (
{"days": 1, "limit": 1},
{"days": 7, "limit": 500},
{"days": alert_store.MAX_DAYS, "limit": alert_store.MAX_RECORDS},
):
resp = client.get("/api/alerts", params=params)
assert resp.status_code == 200, resp.text
assert len(resp.json()["alerts"]) == min(3, params["limit"])