feat: expand platform APIs, sources, and test coverage
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped

This commit is contained in:
2026-03-17 12:56:48 +01:00
parent b505c67968
commit 3d298ce352
101 changed files with 8387 additions and 292 deletions

View File

@@ -13,7 +13,12 @@ from apps.parsers.clients.base import (
)
from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient
from apps.parsers.clients.minpromtorg.manufactures import ManufacturesClient
from apps.parsers.clients.minpromtorg.schemas import IndustrialCertificate, Manufacturer
from apps.parsers.clients.minpromtorg.products import IndustrialProductsClient
from apps.parsers.clients.minpromtorg.schemas import (
IndustrialCertificate,
IndustrialProduct,
Manufacturer,
)
from apps.parsers.clients.proverki import ProverkiClient
from apps.parsers.clients.proverki.schemas import Inspection
from django.test import TestCase, tag
@@ -23,6 +28,7 @@ from tests.utils import Response, TestHTTPServer
from tests.utils.fixtures import (
build_minpromtorg_certificates_excel,
build_minpromtorg_manufacturers_excel,
build_minpromtorg_products_excel,
build_proverki_xml,
fake,
)
@@ -43,6 +49,10 @@ def _proxy_address() -> str:
return f"http://{fake.ipv4()}:{fake.port_number()}"
def _digits(length: int) -> str:
return "".join(str(fake.random_int(0, 9)) for _ in range(length))
class _RaisingAdapter(BaseAdapter):
def __init__(self, exc: Exception) -> None:
super().__init__()
@@ -440,6 +450,126 @@ class ManufacturesClientTest(TestCase):
self.assertEqual(result.address, "")
class IndustrialProductsClientTest(TestCase):
"""Tests for IndustrialProductsClient."""
def test_client_initialization(self):
client = IndustrialProductsClient()
self.assertIsNone(client.proxies)
self.assertEqual(client.host, "minpromtorg.gov.ru")
def test_fetch_products_success(self):
excel_bytes, rows = build_minpromtorg_products_excel(count=5)
date_str = fake.date_between(start_date="-30d", end_date="today").strftime(
"%Y%m%d"
)
file_name = f"industrial_products_{date_str}.xlsx"
with TestHTTPServer() as server:
server.add_json(
"/api/kss-document-preview",
{
"data": [
{
"name": IndustrialProductsClient().query,
"files": [
{"name": file_name, "url": f"/files/{file_name}"}
],
}
]
},
)
server.add_bytes(
f"/files/{file_name}",
excel_bytes,
content_type=(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
),
)
client = IndustrialProductsClient(
host=_host_from_base_url(server.base_url),
scheme="http",
http_adapter=server.adapter,
)
products = client.fetch_products()
self.assertEqual(len(products), len(rows))
self.assertIsInstance(products[0], IndustrialProduct)
self.assertSetEqual(
{product.registry_number for product in products},
{row.registry_number for row in rows},
)
def test_fetch_products_no_files(self):
with TestHTTPServer() as server:
server.add_json("/api/kss-document-preview", {"data": []})
client = IndustrialProductsClient(
host=_host_from_base_url(server.base_url),
scheme="http",
http_adapter=server.adapter,
)
products = client.fetch_products()
self.assertEqual(products, [])
def test_get_latest_file_url_falls_back_to_excel_file(self):
client = IndustrialProductsClient()
files = [
{"name": "readme.txt", "url": "/files/readme.txt"},
{"name": "registry.xlsx", "url": "/files/registry.xlsx"},
]
url = client._get_latest_file_url(files)
self.assertEqual(url, "https://minpromtorg.gov.ru/files/registry.xlsx")
def test_parse_row_valid(self):
client = IndustrialProductsClient()
header_map = {
"full_organisation_name": 0,
"ogrn": 1,
"inn": 2,
"registry_number": 3,
"product_name": 4,
"product_model": 5,
"okpd2_code": 6,
"tnved_code": 7,
"regulatory_document": 8,
}
row = (
fake.company(),
_digits(13),
_digits(10),
f"MPP-{_digits(8)}",
fake.sentence(nb_words=4),
fake.bothify(text="MODEL-###"),
"25.11",
_digits(10),
fake.sentence(nb_words=5),
)
result = client._parse_row(row, header_map)
self.assertIsInstance(result, IndustrialProduct)
self.assertEqual(result.registry_number, row[3])
self.assertEqual(result.product_name, row[4])
def test_parse_row_without_required_fields(self):
client = IndustrialProductsClient()
header_map = {
"full_organisation_name": 0,
"ogrn": 1,
"inn": 2,
"registry_number": 3,
"product_name": 4,
}
result = client._parse_row(
(fake.company(), _digits(13), _digits(10), "", ""), header_map
)
self.assertIsNone(result)
@tag("integration", "slow")
class IndustrialProductionClientIntegrationTest(TestCase):
"""Integration test using local HTTP server instead of external API."""
@@ -516,6 +646,44 @@ class ManufacturesClientIntegrationTest(TestCase):
self.assertEqual(len(manufacturers), len(rows))
@tag("integration", "slow")
class IndustrialProductsClientIntegrationTest(TestCase):
"""Integration test using local HTTP server instead of external API."""
def test_fetch_products_local_server(self):
excel_bytes, rows = build_minpromtorg_products_excel(count=3)
date_str = fake.date_between(start_date="-30d", end_date="today").strftime(
"%Y%m%d"
)
file_name = f"industrial_products_{date_str}.xlsx"
with TestHTTPServer() as server:
server.add_json(
"/api/kss-document-preview",
{
"data": [
{
"name": IndustrialProductsClient().query,
"files": [
{"name": file_name, "url": f"/files/{file_name}"}
],
}
]
},
)
server.add_bytes(f"/files/{file_name}", excel_bytes)
client = IndustrialProductsClient(
host=_host_from_base_url(server.base_url),
scheme="http",
timeout=30,
http_adapter=server.adapter,
)
products = client.fetch_products()
self.assertEqual(len(products), len(rows))
class ProverkiClientTest(TestCase):
"""Tests for ProverkiClient."""
@@ -574,7 +742,7 @@ class ProverkiClientTest(TestCase):
self.assertEqual(inspections[0].control_authority, authority)
def test_parse_xml_record_with_attributes(self):
from xml.etree import ElementTree as ET
from defusedxml import ElementTree as ET
row_inn = "".join(str(fake.random_int(0, 9)) for _ in range(10))
reg_num = "".join(str(fake.random_int(0, 9)) for _ in range(12))
@@ -590,7 +758,7 @@ class ProverkiClientTest(TestCase):
self.assertEqual(result.registration_number, reg_num)
def test_parse_xml_record_invalid(self):
from xml.etree import ElementTree as ET
from defusedxml import ElementTree as ET
element = ET.fromstring("<empty_record></empty_record>") # noqa: S314
client = ProverkiClient()