Fix news risk window query parsing
Development CI / check-and-build (push) Successful in 55s
GitHub Git mirror / mirror (push) Successful in 6s
Development CI / deploy-development (push) Successful in 3m9s

This commit is contained in:
eezd committed 2026-10-30 18:57:04 +08:00
1 parent 91bc65df75
commit b801c83c60
3 files changed
+43 -3

No files matched your search

+10 -3
View File
@@ -1,6 +1,7 @@
"""Authenticated news risk research endpoints.""" """Authenticated news risk research endpoints."""
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
from typing import Annotated, Literal from enum import IntEnum
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from sqlalchemy import select, func from sqlalchemy import select, func
@@ -17,6 +18,12 @@ router = APIRouter(prefix='/api/risk', tags=['risk'], dependencies=[Depends(requ
DB = Annotated[AsyncSession, Depends(get_session)] DB = Annotated[AsyncSession, Depends(get_session)]
class RiskWindow(IntEnum):
DAY = 24
TWO_DAYS = 48
WEEK = 168
class SettingsIn(BaseModel): class SettingsIn(BaseModel):
enabled: bool = False enabled: bool = False
provider_id: int = Field(gt=0) provider_id: int = Field(gt=0)
@@ -72,7 +79,7 @@ async def coverage(session):
@router.get('/overview') @router.get('/overview')
async def overview(session: DB, hours: Literal[24, 48, 168] = 24): async def overview(session: DB, hours: RiskWindow = RiskWindow.DAY):
now = datetime.now(UTC); cutoff = now - timedelta(hours=hours) now = datetime.now(UTC); cutoff = now - timedelta(hours=hours)
counts = dict((await session.execute(select(RiskEvent.stars, func.count()).where( counts = dict((await session.execute(select(RiskEvent.stars, func.count()).where(
RiskEvent.updated_at >= cutoff, RiskEvent.result["verification"].astext != "insufficient").group_by(RiskEvent.stars))).all()) RiskEvent.updated_at >= cutoff, RiskEvent.result["verification"].astext != "insufficient").group_by(RiskEvent.stars))).all())
@@ -86,7 +93,7 @@ async def overview(session: DB, hours: Literal[24, 48, 168] = 24):
@router.get('/events') @router.get('/events')
async def events(session: DB, hours: Literal[24, 48, 168] | None = None, stars: int = Query(1, ge=0, le=5), async def events(session: DB, hours: RiskWindow | None = None, stars: int = Query(1, ge=0, le=5),
q: str = '', start: datetime | None = None, end: datetime | None = None, q: str = '', start: datetime | None = None, end: datetime | None = None,
page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100)): page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100)):
conditions = [RiskEvent.stars >= stars] conditions = [RiskEvent.stars >= stars]
+27
View File
@@ -141,3 +141,30 @@ def test_context_budget_includes_unicode_and_tool_schema(risk):
from types import SimpleNamespace from types import SimpleNamespace
provider = SimpleNamespace(max_tokens=256, context_window=4096) provider = SimpleNamespace(max_tokens=256, context_window=4096)
assert not risk.fits_context(provider, risk.SYSTEM, {'content': '风'*1000}, risk.Labels) assert not risk.fits_context(provider, risk.SYSTEM, {'content': '风'*1000}, risk.Labels)
@pytest.mark.parametrize('endpoint', ['overview', 'events'])
@pytest.mark.parametrize('hours,status', [(None, 200), ('24', 200), ('48', 200), ('168', 200), ('25', 422), ('0', 422), ('abc', 422)])
def test_window_query_parameters_over_http(risk, monkeypatch, endpoint, hours, status):
from types import SimpleNamespace
from unittest.mock import AsyncMock
from fastapi import FastAPI
import httpx
from alphapulse.api import risk as api
session = SimpleNamespace(execute=AsyncMock(return_value=SimpleNamespace(all=lambda: [])),
scalar=AsyncMock(return_value=None), scalars=AsyncMock(return_value=SimpleNamespace(all=lambda: [])))
app = FastAPI()
app.include_router(api.router)
app.dependency_overrides[api.require_admin] = lambda: None
app.dependency_overrides[api.get_session] = lambda: session
monkeypatch.setattr(api, 'coverage', AsyncMock(return_value={}))
monkeypatch.setattr(api.redis, 'get', AsyncMock(return_value=None))
async def check():
async with httpx.AsyncClient(transport=httpx.ASGITransport(app), base_url='http://test') as client:
response = await client.get('/api/risk/'+endpoint, params={} if hours is None else {'hours': hours})
assert response.status_code == status, response.text
if status == 200:
assert response.json()['code'] == 0
asyncio.run(check())
+6
View File
@@ -46,6 +46,12 @@ async def main():
response.raise_for_status() response.raise_for_status()
assert response.json()['code'] == 0, path assert response.json()['code'] == 0, path
assert 'CI synthetic news' in (await client.get('/api/news?q=CI')).text assert 'CI synthetic news' in (await client.get('/api/news?q=CI')).text
for endpoint in ('overview', 'events'):
for hours in (24, 48, 168):
response = await client.get('/api/risk/'+endpoint, params={'hours': hours})
response.raise_for_status()
assert response.json()['code'] == 0
assert (await client.get('/api/risk/'+endpoint, params={'hours': 25})).status_code == 422
item = (await client.get('/api/news?q=CI')).json()['data']['items'][0] item = (await client.get('/api/news?q=CI')).json()['data']['items'][0]
selected = await client.get('/api/news', params=[('ids', item['id']), ('ids', 2147483647)]) selected = await client.get('/api/news', params=[('ids', item['id']), ('ids', 2147483647)])
assert selected.status_code == 200 and [n['id'] for n in selected.json()['data']['items']] == [item['id']] assert selected.status_code == 200 and [n['id'] for n in selected.json()['data']['items']] == [item['id']]