"""Tests for organizations API v2.""" from apps.parsers.models import ( FinancialReport, FinancialReportLine, GenericParserRecord, 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.models import Organization, OrganizationDataSnapshot from organizations.services import OrganizationDataSnapshotRefreshService from rest_framework import status from rest_framework.test import APITestCase from tests.apps.parsers.factories import ( IndustrialCertificateRecordFactory, IndustrialProductRecordFactory, InspectionRecordFactory, ManufacturerRecordFactory, ProcurementRecordFactory, ) 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", "has_fns_reports", "data", "exclude_data", ): self.assertIn(expected_name, list_parameters) self.assertIn( "по умолчанию true", list_parameters["has_registry"]["description"] ) self.assertIn( "industrial_products", list_parameters["data"]["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.assertIn("data", detail_parameters) self.assertIn("exclude_data", detail_parameters) self.assertIn( "Пагинированный", list_operation["responses"]["200"]["description"] ) self.assertIn( "Карточка организации", detail_operation["responses"]["200"]["description"], ) 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_omits_full_snapshot_data_by_default_but_keeps_summary(self): organization = Organization.objects.create( name='ООО "Легкий список"', inn="7712345682", kpp="771201005", ogrn="1027700132200", ) OrganizationDataSnapshot.objects.create( organization=organization, data={ "industrial": [{"id": 1}], "fns_reports": [{"id": 2}, {"id": 3}], }, registries=[], ) 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["data"], {}) self.assertEqual( item["data_sources"], [ {"source": "fns_reports", "count": 2}, {"source": "industrial", "count": 1}, ], ) def test_list_returns_snapshot_data_when_sources_are_requested(self): organization = Organization.objects.create( name='ООО "Явные данные"', inn="7712345683", kpp="771201006", ogrn="1027700132201", ) OrganizationDataSnapshot.objects.create( organization=organization, data={ "industrial": [{"id": 1}], "fns_reports": [{"id": 2}], }, registries=[], ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), { "inn": organization.inn, "has_registry": "false", "data": "industrial", }, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( response.data["data"][0]["data"], {"industrial": [{"id": 1}]}, ) def test_detail_keeps_full_snapshot_data_by_default(self): organization = Organization.objects.create( name='ООО "Полная карточка"', inn="7712345684", kpp="771201007", ogrn="1027700132202", ) OrganizationDataSnapshot.objects.create( organization=organization, data={"industrial": [{"id": 1}]}, registries=[], ) 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["data"], {"industrial": [{"id": 1}]}) def test_list_keeps_data_source_summary_when_data_payload_is_excluded(self): organization = Organization.objects.create( name='ООО "Сводка данных"', inn="7712345681", kpp="771201004", ogrn="1027700132199", ) IndustrialCertificateRecordFactory( inn=organization.inn, ogrn=organization.ogrn, ) OrganizationDataSnapshotRefreshService.refresh( organization_uids=[str(organization.uid)] ) response = self.client.get( reverse("api_v2:organizations:organizations-list"), { "inn": organization.inn, "has_registry": "false", "exclude_data": ( "industrial,industrial_products,manufactures,inspections," "procurements,procurements_44fz,procurements_223fz,contracts," "unfair_suppliers,fas_goz,arbitration,fedresurs_bankruptcy," "fstec,vacancies,fns_reports" ), }, ) self.assertEqual(response.status_code, status.HTTP_200_OK) item = response.data["data"][0] self.assertEqual(item["data"], {}) self.assertEqual( item["data_sources"], [{"source": "industrial", "count": 1}], ) 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_parser_presence_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, ) certificate = IndustrialCertificateRecordFactory( inn=organization.inn, ogrn=organization.ogrn, certificate_number="CERT-ORG-V2-1", ) product = IndustrialProductRecordFactory( inn=organization.inn, ogrn=organization.ogrn, registry_number="PRODUCT-ORG-V2-1", ) manufacturer = ManufacturerRecordFactory( inn=organization.inn, ogrn=organization.ogrn, full_legal_name='ООО "Данные"', ) inspection = InspectionRecordFactory( inn=organization.inn, ogrn=organization.ogrn, registration_number="INSPECTION-ORG-V2-1", ) procurement = ProcurementRecordFactory( customer_inn=organization.inn, customer_ogrn=organization.ogrn, purchase_number="PROCUREMENT-ORG-V2-1", ) generic_record = GenericParserRecord.objects.create( load_batch=1, source=ParserLoadLog.Source.FSTEC, external_id="fstec-1", inn=organization.inn, ogrn=organization.ogrn, organisation_name=organization.name, title="FSTEC record", payload={"source": "fstec"}, ) GenericParserRecord.objects.create( load_batch=1, source=ParserLoadLog.Source.PROCUREMENTS_44FZ, external_id="procurements-44fz-1", inn=organization.inn, ogrn=organization.ogrn, organisation_name=organization.name, title="44-FZ record", ) financial_report = FinancialReport.objects.create( external_id="fin-1", ogrn=organization.ogrn, file_name="fin.xlsx", file_hash="a" * 64, load_batch=1, status=FinancialReport.Status.SUCCESS, source=FinancialReport.SourceType.API, ) FinancialReportLine.objects.create( report=financial_report, form_code="1", line_code="1100", line_name="Внеоборотные активы", year=2021, period_start=100, period_end=200, ) FinancialReportLine.objects.create( report=financial_report, form_code="1", line_code="1300", line_name="Капитал и резервы", year=2021, period_start=300, period_end=400, ) FinancialReportLine.objects.create( report=financial_report, 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, "data": ( "industrial,industrial_products,manufactures,inspections," "procurements,procurements_44fz,fstec,fns_reports" ), }, ) 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.assertIn("data", item) self.assertEqual( item["data"]["industrial"], [ { "id": certificate.id, "load_batch": certificate.load_batch, "issue_date": certificate.issue_date, "issue_date_normalized": None, "certificate_number": "CERT-ORG-V2-1", "expiry_date": certificate.expiry_date, "expiry_date_normalized": None, "certificate_file_url": certificate.certificate_file_url, "organisation_name": certificate.organisation_name, "inn": organization.inn, "ogrn": organization.ogrn, "registry_organization": None, "created_at": certificate.created_at.isoformat().replace( "+00:00", "Z" ), "updated_at": certificate.updated_at.isoformat().replace( "+00:00", "Z" ), } ], ) self.assertEqual( item["data"]["industrial_products"][0]["registry_number"], "PRODUCT-ORG-V2-1", ) self.assertEqual( item["data"]["industrial_products"][0]["id"], product.id, ) self.assertEqual( item["data"]["manufactures"][0]["id"], manufacturer.id, ) self.assertEqual( item["data"]["inspections"][0]["registration_number"], "INSPECTION-ORG-V2-1", ) self.assertEqual( item["data"]["inspections"][0]["id"], inspection.id, ) self.assertEqual( item["data"]["procurements"][0]["purchase_number"], "PROCUREMENT-ORG-V2-1", ) self.assertEqual( item["data"]["procurements"][0]["id"], procurement.id, ) self.assertEqual( item["data"]["procurements_44fz"][0]["external_id"], "procurements-44fz-1", ) self.assertEqual( item["data"]["fstec"][0]["id"], generic_record.id, ) self.assertEqual( item["data"]["fns_reports"][0]["external_id"], "fin-1", ) self.assertEqual( item["data"]["fns_reports"][0]["id"], financial_report.id, ) self.assertEqual(item["data"]["fns_reports"][0]["lines_count"], 3) self.assertEqual( item["data"]["fns_reports"][0]["lines"]["2021"]["active"]["1100"], { "form_code": "1", "name": "Внеоборотные активы", "period_start": 100, "period_end": 200, }, ) self.assertEqual( item["data"]["fns_reports"][0]["lines"]["2021"]["passive"]["1300"], { "form_code": "1", "name": "Капитал и резервы", "period_start": 300, "period_end": 400, }, ) self.assertEqual( item["data"]["fns_reports"][0]["lines"]["2022"]["form_2"]["2110"], { "form_code": "2", "name": "Выручка", "period_start": None, "period_end": 500, }, ) self.assertTrue(all(isinstance(value, list) for value in item["data"].values())) 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_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", ) IndustrialCertificateRecordFactory( inn=with_industrial.inn, ogrn=with_industrial.ogrn, ) FinancialReport.objects.create( external_id="fin-presence-1", ogrn=with_fns.ogrn, file_name="fin_presence.xlsx", file_hash="b" * 64, load_batch=1, status=FinancialReport.Status.SUCCESS, source=FinancialReport.SourceType.API, ) 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_limits_response_data_sources(self): organization = Organization.objects.create( name='ООО "Источник"', inn="7800000201", kpp="780002101", ogrn="1027700133201", ) IndustrialCertificateRecordFactory( inn=organization.inn, ogrn=organization.ogrn, ) GenericParserRecord.objects.create( load_batch=1, source=ParserLoadLog.Source.FSTEC, external_id="fstec-source-limit", inn=organization.inn, ogrn=organization.ogrn, organisation_name=organization.name, ) FinancialReport.objects.create( external_id="fin-source-limit", ogrn=organization.ogrn, file_name="fin_source_limit.xlsx", file_hash="c" * 64, load_batch=1, status=FinancialReport.Status.SUCCESS, source=FinancialReport.SourceType.API, ) detail_url = reverse( "api_v2:organizations:organizations-detail", args=[organization.uid], ) only_response = self.client.get( detail_url, {"data": "industrial,fns_reports"}, ) excluded_response = self.client.get( detail_url, {"exclude_data": "fns_reports"}, ) repeated_response = self.client.get( detail_url, [("data", "industrial"), ("data", "fstec")], ) empty_response = self.client.get( detail_url, {"data": "industrial", "exclude_data": "industrial"}, ) self.assertEqual(only_response.status_code, status.HTTP_200_OK) self.assertEqual( set(only_response.data["data"]), {"industrial", "fns_reports"}, ) self.assertEqual(len(only_response.data["data"]["industrial"]), 1) self.assertEqual(len(only_response.data["data"]["fns_reports"]), 1) self.assertEqual(excluded_response.status_code, status.HTTP_200_OK) self.assertNotIn("fns_reports", excluded_response.data["data"]) self.assertIn("industrial", excluded_response.data["data"]) self.assertEqual(repeated_response.status_code, status.HTTP_200_OK) self.assertEqual( set(repeated_response.data["data"]), {"industrial", "fstec"}, ) self.assertEqual(empty_response.status_code, status.HTTP_200_OK) self.assertEqual(empty_response.data["data"], {}) def test_rejects_unknown_response_data_source(self): organization = Organization.objects.create( name='ООО "Неверный источник"', inn="7800000202", kpp="780002102", ogrn="1027700133202", ) response = self.client.get( reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ), {"data": "unknown"}, ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("data", response.data) def test_selected_data_sources_keep_constant_query_count(self): organization = Organization.objects.create( name='ООО "Без N+1"', inn="7800000203", kpp="780002103", ogrn="1027700133203", ) IndustrialCertificateRecordFactory( inn=organization.inn, ogrn=organization.ogrn, ) for index, year in enumerate((2021, 2022, 2023, 2024), start=1): report = FinancialReport.objects.create( external_id=f"fin-query-count-{year}", ogrn=organization.ogrn, file_name=f"fin_query_count_{year}.xlsx", file_hash=f"{index}" * 64, load_batch=1, status=FinancialReport.Status.SUCCESS, source=FinancialReport.SourceType.API, ) FinancialReportLine.objects.bulk_create( [ FinancialReportLine( report=report, form_code="1", line_code=str(1100 + line_index), line_name=f"Line {line_index}", year=year, period_start=line_index, period_end=line_index * 2, ) for line_index in range(1, 101) ] ) for source in ( ParserLoadLog.Source.FSTEC, ParserLoadLog.Source.CONTRACTS, ParserLoadLog.Source.TRUDVSEM, ): GenericParserRecord.objects.create( load_batch=1, source=source, external_id=f"{source}-query-count", inn=organization.inn, ogrn=organization.ogrn, organisation_name=organization.name, ) OrganizationDataSnapshotRefreshService.refresh( organization_uids=[str(organization.uid)], ) url = reverse( "api_v2:organizations:organizations-detail", args=[organization.uid] ) with CaptureQueriesContext(connection) as captured: response = self.client.get( url, {"data": "industrial,fns_reports,fstec,contracts,vacancies"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( set(response.data["data"]), {"industrial", "fns_reports", "fstec", "contracts", "vacancies"}, ) self.assertNotIn("trudvsem", response.data["data"]) self.assertEqual(len(response.data["data"]["fns_reports"]), 4) self.assertLessEqual(len(captured), 1) def test_v2_renames_trudvsem_data_source_to_vacancies(self): organization = Organization.objects.create( name='ООО "Вакансии"', inn="7800000204", kpp="780002104", ogrn="1027700133204", ) GenericParserRecord.objects.create( load_batch=1, source=ParserLoadLog.Source.TRUDVSEM, external_id="vacancy-1", inn=organization.inn, ogrn=organization.ogrn, organisation_name=organization.name, title="Инженер", ) OrganizationDataSnapshotRefreshService.refresh( organization_uids=[str(organization.uid)], ) detail_url = reverse( "api_v2:organizations:organizations-detail", args=[organization.uid], ) vacancies_response = self.client.get(detail_url, {"data": "vacancies"}) internal_source_response = self.client.get(detail_url, {"data": "trudvsem"}) has_vacancies_response = self.client.get( reverse("api_v2:organizations:organizations-list"), { "has_vacancies": "true", "has_registry": "false", "data": "vacancies", }, ) self.assertEqual(vacancies_response.status_code, status.HTTP_200_OK) self.assertEqual(set(vacancies_response.data["data"]), {"vacancies"}) self.assertEqual( vacancies_response.data["data"]["vacancies"][0]["external_id"], "vacancy-1", ) self.assertNotIn("trudvsem", vacancies_response.data["data"]) self.assertEqual( internal_source_response.status_code, status.HTTP_400_BAD_REQUEST, ) self.assertIn("trudvsem", str(internal_source_response.data)) self.assertEqual(has_vacancies_response.status_code, status.HTTP_200_OK) self.assertEqual( has_vacancies_response.data["data"][0]["uid"], str(organization.uid), ) @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)