feat: expand registry ingestion and demo exchange
All checks were successful
CI/CD Pipeline / Quality Gate (push) Successful in 36s
CI/CD Pipeline / Build and Push Images (push) Successful in 15s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 24s

This commit is contained in:
2026-07-16 12:59:23 +02:00
parent cc5aa84556
commit 007cecc8d5
43 changed files with 3723 additions and 352 deletions

View File

@@ -31,6 +31,12 @@ from rest_framework.test import APITestCase
from tests.apps.user.factories import UserFactory
def create_frontend_organization(**kwargs):
"""Create an organization visible through the frontend API by default."""
kwargs.setdefault("opk_registry_membership", True)
return Organization.objects.create(**kwargs)
class OrganizationsApiV2Test(APITestCase):
"""Checks v2 canonical organizations endpoints."""
@@ -51,13 +57,13 @@ class OrganizationsApiV2Test(APITestCase):
worksheet.append([key, value])
def test_list_is_paginated_and_available_only_under_v2(self):
Organization.objects.create(
create_frontend_organization(
name='ООО "Альфа"',
inn="7707083893",
kpp="770701001",
ogrn="1027700132195",
)
Organization.objects.create(
create_frontend_organization(
name="ИП Иванов Иван Иванович",
inn="500100732259",
ogrip="304500116000157",
@@ -174,16 +180,13 @@ class OrganizationsApiV2Test(APITestCase):
"search",
"ordering",
"registry",
"has_registry",
"identity_status",
"source_group",
"has_financial_indicators",
"has_planned_inspections",
):
self.assertIn(expected_name, list_parameters)
self.assertIn(
"по умолчанию true", list_parameters["has_registry"]["description"]
)
self.assertNotIn("has_registry", list_parameters)
self.assertIn(
"группе источников",
list_parameters["source_group"]["description"],
@@ -206,7 +209,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertIn("/api/v2/organization-sources/{uid}/records/", paths)
def test_retrieve_returns_item_by_uid(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Ромашка"',
inn="7712345678",
kpp="771201001",
@@ -224,7 +227,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(response.data["name"], 'ООО "Ромашка"')
def test_response_includes_normalized_name(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='АКЦИОНЕРНОЕ ОБЩЕСТВО "СЕВЕРНЫЙ МОСТ"',
inn="7712345679",
kpp="771201002",
@@ -242,7 +245,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(response.data["normalized_name"], 'АО "Северный Мост"')
def test_list_returns_compact_source_summaries_without_records(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Легкий список"',
inn="7712345682",
kpp="771201005",
@@ -279,7 +282,7 @@ class OrganizationsApiV2Test(APITestCase):
)
def test_list_source_summaries_do_not_load_source_records(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Легкие источники"',
inn="7712345685",
kpp="771201008",
@@ -316,7 +319,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(source_record_queries, [])
def test_source_records_endpoint_returns_requested_source_payload(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Явные данные"',
full_name='Общество с ограниченной ответственностью "Явные данные"',
short_name='ООО "Явные данные"',
@@ -387,7 +390,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(embedded_organization["is_branch"], False)
def test_detail_returns_compact_source_summaries_by_default(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Полная карточка"',
inn="7712345684",
kpp="771201007",
@@ -410,13 +413,13 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(response.data["sources"][0]["source_group"], "financial_indicators")
def test_source_group_filter_limits_organizations_by_source_extension(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Сводка данных"',
inn="7712345681",
kpp="771201004",
ogrn="1027700132199",
)
other = Organization.objects.create(
other = create_frontend_organization(
name='ООО "Без промышленности"',
inn="7712345686",
kpp="771201009",
@@ -446,7 +449,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(response.data["data"][0]["uid"], str(organization.uid))
def test_normalized_name_compacts_scientific_production_forms(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='Научно-Производственное Предприятие "ИМПУЛЬС"',
inn="7712345680",
kpp="771201003",
@@ -463,13 +466,13 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(response.data["normalized_name"], 'НПП "Импульс"')
def test_list_filters_by_identifiers_and_searches_by_name(self):
target = Organization.objects.create(
target = create_frontend_organization(
name='ООО "Северный мост"',
inn="7711111111",
kpp="771101001",
ogrn="1027700132111",
)
Organization.objects.create(
create_frontend_organization(
name='АО "Южный путь"',
inn="7722222222",
kpp="772201001",
@@ -494,13 +497,13 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(by_search.data["data"][0]["uid"], str(target.uid))
def test_list_searches_by_identifier_fragments_for_dashboard(self):
target = Organization.objects.create(
target = create_frontend_organization(
name='ООО "Поиск по реквизитам"',
inn="7711111122",
kpp="771102222",
ogrn="1027700132122",
)
Organization.objects.create(
create_frontend_organization(
name='ООО "Другая организация"',
inn="7722222233",
kpp="772203333",
@@ -520,7 +523,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(response.data["data"][0]["uid"], str(target.uid))
def test_list_response_is_cached_by_query_string(self):
Organization.objects.create(
create_frontend_organization(
name='ООО "Кеш"',
inn="7733333333",
kpp="773301001",
@@ -532,7 +535,7 @@ class OrganizationsApiV2Test(APITestCase):
url,
{"inn": "7733333333", "has_registry": "false"},
)
Organization.objects.create(
create_frontend_organization(
name='ООО "После кеша"',
inn="7744444444",
kpp="774401001",
@@ -555,7 +558,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(different_query_response.data["data"][0]["inn"], "7744444444")
def test_list_response_cache_is_invalidated_by_version_bump(self):
Organization.objects.create(
create_frontend_organization(
name='ООО "Версия кеша"',
inn="7744444445",
kpp="774401002",
@@ -575,7 +578,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(third_response["X-Cache"], "MISS")
def test_source_update_invalidates_organization_cache(self):
Organization.objects.create(
create_frontend_organization(
name='ООО "Источник сброса"',
inn="7744444446",
kpp="774401003",
@@ -599,7 +602,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(third_response["X-Cache"], "MISS")
def test_explicit_invalidation_refreshes_organization_cache(self):
Organization.objects.create(
create_frontend_organization(
name='ООО "Реестр сброса"',
inn="7744444447",
kpp="774401004",
@@ -618,7 +621,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(third_response["X-Cache"], "MISS")
def test_retrieve_response_is_cached(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Деталь"',
inn="7755555555",
kpp="775501001",
@@ -639,7 +642,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(second_response.data["name"], 'ООО "Деталь"')
def test_list_includes_source_groups_and_active_registries(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Данные"',
inn="7777777777",
kpp="777701001",
@@ -783,8 +786,8 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(records_response.data["data"][0]["external_id"], "fin-1")
self.assertEqual(len(records_response.data["data"][0]["financial_lines"]), 3)
def test_filters_by_registry_and_has_registry(self):
with_registry = Organization.objects.create(
def test_frontend_scope_cannot_be_lifted_by_has_registry_parameter(self):
with_registry = create_frontend_organization(
name='ООО "В реестре"',
inn="7788888888",
kpp="778801001",
@@ -793,11 +796,32 @@ class OrganizationsApiV2Test(APITestCase):
ropk_razdel_num="opk-space",
ropk_razdel_name="Роскосмос ОПК",
)
without_registry = Organization.objects.create(
without_registry = create_frontend_organization(
name='ООО "Без реестра"',
inn="7799999999",
kpp="779901001",
ogrn="1027700132999",
opk_registry_membership=False,
)
rosatom_without_opk = create_frontend_organization(
name='АО "Росатом без ОПК"',
inn="7799999998",
kpp="779801001",
ogrn="1027700132998",
gk_code="rosatom",
gk_name="Росатом",
goz_participation=True,
opk_registry_membership=False,
)
roscosmos_without_opk = create_frontend_organization(
name='АО "Роскосмос без ОПК"',
inn="7799999997",
kpp="779701001",
ogrn="1027700132997",
gk_code="roscosmos",
gk_name="Роскосмос",
ropk_num="historical-number-without-membership",
opk_registry_membership=False,
)
by_registry = self.client.get(
reverse("api_v2:organizations:organizations-list"),
@@ -820,22 +844,31 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(has_registry.data["data"][0]["uid"], str(with_registry.uid))
self.assertEqual(no_registry.data["meta"]["pagination"]["total_count"], 1)
self.assertEqual(no_registry.data["data"][0]["uid"], str(without_registry.uid))
def test_has_registry_filter_uses_uncorrelated_identity_subqueries(self):
filterset = OrganizationFilter(
data={"has_registry": "true"},
queryset=Organization.objects.all(),
self.assertEqual(no_registry.data["data"][0]["uid"], str(with_registry.uid))
returned_uids = {item["uid"] for item in no_registry.data["data"]}
self.assertTrue(
returned_uids.isdisjoint(
{
str(without_registry.uid),
str(rosatom_without_opk.uid),
str(roscosmos_without_opk.uid),
}
)
)
self.assertTrue(filterset.is_valid(), filterset.errors)
sql = str(filterset.qs.query).upper()
hidden_detail = self.client.get(
reverse(
"api_v2:organizations:organizations-detail",
args=[rosatom_without_opk.uid],
)
)
self.assertEqual(hidden_detail.status_code, status.HTTP_404_NOT_FOUND)
self.assertIn("OPK_REGISTRY_MEMBERSHIP", sql)
self.assertNotIn("EXISTS", sql)
def test_has_registry_is_not_a_public_filter(self):
self.assertNotIn("has_registry", OrganizationFilter.base_filters)
def test_list_defaults_to_has_registry_true(self):
with_registry = Organization.objects.create(
with_registry = create_frontend_organization(
name='ООО "Дефолтный реестр"',
inn="7800000101",
kpp="780001101",
@@ -844,11 +877,21 @@ class OrganizationsApiV2Test(APITestCase):
ropk_razdel_num="default",
ropk_razdel_name="Дефолтный реестр",
)
Organization.objects.create(
create_frontend_organization(
name='ООО "Скрыто по дефолту"',
inn="7800000102",
kpp="780001102",
ogrn="1027700133102",
opk_registry_membership=False,
)
create_frontend_organization(
name='АО "Росатом без ОПК скрыт по дефолту"',
inn="7800000103",
kpp="780001103",
ogrn="1027700133103",
gk_name="Росатом",
goz_participation=True,
opk_registry_membership=False,
)
default_response = self.client.get(
reverse("api_v2:organizations:organizations-list")
@@ -869,24 +912,24 @@ class OrganizationsApiV2Test(APITestCase):
1,
)
self.assertEqual(
explicit_false_response.data["data"][0]["inn"],
"7800000102",
{item["inn"] for item in explicit_false_response.data["data"]},
{"7800000101"},
)
def test_filters_by_data_presence(self):
with_industrial = Organization.objects.create(
with_industrial = create_frontend_organization(
name='ООО "С промышленностью"',
inn="7800000001",
kpp="780001001",
ogrn="1027700133001",
)
with_fns = Organization.objects.create(
with_fns = create_frontend_organization(
name='ООО "С отчетностью"',
inn="7800000002",
kpp="780001002",
ogrn="1027700133002",
)
without_data = Organization.objects.create(
without_data = create_frontend_organization(
name='ООО "Без данных"',
inn="7800000003",
kpp="780001003",
@@ -937,7 +980,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(has_fns.data["data"][0]["uid"], str(with_fns.uid))
def test_has_fns_reports_filter_does_not_preload_report_identities(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Отчетность без preload"',
inn="7800000004",
kpp="780001004",
@@ -966,13 +1009,13 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(distinct_report_queries, [])
def test_sources_action_returns_only_current_organization_extensions(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Источник"',
inn="7800000201",
kpp="780002101",
ogrn="1027700133201",
)
other = Organization.objects.create(
other = create_frontend_organization(
name='ООО "Другой источник"',
inn="7800000205",
kpp="780002105",
@@ -1010,7 +1053,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(sources["industrial_production"]["uid"], str(industrial.uid))
def test_unknown_source_group_filter_returns_empty_page(self):
Organization.objects.create(
create_frontend_organization(
name='ООО "Неверная группа"',
inn="7800000202",
kpp="780002102",
@@ -1026,7 +1069,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(response.data["meta"]["pagination"]["total_count"], 0)
def test_detail_source_summaries_do_not_load_source_records(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Без N+1"',
inn="7800000203",
kpp="780002103",
@@ -1084,7 +1127,7 @@ class OrganizationsApiV2Test(APITestCase):
self.assertEqual(source_record_queries, [])
def test_trudvsem_filter_alias_uses_vacancies_source_group(self):
organization = Organization.objects.create(
organization = create_frontend_organization(
name='ООО "Вакансии"',
inn="7800000204",
kpp="780002104",
@@ -1128,7 +1171,7 @@ class OrganizationsApiV2Test(APITestCase):
@override_settings(ORGANIZATIONS_V2_ALLOW_ANONYMOUS=True)
def test_dev_flag_allows_anonymous_access(self):
self.client.force_authenticate(user=None)
Organization.objects.create(
create_frontend_organization(
name='ООО "Без токена"',
inn="7766666666",
kpp="776601001",