Files
mostovik-backend/tests/apps/parsers/test_structured_data_client.py
Aleksandr Meshchriakov 007cecc8d5
All checks were successful
CI/CD Pipeline / Quality Gate (push) Successful in 36s
CI/CD Pipeline / Build and Push Images (push) Successful in 15s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 24s
feat: expand registry ingestion and demo exchange
2026-07-16 12:59:23 +02:00

354 lines
14 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.
from __future__ import annotations
from apps.parsers.clients.base import HTTPClientError
from apps.parsers.clients.common.structured import (
EIS_CA_BUNDLE_PATH,
StructuredDataClient,
)
from django.test import SimpleTestCase
class _FakeHTTPClient:
def __init__(self, responses: dict[str, bytes | Exception]) -> None:
self.responses = responses
self.downloaded_urls: list[str] = []
def download_file(self, endpoint: str, **_kwargs) -> bytes:
self.downloaded_urls.append(endpoint)
response = self.responses[endpoint]
if isinstance(response, Exception):
raise response
return response
class _FakeGispHTTPClient:
def __init__(self) -> None:
self.endpoint = ""
self.payload: dict | None = None
def post_json(
self,
endpoint: str,
*,
payload: dict | None = None,
**_kwargs,
) -> dict:
self.endpoint = endpoint
self.payload = payload
return {"items": [{"res_number": "123", "_product_name": "Станок"}]}
class StructuredDataClientGispTest(SimpleTestCase):
def test_products_request_omits_unsupported_res_date_sort(self):
client = StructuredDataClient(source="mpt_products")
fake_http_client = _FakeGispHTTPClient()
client._http_client = fake_http_client
records = client.fetch_records(file_url="https://gisp.gov.ru/pp719v2/pub/prod/")
self.assertEqual(len(records), 1)
self.assertEqual(records[0].external_id, "123")
self.assertEqual(
fake_http_client.endpoint,
"https://gisp.gov.ru/pp719v2/pub/prod/b/",
)
self.assertEqual(
fake_http_client.payload,
{
"opt": {
"skip": 0,
"take": 100,
"requireTotalCount": True,
}
},
)
class StructuredDataClientFasGozParsingTest(SimpleTestCase):
def test_person_name_populates_record_title(self):
client = StructuredDataClient(source="fas_goz")
records = client.fetch_records(
content="""
<table>
<tr>
<td>РГОЗ-001</td>
<td>ФАС России</td>
<td>Постановление № 1</td>
<td>14.07.2026</td>
<td>Исполнено</td>
<td>ООО ПРОГРЕСС</td>
<td>ПРОГРЕСС</td>
<td>г. Москва</td>
<td>7707083893</td>
</tr>
</table>
""".encode(),
file_name="register.html",
)
self.assertEqual(len(records), 1)
self.assertEqual(records[0].organisation_name, "ООО ПРОГРЕСС")
self.assertEqual(records[0].title, "ООО ПРОГРЕСС")
class StructuredDataClientEisCardEnrichmentTest(SimpleTestCase):
def test_eis_client_uses_project_scoped_trusted_ca_bundle(self):
client = StructuredDataClient(source="procurements_44fz")
self.assertTrue(EIS_CA_BUNDLE_PATH.is_file())
self.assertEqual(client.http_client.verify_ssl, str(EIS_CA_BUNDLE_PATH))
def test_procurement_card_enriches_customer_identity_from_detail_page(self):
detail_url = (
"https://zakupki.gov.ru/epz/order/notice/ea20/view/"
"common-info.html?regNumber=0338100002026000022"
)
client = StructuredDataClient(source="procurements_44fz")
client._http_client = _FakeHTTPClient(
{
detail_url: """
<html>
<body>
<h2>Сведения о заказчике</h2>
<div>Полное наименование</div>
<div>ФЕДЕРАЛЬНОЕ ГБУ НАУКИ</div>
<div>ИНН / КПП</div>
<div>4101020011 / 410101001</div>
<div>ОГРН</div>
<div>1024101023456</div>
</body>
</html>
""".encode()
}
)
records = client.fetch_records(
content=f"""
<html>
<body>
<div class="search-registry-entry-block">
<a href="{detail_url}">№ 0338100002026000022</a>
<div>Работа комиссии</div>
<div>Объект закупки</div>
<div>Поставка оборудования</div>
<div>Заказчик</div>
<div>ФЕДЕРАЛЬНОЕ ГБУ НАУКИ</div>
<div>Начальная цена</div>
<div>1 000,00 ₽</div>
<div>Размещено</div>
<div>19.05.2026</div>
</div>
</body>
</html>
""".encode(),
file_name="results.html",
)
self.assertEqual(len(records), 1)
record = records[0]
self.assertEqual(record.inn, "4101020011")
self.assertEqual(record.ogrn, "1024101023456")
self.assertEqual(record.payload["kpp"], "410101001")
self.assertEqual(record.payload["detail_url"], detail_url)
self.assertEqual(client._http_client.downloaded_urls, [detail_url])
def test_procurement_card_keeps_record_when_detail_page_is_unavailable(self):
detail_url = (
"https://zakupki.gov.ru/epz/order/notice/ea20/view/"
"common-info.html?regNumber=0338100002026000022"
)
client = StructuredDataClient(source="procurements_44fz")
client._http_client = _FakeHTTPClient(
{detail_url: HTTPClientError("detail unavailable", url=detail_url)}
)
records = client.fetch_records(
content=f"""
<html>
<body>
<div class="search-registry-entry-block">
<a href="{detail_url}">№ 0338100002026000022</a>
<div>Объект закупки</div>
<div>Поставка оборудования</div>
<div>Заказчик</div>
<div>ФЕДЕРАЛЬНОЕ ГБУ НАУКИ</div>
</div>
</body>
</html>
""".encode(),
file_name="results.html",
)
self.assertEqual(len(records), 1)
self.assertEqual(records[0].inn, "")
self.assertEqual(records[0].ogrn, "")
self.assertEqual(records[0].organisation_name, "ФЕДЕРАЛЬНОЕ ГБУ НАУКИ")
def test_procurement_card_prefers_organization_page_for_identity(self):
detail_url = (
"https://zakupki.gov.ru/epz/order/notice/zk20/view/"
"common-info.html?regNumber=0322300001726000667"
)
identity_url = (
"https://zakupki.gov.ru/epz/organization/view/"
"info.html?organizationCode=03223000017"
)
client = StructuredDataClient(source="procurements_44fz")
client._http_client = _FakeHTTPClient(
{
identity_url: """
<div>Заказчик</div>
<div>21.07.2016</div>
<div>ОГРН</div><div>1022701405737</div>
<div>ИНН</div><div>2725006476</div>
<div>КПП</div><div>272501001</div>
<div>Полное наименование</div><div>КГБУЗ БОЛЬНИЦА</div>
""".encode()
}
)
records = client.fetch_records(
content=f"""
<div class="search-registry-entry-block">
<div>44-ФЗ</div>
<a href="{detail_url}">№ 0322300001726000667</a>
<div>Объект закупки</div><div>Поставка оборудования</div>
<div>Заказчик</div><div>КГБУЗ БОЛЬНИЦА</div>
<a href="{identity_url}">КГБУЗ БОЛЬНИЦА</a>
</div>
""".encode(),
file_name="results.html",
)
self.assertEqual(records[0].url, detail_url)
self.assertEqual(records[0].inn, "2725006476")
self.assertEqual(records[0].ogrn, "1022701405737")
self.assertEqual(records[0].organisation_name, "КГБУЗ БОЛЬНИЦА")
self.assertEqual(client._http_client.downloaded_urls, [identity_url])
def test_procurement_card_rejects_untrusted_detail_urls(self):
loopback_identity_url = (
"http://127.0.0.1/epz/organization/view/" "info.html?inn=2725006476"
)
foreign_detail_url = (
"https://example.test/epz/order/notice/zk20/view/"
"common-info.html?regNumber=0322300001726000667"
)
client = StructuredDataClient(source="procurements_44fz")
client._http_client = _FakeHTTPClient(
{
loopback_identity_url: b"",
foreign_detail_url: b"",
}
)
records = client.fetch_records(
content=f"""
<div class="search-registry-entry-block">
<div>44-ФЗ</div>
<a href="{foreign_detail_url}">№ 0322300001726000667</a>
<div>Объект закупки</div><div>Поставка оборудования</div>
<div>Заказчик</div><div>КГБУЗ БОЛЬНИЦА</div>
<a href="{loopback_identity_url}">КГБУЗ БОЛЬНИЦА</a>
</div>
""".encode(),
file_name="results.html",
)
self.assertNotIn("detail_url", records[0].payload)
self.assertNotIn("identity_url", records[0].payload)
self.assertEqual(client._http_client.downloaded_urls, [])
def test_procurement_card_resolves_relative_official_urls(self):
detail_href = (
"/epz/order/notice/zk20/view/"
"common-info.html?regNumber=0322300001726000667"
)
identity_href = "/epz/organization/view/info.html?organizationCode=03223000017"
client = StructuredDataClient(
source="procurements_44fz",
enrich_eis_detail_pages=False,
)
records = client.fetch_records(
content=f"""
<div class="search-registry-entry-block">
<div>44-ФЗ</div>
<a href="{detail_href}">№ 0322300001726000667</a>
<div>Объект закупки</div><div>Поставка оборудования</div>
<div>Заказчик</div><div>КГБУЗ БОЛЬНИЦА</div>
<a href="{identity_href}">КГБУЗ БОЛЬНИЦА</a>
</div>
""".encode(),
file_name="results.html",
)
self.assertEqual(
records[0].payload["detail_url"],
f"https://zakupki.gov.ru{detail_href}",
)
self.assertEqual(
records[0].payload["identity_url"],
f"https://zakupki.gov.ru{identity_href}",
)
class StructuredDataClientEisCardParsingTest(SimpleTestCase):
def test_contract_card_prefers_contract_details_over_order_notice(self):
contract_url = (
"https://zakupki.gov.ru/epz/contract/contractCard/"
"common-info.html?reestrNumber=3310300251025000007"
)
notice_url = (
"https://zakupki.gov.ru/epz/order/notice/view/"
"common-info.html?regNumber=0126300014725000080"
)
client = StructuredDataClient(
source="contracts",
enrich_eis_detail_pages=False,
)
records = client.fetch_records(
content=f"""
<div class="search-registry-entry-block">
<a href="{contract_url}">№ 3310300251025000007</a>
<div>Заказчик</div><div>МБДОУ СКАЗКА</div>
<div>Объекты закупки</div><div>Мясо птицы</div>
<div>Размещен контракт в реестре контрактов</div>
<div>04.12.2025</div>
<a href="{notice_url}">Сведения закупки</a>
</div>
""".encode(),
file_name="results.html",
)
self.assertEqual(records[0].url, contract_url)
self.assertEqual(records[0].organisation_name, "МБДОУ СКАЗКА")
self.assertEqual(records[0].record_date, "04.12.2025")
def test_unfair_supplier_skips_empty_placed_label(self):
client = StructuredDataClient(
source="unfair_suppliers",
enrich_eis_detail_pages=False,
)
records = client.fetch_records(
content="""
<div class="search-registry-entry-block">
<div>44-ФЗ</div>
<div>№ 26007353</div>
<div>Размещено</div>
<div>Наименование (ФИО) недобросовестного поставщика</div>
<div>ООО ПРОГРЕСС</div>
<div>ИНН (аналог ИНН)</div><div>9728104322</div>
<div>Включено</div><div>14.07.2026</div>
</div>
""".encode(),
file_name="results.html",
)
self.assertEqual(records[0].title, "ООО ПРОГРЕСС")
self.assertEqual(records[0].organisation_name, "ООО ПРОГРЕСС")
self.assertEqual(records[0].record_date, "14.07.2026")