Files

1059 lines
40 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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)