- перенесены тесты parsers из src/apps/parsers/tests в tests/apps/parsers - обновлены тесты задач под текущее поведение Celery (ошибки пробрасываются исключениями) - убрана зависимость тестов от внешнего брокера через локальные eager-вызовы - добавлены/уточнены фабрики и импорты для единой структуры тестов - обновлены README и CHANGELOG с новым правилом размещения тестов и запуском
562 lines
18 KiB
Python
562 lines
18 KiB
Python
"""
|
||
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))
|