feat(parsers): добавлен API клиент для checko.ru
- Реализован CheckoClient с поддержкой всех 10 эндпоинтов API v2 - Frozen dataclass модели для запросов и ответов - Справочники ОКВЭД2, ОКФС, ОКОПФ, ОКПД, статусы компаний - Маппинг русских полей API на английские имена - Unit тесты с моками - E2E тесты с реальными запросами - Настройка CHECKO_API_KEY в settings.py
This commit is contained in:
631
tests/apps/parsers/test_checko.py
Normal file
631
tests/apps/parsers/test_checko.py
Normal file
@@ -0,0 +1,631 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user