Files
dify/api/tests/unit_tests/dev/test_openapi_source_locations.py
T
Stephen Zhouandautofix-ci[bot] 04ba877c1a feat(web): export OpenAPI mapper types and compatible contract subpaths (#44915)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-10-28 14:39:37 +00:00

76 lines
2.9 KiB
Python

"""Editor metadata must follow registered Python identities without changing live schemas."""
import inspect
from collections.abc import Callable
from copy import deepcopy
from functools import wraps
from pathlib import Path
from flask_restx import Api, Namespace, Resource
from pydantic import BaseModel
from controllers.common.schema import register_schema_model, registered_schema_sources
from dev.export_console_openapi import attach_api_sources, resolve_source_type, source_location
class NavigationPayload(BaseModel):
name: str
class Container:
class Nested(BaseModel):
value: str
def decorated[**P, R](handler: Callable[P, R]) -> Callable[P, R]:
@wraps(handler)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return handler(*args, **kwargs)
return wrapper
class NavigationResource(Resource):
@decorated
def post(self) -> dict[str, str]:
return {}
def test_registered_models_and_decorated_operations_have_exact_origins() -> None:
namespace = Namespace("navigation", path="/navigation")
register_schema_model(namespace, NavigationPayload)
namespace.add_resource(NavigationResource, "")
api = Api()
api.add_namespace(namespace)
live_schema = deepcopy(namespace.models["NavigationPayload"].__schema__)
document = {
"components": {"schemas": {"NavigationPayload": deepcopy(live_schema), "Synthetic": {"type": "object"}}},
"paths": {"/navigation": {"post": {"description": "Create"}}},
}
attach_api_sources(document, api)
model_source = document["components"]["schemas"]["NavigationPayload"]["x-source-location"]
assert model_source == {"file": str(Path(__file__).resolve()), "line": inspect.getsourcelines(NavigationPayload)[1]}
operation_source = document["paths"]["/navigation"]["post"]["x-source-location"]
expected_line = next(
line
for line, text in enumerate(Path(__file__).read_text().splitlines(), 1)
if text.strip() == "def post(self) -> dict[str, str]:"
)
assert operation_source == {"file": str(Path(__file__).resolve()), "line": expected_line}
assert "x-source-location" not in document["components"]["schemas"]["Synthetic"]
assert namespace.models["NavigationPayload"].__schema__ == live_schema
sources = registered_schema_sources(namespace)
sources.clear()
assert registered_schema_sources(namespace)["NavigationPayload"].endswith(".NavigationPayload")
def test_exact_nested_and_generic_identities_resolve_without_name_guessing() -> None:
assert resolve_source_type(f"{__name__}.Container.Nested") is Container.Nested
assert resolve_source_type(f"{__name__}.Container.Nested[builtins.str]") is Container.Nested
assert resolve_source_type("unknown.module.NavigationPayload") is None
assert resolve_source_type(f"{__name__}.Missing.NavigationPayload") is None
assert source_location(None) is None
assert source_location(str) is None