Add state-corp exchange package sender
All checks were successful
All checks were successful
This commit is contained in:
146
tests/apps/exchange/test_state_corp_services.py
Normal file
146
tests/apps/exchange/test_state_corp_services.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Tests for state-corp package build and delivery from mostovik."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
import zlib
|
||||
from io import BytesIO
|
||||
from unittest.mock import Mock, patch
|
||||
from zipfile import ZipFile
|
||||
|
||||
from apps.exchange.state_corp_services import StateCorpExchangeService
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from tests.apps.parsers.factories import (
|
||||
IndustrialProductRecordFactory,
|
||||
InspectionRecordFactory,
|
||||
ProcurementRecordFactory,
|
||||
)
|
||||
from tests.apps.registers.factories import OrganizationFactory
|
||||
|
||||
|
||||
def _b64url_decode(value: str) -> bytes:
|
||||
normalized = value + ("=" * (-len(value) % 4))
|
||||
return base64.urlsafe_b64decode(normalized.encode("ascii"))
|
||||
|
||||
|
||||
TEST_STATE_CORP_TOKEN = "state-corp-test-exchange-token" # noqa: S105
|
||||
|
||||
|
||||
@override_settings(
|
||||
STATE_CORP_EXCHANGE_TOKEN=TEST_STATE_CORP_TOKEN,
|
||||
STATE_CORP_EXCHANGE_URL="http://state-corp.test/api/v1/exchange/packages/upload/",
|
||||
)
|
||||
class StateCorpExchangeServiceTest(TestCase):
|
||||
"""Verify package compatibility with state-corp receiver contract."""
|
||||
|
||||
def test_build_package_contains_expected_payload(self):
|
||||
organization = OrganizationFactory.create(
|
||||
mn_inn=7707083893,
|
||||
mn_ogrn=1027700132195,
|
||||
pn_name="АО Альфа",
|
||||
in_kpp=770701001,
|
||||
mn_okpo="12345678",
|
||||
)
|
||||
IndustrialProductRecordFactory.create(
|
||||
inn=str(organization.mn_inn),
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
full_organisation_name=organization.pn_name,
|
||||
registry_number="prod-001",
|
||||
product_name="Система связи М-1",
|
||||
product_model="Связь",
|
||||
registry_organization=organization,
|
||||
)
|
||||
InspectionRecordFactory.create(
|
||||
inn=str(organization.mn_inn),
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
organisation_name=organization.pn_name,
|
||||
registration_number="insp-001",
|
||||
control_authority="Минпромторг",
|
||||
legal_basis="294-ФЗ",
|
||||
start_date="2026-03-10",
|
||||
start_date_normalized="2026-03-10",
|
||||
status="active",
|
||||
registry_organization=organization,
|
||||
)
|
||||
ProcurementRecordFactory.create(
|
||||
customer_inn=str(organization.mn_inn),
|
||||
customer_ogrn=str(organization.mn_ogrn),
|
||||
customer_name=organization.pn_name,
|
||||
purchase_number="purchase-001",
|
||||
purchase_name="Поставка оборудования",
|
||||
law_type="223-ФЗ",
|
||||
status="executing",
|
||||
publish_date="2026-02-15",
|
||||
publish_date_normalized="2026-02-15",
|
||||
end_date="2026-11-30",
|
||||
end_date_normalized="2026-11-30",
|
||||
max_price_amount="4500000.75",
|
||||
registry_organization=organization,
|
||||
)
|
||||
|
||||
package = StateCorpExchangeService.build_package()
|
||||
self.assertEqual(package.payload_counts["organizations"], 1)
|
||||
self.assertEqual(package.payload_counts["industrial_products"], 1)
|
||||
self.assertEqual(package.payload_counts["prosecutor_checks"], 1)
|
||||
self.assertEqual(package.payload_counts["public_procurements"], 1)
|
||||
|
||||
with ZipFile(BytesIO(package.archive_bytes)) as archive:
|
||||
bin_names = [name for name in archive.namelist() if name.endswith(".bin")]
|
||||
self.assertEqual(len(bin_names), 1)
|
||||
bin_bytes = archive.read(bin_names[0])
|
||||
|
||||
header_size = struct.unpack(">I", bin_bytes[5:9])[0]
|
||||
header_end = 9 + header_size
|
||||
header = json.loads(bin_bytes[9:header_end].decode("utf-8"))
|
||||
encrypted_payload = bin_bytes[header_end:]
|
||||
raw_key = hashlib.sha256(TEST_STATE_CORP_TOKEN.encode("utf-8")).digest()
|
||||
compressed_payload = AESGCM(raw_key).decrypt(
|
||||
_b64url_decode(header["nonce"]),
|
||||
encrypted_payload,
|
||||
_b64url_decode(header["aad"]),
|
||||
)
|
||||
payload = json.loads(zlib.decompress(compressed_payload).decode("utf-8"))
|
||||
|
||||
self.assertEqual(payload["format"], StateCorpExchangeService.PAYLOAD_FORMAT)
|
||||
self.assertEqual(payload["manifest"]["source_system"], "mostovik")
|
||||
self.assertEqual(payload["data"]["organizations"][0]["inn"], "7707083893")
|
||||
self.assertEqual(
|
||||
payload["data"]["industrial_products"][0]["registry_number"],
|
||||
"prod-001",
|
||||
)
|
||||
self.assertEqual(
|
||||
payload["data"]["prosecutor_checks"][0]["registration_number"],
|
||||
"insp-001",
|
||||
)
|
||||
self.assertEqual(
|
||||
payload["data"]["public_procurements"][0]["purchase_number"],
|
||||
"purchase-001",
|
||||
)
|
||||
self.assertEqual(payload["data"]["arbitration_cases"], [])
|
||||
|
||||
@patch("apps.exchange.state_corp_services.requests.post")
|
||||
def test_send_package_posts_multipart_archive(self, post_mock):
|
||||
response_mock = Mock(status_code=201)
|
||||
response_mock.json.return_value = {
|
||||
"message": "ok",
|
||||
"result": {"duplicate": False},
|
||||
}
|
||||
post_mock.return_value = response_mock
|
||||
|
||||
package = StateCorpExchangeService.build_package()
|
||||
result = StateCorpExchangeService.send_package(package=package)
|
||||
|
||||
self.assertEqual(result["status_code"], 201)
|
||||
self.assertEqual(result["response"]["result"]["duplicate"], False)
|
||||
post_mock.assert_called_once()
|
||||
_, kwargs = post_mock.call_args
|
||||
self.assertEqual(
|
||||
kwargs["headers"]["X-Exchange-Token"],
|
||||
TEST_STATE_CORP_TOKEN,
|
||||
)
|
||||
self.assertEqual(kwargs["files"]["file"][0], package.archive_name)
|
||||
Reference in New Issue
Block a user