"""Tests for organizations API v2.""" from apps.parsers.models import ParserLoadLog from django.core.cache import cache from django.db import connection from django.test import override_settings from django.test.utils import CaptureQueriesContext from django.urls import reverse from organizations.cache import invalidate_organization_api_cache from organizations.filters import OrganizationFilter from organizations.models import ( FinancialIndicatorsExtension, GovernmentProcurementExtension, IndustrialProductionExtension, Organization, OrganizationSourceFinancialLine, OrganizationSourceRecord, PlannedInspectionExtension, SecurityRegistryExtension, VacancyExtension, ) from rest_framework import status from rest_framework.test import APITestCase from tests.apps.registers.factories import ( OrganizationFactory as RegistryOrganizationFactory, ) from tests.apps.registers.factories import ( RegisterFactory, RegistryMembershipPeriodFactory, ) from tests.apps.user.factories import UserFactory class OrganizationsApiV2Test(APITestCase): """Checks v2 canonical organizations endpoints.""" def setUp(self): cache.clear() self.user = UserFactory.create_user() self.client.force_authenticate(self.user) def test_list_is_paginated_and_available_only_under_v2(self): Organization.objects.create( name='ООО "Альфа"', inn="7707083893", kpp="770701001", ogrn="1027700132195", ) Organization.objects.create( name="ИП Иванов Иван Иванович", inn="500100732259", ogrip="304500116000157", ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), {"page_size": 1, "has_registry": "false"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["success"], True) self.assertEqual(len(response.data["data"]), 1) self.assertEqual(response.data["meta"]["pagination"]["total_count"], 2) self.assertEqual(response.data["meta"]["pagination"]["page_size"], 1) v1_response = self.client.get("/api/v1/organizations/") self.assertEqual(v1_response.status_code, status.HTTP_404_NOT_FOUND) def test_openapi_documents_v2_organizations_endpoints(self): response = self.client.get( reverse("schema-swagger-ui"), {"format": "openapi"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) paths = response.data["paths"] list_operation = paths["/api/v2/organizations/"]["get"] detail_operation = paths["/api/v2/organizations/{uid}/"]["get"] self.assertEqual(list_operation["tags"], ["Организации"]) self.assertEqual(detail_operation["tags"], ["Организации"]) self.assertEqual( list_operation["operationId"], "v2_organizations_list", ) self.assertEqual( detail_operation["operationId"], "v2_organizations_retrieve", ) list_parameters = { parameter["name"]: parameter for parameter in list_operation["parameters"] } for expected_name in ( "page", "page_size", "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.assertIn( "группе источников", list_parameters["source_group"]["description"], ) detail_parameters = { parameter["name"]: parameter for parameter in detail_operation["parameters"] } self.assertEqual(detail_parameters["uid"]["type"], "string") self.assertEqual(detail_parameters["uid"]["format"], "uuid") self.assertNotIn("data", detail_parameters) self.assertNotIn("exclude_data", detail_parameters) self.assertIn( "Пагинированный", list_operation["responses"]["200"]["description"] ) self.assertIn( "Карточка организации", detail_operation["responses"]["200"]["description"], ) self.assertIn("/api/v2/organizations/{uid}/sources/", paths) self.assertIn("/api/v2/organization-sources/{uid}/records/", paths) def test_retrieve_returns_item_by_uid(self): organization = Organization.objects.create( name='ООО "Ромашка"', inn="7712345678", kpp="771201001", ogrn="1027700132196", ) response = self.client.get( reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ) ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["uid"], str(organization.uid)) self.assertEqual(response.data["name"], 'ООО "Ромашка"') def test_response_includes_normalized_name(self): organization = Organization.objects.create( name='АКЦИОНЕРНОЕ ОБЩЕСТВО "СЕВЕРНЫЙ МОСТ"', inn="7712345679", kpp="771201002", ogrn="1027700132197", ) response = self.client.get( reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ) ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["name"], 'АКЦИОНЕРНОЕ ОБЩЕСТВО "СЕВЕРНЫЙ МОСТ"') self.assertEqual(response.data["normalized_name"], 'АО "Северный Мост"') def test_list_returns_compact_source_summaries_without_records(self): organization = Organization.objects.create( name='ООО "Легкий список"', inn="7712345682", kpp="771201005", ogrn="1027700132200", ) extension = IndustrialProductionExtension.objects.create( organization=organization, title="Производители и продукция России", records_count=2, metadata={"sources": ["industrial", "industrial_products"]}, ) OrganizationSourceRecord.objects.create( extension=extension, record_type="industrial_certificate", source="industrial", external_id="cert-list-summary", payload={"certificate_number": "CERT-LIST"}, ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), {"inn": organization.inn, "has_registry": "false"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) item = response.data["data"][0] self.assertNotIn("data", item) self.assertNotIn("data_sources", item) self.assertEqual(item["sources"][0]["source_group"], "industrial_production") self.assertEqual(item["sources"][0]["records_count"], 2) self.assertEqual( item["sources"][0]["metadata"], {"sources": ["industrial", "industrial_products"]}, ) def test_list_source_summaries_do_not_load_source_records(self): organization = Organization.objects.create( name='ООО "Легкие источники"', inn="7712345685", kpp="771201008", ogrn="1027700132203", ) extension = FinancialIndicatorsExtension.objects.create( organization=organization, title="Финансово-экономические показатели", records_count=100, ) for index in range(100): OrganizationSourceRecord.objects.create( extension=extension, record_type="financial_report", source="fns_reports", external_id=f"summary-no-record-{index}", title=f"Report {index}", ) with CaptureQueriesContext(connection) as captured: response = self.client.get( reverse("api_v2:organizations:organizations-list"), {"inn": organization.inn, "has_registry": "false"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) item = response.data["data"][0] self.assertEqual(item["sources"][0]["records_count"], 100) source_record_queries = [ query["sql"] for query in captured if "ORGANIZATIONS_SOURCE_RECORD" in query["sql"].upper() ] self.assertEqual(source_record_queries, []) def test_source_records_endpoint_returns_requested_source_payload(self): organization = Organization.objects.create( name='ООО "Явные данные"', inn="7712345683", kpp="771201006", ogrn="1027700132201", ) extension = IndustrialProductionExtension.objects.create( organization=organization, title="Производители и продукция России", records_count=1, ) OrganizationSourceRecord.objects.create( extension=extension, record_type="industrial_certificate", source="industrial", external_id="cert-source-records", payload={"certificate_number": "CERT-SOURCE-RECORDS"}, ) response = self.client.get( reverse( "api_v2:organizations:organization-sources-records", args=[extension.uid], ) ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( response.data["data"][0]["payload"], {"certificate_number": "CERT-SOURCE-RECORDS"}, ) def test_detail_returns_compact_source_summaries_by_default(self): organization = Organization.objects.create( name='ООО "Полная карточка"', inn="7712345684", kpp="771201007", ogrn="1027700132202", ) FinancialIndicatorsExtension.objects.create( organization=organization, title="Финансово-экономические показатели", records_count=1, ) response = self.client.get( reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ) ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn("data", response.data) 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( name='ООО "Сводка данных"', inn="7712345681", kpp="771201004", ogrn="1027700132199", ) other = Organization.objects.create( name='ООО "Без промышленности"', inn="7712345686", kpp="771201009", ogrn="1027700132204", ) IndustrialProductionExtension.objects.create( organization=organization, title="Производители и продукция России", records_count=1, ) FinancialIndicatorsExtension.objects.create( organization=other, title="Финансово-экономические показатели", records_count=1, ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), { "source_group": "industrial_production", "has_registry": "false", }, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1) self.assertEqual(response.data["data"][0]["uid"], str(organization.uid)) def test_normalized_name_compacts_scientific_production_forms(self): organization = Organization.objects.create( name='Научно-Производственное Предприятие "ИМПУЛЬС"', inn="7712345680", kpp="771201003", ogrn="1027700132198", ) response = self.client.get( reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ) ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["normalized_name"], 'НПП "Импульс"') def test_list_filters_by_identifiers_and_searches_by_name(self): target = Organization.objects.create( name='ООО "Северный мост"', inn="7711111111", kpp="771101001", ogrn="1027700132111", ) Organization.objects.create( name='АО "Южный путь"', inn="7722222222", kpp="772201001", ogrn="1027700132222", ) by_inn = self.client.get( reverse("api_v2:organizations:organizations-list"), {"inn": target.inn, "has_registry": "false"}, ) by_search = self.client.get( reverse("api_v2:organizations:organizations-list"), {"search": "мост", "has_registry": "false"}, ) self.assertEqual(by_inn.status_code, status.HTTP_200_OK) self.assertEqual(by_inn.data["meta"]["pagination"]["total_count"], 1) self.assertEqual(by_inn.data["data"][0]["uid"], str(target.uid)) self.assertEqual(by_search.status_code, status.HTTP_200_OK) self.assertEqual(by_search.data["meta"]["pagination"]["total_count"], 1) 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( name='ООО "Поиск по реквизитам"', inn="7711111122", kpp="771102222", ogrn="1027700132122", ) Organization.objects.create( name='ООО "Другая организация"', inn="7722222233", kpp="772203333", ogrn="1027700132233", ) url = reverse("api_v2:organizations:organizations-list") for search_value in ("111122", "1027700132122", "102222"): with self.subTest(search_value=search_value): response = self.client.get( url, {"search": search_value, "has_registry": "false"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1) self.assertEqual(response.data["data"][0]["uid"], str(target.uid)) def test_list_response_is_cached_by_query_string(self): Organization.objects.create( name='ООО "Кеш"', inn="7733333333", kpp="773301001", ogrn="1027700132333", ) url = reverse("api_v2:organizations:organizations-list") first_response = self.client.get( url, {"inn": "7733333333", "has_registry": "false"}, ) Organization.objects.create( name='ООО "После кеша"', inn="7744444444", kpp="774401001", ogrn="1027700132444", ) second_response = self.client.get( url, {"inn": "7733333333", "has_registry": "false"}, ) different_query_response = self.client.get( url, {"inn": "7744444444", "has_registry": "false"}, ) self.assertEqual(first_response.status_code, status.HTTP_200_OK) self.assertEqual(first_response["X-Cache"], "MISS") self.assertEqual(second_response["X-Cache"], "HIT") self.assertEqual(second_response.data, first_response.data) self.assertEqual(different_query_response["X-Cache"], "MISS") self.assertEqual(different_query_response.data["data"][0]["inn"], "7744444444") def test_list_response_cache_is_invalidated_by_version_bump(self): Organization.objects.create( name='ООО "Версия кеша"', inn="7744444445", kpp="774401002", ogrn="1027700132445", ) url = reverse("api_v2:organizations:organizations-list") params = {"inn": "7744444445", "has_registry": "false"} first_response = self.client.get(url, params) second_response = self.client.get(url, params) invalidate_organization_api_cache() third_response = self.client.get(url, params) self.assertEqual(first_response.status_code, status.HTTP_200_OK) self.assertEqual(first_response["X-Cache"], "MISS") self.assertEqual(second_response["X-Cache"], "HIT") self.assertEqual(third_response["X-Cache"], "MISS") def test_source_update_invalidates_organization_cache(self): Organization.objects.create( name='ООО "Источник сброса"', inn="7744444446", kpp="774401003", ogrn="1027700132446", ) url = reverse("api_v2:organizations:organizations-list") params = {"inn": "7744444446", "has_registry": "false"} first_response = self.client.get(url, params) second_response = self.client.get(url, params) with self.captureOnCommitCallbacks(execute=True): ParserLoadLog.objects.create( source=ParserLoadLog.Source.FSTEC, batch_id=1, status=ParserLoadLog.Status.SUCCESS, ) third_response = self.client.get(url, params) self.assertEqual(first_response["X-Cache"], "MISS") self.assertEqual(second_response["X-Cache"], "HIT") self.assertEqual(third_response["X-Cache"], "MISS") def test_registry_update_invalidates_organization_cache(self): Organization.objects.create( name='ООО "Реестр сброса"', inn="7744444447", kpp="774401004", ogrn="1027700132447", ) url = reverse("api_v2:organizations:organizations-list") params = {"inn": "7744444447", "has_registry": "false"} first_response = self.client.get(url, params) second_response = self.client.get(url, params) with self.captureOnCommitCallbacks(execute=True): RegisterFactory(name="Реестр для сброса кеша") third_response = self.client.get(url, params) self.assertEqual(first_response["X-Cache"], "MISS") self.assertEqual(second_response["X-Cache"], "HIT") self.assertEqual(third_response["X-Cache"], "MISS") def test_retrieve_response_is_cached(self): organization = Organization.objects.create( name='ООО "Деталь"', inn="7755555555", kpp="775501001", ogrn="1027700132555", ) url = reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ) first_response = self.client.get(url) organization.name = 'ООО "Изменено"' organization.save(update_fields=["name"]) second_response = self.client.get(url) self.assertEqual(first_response.status_code, status.HTTP_200_OK) self.assertEqual(first_response["X-Cache"], "MISS") self.assertEqual(second_response["X-Cache"], "HIT") self.assertEqual(second_response.data["name"], 'ООО "Деталь"') def test_list_includes_source_groups_and_active_registries(self): organization = Organization.objects.create( name='ООО "Данные"', inn="7777777777", kpp="777701001", ogrn="1027700132777", ) registry = RegisterFactory(name="Росатом ГОЗ") registry_organization = RegistryOrganizationFactory( mn_inn=int(organization.inn), mn_ogrn=int(organization.ogrn), ) RegistryMembershipPeriodFactory( registry=registry, organization=registry_organization, ) industrial = IndustrialProductionExtension.objects.create( organization=organization, title="Производители и продукция России", records_count=3, ) planned = PlannedInspectionExtension.objects.create( organization=organization, title="Плановые проверки Генпрокуратуры России", records_count=1, ) procurements = GovernmentProcurementExtension.objects.create( organization=organization, title="Государственные закупки по 44-ФЗ и 223-ФЗ", records_count=2, ) security = SecurityRegistryExtension.objects.create( organization=organization, title="Реестры по информационной безопасности", records_count=1, ) financial = FinancialIndicatorsExtension.objects.create( organization=organization, title="Финансово-экономические показатели", records_count=1, ) OrganizationSourceRecord.objects.create( extension=industrial, record_type="industrial_certificate", source="industrial", external_id="CERT-ORG-V2-1", payload={"certificate_number": "CERT-ORG-V2-1"}, ) OrganizationSourceRecord.objects.create( extension=planned, record_type="inspection", source="inspections", external_id="INSPECTION-ORG-V2-1", payload={"registration_number": "INSPECTION-ORG-V2-1"}, ) OrganizationSourceRecord.objects.create( extension=procurements, record_type="procurement", source="procurements", external_id="PROCUREMENT-ORG-V2-1", payload={"purchase_number": "PROCUREMENT-ORG-V2-1"}, ) OrganizationSourceRecord.objects.create( extension=procurements, record_type="procurement", source="procurements_44fz", external_id="procurements-44fz-1", payload={"external_id": "procurements-44fz-1"}, ) OrganizationSourceRecord.objects.create( extension=security, record_type="security_registry", source="fstec", external_id="fstec-1", payload={"source": "fstec"}, ) report_record = OrganizationSourceRecord.objects.create( extension=financial, record_type="financial_report", source="fns_reports", external_id="fin-1", payload={"external_id": "fin-1"}, ) OrganizationSourceFinancialLine.objects.create( source_record=report_record, form_code="1", line_code="1100", line_name="Внеоборотные активы", year=2021, period_start=100, period_end=200, ) OrganizationSourceFinancialLine.objects.create( source_record=report_record, form_code="1", line_code="1300", line_name="Капитал и резервы", year=2021, period_start=300, period_end=400, ) OrganizationSourceFinancialLine.objects.create( source_record=report_record, form_code="2", line_code="2110", line_name="Выручка", year=2022, period_start=None, period_end=500, ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), { "inn": organization.inn, "has_registry": "true", }, ) self.assertEqual(response.status_code, status.HTTP_200_OK) item = response.data["data"][0] self.assertEqual( item["registries"], [{"id": str(registry.id), "name": "Росатом ГОЗ"}], ) self.assertNotIn("data_presence", item) self.assertNotIn("data", item) sources = {source["source_group"]: source for source in item["sources"]} self.assertEqual( set(sources), { "financial_indicators", "government_procurements", "industrial_production", "planned_inspections", "security_registries", }, ) self.assertEqual(sources["industrial_production"]["records_count"], 3) self.assertEqual(sources["financial_indicators"]["records_count"], 1) records_response = self.client.get( reverse( "api_v2:organizations:organization-sources-records", args=[financial.uid], ) ) self.assertEqual(records_response.status_code, status.HTTP_200_OK) 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( name='ООО "В реестре"', inn="7788888888", kpp="778801001", ogrn="1027700132888", ) without_registry = Organization.objects.create( name='ООО "Без реестра"', inn="7799999999", kpp="779901001", ogrn="1027700132999", ) registry = RegisterFactory(name="Роскосмос ОПК") registry_organization = RegistryOrganizationFactory( mn_inn=int(with_registry.inn), mn_ogrn=int(with_registry.ogrn), ) RegistryMembershipPeriodFactory( registry=registry, organization=registry_organization, ) by_registry = self.client.get( reverse("api_v2:organizations:organizations-list"), {"registry": str(registry.id)}, ) has_registry = self.client.get( reverse("api_v2:organizations:organizations-list"), {"has_registry": "true"}, ) no_registry = self.client.get( reverse("api_v2:organizations:organizations-list"), {"has_registry": "false", "ordering": "inn"}, ) self.assertEqual(by_registry.status_code, status.HTTP_200_OK) self.assertEqual(by_registry.data["meta"]["pagination"]["total_count"], 1) self.assertEqual(by_registry.data["data"][0]["uid"], str(with_registry.uid)) self.assertEqual(has_registry.data["meta"]["pagination"]["total_count"], 1) 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.assertTrue(filterset.is_valid(), filterset.errors) sql = str(filterset.qs.query).upper() self.assertIn(" IN ", sql) self.assertNotIn("EXISTS", sql) def test_list_defaults_to_has_registry_true(self): with_registry = Organization.objects.create( name='ООО "Дефолтный реестр"', inn="7800000101", kpp="780001101", ogrn="1027700133101", ) Organization.objects.create( name='ООО "Скрыто по дефолту"', inn="7800000102", kpp="780001102", ogrn="1027700133102", ) registry = RegisterFactory(name="Дефолтный реестр") registry_organization = RegistryOrganizationFactory( mn_inn=int(with_registry.inn), mn_ogrn=int(with_registry.ogrn), ) RegistryMembershipPeriodFactory( registry=registry, organization=registry_organization, ) default_response = self.client.get( reverse("api_v2:organizations:organizations-list") ) explicit_false_response = self.client.get( reverse("api_v2:organizations:organizations-list"), {"has_registry": "false"}, ) self.assertEqual(default_response.status_code, status.HTTP_200_OK) self.assertEqual(default_response.data["meta"]["pagination"]["total_count"], 1) self.assertEqual( default_response.data["data"][0]["uid"], str(with_registry.uid) ) self.assertEqual( explicit_false_response.data["meta"]["pagination"]["total_count"], 1, ) self.assertEqual( explicit_false_response.data["data"][0]["inn"], "7800000102", ) def test_filters_by_data_presence(self): with_industrial = Organization.objects.create( name='ООО "С промышленностью"', inn="7800000001", kpp="780001001", ogrn="1027700133001", ) with_fns = Organization.objects.create( name='ООО "С отчетностью"', inn="7800000002", kpp="780001002", ogrn="1027700133002", ) without_data = Organization.objects.create( name='ООО "Без данных"', inn="7800000003", kpp="780001003", ogrn="1027700133003", ) IndustrialProductionExtension.objects.create( organization=with_industrial, title="Производители и продукция России", records_count=1, ) FinancialIndicatorsExtension.objects.create( organization=with_fns, title="Финансово-экономические показатели", records_count=1, ) has_industrial = self.client.get( reverse("api_v2:organizations:organizations-list"), {"has_industrial": "true", "has_registry": "false"}, ) no_industrial = self.client.get( reverse("api_v2:organizations:organizations-list"), { "has_industrial": "false", "has_registry": "false", "ordering": "inn", }, ) has_fns = self.client.get( reverse("api_v2:organizations:organizations-list"), {"has_fns_reports": "true", "has_registry": "false"}, ) self.assertEqual(has_industrial.status_code, status.HTTP_200_OK) self.assertEqual(has_industrial.data["meta"]["pagination"]["total_count"], 1) self.assertEqual( has_industrial.data["data"][0]["uid"], str(with_industrial.uid), ) self.assertEqual(no_industrial.data["meta"]["pagination"]["total_count"], 2) self.assertEqual( [item["uid"] for item in no_industrial.data["data"]], [str(with_fns.uid), str(without_data.uid)], ) self.assertEqual(has_fns.data["meta"]["pagination"]["total_count"], 1) 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( name='ООО "Отчетность без preload"', inn="7800000004", kpp="780001004", ogrn="1027700133004", ) FinancialIndicatorsExtension.objects.create( organization=organization, title="Финансово-экономические показатели", records_count=1, ) with CaptureQueriesContext(connection) as captured: response = self.client.get( reverse("api_v2:organizations:organizations-list"), {"has_fns_reports": "true", "has_registry": "false"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1) distinct_report_queries = [ query["sql"] for query in captured if "PARSERS_FINANCIAL_REPORT" in query["sql"].upper() and "SELECT DISTINCT" in query["sql"].upper() ] self.assertEqual(distinct_report_queries, []) def test_sources_action_returns_only_current_organization_extensions(self): organization = Organization.objects.create( name='ООО "Источник"', inn="7800000201", kpp="780002101", ogrn="1027700133201", ) other = Organization.objects.create( name='ООО "Другой источник"', inn="7800000205", kpp="780002105", ogrn="1027700133205", ) industrial = IndustrialProductionExtension.objects.create( organization=organization, title="Производители и продукция России", records_count=1, ) FinancialIndicatorsExtension.objects.create( organization=organization, title="Финансово-экономические показатели", records_count=1, ) VacancyExtension.objects.create( organization=other, title="Вакансии", records_count=1, ) response = self.client.get( reverse( "api_v2:organizations:organizations-sources", args=[organization.uid], ) ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( {source["source_group"] for source in response.data}, {"financial_indicators", "industrial_production"}, ) sources = {source["source_group"]: source for source in response.data} self.assertEqual(sources["industrial_production"]["uid"], str(industrial.uid)) def test_unknown_source_group_filter_returns_empty_page(self): Organization.objects.create( name='ООО "Неверная группа"', inn="7800000202", kpp="780002102", ogrn="1027700133202", ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), {"source_group": "unknown", "has_registry": "false"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["meta"]["pagination"]["total_count"], 0) def test_detail_source_summaries_do_not_load_source_records(self): organization = Organization.objects.create( name='ООО "Без N+1"', inn="7800000203", kpp="780002103", ogrn="1027700133203", ) financial = FinancialIndicatorsExtension.objects.create( organization=organization, title="Финансово-экономические показатели", records_count=4, ) security = SecurityRegistryExtension.objects.create( organization=organization, title="Реестры по информационной безопасности", records_count=1, ) vacancies = VacancyExtension.objects.create( organization=organization, title="Вакансии", records_count=1, ) for index in range(200): OrganizationSourceRecord.objects.create( extension=financial, record_type="financial_report", source="fns_reports", external_id=f"financial-query-count-{index}", ) for extension, source in ( (security, "fstec"), (vacancies, "vacancies"), ): OrganizationSourceRecord.objects.create( extension=extension, record_type=source, source=source, external_id=f"{source}-query-count", ) url = reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ) with CaptureQueriesContext(connection) as captured: response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( {source["source_group"] for source in response.data["sources"]}, {"financial_indicators", "security_registries", "vacancies"}, ) source_record_queries = [ query["sql"] for query in captured if "ORGANIZATIONS_SOURCE_RECORD" in query["sql"].upper() ] self.assertEqual(source_record_queries, []) def test_trudvsem_filter_alias_uses_vacancies_source_group(self): organization = Organization.objects.create( name='ООО "Вакансии"', inn="7800000204", kpp="780002104", ogrn="1027700133204", ) extension = VacancyExtension.objects.create( organization=organization, title="Вакансии", records_count=1, ) OrganizationSourceRecord.objects.create( extension=extension, record_type="vacancy", source="trudvsem", external_id="vacancy-1", title="Инженер", ) has_vacancies_response = self.client.get( reverse("api_v2:organizations:organizations-list"), { "has_trudvsem": "true", "has_registry": "false", }, ) records_response = self.client.get( reverse( "api_v2:organizations:organization-sources-records", args=[extension.uid], ) ) self.assertEqual(has_vacancies_response.status_code, status.HTTP_200_OK) self.assertEqual( has_vacancies_response.data["data"][0]["uid"], str(organization.uid), ) self.assertEqual(records_response.status_code, status.HTTP_200_OK) self.assertEqual(records_response.data["data"][0]["source"], "trudvsem") @override_settings(ORGANIZATIONS_V2_ALLOW_ANONYMOUS=True) def test_dev_flag_allows_anonymous_access(self): self.client.force_authenticate(user=None) Organization.objects.create( name='ООО "Без токена"', inn="7766666666", kpp="776601001", ogrn="1027700132666", ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), {"has_registry": "false"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1) @override_settings(ORGANIZATIONS_V2_ALLOW_ANONYMOUS=False) def test_without_dev_flag_requires_authentication(self): self.client.force_authenticate(user=None) response = self.client.get(reverse("api_v2:organizations:organizations-list")) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)