922 lines
34 KiB
Python
922 lines
34 KiB
Python
"""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.models import Organization
|
||
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_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_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},
|
||
)
|
||
|
||
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)
|