Add state-corp exchange package sender
All checks were successful
CI/CD Pipeline / Code Quality Checks (push) Successful in 1m54s
CI/CD Pipeline / Run Tests (push) Successful in 3m35s
CI/CD Pipeline / Run API Inventory E2E Tests (push) Successful in 50s
CI/CD Pipeline / Telegram Notify Success (push) Successful in 19s

This commit is contained in:
2026-04-07 16:53:06 +02:00
parent e306ad8f0f
commit 898e492538
9 changed files with 680 additions and 0 deletions

View 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)