dify/api/tests/unit_tests/commands/test_generate_swagger_specs.py
Asuka Minato af99414fc1
chore: port isinstance to match case (#37271)
Co-authored-by: WH-2099 <wh2099@pm.me>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-18 00:18:03 +00:00

152 lines
5.2 KiB
Python

"""Unit tests for the standalone OpenAPI export helper."""
import importlib.util
import json
import sys
from pathlib import Path
def _walk_values(value):
yield value
match value:
case dict():
for child in value.values():
yield from _walk_values(child)
case list():
for child in value:
yield from _walk_values(child)
def _load_generate_swagger_specs_module():
api_dir = Path(__file__).resolve().parents[3]
script_path = api_dir / "dev" / "generate_swagger_specs.py"
spec = importlib.util.spec_from_file_location("generate_swagger_specs", script_path)
assert spec
assert spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module) # type: ignore[attr-defined]
return module
def _operation_ids(payload):
methods = {"delete", "get", "head", "options", "patch", "post", "put", "trace"}
for path_item in payload["paths"].values():
for method, operation in path_item.items():
if method in methods and isinstance(operation, dict) and "operationId" in operation:
yield operation["operationId"]
def _get_operations(payload):
for path_item in payload["paths"].values():
operation = path_item.get("get")
if isinstance(operation, dict):
yield operation
def test_generate_specs_writes_console_web_and_service_openapi_files(tmp_path):
module = _load_generate_swagger_specs_module()
written_paths = module.generate_specs(tmp_path)
assert [path.name for path in written_paths] == [
"console-openapi.json",
"web-openapi.json",
"service-openapi.json",
"openapi-openapi.json",
]
for path in written_paths:
payload = json.loads(path.read_text(encoding="utf-8"))
assert payload["openapi"].startswith("3.")
assert "paths" in payload
def test_generate_specs_writes_openapi_with_resolvable_references_and_no_nulls(tmp_path):
module = _load_generate_swagger_specs_module()
written_paths = module.generate_specs(tmp_path)
for path in written_paths:
payload = json.loads(path.read_text(encoding="utf-8"))
schemas = payload["components"]["schemas"]
refs = {
item["$ref"].removeprefix("#/components/schemas/")
for item in _walk_values(payload)
if isinstance(item, dict)
and isinstance(item.get("$ref"), str)
and item["$ref"].startswith("#/components/schemas/")
}
assert refs <= set(schemas)
assert all(value is not None for value in _walk_values(payload))
def test_generate_specs_writes_unique_operation_ids(tmp_path):
module = _load_generate_swagger_specs_module()
written_paths = module.generate_specs(tmp_path)
for path in written_paths:
payload = json.loads(path.read_text(encoding="utf-8"))
operation_ids = list(_operation_ids(payload))
assert len(operation_ids) == len(set(operation_ids))
def test_generate_specs_writes_get_operations_without_request_bodies(tmp_path):
module = _load_generate_swagger_specs_module()
written_paths = module.generate_specs(tmp_path)
for path in written_paths:
payload = json.loads(path.read_text(encoding="utf-8"))
assert all("requestBody" not in operation for operation in _get_operations(payload))
def test_generate_specs_writes_service_api_reference_descriptions(tmp_path):
module = _load_generate_swagger_specs_module()
written_paths = module.generate_specs(tmp_path)
service_path = next(path for path in written_paths if path.name == "service-openapi.json")
payload = json.loads(service_path.read_text(encoding="utf-8"))
chat_operation = payload["paths"]["/chat-messages"]["post"]
assert chat_operation["summary"] == "Send Chat Message"
assert chat_operation["description"] == "Send a request to the chat application."
assert chat_operation["tags"] == ["Chatflows", "Chats"]
rename_operation = payload["paths"]["/conversations/{c_id}/name"]["post"]
assert rename_operation["summary"] == "Rename Conversation"
def test_standalone_inline_model_name_includes_list_constraints():
module = _load_generate_swagger_specs_module()
from flask_restx import fields
cases = (
({"min_items": 1}, {"min_items": 2}),
({"max_items": 1}, {"max_items": 2}),
({"unique": True}, {"unique": False}),
)
for first_kwargs, second_kwargs in cases:
first_inline_model = {"items": fields.List(fields.String, **first_kwargs)}
second_inline_model = {"items": fields.List(fields.String, **second_kwargs)}
assert module._inline_model_name(first_inline_model) != module._inline_model_name(second_inline_model)
def test_generate_specs_is_idempotent(tmp_path):
module = _load_generate_swagger_specs_module()
first_paths = module.generate_specs(tmp_path / "first")
second_paths = module.generate_specs(tmp_path / "second")
assert [path.name for path in first_paths] == [path.name for path in second_paths]
for first_path, second_path in zip(first_paths, second_paths):
assert first_path.read_text(encoding="utf-8") == second_path.read_text(encoding="utf-8")