175 lines
6.1 KiB
Python
175 lines
6.1 KiB
Python
from __future__ import annotations
|
||
|
||
import json
|
||
|
||
from apps.parsers.clients.gisp import GispProductsClient, GispProductsClientError
|
||
from django.test import SimpleTestCase
|
||
|
||
from tests.utils import Response
|
||
from tests.utils import TestHTTPServer as HTTPTestServer
|
||
|
||
|
||
def _gisp_item(number: int) -> dict:
|
||
return {
|
||
"_product_reg_number_2023": str(number),
|
||
"_product_reg_number_2022": f"old-{number}",
|
||
"_res_number": f"resolution-{number}",
|
||
"_org_inn": "7701000001",
|
||
"_org_ogrn": "1027700000001",
|
||
"_org_name": "АО Завод",
|
||
"_product_name": f"Станок {number}",
|
||
"_product_spec": f"Модель {number}",
|
||
"_product_okpd2": "28.41.1",
|
||
"_product_tnved": "8456",
|
||
"_basedondoc_name": "Заключение",
|
||
"_basedondoc_num": "42",
|
||
"_basedondoc_date": "2026-01-15",
|
||
}
|
||
|
||
|
||
class GispProductsClientTest(SimpleTestCase):
|
||
def test_rejects_page_size_above_safe_bound(self):
|
||
with self.assertRaisesMessage(
|
||
ValueError,
|
||
"page_size must be between 1 and 100",
|
||
):
|
||
GispProductsClient(page_size=101)
|
||
|
||
def test_rejects_max_pages_above_safe_bound(self):
|
||
with self.assertRaisesMessage(
|
||
ValueError,
|
||
"max_pages must be between 1 and 100",
|
||
):
|
||
GispProductsClient(max_pages=101)
|
||
|
||
def test_rejects_max_records_above_safe_bound(self):
|
||
with self.assertRaisesMessage(
|
||
ValueError,
|
||
"max_records must be between 1 and 10000",
|
||
):
|
||
GispProductsClient(max_records=10_001)
|
||
|
||
def test_fetch_products_follows_skip_pagination_and_maps_items(self):
|
||
requests: list[dict] = []
|
||
|
||
def handle_page(_request, body: bytes) -> Response:
|
||
payload = json.loads(body)
|
||
requests.append(payload)
|
||
options = payload["opt"]
|
||
skip = options["skip"]
|
||
take = options["take"]
|
||
items = [_gisp_item(number) for number in range(skip + 1, 6)][:take]
|
||
return Response(
|
||
body=json.dumps(
|
||
{"ok": True, "total_count": 5, "items": items}
|
||
).encode(),
|
||
headers={"Content-Type": "application/json"},
|
||
)
|
||
|
||
with HTTPTestServer() as server:
|
||
server.add_route("POST", "/pp719v2/pub/prod/b/", handle_page)
|
||
with GispProductsClient(
|
||
base_url=server.base_url,
|
||
page_size=2,
|
||
max_pages=10,
|
||
max_records=5,
|
||
http_adapter=server.adapter,
|
||
) as client:
|
||
result = client.fetch_products()
|
||
|
||
self.assertEqual(
|
||
[payload["opt"] for payload in requests],
|
||
[
|
||
{"skip": 0, "take": 2, "requireTotalCount": True},
|
||
{"skip": 2, "take": 2, "requireTotalCount": True},
|
||
{"skip": 4, "take": 1, "requireTotalCount": True},
|
||
],
|
||
)
|
||
self.assertEqual(result.total_count, 5)
|
||
self.assertEqual(result.pages_fetched, 3)
|
||
self.assertEqual(result.items_fetched, 5)
|
||
self.assertFalse(result.truncated)
|
||
self.assertEqual(len(result.products), 5)
|
||
self.assertEqual(result.products[0].registry_number, "1")
|
||
self.assertEqual(result.products[0].inn, "7701000001")
|
||
self.assertEqual(result.products[0].product_name, "Станок 1")
|
||
self.assertEqual(
|
||
result.products[0].regulatory_document,
|
||
"Заключение | 42 | 2026-01-15",
|
||
)
|
||
|
||
def test_fetch_products_stops_at_max_pages_and_reports_truncation(self):
|
||
calls = 0
|
||
|
||
def handle_page(_request, body: bytes) -> Response:
|
||
nonlocal calls
|
||
calls += 1
|
||
options = json.loads(body)["opt"]
|
||
items = [
|
||
_gisp_item(number)
|
||
for number in range(options["skip"] + 1, options["skip"] + 3)
|
||
]
|
||
return Response(
|
||
body=json.dumps(
|
||
{"ok": True, "total_count": 100, "items": items}
|
||
).encode(),
|
||
headers={"Content-Type": "application/json"},
|
||
)
|
||
|
||
with HTTPTestServer() as server:
|
||
server.add_route("POST", "/pp719v2/pub/prod/b/", handle_page)
|
||
with GispProductsClient(
|
||
base_url=server.base_url,
|
||
page_size=2,
|
||
max_pages=2,
|
||
max_records=100,
|
||
http_adapter=server.adapter,
|
||
) as client:
|
||
result = client.fetch_products()
|
||
|
||
self.assertEqual(calls, 2)
|
||
self.assertEqual(result.items_fetched, 4)
|
||
self.assertTrue(result.truncated)
|
||
|
||
def test_fetch_products_rejects_invalid_response_shape(self):
|
||
with HTTPTestServer() as server:
|
||
server.add_route(
|
||
"POST",
|
||
"/pp719v2/pub/prod/b/",
|
||
lambda _request, _body: Response(
|
||
body=b'{"ok":true,"total_count":1}',
|
||
headers={"Content-Type": "application/json"},
|
||
),
|
||
)
|
||
client = GispProductsClient(
|
||
base_url=server.base_url,
|
||
http_adapter=server.adapter,
|
||
)
|
||
|
||
with self.assertRaisesMessage(
|
||
GispProductsClientError,
|
||
"returned no items list",
|
||
):
|
||
client.fetch_products()
|
||
|
||
def test_fetch_products_rejects_empty_page_before_known_total(self):
|
||
with HTTPTestServer() as server:
|
||
server.add_route(
|
||
"POST",
|
||
"/pp719v2/pub/prod/b/",
|
||
lambda _request, _body: Response(
|
||
body=b'{"ok":true,"total_count":10,"items":[]}',
|
||
headers={"Content-Type": "application/json"},
|
||
),
|
||
)
|
||
client = GispProductsClient(
|
||
base_url=server.base_url,
|
||
http_adapter=server.adapter,
|
||
)
|
||
|
||
with self.assertRaisesMessage(
|
||
GispProductsClientError,
|
||
"empty page before total_count",
|
||
):
|
||
client.fetch_products()
|