mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
fix(alerts): 触发记录查询的 days/limit 补上范围约束
GET /api/alerts 的 days、limit 是裸 int,没有任何约束,越界值不报错而是
静默返回错误结果:
- days=-1 → cutoff 被推到未来,200 但 alerts 为空(total 仍是 3)
- limit=-1 → out[:limit] 变成负数切片,3 条记录只返回 2 条,
静默丢掉最旧一条,调用方无从察觉
- limit=0 → 200 但 alerts 为空
同类列表端点已有正确写法:abnormal.py 用 `limit: int = Query(500, ge=1,
le=2000)`,rps.py 用 `days: int = Query(12, ge=7, le=30)`,越界直接 422。
本次把 /api/alerts 补齐到同一口径,上下限取存储侧保留策略本身
(alert_store.MAX_DAYS / MAX_RECORDS),不新增常量,超出保留窗口的请求
本来也没有可返回的数据。
前端 alertsList 实际只传 days=7、limit=1/10/500,全部落在新区间内;
新增的 tests/test_alerts_query_bounds.py 同时锁住这些合法取值仍返回 200
且记录条数不变。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9a4bdcd07d
commit
68005c92e6
@@ -5,7 +5,7 @@ import random
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from fastapi import APIRouter, HTTPException, Query, Request
|
||||
|
||||
from app.services import alert_store
|
||||
|
||||
@@ -19,8 +19,10 @@ def _data_dir(request: Request) -> Path:
|
||||
@router.get("")
|
||||
def list_alerts(
|
||||
request: Request,
|
||||
days: int = 7,
|
||||
limit: int = 5000,
|
||||
# 上限取存储侧保留策略 (alert_store.MAX_DAYS / MAX_RECORDS): 超出也没有可返回的记录。
|
||||
# 无下限时 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,
|
||||
type: str | None = None,
|
||||
ext_columns: str | None = None,
|
||||
|
||||
@@ -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"])
|
||||
Reference in New Issue
Block a user