perf(api): GET сводов 1 и 2 отдают готовый JSON из Postgres
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d8dba2f7be
commit
ca7160124a
@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from src.api.v1.deps import get_current_user
|
from src.api.v1.deps import get_current_user
|
||||||
from src.db.models.app_user import AppUser
|
from src.db.models.app_user import AppUser
|
||||||
|
from src.api.v1.json_response import list_json_response
|
||||||
from src.db.session import get_db
|
from src.db.session import get_db
|
||||||
from src.domain.schemas import BaseListResponse, SheetResponse
|
from src.domain.schemas import BaseListResponse, SheetResponse
|
||||||
from src.services.summary_service import SummaryService
|
from src.services.summary_service import SummaryService
|
||||||
@ -26,17 +27,6 @@ VALID_SUMMARY_SHEETS = {
|
|||||||
VALID_SUMMARY2_SHEETS = {"GO", "RF"}
|
VALID_SUMMARY2_SHEETS = {"GO", "RF"}
|
||||||
|
|
||||||
|
|
||||||
def _rows_to_sheet_response(result: list[tuple]) -> list[SheetResponse]:
|
|
||||||
return [
|
|
||||||
SheetResponse(
|
|
||||||
row_type=el[0],
|
|
||||||
depth=el[1],
|
|
||||||
sort_order=el[2],
|
|
||||||
data=el[3],
|
|
||||||
) for el in result
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_org_ids(org_ids: Optional[str]) -> Optional[list[int]]:
|
def _parse_org_ids(org_ids: Optional[str]) -> Optional[list[int]]:
|
||||||
if not org_ids:
|
if not org_ids:
|
||||||
return None
|
return None
|
||||||
@ -47,15 +37,14 @@ def _parse_org_ids(org_ids: Optional[str]) -> Optional[list[int]]:
|
|||||||
return parsed or None
|
return parsed or None
|
||||||
|
|
||||||
|
|
||||||
@router.get("/1/{sheet}/{year}")
|
@router.get("/1/{sheet}/{year}", response_model=BaseListResponse[SheetResponse])
|
||||||
async def get_summary(
|
async def get_summary(
|
||||||
sheet: str,
|
sheet: str,
|
||||||
year: int,
|
year: int,
|
||||||
response: Response,
|
|
||||||
org_ids: Optional[str] = None,
|
org_ids: Optional[str] = None,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: AppUser = Depends(get_current_user),
|
current_user: AppUser = Depends(get_current_user),
|
||||||
) -> BaseListResponse[SheetResponse]:
|
) -> Response:
|
||||||
if sheet not in VALID_SUMMARY_SHEETS:
|
if sheet not in VALID_SUMMARY_SHEETS:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
404,
|
404,
|
||||||
@ -64,14 +53,13 @@ async def get_summary(
|
|||||||
|
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
summary_service = SummaryService(db)
|
summary_service = SummaryService(db)
|
||||||
rows = await summary_service.get_svod1_rows(
|
rows = await summary_service.get_svod1_json(
|
||||||
year=year,
|
year=year,
|
||||||
sheet=sheet,
|
sheet=sheet,
|
||||||
user=current_user,
|
user=current_user,
|
||||||
org_ids=_parse_org_ids(org_ids),
|
org_ids=_parse_org_ids(org_ids),
|
||||||
)
|
)
|
||||||
db_ms = (time.perf_counter() - t0) * 1000
|
db_ms = (time.perf_counter() - t0) * 1000
|
||||||
response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
|
|
||||||
|
|
||||||
extra = {
|
extra = {
|
||||||
"type": "db_time",
|
"type": "db_time",
|
||||||
@ -84,17 +72,13 @@ async def get_summary(
|
|||||||
}
|
}
|
||||||
logger.info(f"db_time: {extra}", extra=extra)
|
logger.info(f"db_time: {extra}", extra=extra)
|
||||||
|
|
||||||
return BaseListResponse(
|
return list_json_response(rows, headers={"X-DB-Time-Ms": f"{db_ms:.2f}"})
|
||||||
count=len(rows),
|
|
||||||
result=_rows_to_sheet_response(rows),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/2/{sheet}/{year}")
|
@router.get("/2/{sheet}/{year}", response_model=BaseListResponse[SheetResponse])
|
||||||
async def get_summary2(
|
async def get_summary2(
|
||||||
sheet: str,
|
sheet: str,
|
||||||
year: int,
|
year: int,
|
||||||
response: Response,
|
|
||||||
org_ids: Optional[str] = None,
|
org_ids: Optional[str] = None,
|
||||||
offset: Optional[int] = Query(default=None, ge=0),
|
offset: Optional[int] = Query(default=None, ge=0),
|
||||||
limit: Optional[int] = Query(default=None, ge=1),
|
limit: Optional[int] = Query(default=None, ge=1),
|
||||||
@ -102,7 +86,7 @@ async def get_summary2(
|
|||||||
sort_direction: Literal["asc", "desc"] = "asc",
|
sort_direction: Literal["asc", "desc"] = "asc",
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: AppUser = Depends(get_current_user),
|
current_user: AppUser = Depends(get_current_user),
|
||||||
) -> BaseListResponse[SheetResponse]:
|
) -> Response:
|
||||||
sheet = sheet.upper()
|
sheet = sheet.upper()
|
||||||
if sheet not in VALID_SUMMARY2_SHEETS:
|
if sheet not in VALID_SUMMARY2_SHEETS:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@ -112,7 +96,7 @@ async def get_summary2(
|
|||||||
|
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
summary_service = SummaryService(db)
|
summary_service = SummaryService(db)
|
||||||
rows = await summary_service.get_svod2_rows(
|
rows = await summary_service.get_svod2_json(
|
||||||
year=year,
|
year=year,
|
||||||
sheet=sheet,
|
sheet=sheet,
|
||||||
user=current_user,
|
user=current_user,
|
||||||
@ -123,7 +107,6 @@ async def get_summary2(
|
|||||||
sort_direction=sort_direction,
|
sort_direction=sort_direction,
|
||||||
)
|
)
|
||||||
db_ms = (time.perf_counter() - t0) * 1000
|
db_ms = (time.perf_counter() - t0) * 1000
|
||||||
response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
|
|
||||||
|
|
||||||
extra = {
|
extra = {
|
||||||
"type": "db_time",
|
"type": "db_time",
|
||||||
@ -140,7 +123,4 @@ async def get_summary2(
|
|||||||
}
|
}
|
||||||
logger.info(f"db_time: {extra}", extra=extra)
|
logger.info(f"db_time: {extra}", extra=extra)
|
||||||
|
|
||||||
return BaseListResponse(
|
return list_json_response(rows, headers={"X-DB-Time-Ms": f"{db_ms:.2f}"})
|
||||||
count=len(rows),
|
|
||||||
result=_rows_to_sheet_response(rows),
|
|
||||||
)
|
|
||||||
|
|||||||
@ -3,27 +3,63 @@ from typing import Any
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from src.repository.row_json import ROW_COLUMNS, fetch_row_texts, row_json_select
|
||||||
|
|
||||||
|
|
||||||
class SummaryRepository:
|
class SummaryRepository:
|
||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _svod1_source() -> str:
|
||||||
|
return (
|
||||||
|
"FROM v3.v_summary_view("
|
||||||
|
"CAST(:year AS INT), "
|
||||||
|
"CAST(:sheet AS TEXT), "
|
||||||
|
"CAST(:org_ids AS INT[]))"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _svod2_source(sheet: str) -> str:
|
||||||
|
function_name = {
|
||||||
|
"GO": "v3.v_svod2_go",
|
||||||
|
"RF": "v3.v_svod2_rf",
|
||||||
|
}[sheet]
|
||||||
|
return (
|
||||||
|
f"FROM {function_name}("
|
||||||
|
"CAST(:year AS INT), "
|
||||||
|
"CAST(:org_ids AS INT[]), "
|
||||||
|
"CAST(:offset AS INT), "
|
||||||
|
"CAST(:limit AS INT), "
|
||||||
|
"CAST(:sort_by AS VARCHAR), "
|
||||||
|
"CAST(:sort_direction AS VARCHAR))"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _svod2_params(
|
||||||
|
year: int,
|
||||||
|
org_ids: list[int] | None,
|
||||||
|
offset: int | None,
|
||||||
|
limit: int | None,
|
||||||
|
sort_by: str,
|
||||||
|
sort_direction: str,
|
||||||
|
) -> dict:
|
||||||
|
return {
|
||||||
|
"year": year,
|
||||||
|
"org_ids": org_ids,
|
||||||
|
"offset": offset,
|
||||||
|
"limit": limit,
|
||||||
|
"sort_by": sort_by,
|
||||||
|
"sort_direction": sort_direction,
|
||||||
|
}
|
||||||
|
|
||||||
async def get_svod1_rows(
|
async def get_svod1_rows(
|
||||||
self,
|
self,
|
||||||
year: int,
|
year: int,
|
||||||
sheet: str,
|
sheet: str,
|
||||||
org_ids: list[int] | None = None,
|
org_ids: list[int] | None = None,
|
||||||
) -> list[tuple]:
|
) -> list[tuple]:
|
||||||
query = text(
|
query = text(f"SELECT {ROW_COLUMNS} {self._svod1_source()}")
|
||||||
"""
|
|
||||||
SELECT row_type, depth, sort_order, data
|
|
||||||
FROM v3.v_summary_view(
|
|
||||||
CAST(:year AS INT),
|
|
||||||
CAST(:sheet AS TEXT),
|
|
||||||
CAST(:org_ids AS INT[])
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
await self.db.execute(
|
await self.db.execute(
|
||||||
query,
|
query,
|
||||||
@ -31,6 +67,19 @@ class SummaryRepository:
|
|||||||
)
|
)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
async def get_svod1_json(
|
||||||
|
self,
|
||||||
|
year: int,
|
||||||
|
sheet: str,
|
||||||
|
org_ids: list[int] | None = None,
|
||||||
|
) -> list[str]:
|
||||||
|
sql = f"{row_json_select()} {self._svod1_source()}"
|
||||||
|
return await fetch_row_texts(
|
||||||
|
self.db,
|
||||||
|
sql,
|
||||||
|
{"year": year, "sheet": sheet, "org_ids": org_ids},
|
||||||
|
)
|
||||||
|
|
||||||
async def get_svod2_rows(
|
async def get_svod2_rows(
|
||||||
self,
|
self,
|
||||||
year: int,
|
year: int,
|
||||||
@ -41,41 +90,34 @@ class SummaryRepository:
|
|||||||
sort_by: str = "org_name",
|
sort_by: str = "org_name",
|
||||||
sort_direction: str = "asc",
|
sort_direction: str = "asc",
|
||||||
) -> list[tuple]:
|
) -> list[tuple]:
|
||||||
function_name = {
|
|
||||||
"GO": "v3.v_svod2_go",
|
|
||||||
"RF": "v3.v_svod2_rf",
|
|
||||||
}[sheet]
|
|
||||||
query = text(
|
query = text(
|
||||||
f"""
|
f"SELECT 'INPUT'::VARCHAR AS row_type, 3 AS depth, sort_order, data "
|
||||||
SELECT
|
f"{self._svod2_source(sheet)}"
|
||||||
'INPUT'::VARCHAR AS row_type,
|
|
||||||
3 AS depth,
|
|
||||||
sort_order,
|
|
||||||
data
|
|
||||||
FROM {function_name}(
|
|
||||||
CAST(:year AS INT),
|
|
||||||
CAST(:org_ids AS INT[]),
|
|
||||||
CAST(:offset AS INT),
|
|
||||||
CAST(:limit AS INT),
|
|
||||||
CAST(:sort_by AS VARCHAR),
|
|
||||||
CAST(:sort_direction AS VARCHAR)
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
await self.db.execute(
|
await self.db.execute(
|
||||||
query,
|
query,
|
||||||
{
|
self._svod2_params(year, org_ids, offset, limit, sort_by, sort_direction),
|
||||||
"year": year,
|
|
||||||
"org_ids": org_ids,
|
|
||||||
"offset": offset,
|
|
||||||
"limit": limit,
|
|
||||||
"sort_by": sort_by,
|
|
||||||
"sort_direction": sort_direction,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
async def get_svod2_json(
|
||||||
|
self,
|
||||||
|
year: int,
|
||||||
|
sheet: str,
|
||||||
|
org_ids: list[int] | None = None,
|
||||||
|
offset: int | None = None,
|
||||||
|
limit: int | None = None,
|
||||||
|
sort_by: str = "org_name",
|
||||||
|
sort_direction: str = "asc",
|
||||||
|
) -> list[str]:
|
||||||
|
select = row_json_select(row_type="'INPUT'::VARCHAR", depth="3")
|
||||||
|
return await fetch_row_texts(
|
||||||
|
self.db,
|
||||||
|
f"{select} {self._svod2_source(sheet)}",
|
||||||
|
self._svod2_params(year, org_ids, offset, limit, sort_by, sort_direction),
|
||||||
|
)
|
||||||
|
|
||||||
async def get_svod2_go_export_rows(
|
async def get_svod2_go_export_rows(
|
||||||
self,
|
self,
|
||||||
year: int,
|
year: int,
|
||||||
|
|||||||
@ -52,6 +52,46 @@ class SummaryService:
|
|||||||
sort_direction=sort_direction,
|
sort_direction=sort_direction,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_svod1_json(
|
||||||
|
self,
|
||||||
|
year: int,
|
||||||
|
sheet: str,
|
||||||
|
user: AppUser,
|
||||||
|
org_ids: list[int] | None = None,
|
||||||
|
) -> list[str]:
|
||||||
|
org_ids = await self._allowed_org_ids(user, org_ids)
|
||||||
|
if org_ids == []:
|
||||||
|
return []
|
||||||
|
return await self.summary_repo.get_svod1_json(
|
||||||
|
year=year,
|
||||||
|
sheet=sheet,
|
||||||
|
org_ids=org_ids,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_svod2_json(
|
||||||
|
self,
|
||||||
|
year: int,
|
||||||
|
sheet: str,
|
||||||
|
user: AppUser,
|
||||||
|
org_ids: list[int] | None = None,
|
||||||
|
offset: int | None = None,
|
||||||
|
limit: int | None = None,
|
||||||
|
sort_by: str = "org_name",
|
||||||
|
sort_direction: str = "asc",
|
||||||
|
) -> list[str]:
|
||||||
|
org_ids = await self._allowed_org_ids(user, org_ids)
|
||||||
|
if org_ids == []:
|
||||||
|
return []
|
||||||
|
return await self.summary_repo.get_svod2_json(
|
||||||
|
year=year,
|
||||||
|
sheet=sheet,
|
||||||
|
org_ids=org_ids,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_direction=sort_direction,
|
||||||
|
)
|
||||||
|
|
||||||
async def get_svod2_go_export_rows(
|
async def get_svod2_go_export_rows(
|
||||||
self,
|
self,
|
||||||
year: int,
|
year: int,
|
||||||
|
|||||||
@ -11,6 +11,8 @@ from sqlalchemy import text
|
|||||||
|
|
||||||
from src.main import app
|
from src.main import app
|
||||||
from src.repository.sheet_repository import SheetRepository
|
from src.repository.sheet_repository import SheetRepository
|
||||||
|
from src.repository.summary_repository import SummaryRepository
|
||||||
|
from src.services.summary_service import SummaryService
|
||||||
|
|
||||||
ADMIN = SimpleNamespace(id=1, role_id=1) # fixture.sql: admin
|
ADMIN = SimpleNamespace(id=1, role_id=1) # fixture.sql: admin
|
||||||
ISP = SimpleNamespace(id=2, role_id=2) # fixture.sql: isp1, EXECUTOR_DFIP
|
ISP = SimpleNamespace(id=2, role_id=2) # fixture.sql: isp1, EXECUTOR_DFIP
|
||||||
@ -122,3 +124,85 @@ def test_sheet_get_does_not_use_old_path(client, admin_tokens, auth_headers, mon
|
|||||||
raise AssertionError("GET листа не должен вызывать SheetRepository.get")
|
raise AssertionError("GET листа не должен вызывать SheetRepository.get")
|
||||||
monkeypatch.setattr(SheetRepository, "get", boom)
|
monkeypatch.setattr(SheetRepository, "get", boom)
|
||||||
get_ok(client, "/api/v1/form/4/sheet/AHR", auth_headers(admin_tokens))
|
get_ok(client, "/api/v1/form/4/sheet/AHR", auth_headers(admin_tokens))
|
||||||
|
|
||||||
|
|
||||||
|
SVOD2_PLAN_LINE_SQL = text(
|
||||||
|
"WITH bf AS (INSERT INTO v3.budget_form (form_type_code, year, org_unit_id) "
|
||||||
|
"VALUES ('FORM_1', :year, 2) RETURNING id), "
|
||||||
|
"bl AS (INSERT INTO v3.budget_line (budget_form_id, expense_item_id) "
|
||||||
|
"SELECT id, :expense_item_id FROM bf RETURNING id) "
|
||||||
|
"INSERT INTO v3.plan (line_id, plan_q1) SELECT id, 1000 FROM bl"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def svod2_plan_line(client, db_session):
|
||||||
|
"""Фикстура не даёт свода 2 строк за YEAR: вставляем FORM_1 с ненулевым планом.
|
||||||
|
Откатывается вместе с сессией теста (POST строки без плана свод 2 не учитывает)."""
|
||||||
|
async def _set():
|
||||||
|
await db_session.execute(SVOD2_PLAN_LINE_SQL, {"year": YEAR, "expense_item_id": 88})
|
||||||
|
client.portal.call(_set)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("sheet", ["FORM_1", "FORM_2", "FORM_3", "FORM_4", "MAIN"])
|
||||||
|
def test_svod1_matches_old_path(client, db_session, admin_tokens, auth_headers, sheet):
|
||||||
|
result = get_ok(client, f"/api/v1/svod/1/{sheet}/{YEAR}", auth_headers(admin_tokens))
|
||||||
|
old = old_rows(client, lambda: SummaryRepository(db_session).get_svod1_rows(year=YEAR, sheet=sheet, org_ids=None))
|
||||||
|
assert result == expected_sheet(old)
|
||||||
|
|
||||||
|
|
||||||
|
def test_svod1_org_ids_matches_old_path(client, db_session, admin_tokens, auth_headers):
|
||||||
|
result = get_ok(client, f"/api/v1/svod/1/FORM_1/{YEAR}", auth_headers(admin_tokens), {"org_ids": "1,2,3"})
|
||||||
|
old = old_rows(client, lambda: SummaryRepository(db_session).get_svod1_rows(year=YEAR, sheet="FORM_1", org_ids=[1, 2, 3]))
|
||||||
|
assert result
|
||||||
|
assert result == expected_sheet(old)
|
||||||
|
|
||||||
|
|
||||||
|
SVOD2_CASES = [
|
||||||
|
("GO", {}, {}),
|
||||||
|
("RF", {}, {}),
|
||||||
|
("GO", {"org_ids": "1,2"}, {"org_ids": [1, 2]}),
|
||||||
|
("GO", {"offset": 1, "limit": 2}, {"offset": 1, "limit": 2}),
|
||||||
|
("RF", {"sort_by": "section_code", "sort_direction": "desc"}, {"sort_by": "section_code", "sort_direction": "desc"}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("svod2_plan_line")
|
||||||
|
@pytest.mark.parametrize("sheet,query,repo_kwargs", SVOD2_CASES)
|
||||||
|
def test_svod2_matches_old_path(client, db_session, admin_tokens, auth_headers, sheet, query, repo_kwargs):
|
||||||
|
result = get_ok(client, f"/api/v1/svod/2/{sheet}/{YEAR}", auth_headers(admin_tokens), query)
|
||||||
|
old = old_rows(client, lambda: SummaryRepository(db_session).get_svod2_rows(year=YEAR, sheet=sheet, **repo_kwargs))
|
||||||
|
assert result == expected_sheet(old)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("svod2_plan_line")
|
||||||
|
def test_svod2_fixture_not_empty(client, admin_tokens, auth_headers):
|
||||||
|
"""Иначе сравнения свода 2 выше проходят на пустых списках."""
|
||||||
|
go = get_ok(client, f"/api/v1/svod/2/GO/{YEAR}", auth_headers(admin_tokens))
|
||||||
|
rf = get_ok(client, f"/api/v1/svod/2/RF/{YEAR}", auth_headers(admin_tokens))
|
||||||
|
assert go or rf
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("svod2_plan_line")
|
||||||
|
def test_svod2_isp_matches_service_old_path(client, db_session, isp_tokens, auth_headers):
|
||||||
|
result = get_ok(client, f"/api/v1/svod/2/GO/{YEAR}", auth_headers(isp_tokens))
|
||||||
|
old = old_rows(client, lambda: SummaryService(db_session).get_svod2_rows(year=YEAR, sheet="GO", user=ISP))
|
||||||
|
assert result == expected_sheet(old)
|
||||||
|
|
||||||
|
|
||||||
|
def test_svod2_foreign_org_ids_empty_body(client, isp_tokens, auth_headers):
|
||||||
|
r = client.get(f"/api/v1/svod/2/GO/{YEAR}", headers=auth_headers(isp_tokens), params={"org_ids": "999999"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.json() == {"success": True, "message": None, "result": [], "count": 0}
|
||||||
|
assert float(r.headers["x-db-time-ms"]) >= 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("url,method", [
|
||||||
|
(f"/api/v1/svod/1/MAIN/{YEAR}", "get_svod1_rows"),
|
||||||
|
(f"/api/v1/svod/2/GO/{YEAR}", "get_svod2_rows"),
|
||||||
|
])
|
||||||
|
def test_svod_get_does_not_use_old_path(client, admin_tokens, auth_headers, monkeypatch, url, method):
|
||||||
|
async def boom(*args, **kwargs):
|
||||||
|
raise AssertionError(f"GET свода не должен вызывать SummaryRepository.{method}")
|
||||||
|
monkeypatch.setattr(SummaryRepository, method, boom)
|
||||||
|
get_ok(client, url, auth_headers(admin_tokens))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user