DFiP_Budget_planing/api/tests/integration/test_heavy_get_json.py
2026-10-28 21:12:09 +04:00

125 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""AURORA-1848: тяжёлые GET отдают готовый JSON из Postgres.
Эталон — старый путь (методы репозиториев и сервисов, которыми пользуется выгрузка Excel)
в той же сессии теста. Спека: openspec/changes/aurora-1848-sheet-json/specs/sheet-read-api/spec.md
"""
import json
from types import SimpleNamespace
import pytest
from sqlalchemy import text
from src.main import app
from src.repository.sheet_repository import SheetRepository
ADMIN = SimpleNamespace(id=1, role_id=1) # fixture.sql: admin
ISP = SimpleNamespace(id=2, role_id=2) # fixture.sql: isp1, EXECUTOR_DFIP
YEAR = 2026
HEAVY_GET_PATHS = [
"/api/v1/form/{form_id}/sheet/{sheet}",
"/api/v1/svod/1/{sheet}/{year}",
"/api/v1/svod/2/{sheet}/{year}",
"/api/v1/projects/{project_id}/report/{year}/{report_type}",
"/api/v1/rf-rollup/{branch_id}/{year}",
"/api/v1/projects/{project_id}/summary",
"/api/v1/projects/plan-summary",
"/api/v1/projects/fact-summary",
"/api/v1/projects/detail-summary",
]
OPENAPI_REF = "#/components/schemas/BaseListResponse_SheetResponse_"
def _payload(rows) -> list[dict]:
"""Строки старого пути в том виде, в каком их отдавал BaseListResponse[SheetResponse]."""
return [{"row_type": r[0], "depth": r[1], "sort_order": r[2], "data": r[3]} for r in rows]
def old_rows(client, coro_factory) -> list[dict]:
"""Вызвать старый метод (coro_factory() -> awaitable list[tuple]) в цикле приложения."""
async def _go():
return _payload(await coro_factory())
return client.portal.call(_go)
def expected_sheet(rows: list[dict]) -> list[dict]:
"""Ожидаемый ответ листа и сводов по эталону старого пути. Фаза B (Task 13) меняет тело на strip_nulls."""
return rows
def get_ok(client, url, headers, params=None) -> list[dict]:
r = client.get(url, headers=headers, params=params)
assert r.status_code == 200, r.text
assert r.headers["content-type"] == "application/json"
assert float(r.headers["x-db-time-ms"]) >= 0
body = r.json()
assert list(body) == ["success", "message", "result", "count"]
assert body["success"] is True and body["message"] is None
assert body["count"] == len(body["result"])
for row in body["result"]:
assert row.keys() == {"row_type", "depth", "sort_order", "data"}
return body["result"]
def test_openapi_heavy_get_schema_unchanged():
spec = app.openapi()
for path in HEAVY_GET_PATHS:
schema = spec["paths"][path]["get"]["responses"]["200"]["content"]["application/json"]["schema"]
assert schema == {"$ref": OPENAPI_REF}, path
SHEET_CASES = [
(1, "AHR", "Support", None), # FORM_1, лист с direction
(4, "AHR", None, None), # FORM_2, без direction
(4, "AHR", None, ["plan", "q1"]), # FORM_2, с sections
]
def _sheet_params(direction, sections):
params = {}
if direction:
params["direction"] = direction
if sections:
params["sections"] = ",".join(sections)
return params
@pytest.mark.parametrize("form_id,sheet,direction,sections", SHEET_CASES)
def test_sheet_matches_old_path(client, db_session, admin_tokens, auth_headers, form_id, sheet, direction, sections):
result = get_ok(client, f"/api/v1/form/{form_id}/sheet/{sheet}", auth_headers(admin_tokens), _sheet_params(direction, sections))
old = old_rows(client, lambda: SheetRepository(db_session).get(form_id=form_id, sheet=sheet, direction=direction, sections=sections))
assert result
assert result[0]["row_type"] == "META" and result[0]["depth"] == -1
assert result == expected_sheet(old)
TRICKY = 'Кавычка " и \\ слэш\nперенос\tтаб'
def test_sheet_text_escaping_matches_old_path(client, db_session, admin_tokens, auth_headers):
# В фикстуре у FORM_2 нет budget_line: вставляем строку с TRICKY в name (header.name листа).
# justification в ответе листа не выводится; вставка откатывается вместе с сессией теста.
async def _set():
await db_session.execute(
text(
"INSERT INTO v3.budget_line (budget_form_id, expense_item_id, name, direction) "
"SELECT 4, t.id, :t, 'Support' FROM v3.mv_expense_item_tree t "
"JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id "
"WHERE eft.form_type_code = 'FORM_2' AND t.sheet = 'AHR' ORDER BY t.id LIMIT 1"
),
{"t": TRICKY},
)
client.portal.call(_set)
result = get_ok(client, "/api/v1/form/4/sheet/AHR", auth_headers(admin_tokens))
old = old_rows(client, lambda: SheetRepository(db_session).get(form_id=4, sheet="AHR", direction=None))
assert result == expected_sheet(old)
assert any((r["data"].get("header") or {}).get("name") == TRICKY for r in result)
def test_sheet_get_does_not_use_old_path(client, admin_tokens, auth_headers, monkeypatch):
async def boom(*args, **kwargs):
raise AssertionError("GET листа не должен вызывать SheetRepository.get")
monkeypatch.setattr(SheetRepository, "get", boom)
get_ok(client, "/api/v1/form/4/sheet/AHR", auth_headers(admin_tokens))