POST /consolidations (версия №1 INITIATED, периметр из текущих членов ГСК, история,
CONSOLIDATION_CREATE с consolidation_version_id), GET /company-groups/{id}/consolidations
и GET /consolidations/versions/{id}. reporting_date по OQ-40; подхват документов — заглушка
bind_confirmed до Task 6, чек-лист в карточке пуст до Task 8. UNIQUE периода -> 409
CONSOLIDATION_EXISTS; RequestAudit.record принимает consolidation_version_id.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
129 lines
5.9 KiB
Python
129 lines
5.9 KiB
Python
"""OpenAPI содержит все маршруты Срезов 1–4 (specs directories-api, directory-import, users-admin,
|
||
consolidations-api) без дублей моделей.
|
||
|
||
Схема строится из объявленных маршрутов без БД и сети. Состав маршрутов — по спекам 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"},
|
||
# primary-files, documents-api (Срез 2c)
|
||
"/api/v1/legal-entities/{legal_entity_id}/files": {"get", "post"},
|
||
"/api/v1/files/{file_id}": {"get", "delete"},
|
||
"/api/v1/documents/{document_id}": {"get"},
|
||
"/api/v1/documents/{document_id}/entries": {"get"},
|
||
"/api/v1/documents/{document_id}/confirm": {"post"},
|
||
# consolidations-api (Срез 4)
|
||
"/api/v1/consolidations": {"post"},
|
||
"/api/v1/company-groups/{group_id}/consolidations": {"get"},
|
||
"/api/v1/consolidations/versions/{version_id}": {"get"},
|
||
}
|
||
|
||
# Префиксы, под которыми маршрутов сверх EXPECTED_ROUTES быть не должно
|
||
CHECKED_PREFIXES = (
|
||
D,
|
||
A,
|
||
"/api/v1/legal-entities",
|
||
"/api/v1/files",
|
||
"/api/v1/documents",
|
||
"/api/v1/consolidations",
|
||
"/api/v1/company-groups",
|
||
)
|
||
|
||
# Модели, которые обязаны быть в схеме ровно один раз под коротким именем
|
||
SINGLE_MODELS = [
|
||
"RegionalOfficeOut",
|
||
"CompanyGroupOut",
|
||
"LegalEntityOut",
|
||
"UserOut",
|
||
"RoleOut",
|
||
"ImportReportOut",
|
||
"FileOut",
|
||
"DocumentOut",
|
||
"EntryOut",
|
||
"VersionCard",
|
||
"ConsolidationPeriodOut",
|
||
]
|
||
|
||
|
||
@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(CHECKED_PREFIXES)}
|
||
expected = {path for path in EXPECTED_ROUTES if path.startswith(CHECKED_PREFIXES)}
|
||
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`.
|
||
|
||
Имена — ключи словаря, поэтому дубликатов быть не может; реальный страж — проверка на `__`/`-` (FastAPI даёт такие
|
||
имена, когда две разные модели совпали по имени или одна модель разошлась на вход/выход).
|
||
"""
|
||
names = list(schema["components"]["schemas"])
|
||
for model in SINGLE_MODELS:
|
||
assert model in names, model
|
||
assert not [name for name in names if "__" in name or "-" in name], names
|
||
|
||
|
||
def test_consolidation_routes_declare_documented_responses(schema: dict[str, Any]) -> None:
|
||
"""Создание — 201 с карточкой версии и ошибки 404/409/422; чтение карточки и списка — 404 (ГК вне видимости)."""
|
||
create = schema["paths"]["/api/v1/consolidations"]["post"]["responses"]
|
||
assert {"201", "404", "409", "422"} <= set(create)
|
||
assert create["201"]["content"]["application/json"]["schema"]["$ref"].endswith("/VersionCard")
|
||
card = schema["paths"]["/api/v1/consolidations/versions/{version_id}"]["get"]["responses"]
|
||
assert {"200", "404"} <= set(card)
|
||
listing = schema["paths"]["/api/v1/company-groups/{group_id}/consolidations"]["get"]["responses"]
|
||
assert {"200", "404"} <= set(listing)
|