aps-agent/tests/golden/test_openapi_schema.py

92 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# OpenAPI schema 生成门禁(round-52 回归守护)
#
# 根因:`server/gateway/app.py` 的 create_app() 函数体内定义的局部
# Pydantic 请求模型类,会让 FastAPI 生成 /openapi.json 时抛
# PydanticUserError(ForwardRef 无法按模块全局解析),线上表现为
# GET /openapi.json 500。
#
# 本测试守护两件事:
# 1) openapi() 必须能完整生成(所有路由请求模型可解析);
# 2) 曾出问题的 12 个请求模型必须是模块级定义(可被
# `from server.gateway.app import ...` 导入,且 __qualname__
# 不含 create_app)。
# ============================================================
from __future__ import annotations
import pytest
from tests.auth_provider import install_test_auth
@pytest.fixture()
def gateway_app(tmp_path, monkeypatch):
monkeypatch.setenv("APS_DB_PATH", str(tmp_path / "openapi.db"))
monkeypatch.setenv("APS_WORLD_PATH", str(tmp_path / "world.json"))
from server.db.database import reset_engine
from server.state import store as world_store
install_test_auth(monkeypatch, "tenant-a-000000000000000000000000001")
world_store._stores.clear()
reset_engine()
from server.gateway.app import create_app
app = create_app()
yield app
reset_engine()
world_store._stores.clear()
# round-52 曾定义在 create_app() 局部、导致 OpenAPI 生成崩溃的 12 个模型
MODULE_SCOPE_MODELS = (
"ImportCommitRequest",
"TemplateApplyRequest",
"RagQueryRequest",
"RebuildStartRequest",
"RebuildRollbackRequest",
"KnowledgeImportRequest",
"SkillRegisterRequest",
"SkillEnableRequest",
"McpPluginRegisterRequest",
"McpPluginEnableRequest",
"McpPermissionRequest",
"JobSubmitRequest",
)
# 上述模型服务的受影响路由
AFFECTED_PATHS = (
"/api/import/commit",
"/api/templates/apply",
"/api/rag/query",
"/api/rebuild/{module_id}",
"/api/rebuild/{module_id}/rollback",
"/api/knowledge/import",
"/api/skills/register",
"/api/skills/{skill_id}/enable",
"/api/mcp/plugins/register",
"/api/mcp/plugins/{plugin_id}/enable",
"/api/mcp/plugins/{plugin_id}/permissions",
"/api/jobs",
)
def test_openapi_schema_generates_without_forward_ref_error(gateway_app):
"""round-52 回归:openapi() 必须能完整生成,不再抛 PydanticUserError。"""
spec = gateway_app.openapi()
assert isinstance(spec, dict)
paths = spec.get("paths") or {}
for path in AFFECTED_PATHS:
assert path in paths, f"openapi schema 缺少受影响路由 {path}"
def test_affected_request_models_are_module_scope(gateway_app):
"""round-52 守护:请求模型必须是模块级定义,不能是 create_app 局部类。"""
import server.gateway.app as gateway_module
for name in MODULE_SCOPE_MODELS:
cls = getattr(gateway_module, name)
assert "create_app" not in cls.__qualname__, (
f"{name} 仍是函数局部定义: {cls.__qualname__}"
)
assert cls.__module__ == "server.gateway.app", cls.__module__