refactor(parsers): перенести тесты в ROOT_DIR/tests и синхронизировать контракты задач

- перенесены тесты parsers из src/apps/parsers/tests в tests/apps/parsers

- обновлены тесты задач под текущее поведение Celery (ошибки пробрасываются исключениями)

- убрана зависимость тестов от внешнего брокера через локальные eager-вызовы

- добавлены/уточнены фабрики и импорты для единой структуры тестов

- обновлены README и CHANGELOG с новым правилом размещения тестов и запуском
This commit is contained in:
2026-03-04 15:35:50 +01:00
parent 0738c53040
commit 052389d921
18 changed files with 657 additions and 284 deletions

View File

@@ -0,0 +1,561 @@
"""
E2E тесты для Checko API клиента.
Тесты выполняются через локальный HTTP сервер (без внешних API).
"""
from __future__ import annotations
import json
from urllib.parse import parse_qs
from apps.parsers.clients.checko import (
CheckoAPIError,
CheckoClient,
CheckoNotFoundError,
CompanyRequest,
ContractLaw,
ContractsRequest,
EnforcementsRequest,
FinancesRequest,
InspectionsRequest,
LegalCasesRequest,
ObjectType,
SearchRequest,
SearchType,
)
from apps.parsers.clients.checko.datasets import (
OKFS,
OKOPF,
OKVED2,
AccountCodes,
)
from django.test import TestCase
from tests.utils import Response, TestHTTPServer
from tests.utils.fixtures import fake
def _digits(length: int) -> str:
return "".join(str(fake.random_int(0, 9)) for _ in range(length))
def _meta_ok() -> dict:
return {
"status": "ok",
"today_request_count": fake.random_int(min=1, max=10),
"balance": float(fake.pydecimal(left_digits=2, right_digits=2, positive=True)),
}
def _client_for(server: TestHTTPServer, api_key: str = "test_key") -> CheckoClient:
return CheckoClient(
api_key=api_key,
base_url=f"{server.base_url}/v2",
http_adapter=server.adapter,
)
def _json_response(payload: dict) -> Response:
return Response(
status=200,
body=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers={"Content-Type": "application/json; charset=utf-8"},
)
class CheckoClientE2ETestCase(TestCase):
"""E2E тесты клиента CheckoClient через локальный HTTP сервер."""
def test_get_company_by_inn(self):
inn = _digits(10)
ogrn = _digits(13)
short_name = fake.company()
with TestHTTPServer() as server:
server.add_json(
"/v2/company",
{
"data": {
"ogrn": ogrn,
"inn": inn,
"short_name": short_name,
"full_name": fake.company(),
"status": {"code": "100", "name": "Действующее"},
"legal_address": {
"full_address": fake.address().replace("\n", ", "),
"postal_code": _digits(6),
},
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_company(CompanyRequest(inn=inn))
self.assertEqual(response.meta.status, "ok")
self.assertEqual(response.data.inn, inn)
self.assertEqual(response.data.ogrn, ogrn)
self.assertEqual(response.data.short_name, short_name)
self.assertIsNotNone(response.data.legal_address)
def test_get_company_by_ogrn(self):
inn = _digits(10)
ogrn = _digits(13)
with TestHTTPServer() as server:
server.add_json(
"/v2/company",
{
"data": {
"ogrn": ogrn,
"inn": inn,
"short_name": fake.company(),
"status": {"code": "100", "name": "Действующее"},
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_company(CompanyRequest(ogrn=ogrn))
self.assertEqual(response.meta.status, "ok")
self.assertEqual(response.data.ogrn, ogrn)
self.assertEqual(response.data.inn, inn)
def test_get_company_with_source(self):
inn = _digits(10)
source_data = {"raw": fake.text(max_nb_chars=50)}
with TestHTTPServer() as server:
server.add_json(
"/v2/company",
{
"data": {
"inn": inn,
"ogrn": _digits(13),
"short_name": fake.company(),
"status": {"code": "100", "name": "Действующее"},
},
"meta": _meta_ok(),
"source_data": source_data,
},
)
client = _client_for(server)
response = client.get_company(CompanyRequest(inn=inn, source=True))
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.source_data)
def test_search_by_name(self):
inn = _digits(10)
ogrn = _digits(13)
name = fake.company()
with TestHTTPServer() as server:
server.add_json(
"/v2/search",
{
"data": {
"records": [
{"inn": inn, "ogrn": ogrn, "short_name": name},
],
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.search(
SearchRequest(
by=SearchType.NAME,
obj=ObjectType.ORGANIZATION,
query=f"{name} {fake.word()}",
limit=10,
)
)
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
self.assertGreater(len(response.data.organizations), 0)
def test_search_active_only(self):
inn = _digits(10)
with TestHTTPServer() as server:
server.add_json(
"/v2/search",
{
"data": {
"records": [
{
"inn": inn,
"status": {"code": "100", "name": "Действующее"},
},
],
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.search(
SearchRequest(
by=SearchType.NAME,
obj=ObjectType.ORGANIZATION,
query=fake.company(),
active=True,
limit=5,
)
)
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
def test_get_finances(self):
inn = _digits(10)
ogrn = _digits(13)
with TestHTTPServer() as server:
server.add_json(
"/v2/finances",
{
"data": {
"inn": inn,
"ogrn": ogrn,
"reports": [
{
"year": fake.random_int(min=2020, max=2025),
"period": 12,
"lines": [
{
"code": _digits(4),
"value": fake.random_int(10, 9999),
}
],
}
],
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_finances(FinancesRequest(inn=inn))
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
self.assertEqual(response.data.inn, inn)
def test_get_contracts_fz44(self):
inn = _digits(10)
registry_number = _digits(12)
with TestHTTPServer() as server:
server.add_json(
"/v2/contracts",
{
"data": {
"contracts": [
{"registry_number": registry_number, "law": "44-FZ"},
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_contracts(
ContractsRequest(inn=inn, law=ContractLaw.FZ44, limit=10)
)
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
self.assertGreater(len(response.data.contracts), 0)
def test_get_contracts_fz223(self):
inn = _digits(10)
with TestHTTPServer() as server:
server.add_json(
"/v2/contracts",
{
"data": {
"contracts": [
{"registry_number": _digits(12), "law": "223-FZ"},
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_contracts(
ContractsRequest(inn=inn, law=ContractLaw.FZ223, limit=10)
)
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
def test_get_inspections(self):
inn = _digits(10)
with TestHTTPServer() as server:
server.add_json(
"/v2/inspections",
{
"data": {
"inspections": [
{
"id": fake.uuid4(),
"status": fake.word(),
"authority_name": fake.company(),
"subject": fake.word(),
}
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_inspections(InspectionsRequest(inn=inn, limit=10))
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
self.assertGreater(len(response.data.inspections), 0)
def test_get_enforcements(self):
inn = _digits(10)
with TestHTTPServer() as server:
server.add_json(
"/v2/enforcements",
{
"data": {
"enforcements": [
{
"number": _digits(12),
"status": fake.word(),
"debt_amount": fake.random_int(1_000, 10_000),
}
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
"total_debt": fake.random_int(1_000, 100_000),
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_enforcements(EnforcementsRequest(inn=inn, limit=10))
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
self.assertGreater(len(response.data.enforcements), 0)
def test_get_legal_cases(self):
inn = _digits(10)
with TestHTTPServer() as server:
server.add_json(
"/v2/legal-cases",
{
"data": {
"cases": [
{
"case_number": fake.bothify(text="A-####/##"),
"status": fake.word(),
},
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
"total_claim_amount": fake.random_int(1000, 10000),
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_legal_cases(LegalCasesRequest(inn=inn, limit=10))
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
self.assertGreater(len(response.data.cases), 0)
def test_get_legal_cases_filtered(self):
inn = _digits(10)
with TestHTTPServer() as server:
server.add_json(
"/v2/legal-cases",
{
"data": {
"cases": [
{
"case_number": fake.bothify(text="A-####/##"),
"status": fake.word(),
},
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
},
"meta": _meta_ok(),
},
)
client = _client_for(server)
response = client.get_legal_cases(
LegalCasesRequest(inn=inn, actual=True, active=True, limit=5)
)
self.assertEqual(response.meta.status, "ok")
self.assertIsNotNone(response.data)
class CheckoClientIteratorsE2ETestCase(TestCase):
"""E2E тесты итераторов с автопагинацией."""
def test_iter_contracts_pagination(self):
inn = _digits(10)
def contracts_handler(request, _body):
page = int(parse_qs(request.query).get("page", ["1"])[0])
total_pages = 3
contracts = [
{"registry_number": f"{page}-{_digits(10)}", "law": "44-FZ"},
]
return _json_response(
{
"data": {
"contracts": contracts,
"pagination": {
"total_records": total_pages,
"total_pages": total_pages,
"current_page": page,
},
},
"meta": _meta_ok(),
}
)
with TestHTTPServer() as server:
server.add_route("GET", "/v2/contracts", contracts_handler)
client = _client_for(server)
contracts = list(
client.iter_contracts(
ContractsRequest(inn=inn, law=ContractLaw.FZ44, limit=1)
)
)
self.assertGreater(len(contracts), 1)
def test_iter_legal_cases_pagination(self):
inn = _digits(10)
def cases_handler(request, _body):
page = int(parse_qs(request.query).get("page", ["1"])[0])
total_pages = 2
cases = [
{"case_number": f"A-{page}-{_digits(5)}", "status": fake.word()},
]
return _json_response(
{
"data": {
"cases": cases,
"pagination": {
"total_records": total_pages,
"total_pages": total_pages,
"current_page": page,
},
},
"meta": _meta_ok(),
}
)
with TestHTTPServer() as server:
server.add_route("GET", "/v2/legal-cases", cases_handler)
client = _client_for(server)
cases = list(client.iter_legal_cases(LegalCasesRequest(inn=inn, limit=1)))
self.assertGreater(len(cases), 1)
class CheckoClientErrorE2ETestCase(TestCase):
"""E2E тесты обработки ошибок."""
def test_company_not_found(self):
with TestHTTPServer() as server:
server.add_json(
"/v2/company",
{
"data": None,
"meta": {"status": "error", "message": "не найден"},
},
)
client = _client_for(server)
with self.assertRaises(CheckoNotFoundError):
client.get_company(CompanyRequest(inn=_digits(10)))
def test_invalid_api_key(self):
with TestHTTPServer() as server:
server.add_json(
"/v2/company",
{
"data": None,
"meta": {"status": "error", "message": "Invalid API key"},
},
)
client = _client_for(server, api_key="invalid_key")
with self.assertRaises(CheckoAPIError):
client.get_company(CompanyRequest(inn=_digits(10)))
class CheckoDatasetsE2ETestCase(TestCase):
"""E2E тесты справочников (локальные JSON данные)."""
def test_okved2_load_and_search(self):
items = OKVED2.all()
self.assertGreater(len(items), 0)
sample = items[0]
self.assertIsNotNone(OKVED2.get(sample.code))
search_term = sample.name.split(" ")[0]
results = OKVED2.search(search_term)
self.assertGreater(len(results), 0)
def test_okfs_load(self):
items = OKFS.all()
self.assertGreater(len(items), 0)
def test_okopf_load(self):
items = OKOPF.all()
self.assertGreater(len(items), 0)
def test_account_codes_load(self):
items = AccountCodes.all()
self.assertGreater(len(items), 0)
sample = items[0]
self.assertIsNotNone(AccountCodes.get(sample.code))