fix: align ci with organization redesign
This commit is contained in:
@@ -106,29 +106,24 @@ class BackupExportServiceTest(TestCase):
|
||||
)
|
||||
|
||||
IndustrialCertificateRecordFactory(
|
||||
registry_organization=organization,
|
||||
inn=str(organization.mn_inn),
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
)
|
||||
ManufacturerRecordFactory(
|
||||
registry_organization=organization,
|
||||
inn=str(organization.mn_inn),
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
)
|
||||
InspectionRecordFactory(
|
||||
registry_organization=organization,
|
||||
inn=str(organization.mn_inn),
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
)
|
||||
ProcurementRecordFactory(
|
||||
registry_organization=organization,
|
||||
customer_inn=str(organization.mn_inn),
|
||||
customer_ogrn=str(organization.mn_ogrn),
|
||||
)
|
||||
report = FinancialReport.objects.create(
|
||||
external_id="100500",
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
registry_organization=organization,
|
||||
file_name="fin_100500_10277001189840.xlsx",
|
||||
file_hash="f" * 64,
|
||||
load_batch=1,
|
||||
|
||||
@@ -95,14 +95,12 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
expiry_date="2027-01-10",
|
||||
expiry_date_normalized="2027-01-10",
|
||||
certificate_file_url="https://minpromtorg.gov.ru/cert/001",
|
||||
registry_organization=organization,
|
||||
)
|
||||
ManufacturerRecordFactory.create(
|
||||
inn=str(organization.mn_inn),
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
full_legal_name=organization.pn_name,
|
||||
address="г. Москва, ул. Тверская, д. 1",
|
||||
registry_organization=organization,
|
||||
)
|
||||
IndustrialProductRecordFactory.create(
|
||||
inn=str(organization.mn_inn),
|
||||
@@ -111,7 +109,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
registry_number="prod-001",
|
||||
product_name="Система связи М-1",
|
||||
product_model="Связь",
|
||||
registry_organization=organization,
|
||||
)
|
||||
InspectionRecordFactory.create(
|
||||
inn=str(organization.mn_inn),
|
||||
@@ -123,7 +120,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
start_date="2026-03-10",
|
||||
start_date_normalized="2026-03-10",
|
||||
status="active",
|
||||
registry_organization=organization,
|
||||
)
|
||||
ProcurementRecordFactory.create(
|
||||
customer_inn=str(organization.mn_inn),
|
||||
@@ -138,12 +134,10 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
end_date="2026-11-30",
|
||||
end_date_normalized="2026-11-30",
|
||||
max_price_amount="4500000.75",
|
||||
registry_organization=organization,
|
||||
)
|
||||
financial_report = FinancialReport.objects.create(
|
||||
external_id="fin-001",
|
||||
ogrn=str(organization.mn_ogrn),
|
||||
registry_organization=organization,
|
||||
file_name="fin_001_1027700132195.xlsx",
|
||||
file_hash="f" * 64,
|
||||
load_batch=1,
|
||||
@@ -174,7 +168,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
"registry_number": "purchase-generic-001",
|
||||
"Окончание подачи заявок": "20.02.2026",
|
||||
},
|
||||
registry_organization=organization,
|
||||
)
|
||||
GenericParserRecord.objects.create(
|
||||
source=ParserLoadLog.Source.ARBITRATION,
|
||||
@@ -190,7 +183,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
"court_name": "АС города Москвы",
|
||||
"target": {"role": "ответчик"},
|
||||
},
|
||||
registry_organization=organization,
|
||||
)
|
||||
GenericParserRecord.objects.create(
|
||||
source=ParserLoadLog.Source.FEDRESURS_BANKRUPTCY,
|
||||
@@ -207,7 +199,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
"date": "2026-03-26",
|
||||
"case_number": "А40-555/2026",
|
||||
},
|
||||
registry_organization=organization,
|
||||
)
|
||||
GenericParserRecord.objects.create(
|
||||
source=ParserLoadLog.Source.FAS_GOZ,
|
||||
@@ -224,7 +215,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
"Полное наименование лица": organization.pn_name,
|
||||
"Дата вступления постановления": "2026-02-20",
|
||||
},
|
||||
registry_organization=organization,
|
||||
)
|
||||
GenericParserRecord.objects.create(
|
||||
source=ParserLoadLog.Source.FSTEC,
|
||||
@@ -241,7 +231,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
"issued_at": "2026-01-10",
|
||||
"expires_at": "2027-01-10",
|
||||
},
|
||||
registry_organization=organization,
|
||||
)
|
||||
GenericParserRecord.objects.create(
|
||||
source=ParserLoadLog.Source.TRUDVSEM,
|
||||
@@ -255,7 +244,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
status="open",
|
||||
url="https://trudvsem.ru/vacancy/001",
|
||||
payload={"vacancy_source": "trudvsem"},
|
||||
registry_organization=organization,
|
||||
)
|
||||
|
||||
package = StateCorpExchangeService.build_package(actual_date="2026-03-15")
|
||||
@@ -372,12 +360,10 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
IndustrialProductRecordFactory.create(
|
||||
inn=str(target.mn_inn),
|
||||
registry_number="target-product",
|
||||
registry_organization=target,
|
||||
)
|
||||
IndustrialProductRecordFactory.create(
|
||||
inn=str(non_target.mn_inn),
|
||||
registry_number="non-target-product",
|
||||
registry_organization=non_target,
|
||||
)
|
||||
GenericParserRecord.objects.create(
|
||||
source=ParserLoadLog.Source.TRUDVSEM,
|
||||
@@ -386,7 +372,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
inn=str(target.mn_inn),
|
||||
title="Целевая вакансия",
|
||||
record_date="2026-03-10",
|
||||
registry_organization=target,
|
||||
)
|
||||
GenericParserRecord.objects.create(
|
||||
source=ParserLoadLog.Source.TRUDVSEM,
|
||||
@@ -395,7 +380,6 @@ class StateCorpExchangeServiceTest(TestCase):
|
||||
inn=str(non_target.mn_inn),
|
||||
title="Лишняя вакансия",
|
||||
record_date="2026-03-10",
|
||||
registry_organization=non_target,
|
||||
)
|
||||
|
||||
package = StateCorpExchangeService.build_package(actual_date="2026-03-15")
|
||||
|
||||
@@ -1,631 +1,18 @@
|
||||
"""Integration tests for registers API views."""
|
||||
"""Regression tests for removed registers API routes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from datetime import date
|
||||
from unittest.mock import patch
|
||||
|
||||
from apps.registers.models import Organization, Register, RegistryMembershipPeriod
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db import IntegrityError
|
||||
from django.urls import reverse
|
||||
from openpyxl import Workbook
|
||||
from rest_framework import status
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from tests.apps.registers.factories import (
|
||||
OrganizationFactory,
|
||||
RegisterFactory,
|
||||
RegisterUploadFactory,
|
||||
RegistryMembershipPeriodFactory,
|
||||
)
|
||||
from tests.apps.user.factories import UserFactory
|
||||
|
||||
class RegistersViewsRemovedTest(APITestCase):
|
||||
"""Old registers routes are intentionally absent after organization redesign."""
|
||||
|
||||
def _build_register_excel_bytes(rows: list[dict], *, with_kpp: bool = True) -> bytes:
|
||||
workbook = Workbook()
|
||||
worksheet = workbook.active
|
||||
def test_registers_api_v1_namespace_is_not_registered(self):
|
||||
with self.assertRaises(NoReverseMatch):
|
||||
reverse("api_v1:registers:registries-list")
|
||||
|
||||
headers = ["pn_name", "mn_ogrn", "mn_inn"]
|
||||
if with_kpp:
|
||||
headers.append("in_kpp")
|
||||
headers.append("mn_okpo")
|
||||
|
||||
worksheet.append(headers)
|
||||
|
||||
for row in rows:
|
||||
values = [row["pn_name"], row["mn_ogrn"], row["mn_inn"]]
|
||||
if with_kpp:
|
||||
values.append(row.get("in_kpp"))
|
||||
values.append(row["mn_okpo"])
|
||||
worksheet.append(values)
|
||||
|
||||
buffer = io.BytesIO()
|
||||
workbook.save(buffer)
|
||||
workbook.close()
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
def _extract_results(response_data):
|
||||
if hasattr(response_data, "get"):
|
||||
data = response_data.get("data")
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
|
||||
results = response_data.get("results")
|
||||
if results is not None:
|
||||
return results
|
||||
return response_data
|
||||
|
||||
|
||||
class RegistersViewsTest(APITestCase):
|
||||
def setUp(self):
|
||||
self.user = UserFactory.create_user()
|
||||
self.admin = UserFactory.create_user(is_staff=True)
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
def _post_upload(
|
||||
self,
|
||||
*,
|
||||
registry,
|
||||
rows: list[dict],
|
||||
actual_date_value: date,
|
||||
with_kpp: bool = True,
|
||||
file_name: str = "registry.xlsx",
|
||||
):
|
||||
self.client.force_authenticate(self.admin)
|
||||
content = _build_register_excel_bytes(rows, with_kpp=with_kpp)
|
||||
upload = SimpleUploadedFile(
|
||||
file_name,
|
||||
content,
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("api_v1:registers:register-upload"),
|
||||
{
|
||||
"registry": str(registry.id),
|
||||
"actual_date": actual_date_value.isoformat(),
|
||||
"file": upload,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
self.client.force_authenticate(self.user)
|
||||
return response
|
||||
|
||||
def _post_v2_slug_upload(
|
||||
self,
|
||||
*,
|
||||
slug: str,
|
||||
rows: list[dict],
|
||||
actual_date_value: date,
|
||||
with_kpp: bool = True,
|
||||
file_name: str = "registry.xlsx",
|
||||
):
|
||||
self.client.force_authenticate(self.admin)
|
||||
content = _build_register_excel_bytes(rows, with_kpp=with_kpp)
|
||||
upload = SimpleUploadedFile(
|
||||
file_name,
|
||||
content,
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse(f"api_v2:registers:register-upload-{slug}"),
|
||||
{
|
||||
"actual_date": actual_date_value.isoformat(),
|
||||
"file": upload,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
self.client.force_authenticate(self.user)
|
||||
return response
|
||||
|
||||
def test_registries_list_and_retrieve(self):
|
||||
registry = RegisterFactory(name="Росатом")
|
||||
RegisterUploadFactory(registry=registry)
|
||||
RegistryMembershipPeriodFactory(registry=registry)
|
||||
|
||||
list_response = self.client.get(reverse("api_v1:registers:registries-list"))
|
||||
self.assertEqual(list_response.status_code, status.HTTP_200_OK)
|
||||
list_item = next(
|
||||
item
|
||||
for item in _extract_results(list_response.data)
|
||||
if item["id"] == str(registry.id)
|
||||
)
|
||||
self.assertEqual(list_item["active_organizations"], 1)
|
||||
self.assertEqual(list_item["uploads_count"], 2)
|
||||
|
||||
detail_response = self.client.get(
|
||||
reverse("api_v1:registers:registries-detail", args=[registry.id])
|
||||
)
|
||||
self.assertEqual(detail_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(detail_response.data["name"], "Росатом")
|
||||
self.assertEqual(detail_response.data["active_organizations"], 1)
|
||||
self.assertEqual(detail_response.data["uploads_count"], 2)
|
||||
|
||||
def test_default_registries_are_seeded(self):
|
||||
response = self.client.get(reverse("api_v1:registers:registries-list"))
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
names = {item["name"] for item in _extract_results(response.data)}
|
||||
self.assertIn("Реестр предприятий ОПК", names)
|
||||
self.assertIn("Реестр госкорпорации Роскосмос", names)
|
||||
self.assertIn("Реестр госкорпорации Роскосмос ГОЗ", names)
|
||||
self.assertIn("Реестр госкорпорации Роскосмос ОПК", names)
|
||||
self.assertIn("Реестр госкорпорации Росатом", names)
|
||||
self.assertIn("Реестр госкорпорации Росатом ГОЗ", names)
|
||||
self.assertIn("Реестр госкорпорации Росатом ОПК", names)
|
||||
|
||||
def test_v2_registry_slug_upload_uses_fixed_registry_and_refreshes_snapshots(self):
|
||||
rows = [
|
||||
{
|
||||
"pn_name": 'АО "Росатом ГОЗ"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07506197",
|
||||
}
|
||||
]
|
||||
|
||||
with patch("registers.views._start_snapshot_refresh_task") as refresh_task:
|
||||
response = self._post_v2_slug_upload(
|
||||
slug="rosatom-goz",
|
||||
rows=rows,
|
||||
actual_date_value=date(2026, 5, 7),
|
||||
file_name="rosatom_goz.xlsx",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertTrue(response.data["success"])
|
||||
self.assertEqual(
|
||||
response.data["registry_name"],
|
||||
"Реестр госкорпорации Росатом ГОЗ",
|
||||
)
|
||||
refresh_task.assert_called_once_with()
|
||||
membership = RegistryMembershipPeriod.objects.get(ended_at__isnull=True)
|
||||
self.assertEqual(
|
||||
membership.registry.name,
|
||||
"Реестр госкорпорации Росатом ГОЗ",
|
||||
)
|
||||
|
||||
def test_v2_registry_slug_upload_url_does_not_duplicate_registers_segment(self):
|
||||
self.assertEqual(
|
||||
reverse("api_v2:registers:register-upload-rosatom-goz"),
|
||||
"/api/v2/registers/rosatom-goz/upload/",
|
||||
)
|
||||
|
||||
def test_stat_organizations_endpoint_returns_registry_cards(self):
|
||||
opk_registry, _ = Register.objects.get_or_create(name="Реестр предприятий ОПК")
|
||||
rosatom_registry, _ = Register.objects.get_or_create(
|
||||
name="Реестр госкорпорации Росатом"
|
||||
)
|
||||
roscosmos_goz_registry, _ = Register.objects.get_or_create(
|
||||
name="Реестр госкорпорации Роскосмос ГОЗ"
|
||||
)
|
||||
opk_organization = OrganizationFactory()
|
||||
shared_organization = OrganizationFactory()
|
||||
inactive_organization = OrganizationFactory()
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=opk_registry,
|
||||
organization=opk_organization,
|
||||
)
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=rosatom_registry,
|
||||
organization=opk_organization,
|
||||
)
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=rosatom_registry,
|
||||
organization=shared_organization,
|
||||
)
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=roscosmos_goz_registry,
|
||||
organization=inactive_organization,
|
||||
ended_at=date(2026, 6, 1),
|
||||
)
|
||||
|
||||
response = self.client.get("/api/v1/stat/organizations/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
payload = response.data["data"]
|
||||
self.assertEqual(payload["total_organizations"], 3)
|
||||
self.assertEqual(payload["active_registry_organizations"], 2)
|
||||
self.assertEqual(payload["counts"]["total"], 3)
|
||||
self.assertEqual(payload["counts"]["opk"], 1)
|
||||
self.assertEqual(payload["counts"]["rosatom"], 2)
|
||||
self.assertEqual(payload["counts"]["roscosmos-goz"], 0)
|
||||
cards_by_slug = {item["slug"]: item for item in payload["cards"]}
|
||||
self.assertEqual(
|
||||
cards_by_slug["total"]["title"],
|
||||
"Общее количество организаций",
|
||||
)
|
||||
self.assertEqual(
|
||||
cards_by_slug["rosatom"]["registry_name"],
|
||||
"Реестр госкорпорации Росатом",
|
||||
)
|
||||
|
||||
def test_v2_registry_slug_upload_does_not_refresh_snapshots_after_import_error(self):
|
||||
rows = [
|
||||
{
|
||||
"pn_name": 'АО "Невалидный ОКПО"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07A06197",
|
||||
}
|
||||
]
|
||||
|
||||
with patch("registers.views._start_snapshot_refresh_task") as refresh_task:
|
||||
response = self._post_v2_slug_upload(
|
||||
slug="rosatom-goz",
|
||||
rows=rows,
|
||||
actual_date_value=date(2026, 5, 7),
|
||||
file_name="invalid_rosatom_goz.xlsx",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
refresh_task.assert_not_called()
|
||||
|
||||
def test_organizations_list_and_retrieve(self):
|
||||
organization = OrganizationFactory()
|
||||
|
||||
list_response = self.client.get(reverse("api_v1:registers:organizations-list"))
|
||||
self.assertEqual(list_response.status_code, status.HTTP_200_OK)
|
||||
|
||||
detail_response = self.client.get(
|
||||
reverse("api_v1:registers:organizations-detail", args=[organization.id])
|
||||
)
|
||||
self.assertEqual(detail_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(detail_response.data["id"], organization.id)
|
||||
self.assertIn("periods", detail_response.data)
|
||||
|
||||
def test_organizations_search_by_all_fields(self):
|
||||
organization = OrganizationFactory(
|
||||
pn_name='АО "Тестовая организация"',
|
||||
mn_ogrn=1027600980990,
|
||||
mn_inn=7601000086,
|
||||
in_kpp=760401001,
|
||||
mn_okpo="07506197",
|
||||
)
|
||||
OrganizationFactory()
|
||||
|
||||
search_values = [
|
||||
"Тестовая организация",
|
||||
str(organization.mn_ogrn),
|
||||
str(organization.mn_inn),
|
||||
str(organization.in_kpp),
|
||||
organization.mn_okpo,
|
||||
]
|
||||
|
||||
for search_value in search_values:
|
||||
response = self.client.get(
|
||||
reverse("api_v1:registers:organizations-list"),
|
||||
{"search": search_value},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
result_ids = [item["id"] for item in _extract_results(response.data)]
|
||||
self.assertIn(organization.id, result_ids)
|
||||
|
||||
def test_organizations_filter_by_registry_and_actual_date(self):
|
||||
registry = RegisterFactory(name="Роскосмос")
|
||||
|
||||
organization_old = OrganizationFactory()
|
||||
organization_current = OrganizationFactory()
|
||||
|
||||
upload_start = RegisterUploadFactory(
|
||||
registry=registry, actual_date=date(2026, 1, 1)
|
||||
)
|
||||
upload_end = RegisterUploadFactory(
|
||||
registry=registry, actual_date=date(2026, 2, 1)
|
||||
)
|
||||
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=registry,
|
||||
organization=organization_old,
|
||||
started_at=date(2026, 1, 1),
|
||||
ended_at=date(2026, 2, 1),
|
||||
started_by_upload=upload_start,
|
||||
ended_by_upload=upload_end,
|
||||
)
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=registry,
|
||||
organization=organization_current,
|
||||
started_at=date(2026, 2, 1),
|
||||
started_by_upload=upload_end,
|
||||
)
|
||||
|
||||
response_past = self.client.get(
|
||||
reverse("api_v1:registers:organizations-list"),
|
||||
{"registry": str(registry.id), "actual_date": "2026-01-15"},
|
||||
)
|
||||
self.assertEqual(response_past.status_code, status.HTTP_200_OK)
|
||||
past_ids = {item["id"] for item in _extract_results(response_past.data)}
|
||||
self.assertIn(organization_old.id, past_ids)
|
||||
self.assertNotIn(organization_current.id, past_ids)
|
||||
|
||||
response_latest = self.client.get(
|
||||
reverse("api_v1:registers:organizations-list"),
|
||||
{"registry": str(registry.id)},
|
||||
)
|
||||
self.assertEqual(response_latest.status_code, status.HTTP_200_OK)
|
||||
latest_ids = {item["id"] for item in _extract_results(response_latest.data)}
|
||||
self.assertNotIn(organization_old.id, latest_ids)
|
||||
self.assertIn(organization_current.id, latest_ids)
|
||||
|
||||
def test_registry_specific_organizations_list_endpoint(self):
|
||||
registry = RegisterFactory(name="Росатом ОПК")
|
||||
organization = OrganizationFactory(
|
||||
mn_ogrn=1027600980990,
|
||||
mn_inn=7601000086,
|
||||
mn_okpo="07506197",
|
||||
)
|
||||
upload = RegisterUploadFactory(registry=registry, actual_date=date(2026, 3, 1))
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=registry,
|
||||
organization=organization,
|
||||
started_at=date(2026, 3, 1),
|
||||
started_by_upload=upload,
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"api_v1:registers:registry-organizations-list",
|
||||
args=[registry.id],
|
||||
),
|
||||
{"actual_date": "2026-03-15"},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = _extract_results(response.data)
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0]["id"], organization.id)
|
||||
|
||||
def test_upload_closes_and_reopens_period(self):
|
||||
registry = RegisterFactory(name="Реестр периодов")
|
||||
|
||||
org_a = {
|
||||
"pn_name": 'АО "Орг А"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07506197",
|
||||
}
|
||||
org_b = {
|
||||
"pn_name": 'АО "Орг Б"',
|
||||
"mn_ogrn": "1083340004527",
|
||||
"mn_inn": "3329051460",
|
||||
"in_kpp": "332901001",
|
||||
"mn_okpo": "07518609",
|
||||
}
|
||||
|
||||
first = self._post_upload(
|
||||
registry=registry,
|
||||
rows=[org_a],
|
||||
actual_date_value=date(2026, 1, 1),
|
||||
file_name="first.xlsx",
|
||||
)
|
||||
self.assertEqual(first.status_code, status.HTTP_201_CREATED)
|
||||
self.assertTrue(first.data["success"])
|
||||
self.assertEqual(first.data["message"], "Файл успешно загружен")
|
||||
|
||||
second = self._post_upload(
|
||||
registry=registry,
|
||||
rows=[org_b],
|
||||
actual_date_value=date(2026, 2, 1),
|
||||
file_name="second.xlsx",
|
||||
)
|
||||
self.assertEqual(second.status_code, status.HTTP_201_CREATED)
|
||||
self.assertTrue(second.data["success"])
|
||||
self.assertEqual(second.data["message"], "Файл успешно загружен")
|
||||
|
||||
third = self._post_upload(
|
||||
registry=registry,
|
||||
rows=[org_a],
|
||||
actual_date_value=date(2026, 3, 1),
|
||||
file_name="third.xlsx",
|
||||
)
|
||||
self.assertEqual(third.status_code, status.HTTP_201_CREATED)
|
||||
self.assertTrue(third.data["success"])
|
||||
self.assertEqual(third.data["message"], "Файл успешно загружен")
|
||||
|
||||
organization_a = Organization.objects.get(
|
||||
mn_ogrn=1027600980990, mn_inn=7601000086
|
||||
)
|
||||
periods = list(
|
||||
RegistryMembershipPeriod.objects.filter(
|
||||
registry=registry,
|
||||
organization=organization_a,
|
||||
).order_by("started_at")
|
||||
)
|
||||
|
||||
self.assertEqual(len(periods), 2)
|
||||
self.assertEqual(periods[0].started_at, date(2026, 1, 1))
|
||||
self.assertEqual(periods[0].ended_at, date(2026, 2, 1))
|
||||
self.assertEqual(periods[1].started_at, date(2026, 3, 1))
|
||||
self.assertIsNone(periods[1].ended_at)
|
||||
|
||||
def test_same_organization_can_be_in_multiple_registries(self):
|
||||
registry_a = RegisterFactory(name="Росатом")
|
||||
registry_b = RegisterFactory(name="Роскосмос")
|
||||
|
||||
org_row = {
|
||||
"pn_name": 'АО "Общая организация"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07506197",
|
||||
}
|
||||
|
||||
upload_a = self._post_upload(
|
||||
registry=registry_a,
|
||||
rows=[org_row],
|
||||
actual_date_value=date(2026, 1, 1),
|
||||
file_name="reg_a.xlsx",
|
||||
)
|
||||
upload_b = self._post_upload(
|
||||
registry=registry_b,
|
||||
rows=[org_row],
|
||||
actual_date_value=date(2026, 1, 1),
|
||||
file_name="reg_b.xlsx",
|
||||
)
|
||||
|
||||
self.assertEqual(upload_a.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(upload_b.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
response_a = self.client.get(
|
||||
reverse(
|
||||
"api_v1:registers:registry-organizations-list", args=[registry_a.id]
|
||||
)
|
||||
)
|
||||
response_b = self.client.get(
|
||||
reverse(
|
||||
"api_v1:registers:registry-organizations-list", args=[registry_b.id]
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(response_a.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response_b.status_code, status.HTTP_200_OK)
|
||||
|
||||
ids_a = {item["id"] for item in _extract_results(response_a.data)}
|
||||
ids_b = {item["id"] for item in _extract_results(response_b.data)}
|
||||
self.assertEqual(ids_a, ids_b)
|
||||
self.assertEqual(len(ids_a), 1)
|
||||
|
||||
def test_active_membership_period_is_unique_per_registry_and_organization(self):
|
||||
registry = RegisterFactory(name="Уникальный период")
|
||||
organization = OrganizationFactory()
|
||||
upload = RegisterUploadFactory(registry=registry, actual_date=date(2026, 6, 1))
|
||||
|
||||
RegistryMembershipPeriodFactory(
|
||||
registry=registry,
|
||||
organization=organization,
|
||||
started_at=date(2026, 6, 1),
|
||||
started_by_upload=upload,
|
||||
ended_at=None,
|
||||
)
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
RegistryMembershipPeriod.objects.create(
|
||||
registry=registry,
|
||||
organization=organization,
|
||||
started_at=date(2026, 7, 1),
|
||||
started_by_upload=upload,
|
||||
ended_at=None,
|
||||
)
|
||||
|
||||
def test_upload_without_kpp_column(self):
|
||||
registry = RegisterFactory(name="Роскосмос")
|
||||
response = self._post_upload(
|
||||
registry=registry,
|
||||
rows=[
|
||||
{
|
||||
"pn_name": 'АО "Ярославский радиозавод"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"mn_okpo": "07506197",
|
||||
}
|
||||
],
|
||||
actual_date_value=date(2026, 4, 1),
|
||||
with_kpp=False,
|
||||
file_name="without_kpp.xlsx",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertTrue(response.data["success"])
|
||||
self.assertEqual(response.data["message"], "Файл успешно загружен")
|
||||
organization = Organization.objects.get(
|
||||
mn_ogrn=1027600980990, mn_inn=7601000086
|
||||
)
|
||||
self.assertIsNone(organization.in_kpp)
|
||||
|
||||
def test_upload_rejects_invalid_okpo(self):
|
||||
registry = RegisterFactory(name="Реестр ошибки")
|
||||
response = self._post_upload(
|
||||
registry=registry,
|
||||
rows=[
|
||||
{
|
||||
"pn_name": 'АО "Невалидный ОКПО"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07A06197",
|
||||
}
|
||||
],
|
||||
actual_date_value=date(2026, 5, 1),
|
||||
with_kpp=True,
|
||||
file_name="invalid.xlsx",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(Organization.objects.count(), 0)
|
||||
|
||||
def test_upload_requires_authentication(self):
|
||||
registry = RegisterFactory(name="Закрытый реестр")
|
||||
content = _build_register_excel_bytes(
|
||||
[
|
||||
{
|
||||
"pn_name": 'АО "Закрытый"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07506197",
|
||||
}
|
||||
]
|
||||
)
|
||||
upload = SimpleUploadedFile(
|
||||
"auth.xlsx",
|
||||
content,
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
self.client.force_authenticate(user=None)
|
||||
response = self.client.post(
|
||||
reverse("api_v1:registers:register-upload"),
|
||||
{
|
||||
"registry": str(registry.id),
|
||||
"actual_date": "2026-01-01",
|
||||
"file": upload,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_upload_forbidden_for_regular_user(self):
|
||||
registry = RegisterFactory(name="Только для админа")
|
||||
content = _build_register_excel_bytes(
|
||||
[
|
||||
{
|
||||
"pn_name": 'АО "Ограниченный доступ"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07506197",
|
||||
}
|
||||
]
|
||||
)
|
||||
upload = SimpleUploadedFile(
|
||||
"viewer.xlsx",
|
||||
content,
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
self.client.force_authenticate(self.user)
|
||||
response = self.client.post(
|
||||
reverse("api_v1:registers:register-upload"),
|
||||
{
|
||||
"registry": str(registry.id),
|
||||
"actual_date": "2026-01-01",
|
||||
"file": upload,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
def test_registers_api_v2_namespace_is_not_registered(self):
|
||||
with self.assertRaises(NoReverseMatch):
|
||||
reverse("api_v2:registers:register-upload-rosatom-goz")
|
||||
|
||||
Reference in New Issue
Block a user