"""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)