"""Render synthetic API responses for the generated Orval/Zod contract gate.""" import json import os from pathlib import Path from uuid import uuid4 import pytest from apps.parsers.budget_registry import normalize_budget_record from apps.parsers.models import ParserSourceArtifact from apps.parsers.sme_support import support_payload from apps.parsers.sro_membership import normalize_sro_membership from django.urls import reverse from django.utils import timezone from lxml import etree from organizations.models import Organization, OrganizationSourceRecord from organizations.source_groups import get_source_group_descriptor from rest_framework.test import APIClient from tests.apps.parsers.test_registry_payload_contract import full_budget_fixture from tests.apps.parsers.test_registry_snapshots import _xml from tests.apps.user.factories import UserFactory def _create_record(organization, source, normalized): descriptor = get_source_group_descriptor(source) extension = descriptor.extension_model.objects.create(organization=organization) ParserSourceArtifact.objects.create( source=source, load_batch=9001, status=ParserSourceArtifact.Status.PUBLISHED, source_published_at="2026-08-15", version="4.04" if source == "fns_sme_support_recipients" else "2026-08-15", sha256="a" * 64, original_name="synthetic-contract-fixture.zip", published_count=1, ) return OrganizationSourceRecord.objects.create( extension=extension, source=source, record_type=descriptor.record_type, external_id=f"synthetic-{source}", load_batch=9001, **normalized, ) def _canonical_records(): organization = Organization.objects.create( name="АО Синтетическая организация", full_name="Акционерное общество Синтетическая организация", inn="1234567890", ogrn="1027700132195", okpo="00123456", directory_imported_at=timezone.now(), opk_registry_membership=False, ) raw_budget = full_budget_fixture() # Unknown nested keys, leading zeroes, booleans and nulls must survive Zod. raw_budget["future_block"] = [ {"future_key": {"code": "0007", "nested": [None, False, 0, {"keep": True}]}} ] budget = _create_record( organization, "budget_ubpandnubp", normalize_budget_record(raw_budget) ) xml = etree.fromstring( # noqa: S320 - local synthetic fixture without entities _xml(numbers=("synthetic-support",), units=("1", "2", "3", "4", "5")) ) payload, amount = support_payload( "synthetic-document", xml.find("Документ/СвПредПод"), "2026-08-15" ) sme = _create_record( organization, "fns_sme_support_recipients", { "title": "Синтетическая мера поддержки", "record_date": payload["decision_date"], "amount": amount, "status": "", "url": "https://www.nalog.gov.ru/opendata/7707329152-rsmppp/", "payload": payload, }, ) sro = _create_record( organization, "sro_membership_check", normalize_sro_membership( { "name": organization.name, "full_name": organization.full_name, "inn": organization.inn, "ogrn": organization.ogrn, "region": "Синтетический регион", "sro_name": "Синтетическая СРО", "membership_status_raw": "Является членом", }, organization, sro_id="90001", sro_url="https://www.reestr-sro.ru/test-fixtures/sro-id-90001/", source_url="https://www.reestr-sro.ru/test-fixtures/lookup/", source_registry_date="2026-08-15", admitted=None, missing_reason="not_found", resolution="row_link", lookup_key="ogrn", lookup_value=organization.ogrn, ), ) return [budget, sme, sro], raw_budget @pytest.mark.django_db def test_source_record_runtime_fixtures(): client = APIClient() client.force_authenticate(UserFactory.create_user()) records, raw_budget = _canonical_records() list_url = reverse("api_v2:organizations:organization-source-records-list") cases = [] def capture(name, operation, response, status=200): assert response.status_code == status, (name, response.status_code) # Rendered JSON is the actual wire representation, not DRF's .data. body = response.json() cases.append( {"name": name, "operation": operation, "status": status, "body": body} ) return body mixed = capture("mixed-list", "list", client.get(list_url)) assert len(mixed["data"]) == 3 assert mixed["meta"]["pagination"]["total_count"] == 3 details = {} for record in records: listing = capture( f"{record.source}-list", "list", client.get(list_url, {"source": record.source}), ) assert len(listing["data"]) == 1 detail_url = reverse( "api_v2:organizations:organization-source-records-detail", kwargs={"uid": record.uid}, ) details[record.source] = capture( f"{record.source}-detail", "detail", client.get(detail_url) ) budget_payload = details["budget_ubpandnubp"]["payload"] assert budget_payload["upstream"] == raw_budget assert ( budget_payload["unclassified_blocks"]["future_block"] == raw_budget["future_block"] ) assert len(details["fns_sme_support_recipients"]["payload"]["support_sizes"]) == 5 assert details["sro_membership_check"]["payload"]["admission_date"] is None empty = capture( "empty-list", "list", client.get(list_url, {"search": "absent-fixture-value"}) ) assert empty["data"] == [] assert empty["meta"]["pagination"]["total_count"] == 0 assert empty["meta"]["pagination"]["total_pages"] == 0 capture( "invalid-list", "list", client.get(list_url, {"page_size": "broken"}), 400, ) for name, uid, status in ( ("invalid-detail", "broken", 400), ("missing-detail", str(uuid4()), 404), ): capture( name, "detail", client.get( reverse( "api_v2:organizations:organization-source-records-detail", kwargs={"uid": uid}, ) ), status, ) # Ordinary pytest runs create no artifact. CI opts in with an explicit path. destination = os.environ.get("OPENAPI_RUNTIME_FIXTURES") if destination: Path(destination).write_text( json.dumps({"version": 1, "cases": cases}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", )