Files
mostovik-backend/tests/apps/parsers/test_checko.py
Aleksandr Meshchriakov c36c7b9ba9 feat(parsers): добавлен API клиент для checko.ru
- Реализован CheckoClient с поддержкой всех 10 эндпоинтов API v2
- Frozen dataclass модели для запросов и ответов
- Справочники ОКВЭД2, ОКФС, ОКОПФ, ОКПД, статусы компаний
- Маппинг русских полей API на английские имена
- Unit тесты с моками
- E2E тесты с реальными запросами
- Настройка CHECKO_API_KEY в settings.py
2026-02-03 17:00:19 +01:00

632 lines
21 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.
"""Tests for Checko API client."""
import json
from pathlib import Path
from unittest.mock import MagicMock, patch
from django.test import SimpleTestCase, tag
from apps.parsers.clients.checko import (
CheckoClient,
CheckoAPIError,
CheckoNotFoundError,
CheckoRateLimitError,
CheckoValidationError,
CompanyRequest,
ContractsRequest,
EntrepreneurRequest,
FinancesRequest,
LegalCasesRequest,
PersonRequest,
SearchRequest,
SearchType,
ObjectType,
ContractLaw,
)
from apps.parsers.clients.checko.datasets import (
OKVED2,
OKFS,
OKOPF,
OKPD,
OKPD2,
AccountCodes,
CompanyStatuses,
EntrepreneurStatuses,
)
class CheckoClientInitTest(SimpleTestCase):
"""Tests for CheckoClient initialization."""
def test_client_initialization_default(self):
"""Test client initializes with defaults."""
client = CheckoClient(api_key="test_key")
self.assertEqual(client.api_key, "test_key")
self.assertEqual(client.base_url, "https://api.checko.ru/v2")
self.assertEqual(client.timeout, 30)
self.assertIsNone(client.proxies)
def test_client_initialization_custom(self):
"""Test client initializes with custom params."""
client = CheckoClient(
api_key="test_key",
base_url="https://custom.api.com",
timeout=60,
proxies=["http://proxy:8080"],
)
self.assertEqual(client.base_url, "https://custom.api.com")
self.assertEqual(client.timeout, 60)
self.assertEqual(client.proxies, ["http://proxy:8080"])
def test_context_manager(self):
"""Test client works as context manager."""
with CheckoClient(api_key="test_key") as client:
self.assertIsInstance(client, CheckoClient)
class CheckoClientValidationTest(SimpleTestCase):
"""Tests for request validation."""
def setUp(self):
self.client = CheckoClient(api_key="test_key")
def test_company_request_requires_identifier(self):
"""Test CompanyRequest requires at least one identifier."""
with self.assertRaises(CheckoValidationError) as context:
self.client.get_company(CompanyRequest())
self.assertIn("ogrn", str(context.exception).lower())
def test_entrepreneur_request_requires_identifier(self):
"""Test EntrepreneurRequest requires at least one identifier."""
with self.assertRaises(CheckoValidationError) as context:
self.client.get_entrepreneur(EntrepreneurRequest())
self.assertIn("ogrn", str(context.exception).lower())
def test_search_request_min_query_length(self):
"""Test SearchRequest validates query length."""
with self.assertRaises(CheckoValidationError) as context:
self.client.search(
SearchRequest(
by=SearchType.NAME,
obj=ObjectType.ORGANIZATION,
query="abc", # Too short
)
)
self.assertIn("4", str(context.exception))
def test_finances_request_requires_identifier(self):
"""Test FinancesRequest requires at least one identifier."""
with self.assertRaises(CheckoValidationError) as context:
self.client.get_finances(FinancesRequest())
self.assertIn("ogrn", str(context.exception).lower())
class CheckoClientApiTest(SimpleTestCase):
"""Tests for API requests with mocked responses."""
def setUp(self):
self.client = CheckoClient(api_key="test_key")
@patch.object(CheckoClient, "_request")
def test_get_company_success(self, mock_request):
"""Test successful company retrieval."""
mock_request.return_value = {
"data": {
"ogrn": "1027700132195",
"inn": "7707083893",
"kpp": "773601001",
"full_name": "ПУБЛИЧНОЕ АКЦИОНЕРНОЕ ОБЩЕСТВО \"СБЕРБАНК РОССИИ\"",
"short_name": "ПАО Сбербанк",
"reg_date": "1991-06-20",
"status": {
"restricted_access": False,
"code": "100",
"name": "Действующее",
},
"legal_address": {
"restricted_access": False,
"full_address": "г Москва, ул Вавилова, д 19",
},
},
"meta": {
"status": "ok",
"today_request_count": 1,
"balance": 99.90,
},
}
response = self.client.get_company(CompanyRequest(inn="7707083893"))
self.assertEqual(response.meta.status, "ok")
self.assertEqual(response.data.inn, "7707083893")
self.assertEqual(response.data.short_name, "ПАО Сбербанк")
self.assertEqual(response.data.status.code, "100")
@patch.object(CheckoClient, "_request")
def test_get_company_not_found(self, mock_request):
"""Test company not found error."""
mock_request.side_effect = CheckoNotFoundError(
message="Организация не найдена",
balance=99.0,
)
with self.assertRaises(CheckoNotFoundError):
self.client.get_company(CompanyRequest(inn="0000000000"))
@patch.object(CheckoClient, "_request")
def test_get_entrepreneur_success(self, mock_request):
"""Test successful entrepreneur retrieval."""
mock_request.return_value = {
"data": {
"ogrnip": "304770000000001",
"inn": "770100000001",
"full_name": "Иванов Иван Иванович",
"reg_date": "2010-01-15",
"status": {
"code": "100",
"name": "Действующий",
},
},
"meta": {
"status": "ok",
"today_request_count": 2,
"balance": 99.80,
},
}
response = self.client.get_entrepreneur(
EntrepreneurRequest(inn="770100000001")
)
self.assertEqual(response.data.ogrnip, "304770000000001")
self.assertEqual(response.data.full_name, "Иванов Иван Иванович")
@patch.object(CheckoClient, "_request")
def test_search_organizations(self, mock_request):
"""Test organization search."""
mock_request.return_value = {
"data": {
"organizations": [
{
"ogrn": "1027700132195",
"inn": "7707083893",
"short_name": "ПАО Сбербанк",
"status": "Действующее",
},
{
"ogrn": "1027700000000",
"inn": "7700000000",
"short_name": "Сбербанк Капитал",
"status": "Действующее",
},
],
"pagination": {
"total_records": 2,
"total_pages": 1,
"current_page": 1,
},
},
"meta": {
"status": "ok",
"today_request_count": 3,
"balance": 99.70,
},
}
response = self.client.search(
SearchRequest(
by=SearchType.NAME,
obj=ObjectType.ORGANIZATION,
query="Сбербанк",
)
)
self.assertEqual(len(response.data.organizations), 2)
self.assertEqual(response.data.organizations[0].inn, "7707083893")
self.assertEqual(response.data.pagination.total_records, 2)
@patch.object(CheckoClient, "_request")
def test_get_finances(self, mock_request):
"""Test financial data retrieval."""
mock_request.return_value = {
"data": {
"ogrn": "1027700132195",
"inn": "7707083893",
"reports": [
{
"year": 2023,
"balance": [
{"code": "1100", "current": 1000000, "previous": 900000},
{"code": "1200", "current": 500000, "previous": 450000},
],
"profit_loss": [
{"code": "2110", "current": 2000000,
"previous": 1800000},
],
}
],
"summary": {
"revenue": 2000000,
"profit": 500000,
"assets": 1500000,
},
},
"meta": {
"status": "ok",
"today_request_count": 4,
"balance": 99.60,
},
}
response = self.client.get_finances(FinancesRequest(inn="7707083893"))
self.assertEqual(len(response.data.reports), 1)
self.assertEqual(response.data.reports[0].year, 2023)
self.assertEqual(response.data.summary.revenue, 2000000)
@patch.object(CheckoClient, "_request")
def test_get_contracts(self, mock_request):
"""Test contracts retrieval."""
mock_request.return_value = {
"data": {
"contracts": [
{
"registry_number": "0123456789012345",
"publish_date": "2024-01-15",
"price": 1000000,
"status": "Исполнение",
"subject": "Поставка оборудования",
"law": "44",
}
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
"total_sum": 1000000,
},
"meta": {
"status": "ok",
"today_request_count": 5,
"balance": 99.50,
},
}
response = self.client.get_contracts(
ContractsRequest(inn="7707083893", law=ContractLaw.FZ44)
)
self.assertEqual(len(response.data.contracts), 1)
self.assertEqual(response.data.contracts[0].price, 1000000)
self.assertEqual(response.data.total_sum, 1000000)
@patch.object(CheckoClient, "_request")
def test_get_legal_cases(self, mock_request):
"""Test legal cases retrieval."""
mock_request.return_value = {
"data": {
"cases": [
{
"case_number": "А40-12345/2024",
"court_name": "Арбитражный суд г. Москвы",
"claim_amount": 5000000,
"status": "Рассмотрение дела",
"plaintiffs": [{"name": "ООО Истец", "inn": "1234567890"}],
"defendants": [{"name": "ООО Ответчик", "inn": "0987654321"}],
}
],
"pagination": {
"total_records": 1,
"total_pages": 1,
"current_page": 1,
},
"total_claim_amount": 5000000,
},
"meta": {
"status": "ok",
"today_request_count": 6,
"balance": 99.40,
},
}
response = self.client.get_legal_cases(
LegalCasesRequest(inn="7707083893"))
self.assertEqual(len(response.data.cases), 1)
self.assertEqual(response.data.cases[0].case_number, "А40-12345/2024")
self.assertEqual(len(response.data.cases[0].plaintiffs), 1)
class CheckoClientErrorHandlingTest(SimpleTestCase):
"""Tests for error handling."""
def setUp(self):
self.client = CheckoClient(api_key="test_key")
@patch("apps.parsers.clients.checko.client.BaseHTTPClient.get_json")
def test_api_error_handling(self, mock_get_json):
"""Test API error response handling."""
mock_get_json.return_value = {
"meta": {
"status": "error",
"message": "Invalid API key",
"balance": 0,
"today_request_count": 0,
}
}
with self.assertRaises(CheckoAPIError) as context:
self.client.get_company(CompanyRequest(inn="7707083893"))
self.assertIn("Invalid API key", str(context.exception))
@patch("apps.parsers.clients.checko.client.BaseHTTPClient.get_json")
def test_rate_limit_error_handling(self, mock_get_json):
"""Test rate limit error detection."""
mock_get_json.return_value = {
"meta": {
"status": "error",
"message": "Превышен лимит запросов",
"balance": 0,
"today_request_count": 100,
}
}
with self.assertRaises(CheckoRateLimitError):
self.client.get_company(CompanyRequest(inn="7707083893"))
@patch("apps.parsers.clients.checko.client.BaseHTTPClient.get_json")
def test_not_found_error_handling(self, mock_get_json):
"""Test not found error detection."""
mock_get_json.return_value = {
"meta": {
"status": "error",
"message": "Организация не найдена",
"balance": 99.0,
"today_request_count": 1,
}
}
with self.assertRaises(CheckoNotFoundError):
self.client.get_company(CompanyRequest(inn="0000000000"))
class CheckoRequestModelsTest(SimpleTestCase):
"""Tests for request dataclass models."""
def test_company_request_to_params(self):
"""Test CompanyRequest.to_params()."""
request = CompanyRequest(inn="7707083893", source=True)
params = request.to_params()
self.assertEqual(params["inn"], "7707083893")
self.assertEqual(params["source"], "true")
self.assertNotIn("ogrn", params)
def test_search_request_to_params(self):
"""Test SearchRequest.to_params()."""
request = SearchRequest(
by=SearchType.NAME,
obj=ObjectType.ORGANIZATION,
query="Сбербанк",
region="77",
active=True,
limit=50,
page=2,
)
params = request.to_params()
self.assertEqual(params["by"], "name")
self.assertEqual(params["obj"], "org")
self.assertEqual(params["query"], "Сбербанк")
self.assertEqual(params["region"], "77")
self.assertEqual(params["active"], "true")
self.assertEqual(params["limit"], "50")
self.assertEqual(params["page"], "2")
def test_contracts_request_to_params(self):
"""Test ContractsRequest.to_params()."""
request = ContractsRequest(
inn="7707083893",
law=ContractLaw.FZ44,
)
params = request.to_params()
self.assertEqual(params["inn"], "7707083893")
self.assertEqual(params["law"], "44")
def test_legal_cases_request_to_params(self):
"""Test LegalCasesRequest.to_params()."""
request = LegalCasesRequest(
inn="7707083893",
actual=True,
active=True,
date_from="2024-01-01",
claim_amount_from=1000000,
)
params = request.to_params()
self.assertEqual(params["inn"], "7707083893")
self.assertEqual(params["actual"], "true")
self.assertEqual(params["active"], "true")
self.assertEqual(params["date_from"], "2024-01-01")
self.assertEqual(params["claim_amount_from"], "1000000")
@tag("datasets")
class CheckoDatasetsTest(SimpleTestCase):
"""Tests for reference datasets."""
def test_okved2_get(self):
"""Test OKVED2 dataset get by code."""
item = OKVED2.get("62.01")
self.assertIsNotNone(item)
self.assertEqual(item.code, "62.01")
self.assertIn("программ", item.name.lower())
def test_okved2_get_name(self):
"""Test OKVED2 dataset get_name."""
name = OKVED2.get_name("62.01")
self.assertIsNotNone(name)
self.assertIn("программ", name.lower())
def test_okved2_search(self):
"""Test OKVED2 search functionality."""
results = OKVED2.search("программ")
self.assertGreater(len(results), 0)
for item in results:
self.assertIn("программ", item.name.lower())
def test_okved2_exists(self):
"""Test OKVED2 exists check."""
self.assertTrue(OKVED2.exists("62.01"))
self.assertFalse(OKVED2.exists("99.99.99"))
def test_okved2_get_children(self):
"""Test OKVED2 hierarchy - get children."""
children = OKVED2.get_children("62")
self.assertGreater(len(children), 0)
for child in children:
self.assertTrue(child.code.startswith("62."))
def test_okfs_get(self):
"""Test OKFS dataset."""
item = OKFS.get("12")
self.assertIsNotNone(item)
self.assertEqual(item.code, "12")
def test_okfs_get_name(self):
"""Test OKFS get_name."""
name = OKFS.get_name("12")
self.assertIsNotNone(name)
def test_okopf_get(self):
"""Test OKOPF dataset."""
# Check for common OPF code
item = OKOPF.get("12300") # ООО
if item:
self.assertEqual(item.code, "12300")
def test_account_codes_get(self):
"""Test AccountCodes dataset."""
item = AccountCodes.get("1100")
self.assertIsNotNone(item)
self.assertEqual(item.code, "1100")
def test_company_statuses_get(self):
"""Test CompanyStatuses dataset."""
# Should return builtin value if no JSON
name = CompanyStatuses.get_name("100")
# May be None if no data, but shouldn't raise
self.assertTrue(name is None or isinstance(name, str))
def test_entrepreneur_statuses_get(self):
"""Test EntrepreneurStatuses dataset."""
name = EntrepreneurStatuses.get_name("100")
# May be None if no data, but shouldn't raise
self.assertTrue(name is None or isinstance(name, str))
def test_okpd_get(self):
"""Test OKPD dataset."""
# Check all() works
items = OKPD.all()
self.assertIsInstance(items, list)
def test_okpd2_get(self):
"""Test OKPD2 dataset."""
items = OKPD2.all()
self.assertIsInstance(items, list)
class CheckoClientIteratorsTest(SimpleTestCase):
"""Tests for paginated iterators."""
def setUp(self):
self.client = CheckoClient(api_key="test_key")
@patch.object(CheckoClient, "_request")
def test_iter_contracts_pagination(self, mock_request):
"""Test contracts iterator handles pagination."""
# First page
mock_request.side_effect = [
{
"data": {
"contracts": [
{"registry_number": "0001", "price": 100},
{"registry_number": "0002", "price": 200},
],
"pagination": {
"total_records": 4,
"total_pages": 2,
"current_page": 1,
},
},
"meta": {"status": "ok", "today_request_count": 1, "balance": 99},
},
# Second page
{
"data": {
"contracts": [
{"registry_number": "0003", "price": 300},
{"registry_number": "0004", "price": 400},
],
"pagination": {
"total_records": 4,
"total_pages": 2,
"current_page": 2,
},
},
"meta": {"status": "ok", "today_request_count": 2, "balance": 98},
},
]
contracts = list(
self.client.iter_contracts(
ContractsRequest(inn="7707083893", law=ContractLaw.FZ44)
)
)
self.assertEqual(len(contracts), 4)
self.assertEqual(contracts[0].registry_number, "0001")
self.assertEqual(contracts[3].registry_number, "0004")
@patch.object(CheckoClient, "_request")
def test_iter_legal_cases_empty(self, mock_request):
"""Test legal cases iterator handles empty results."""
mock_request.return_value = {
"data": {
"cases": [],
"pagination": {
"total_records": 0,
"total_pages": 0,
"current_page": 1,
},
},
"meta": {"status": "ok", "today_request_count": 1, "balance": 99},
}
cases = list(
self.client.iter_legal_cases(LegalCasesRequest(inn="0000000000"))
)
self.assertEqual(len(cases), 0)