92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
# ============================================================
|
||
# 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__
|