fix: test_api.py type errors - inline kwargs, fix ParameterSet import, add json=None

This commit is contained in:
LIghtJUNction
2026-04-29 04:38:03 +08:00
parent d8eb3d3869
commit a0c9cc8fe3

View File

@@ -1,127 +1,113 @@
"""Comprehensive API smoke tests covering every registered route.
Each endpoint is hit once — the test only checks that it does not crash
(no 500). Created after a merge broke routes silently; this ensures that
regressions are caught before deployment.
(no 500 / no hang). Created after a merge broke routes silently.
"""
import json
import re
import pytest
import pytest_asyncio
from _pytest.mark.structures import ParameterSet
from quart import Quart
# ===========================================================================
# Route registry — every endpoint the dashboard registers
# ===========================================================================
# (method, path, json_body | None)
# Paths with ``<angle>`` placeholders are skipped (they need runtime IDs).
# Paths that expect a file upload are also skipped (cannot be tested without
# a real file).
ROUTES: list[tuple[str, str, dict | None]] = [
def _p(method: str, path: str, body: dict | None = None) -> ParameterSet:
"""Shorthand to build a parametrize row with readable id."""
name = path.replace("/", "_").replace("?", "_").replace("=", "_")
return pytest.param(method, path, body, id=f"{method.upper()}_{name}".rstrip("_"))
ROUTES = [
# -- auth --
("POST", "/api/auth/login", {"username": "astrbot", "password": "astrbot"}),
_p("POST", "/api/auth/login", {"username": "astrbot", "password": "astrbot"}),
# -- chat --
("GET", "/api/chat/sessions", None),
("POST", "/api/chat/new_session", None),
("POST", "/api/chat/stop", None),
("POST", "/api/chat/send", {"command": "test", "text": "ping"}),
("GET", "/api/chat/get_session?conversation_id=0", None),
("GET", "/api/chat/delete_session?conversation_id=0", None),
("POST", "/api/chat/batch_delete_sessions", {"session_ids": []}),
("POST", "/api/chat/update_session_display_name", {"conversation_id": "0", "name": "t"}),
("GET", "/api/chat/get_file?filename=nonexistent", None),
("POST", "/api/chat/post_file", None),
("GET", "/api/chat/get_attachment?attachment_id=0", None),
_p("GET", "/api/chat/sessions"),
_p("POST", "/api/chat/new_session"),
_p("POST", "/api/chat/stop"),
_p("POST", "/api/chat/send", {"command": "test", "text": "ping"}),
_p("GET", "/api/chat/get_session?conversation_id=0"),
_p("GET", "/api/chat/delete_session?conversation_id=0"),
_p("POST", "/api/chat/batch_delete_sessions", {"session_ids": []}),
_p("POST", "/api/chat/update_session_display_name", {"conversation_id": "0", "name": "t"}),
_p("GET", "/api/chat/get_file?filename=nonexistent"),
_p("POST", "/api/chat/post_file"),
_p("GET", "/api/chat/get_attachment?attachment_id=0"),
# -- conversation --
("GET", "/api/conversation/list", None),
("GET", "/api/conversation/detail?id=0", None),
_p("GET", "/api/conversation/list"),
_p("GET", "/api/conversation/detail?id=0"),
# -- config —
("POST", "/api/config/get", {"keys": []}),
("GET", "/api/config/default", None),
("GET", "/api/config/abconfs", None),
("POST", "/api/config/abconf/new", {"name": "t", "config": {}}),
("GET", "/api/config/abconf?id=0", None),
("POST", "/api/config/abconf/delete", {"id": 0}),
("POST", "/api/config/abconf/update", {"id": 0, "config": {}}),
("GET", "/api/config/umo_abconf_routes", None),
("GET", "/api/config/provider/list", None),
("GET", "/api/config/provider/template", None),
("GET", "/api/config/platform/list", None),
("POST", "/api/config/platform/delete", {"id": 0}),
("GET", "/api/config/provider_sources/models", None),
_p("POST", "/api/config/get", {"keys": []}),
_p("GET", "/api/config/default"),
_p("GET", "/api/config/abconfs"),
_p("POST", "/api/config/abconf/new", {"name": "t", "config": {}}),
_p("GET", "/api/config/abconf?id=0"),
_p("POST", "/api/config/abconf/delete", {"id": 0}),
_p("POST", "/api/config/abconf/update", {"id": 0, "config": {}}),
_p("GET", "/api/config/umo_abconf_routes"),
_p("GET", "/api/config/provider/list"),
_p("GET", "/api/config/provider/template"),
_p("GET", "/api/config/platform/list"),
_p("POST", "/api/config/platform/delete", {"id": 0}),
_p("GET", "/api/config/provider_sources/models"),
# -- persona —
("GET", "/api/persona/list", None),
("POST", "/api/persona/create", {"name": "t", "prompt": "test"}),
("GET", "/api/persona/detail?id=0", None),
("POST", "/api/persona/delete", {"id": 0}),
_p("GET", "/api/persona/list"),
_p("POST", "/api/persona/create", {"name": "t", "prompt": "test"}),
_p("GET", "/api/persona/detail?id=0"),
_p("POST", "/api/persona/delete", {"id": 0}),
# -- cron —
("GET", "/api/cron/jobs", None),
_p("GET", "/api/cron/jobs"),
# -- api key —
("GET", "/api/apikey/list", None),
("POST", "/api/apikey/create", {"name": "t", "scopes": ["chat:read"]}),
("POST", "/api/apikey/delete", {"id": 0}),
_p("GET", "/api/apikey/list"),
_p("POST", "/api/apikey/create", {"name": "t", "scopes": ["chat:read"]}),
_p("POST", "/api/apikey/delete", {"id": 0}),
# — platform —
("GET", "/api/platform/list", None),
("GET", "/api/platform/stats", None),
_p("GET", "/api/platform/list"),
_p("GET", "/api/platform/stats"),
# — knowledge base —
("GET", "/api/kb/list", None),
("POST", "/api/kb/create", {"name": "t"}),
("GET", "/api/kb/get?id=0", None),
("GET", "/api/kb/stats", None),
_p("GET", "/api/kb/list"),
_p("POST", "/api/kb/create", {"name": "t"}),
_p("GET", "/api/kb/get?id=0"),
_p("GET", "/api/kb/stats"),
# — skills —
("POST", "/api/skills/upload", None),
_p("POST", "/api/skills/upload"),
# — tools / MCP —
("GET", "/api/tools/list", None),
("GET", "/api/tools/mcp/servers", None),
("POST", "/api/tools/toggle-tool", {"tool_name": "test", "enabled": False}),
_p("GET", "/api/tools/list"),
_p("GET", "/api/tools/mcp/servers"),
_p("POST", "/api/tools/toggle-tool", {"tool_name": "test", "enabled": False}),
# — chatui project —
("GET", "/api/chatui_project/list", None),
("POST", "/api/chatui_project/create", {"title": "t"}),
("GET", "/api/chatui_project/get", None),
("POST", "/api/chatui_project/delete", {"id": 0}),
_p("GET", "/api/chatui_project/list"),
_p("POST", "/api/chatui_project/create", {"title": "t"}),
_p("GET", "/api/chatui_project/get"),
_p("POST", "/api/chatui_project/delete", {"id": 0}),
# — stat —
("GET", "/api/stat/get", None),
("GET", "/api/stat/version", None),
("GET", "/api/stat/storage", None),
("GET", "/api/stat/changelog", None),
_p("GET", "/api/stat/get"),
_p("GET", "/api/stat/version"),
_p("GET", "/api/stat/storage"),
_p("GET", "/api/stat/changelog"),
# — update —
("GET", "/api/update/check", None),
("GET", "/api/update/releases", None),
_p("GET", "/api/update/check"),
_p("GET", "/api/update/releases"),
# — subagent —
("GET", "/api/subagent/config", None),
_p("GET", "/api/subagent/config"),
# — session management —
("GET", "/api/session/groups", None),
("POST", "/api/session/group/create", {"name": "t"}),
# — backup — (GET only; POST/DELETE need runtime IDs)
("GET", "/api/backup/list", None),
("GET", "/api/backup/progress", None),
("GET", "/api/backup/check", None),
_p("GET", "/api/session/groups"),
_p("POST", "/api/session/group/create", {"name": "t"}),
# — backup —
_p("GET", "/api/backup/list"),
_p("GET", "/api/backup/progress"),
_p("GET", "/api/backup/check"),
# — commands —
("GET", "/api/commands/conflicts", None),
("GET", "/api/commands/permission", None),
_p("GET", "/api/commands/conflicts"),
_p("GET", "/api/commands/permission"),
# — live-log —
("GET", "/api/log-history", None),
_p("GET", "/api/log-history"),
]
def _route_id(params: tuple) -> str:
"""Return a short test-id for each parametrised row."""
method, path, _ = params
name = path.replace("/", "_").replace("?", "_").replace("=", "_")
return f"{method.upper()}_{name}".rstrip("_")
# ===========================================================================
# Tests
# ===========================================================================
class TestAllRoutes:
"""Hit every registered route and assert no 500 / no crash."""
@pytest.mark.asyncio
@pytest.mark.parametrize("method,path,body", ROUTES, ids=_route_id)
@pytest.mark.parametrize("method,path,body", ROUTES)
async def test_route_returns_no_500(
self,
method: str,
@@ -132,22 +118,15 @@ class TestAllRoutes:
):
client = app.test_client()
headers = {**authenticated_header}
kwargs = {"headers": headers}
if body is not None:
kwargs["json"] = body
resp = await client.open(path, method=method, headers=headers, json=body)
else:
resp = await client.open(path, method=method, headers=headers, json=None)
assert resp.status_code != 500, f"{method} {path} returned 500"
resp = await client.open(path, method=method, **kwargs)
assert resp.status_code != 500, (
f"{method} {path} returned 500"
)
# ------------------------------------------------------------------
# Live-log SSE needs a dedicated test (streaming response)
# ------------------------------------------------------------------
# Live-log SSE — just check the response status code
@pytest.mark.asyncio
async def test_live_log_sse(self, app: Quart, authenticated_header: dict):
client = app.test_client()
resp = await client.get("/api/live-log", headers=authenticated_header)
assert resp.status_code == 200
assert resp.mimetype == "text/event-stream"