arm-core/api/tests/test_openapi_routes.py
2026-10-05 01:45:13 +04:00

80 lines
3.8 KiB
Python

"""OpenAPI содержит все маршруты Среза 1 (specs directories-api, directory-import, users-admin) без дублей моделей.
Схема строится из объявленных маршрутов без БД и сети. Состав маршрутов — по спекам change `slice-1-directories`;
проверка нужна, чтобы маршрут не пропал при переносе роутеров и чтобы `RegionalOfficeOut` (R-13) был одной моделью.
"""
import importlib
from typing import Any
import pytest
app_module = importlib.import_module("src.app")
D = "/api/v1/directories"
A = "/api/v1/admin"
# путь → методы (нижний регистр, как в OpenAPI)
EXPECTED_ROUTES: dict[str, set[str]] = {
"/api/v1/me": {"get"},
# directories-api: филиалы, ГК, ГСК/ГСЗ, ЮЛ/ИП
f"{D}/regional-offices": {"get"},
f"{D}/company-groups": {"get", "post"},
f"{D}/company-groups/{{group_id}}": {"get", "patch"},
f"{D}/company-groups/{{group_id}}/archive": {"post"},
f"{D}/company-groups/{{group_id}}/unarchive": {"post"},
f"{D}/company-groups/{{group_id}}/history": {"get"},
f"{D}/company-groups/{{company_group_id}}/related-groups": {"get", "post"},
f"{D}/related-groups/{{group_id}}": {"get", "patch", "delete"},
f"{D}/related-groups/{{group_id}}/members": {"post"},
f"{D}/related-groups/{{group_id}}/members/{{legal_entity_id}}": {"delete"},
f"{D}/legal-entities": {"get", "post"},
f"{D}/legal-entities/{{entity_id}}": {"get", "patch"},
f"{D}/legal-entities/{{entity_id}}/move": {"post"},
f"{D}/legal-entities/{{entity_id}}/withdraw": {"post"},
f"{D}/legal-entities/{{entity_id}}/history": {"get"},
f"{D}/legal-entities/{{entity_id}}/aliases": {"post"},
f"{D}/legal-entities/{{entity_id}}/aliases/{{alias_id}}": {"delete"},
# directory-import
f"{D}/import": {"post"},
# users-admin
f"{A}/users": {"get", "post"},
f"{A}/users/{{user_id}}": {"patch"},
f"{A}/users/{{user_id}}/roles": {"put"},
f"{A}/roles": {"get"},
f"{A}/permissions": {"get"},
f"{A}/roles/{{code}}/permissions": {"put"},
}
# Модели, которые обязаны быть в схеме ровно один раз под коротким именем
SINGLE_MODELS = ["RegionalOfficeOut", "CompanyGroupOut", "LegalEntityOut", "UserOut", "RoleOut", "ImportReportOut"]
@pytest.fixture(scope="module")
def schema() -> dict[str, Any]:
spec: dict[str, Any] = app_module.app.openapi()
return spec
@pytest.mark.parametrize("path", sorted(EXPECTED_ROUTES))
def test_spec_route_is_in_openapi(schema: dict[str, Any], path: str) -> None:
assert path in schema["paths"], f"в OpenAPI нет маршрута {path}"
assert set(schema["paths"][path]) >= EXPECTED_ROUTES[path], (path, sorted(schema["paths"][path]))
def test_no_unexpected_routes_under_directories_and_admin(schema: dict[str, Any]) -> None:
"""Маршрутов сверх спек нет: новые маршруты сначала попадают в спеку и в этот список."""
actual = {path for path in schema["paths"] if path.startswith((D, A))}
expected = {path for path in EXPECTED_ROUTES if path.startswith((D, A))}
assert actual == expected
for path in actual:
assert set(schema["paths"][path]) == EXPECTED_ROUTES[path], path
def test_components_have_no_duplicated_models(schema: dict[str, Any]) -> None:
"""Одна модель — одно имя: без автоимён вида `src__schemas__x__RegionalOfficeOut` и суффиксов `-Input`/`-Output`."""
names = list(schema["components"]["schemas"])
for model in SINGLE_MODELS:
assert names.count(model) == 1, model
assert not [name for name in names if "__" in name or "-" in name], names