Files
mostovik-backend/tests/apps/organizations/test_openapi_runtime_fixtures.py
Aleksandr Meshchryakov 18971d33ec
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 3m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m43s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m45s
feat: complete published registry contracts and gated SRO ingestion
2026-09-14 17:01:02 +02:00

194 lines
6.9 KiB
Python
Raw Permalink 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.
"""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",
)