1059 lines
40 KiB
Python
1059 lines
40 KiB
Python
"""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)
|