test(import): dry_run полной выгрузки ЕЦП (8400 строк) укладывается в 60 с
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
c793b42fba
commit
c9cd839e59
104
api/tests/test_import_full_export.py
Normal file
104
api/tests/test_import_full_export.py
Normal file
@ -0,0 +1,104 @@
|
||||
"""Полная выгрузка ЕЦП (8400 строк): `dry_run` укладывается в 60 с (spec directory-import, сценарий «Полная выгрузка»).
|
||||
|
||||
Тест идёт на отдельной временной БД с зафиксированным seed (`committed_client`): `dry_run` делает явный
|
||||
`session.rollback()`, поэтому «таблицы не выросли» осмысленно только вне SAVEPOINT-обёртки `db_session` (см. docstring
|
||||
`tests/conftest.py`). Файл выгрузки — из `ARM_KA_DOCS` либо соседнего `docs`; нет файла — пропуск с причиной.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
|
||||
from src.models.audit_ex import AuditEvent
|
||||
from src.models.directories import (
|
||||
CompanyGroup,
|
||||
CompanyGroupHistory,
|
||||
LegalEntity,
|
||||
LegalEntityHistory,
|
||||
RegionalOffice,
|
||||
RelatedGroup,
|
||||
RelatedGroupMember,
|
||||
)
|
||||
from src.models.files import S3Object
|
||||
from tests.conftest import AuthClientFactory, FakeStorage
|
||||
|
||||
ADMIN = "admin@armka.local"
|
||||
EXPORT_RELATIVE = Path("source/biq/ecp-export-lagal-entity.xlsx")
|
||||
XLSX_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
HTTP_OK = 200
|
||||
TIME_LIMIT_SECONDS = 60
|
||||
EXPORT_ROWS = 8400
|
||||
EXPORT_OFFICES = 53
|
||||
EXPORT_GROUPS = 344 # R-15: из 345 ГК файла одна целиком состоит из строк-дублей и не создаётся
|
||||
MIN_UNSUPPORTED_TYPE_ROWS = 500
|
||||
TOUCHED_TABLES = (
|
||||
RegionalOffice,
|
||||
CompanyGroup,
|
||||
CompanyGroupHistory,
|
||||
LegalEntity,
|
||||
LegalEntityHistory,
|
||||
RelatedGroup,
|
||||
RelatedGroupMember,
|
||||
AuditEvent,
|
||||
S3Object,
|
||||
)
|
||||
|
||||
|
||||
def _export_path() -> Path:
|
||||
docs = os.environ.get("ARM_KA_DOCS")
|
||||
return (Path(docs) if docs else Path(__file__).resolve().parents[3] / "docs") / EXPORT_RELATIVE
|
||||
|
||||
|
||||
async def _counts(session: AsyncSession) -> dict[str, int]:
|
||||
return {
|
||||
model.__tablename__: (await session.execute(select(func.count()).select_from(model))).scalar_one()
|
||||
for model in TOUCHED_TABLES
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.fixtures
|
||||
async def test_full_ecp_export_dry_run_within_time_limit(
|
||||
committed_client: AuthClientFactory,
|
||||
committed_sessionmaker: async_sessionmaker[AsyncSession],
|
||||
committed_storage: FakeStorage,
|
||||
) -> None:
|
||||
"""`dry_run` выгрузки из 8400 строк: ≤ 60 с, ожидаемые счётчики и коды, ни одна таблица справочника не выросла."""
|
||||
path = _export_path()
|
||||
if not path.is_file():
|
||||
pytest.skip(f"нет выгрузки ЕЦП: {path} (задайте ARM_KA_DOCS или положите docs рядом с arm-core)")
|
||||
content = path.read_bytes()
|
||||
client = await committed_client(ADMIN)
|
||||
async with committed_sessionmaker() as session:
|
||||
before = await _counts(session)
|
||||
|
||||
started = time.perf_counter()
|
||||
response = await client.post(
|
||||
"/directories/import",
|
||||
files={"file": (path.name, content, XLSX_TYPE)},
|
||||
data={"dry_run": "true", "on_existing": "skip"},
|
||||
)
|
||||
elapsed = time.perf_counter() - started
|
||||
|
||||
assert response.status_code == HTTP_OK, response.text
|
||||
assert elapsed <= TIME_LIMIT_SECONDS, f"dry_run занял {elapsed:.1f} с (лимит {TIME_LIMIT_SECONDS} с)"
|
||||
print(f"\nfull export dry_run: {elapsed:.2f} s") # noqa: T201 - замер времени виден при `pytest -s`
|
||||
report: dict[str, Any] = response.json()
|
||||
assert report["rows"] == EXPORT_ROWS
|
||||
assert report["offices_created"] == EXPORT_OFFICES
|
||||
assert report["groups_created"] == EXPORT_GROUPS
|
||||
codes = Counter(line["code"] for line in report["lines"])
|
||||
assert codes["RF_CREATED"] == EXPORT_OFFICES
|
||||
assert codes["UNSUPPORTED_TYPE"] >= MIN_UNSUPPORTED_TYPE_ROWS
|
||||
assert codes["INVALID_INN"] > 0
|
||||
assert codes["DUPLICATE_IN_FILE"] > 0
|
||||
|
||||
async with committed_sessionmaker() as session:
|
||||
assert await _counts(session) == before
|
||||
assert committed_storage.objects == {}
|
||||
@ -172,9 +172,9 @@ async def run(self, *, filename: str, content: bytes, dry_run: bool, on_existing
|
||||
- Create: `api/tests/test_import_full_export.py`
|
||||
- Modify (при необходимости): `api/src/services/directory_import.py` (пакетные вставки `insert(...).values([...])`, если > 60 с)
|
||||
|
||||
- [ ] **Step 1: Тест** `@pytest.mark.integration @pytest.mark.fixtures`: путь `Path(os.environ.get("ARM_KA_DOCS") or Path(__file__).resolve().parents[3] / "docs") / "source/biq/ecp-export-lagal-entity.xlsx"`; нет файла → `pytest.skip("нет выгрузки ЕЦП: …")`. `dry_run=true` под `admin@armka.local`; `time.perf_counter()` ≤ 60 с; `rows == 8400`; `offices_created == 53`; среди кодов есть `UNSUPPORTED_TYPE` (≥ 500 строк), `INVALID_INN`, `DUPLICATE_IN_FILE` (`LE_IN_OTHER_GROUP` на пустой БД невозможен — не проверять); `groups_created == 344` (R-15); таблицы после теста не выросли.
|
||||
- [ ] **Step 2:** запустить; при превышении 60 с — профилировать (`--durations`), перевести вставки на `session.execute(insert(Model), [dict, ...])` пакетами по 1000, повторить.
|
||||
- [ ] **Step 3: Commit** `test(import): dry_run полной выгрузки ЕЦП (8400 строк) укладывается в 60 с`.
|
||||
- [x] **Step 1: Тест** `@pytest.mark.integration @pytest.mark.fixtures`: путь `Path(os.environ.get("ARM_KA_DOCS") or Path(__file__).resolve().parents[3] / "docs") / "source/biq/ecp-export-lagal-entity.xlsx"`; нет файла → `pytest.skip("нет выгрузки ЕЦП: …")`. `dry_run=true` под `admin@armka.local`; `time.perf_counter()` ≤ 60 с; `rows == 8400`; `offices_created == 53`; среди кодов есть `UNSUPPORTED_TYPE` (≥ 500 строк), `INVALID_INN`, `DUPLICATE_IN_FILE` (`LE_IN_OTHER_GROUP` на пустой БД невозможен — не проверять); `groups_created == 344` (R-15); таблицы после теста не выросли.
|
||||
- [x] **Step 2:** запустить; при превышении 60 с — профилировать (`--durations`), перевести вставки на `session.execute(insert(Model), [dict, ...])` пакетами по 1000, повторить.
|
||||
- [x] **Step 3: Commit** `test(import): dry_run полной выгрузки ЕЦП (8400 строк) укладывается в 60 с`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ test-integration`). Пути — от корня репозитория. Доп
|
||||
+ `s3_object`), `POST /api/v1/directories/import` (multipart, `IMPORT_MAX_BYTES` = 20 МБ → 413, `DIRECTORY_ADMIN`),
|
||||
`schemas/directory_import.py`. Интеграционные тесты: сценарии спеки `directory-import` (срез РБ, идемпотентность, `update`,
|
||||
`LE_IN_OTHER_GROUP`, dry_run без следов, `MISSING_COLUMNS`); S3 — через `app.state.storage` с заглушкой в тестах.
|
||||
- [ ] 3.3 **Полная выгрузка (fixtures-тест).** Тест с маркером `fixtures`: `ARM_KA_DOCS/source/biq/ecp-export-lagal-entity.xlsx`,
|
||||
- [x] 3.3 **Полная выгрузка (fixtures-тест).** Тест с маркером `fixtures`: `ARM_KA_DOCS/source/biq/ecp-export-lagal-entity.xlsx`,
|
||||
`dry_run`, `rows = 8400`, `RF_CREATED = 53`, `groups_created = 344`, присутствие кодов, время ≤ 60 с; пропуск с причиной, если файла нет.
|
||||
Профилирование: при превышении — пакетные вставки (`insert().values([...])`).
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user