feat(admin): expand exchange admin and unify admin UX
Some checks failed
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m39s
CI/CD Pipeline / Run Tests (pull_request) Successful in 3m0s
CI/CD Pipeline / Run API Inventory E2E Tests (pull_request) Successful in 35s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
Some checks failed
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m39s
CI/CD Pipeline / Run Tests (pull_request) Successful in 3m0s
CI/CD Pipeline / Run API Inventory E2E Tests (pull_request) Successful in 35s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
This commit is contained in:
@@ -4,6 +4,7 @@ from datetime import date, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from apps.core.admin import BackgroundJobAdmin
|
||||
from apps.core.admin_dashboard import build_admin_dashboard
|
||||
from apps.core.models import BackgroundJob
|
||||
from apps.parsers.models import FinancialReport, FinancialReportLine, ParserLoadLog
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
@@ -165,13 +166,26 @@ class AdminDashboardTest(TestCase):
|
||||
IndustrialCertificateRecordFactory(inn="7700000001")
|
||||
ManufacturerRecordFactory(inn="7700000002")
|
||||
InspectionRecordFactory(inn="7800000001")
|
||||
ProcurementRecordFactory(region_code="77", customer_inn="7700000001")
|
||||
ProcurementRecordFactory(region_code="77", customer_inn="7700000002")
|
||||
ProcurementRecordFactory(region_code="78", customer_inn="7800000001")
|
||||
ProcurementRecordFactory(
|
||||
load_batch=303,
|
||||
region_code="77",
|
||||
customer_inn="7700000001",
|
||||
)
|
||||
ProcurementRecordFactory(
|
||||
load_batch=303,
|
||||
region_code="77",
|
||||
customer_inn="7700000002",
|
||||
)
|
||||
ProcurementRecordFactory(
|
||||
load_batch=303,
|
||||
region_code="78",
|
||||
customer_inn="7800000001",
|
||||
)
|
||||
ProxyFactory(is_active=True)
|
||||
|
||||
ParserLoadLogFactory(
|
||||
source=ParserLoadLog.Source.PROCUREMENTS,
|
||||
batch_id=303,
|
||||
status="success",
|
||||
records_count=3,
|
||||
)
|
||||
@@ -185,6 +199,7 @@ class AdminDashboardTest(TestCase):
|
||||
response = self.client.get(reverse("admin:index"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, "Mostovik")
|
||||
self.assertContains(response, "Панель данных и загрузок")
|
||||
self.assertContains(response, "Обзор")
|
||||
self.assertContains(response, "Аналитика")
|
||||
@@ -204,3 +219,65 @@ class AdminDashboardTest(TestCase):
|
||||
response,
|
||||
reverse("admin:parsers_financialreport_upload_excel"),
|
||||
)
|
||||
|
||||
def test_dashboard_source_cards_use_latest_successful_source_slice(self):
|
||||
InspectionRecordFactory(load_batch=101, registration_number="old-1")
|
||||
InspectionRecordFactory(load_batch=101, registration_number="old-2")
|
||||
InspectionRecordFactory(load_batch=202, registration_number="new-1")
|
||||
ParserLoadLogFactory(
|
||||
source=ParserLoadLog.Source.INSPECTIONS,
|
||||
batch_id=101,
|
||||
status=ParserLoadLog.Status.SUCCESS,
|
||||
records_count=2,
|
||||
)
|
||||
ParserLoadLogFactory(
|
||||
source=ParserLoadLog.Source.INSPECTIONS,
|
||||
batch_id=202,
|
||||
status=ParserLoadLog.Status.SUCCESS,
|
||||
records_count=1,
|
||||
)
|
||||
|
||||
dashboard = build_admin_dashboard()
|
||||
cards = {item["slug"]: item for item in dashboard["source_cards"]}
|
||||
|
||||
self.assertEqual(cards["planned-inspections"]["records_count"], 1)
|
||||
self.assertEqual(cards["planned-inspections"]["organizations_count"], 1)
|
||||
self.assertEqual(
|
||||
cards["planned-inspections"]["metrics_scope_label"],
|
||||
"Последний успешный срез",
|
||||
)
|
||||
|
||||
def test_dashboard_regions_use_latest_successful_procurements_batch(self):
|
||||
ProcurementRecordFactory(
|
||||
load_batch=11,
|
||||
purchase_number="1111111111111111111",
|
||||
region_code="77",
|
||||
customer_inn="7700000001",
|
||||
)
|
||||
ProcurementRecordFactory(
|
||||
load_batch=22,
|
||||
purchase_number="2222222222222222222",
|
||||
region_code="78",
|
||||
customer_inn="7800000001",
|
||||
)
|
||||
ParserLoadLogFactory(
|
||||
source=ParserLoadLog.Source.PROCUREMENTS,
|
||||
batch_id=11,
|
||||
status=ParserLoadLog.Status.SUCCESS,
|
||||
records_count=1,
|
||||
)
|
||||
ParserLoadLogFactory(
|
||||
source=ParserLoadLog.Source.PROCUREMENTS,
|
||||
batch_id=22,
|
||||
status=ParserLoadLog.Status.SUCCESS,
|
||||
records_count=1,
|
||||
)
|
||||
|
||||
dashboard = build_admin_dashboard()
|
||||
|
||||
self.assertEqual(len(dashboard["region_rows"]), 1)
|
||||
self.assertEqual(dashboard["region_rows"][0]["code"], "78")
|
||||
self.assertEqual(
|
||||
dashboard["region_rows_note"],
|
||||
"Показываем только последний успешный срез ЕИС закупок.",
|
||||
)
|
||||
|
||||
224
tests/apps/exchange/test_admin.py
Normal file
224
tests/apps/exchange/test_admin.py
Normal file
@@ -0,0 +1,224 @@
|
||||
"""Tests for exchange admin configuration."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from apps.exchange.admin import ExchangeConnectionAdmin
|
||||
from apps.exchange.forms import ExchangeConnectionAdminForm
|
||||
from apps.exchange.models import ExchangeConnection
|
||||
from apps.exchange.services import ExchangePeriodicTaskService
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.contrib.messages.storage.fallback import FallbackStorage
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
||||
|
||||
from tests.apps.exchange.factories import ExchangeConnectionFactory
|
||||
from tests.apps.user.factories import UserFactory
|
||||
|
||||
_DB_PASSWORD = "secret" # noqa: S105
|
||||
|
||||
|
||||
class ExchangeAdminTest(TestCase):
|
||||
def setUp(self):
|
||||
self.site = AdminSite()
|
||||
self.admin = ExchangeConnectionAdmin(ExchangeConnection, self.site)
|
||||
self.factory = RequestFactory()
|
||||
self.user = UserFactory.create_superuser()
|
||||
|
||||
def _request(self, path: str):
|
||||
request = self.factory.get(path)
|
||||
request.user = self.user
|
||||
request.session = {}
|
||||
request._messages = FallbackStorage(request)
|
||||
return request
|
||||
|
||||
def _post_request(self, path: str, data: dict):
|
||||
request = self.factory.post(path, data=data)
|
||||
request.user = self.user
|
||||
request.session = {}
|
||||
request._messages = FallbackStorage(request)
|
||||
return request
|
||||
|
||||
def test_exchange_admin_has_custom_routes(self):
|
||||
route_names = [route.name for route in self.admin.get_urls()]
|
||||
|
||||
self.assertIn("exchange_exchangeconnection_test_connection", route_names)
|
||||
self.assertIn("exchange_exchangeconnection_copy_data", route_names)
|
||||
self.assertIn("exchange_exchangeconnection_periodic_tasks", route_names)
|
||||
self.assertIn("exchange_exchangeconnection_periodic_task_change", route_names)
|
||||
|
||||
def test_exchange_admin_changelist_renders_custom_buttons(self):
|
||||
response = self.admin.changelist_view(
|
||||
self._request("/admin/exchange/exchangeconnection/")
|
||||
)
|
||||
response.render()
|
||||
content = response.content.decode("utf-8")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Проверить новое подключение", content)
|
||||
self.assertIn("Запустить обмен", content)
|
||||
self.assertIn("Настроить периодический обмен", content)
|
||||
self.assertIn("Добавить подключение обмена", content)
|
||||
|
||||
def test_exchange_connection_admin_form_keeps_existing_password_when_blank(self):
|
||||
connection = ExchangeConnectionFactory(password=_DB_PASSWORD)
|
||||
form = ExchangeConnectionAdminForm(
|
||||
data={
|
||||
"server": connection.server,
|
||||
"port": connection.port,
|
||||
"username": connection.username,
|
||||
"password": "",
|
||||
"database_name": connection.database_name,
|
||||
"schema_name": connection.schema_name,
|
||||
},
|
||||
instance=connection,
|
||||
)
|
||||
|
||||
self.assertTrue(form.is_valid(), form.errors)
|
||||
saved_connection = form.save()
|
||||
|
||||
self.assertEqual(saved_connection.get_decrypted_password(), _DB_PASSWORD)
|
||||
|
||||
@patch("apps.exchange.admin.ExchangeConnectionService.validate_saved_connection")
|
||||
def test_validate_selected_connections_calls_service(self, validate_mock):
|
||||
connection = ExchangeConnectionFactory()
|
||||
|
||||
self.admin.validate_selected_connections(
|
||||
self._request("/admin/exchange/exchangeconnection/"),
|
||||
ExchangeConnection.objects.filter(id=connection.id),
|
||||
)
|
||||
|
||||
validate_mock.assert_called_once_with(connection)
|
||||
|
||||
@patch("apps.exchange.admin.ExchangeConnectionService.activate_connection")
|
||||
def test_activate_selected_connection_calls_service(self, activate_mock):
|
||||
connection = ExchangeConnectionFactory()
|
||||
|
||||
self.admin.activate_selected_connection(
|
||||
self._request("/admin/exchange/exchangeconnection/"),
|
||||
ExchangeConnection.objects.filter(id=connection.id),
|
||||
)
|
||||
|
||||
activate_mock.assert_called_once_with(connection)
|
||||
|
||||
@patch("apps.exchange.admin.ExchangeConnectionService.test_connection_payload")
|
||||
def test_test_connection_view_success_redirects_after_validation(
|
||||
self,
|
||||
test_connection_mock,
|
||||
):
|
||||
test_connection_mock.return_value = {
|
||||
"status": "success",
|
||||
"message": "Подключение проверено.",
|
||||
}
|
||||
request = self._post_request(
|
||||
"/admin/exchange/exchangeconnection/test-connection/",
|
||||
{
|
||||
"server": "127.0.0.1",
|
||||
"port": 5432,
|
||||
"username": "postgres",
|
||||
"password": _DB_PASSWORD,
|
||||
"database_name": "target_db",
|
||||
"schema_name": "public",
|
||||
},
|
||||
)
|
||||
|
||||
response = self.admin.test_connection_view(request)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
test_connection_mock.assert_called_once_with(
|
||||
server="127.0.0.1",
|
||||
port=5432,
|
||||
username="postgres",
|
||||
password=_DB_PASSWORD,
|
||||
database_name="target_db",
|
||||
schema_name="public",
|
||||
)
|
||||
|
||||
@patch("apps.exchange.admin.BackgroundJobService.create_job")
|
||||
@patch("apps.exchange.admin.copy_parsers_data_async")
|
||||
def test_copy_data_view_enqueues_background_job(self, task_mock, create_job_mock):
|
||||
active_connection = ExchangeConnectionFactory(is_active=True)
|
||||
task_mock.delay.return_value = SimpleNamespace(id="task-123")
|
||||
request = self._post_request(
|
||||
"/admin/exchange/exchangeconnection/copy-data/",
|
||||
{
|
||||
"mode": "all",
|
||||
"truncate_before_copy": "on",
|
||||
},
|
||||
)
|
||||
|
||||
response = self.admin.copy_data_view(request)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
task_mock.delay.assert_called_once_with(
|
||||
connection_id=active_connection.id,
|
||||
payload={
|
||||
"mode": "all",
|
||||
"table": None,
|
||||
"tables": None,
|
||||
"truncate_before_copy": True,
|
||||
},
|
||||
requested_by_id=self.user.id,
|
||||
)
|
||||
create_job_mock.assert_called_once()
|
||||
|
||||
def test_periodic_tasks_view_lists_existing_tasks(self):
|
||||
interval = IntervalSchedule.objects.create(every=2, period="hours")
|
||||
PeriodicTask.objects.create(
|
||||
name="exchange-copy-hourly",
|
||||
task=ExchangePeriodicTaskService.TASK_NAME,
|
||||
interval=interval,
|
||||
kwargs='{"payload": {"mode": "all", "truncate_before_copy": true}}',
|
||||
)
|
||||
|
||||
response = self.admin.periodic_tasks_view(
|
||||
self._request("/admin/exchange/exchangeconnection/periodic-tasks/")
|
||||
)
|
||||
response.render()
|
||||
content = response.content.decode("utf-8")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("exchange-copy-hourly", content)
|
||||
self.assertIn("Каждые 2 hours", content)
|
||||
|
||||
@patch("apps.exchange.admin.ExchangePeriodicTaskService.create_periodic_task")
|
||||
def test_periodic_tasks_view_creates_task(self, create_task_mock):
|
||||
request = self._post_request(
|
||||
"/admin/exchange/exchangeconnection/periodic-tasks/",
|
||||
{
|
||||
"name": "exchange-copy-nightly",
|
||||
"description": "Nightly sync",
|
||||
"enabled": "on",
|
||||
"mode": "selected",
|
||||
"tables": ["parsers_manufacturer"],
|
||||
"truncate_before_copy": "on",
|
||||
"notify_on_error": "on",
|
||||
"schedule_type": "daily",
|
||||
"crontab_minute": 15,
|
||||
"crontab_hour": 3,
|
||||
},
|
||||
)
|
||||
|
||||
response = self.admin.periodic_tasks_view(request)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
create_task_mock.assert_called_once_with(
|
||||
name="exchange-copy-nightly",
|
||||
description="Nightly sync",
|
||||
enabled=True,
|
||||
payload={
|
||||
"mode": "selected",
|
||||
"table": None,
|
||||
"tables": ["parsers_manufacturer"],
|
||||
"truncate_before_copy": True,
|
||||
"notify_on_error": True,
|
||||
},
|
||||
schedule={
|
||||
"type": "crontab",
|
||||
"minute": "15",
|
||||
"hour": "3",
|
||||
"day_of_week": "*",
|
||||
"day_of_month": "*",
|
||||
"month_of_year": "*",
|
||||
},
|
||||
)
|
||||
@@ -101,3 +101,16 @@ class ExchangePeriodicTaskUpsertSerializerTest(SimpleTestCase):
|
||||
|
||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||
self.assertTrue(serializer.validated_data["payload"]["notify_on_error"])
|
||||
|
||||
def test_truncate_before_copy_is_added_to_payload(self):
|
||||
serializer = ExchangePeriodicTaskUpsertSerializer(
|
||||
data={
|
||||
"schedule_type": "interval",
|
||||
"interval_every": 1,
|
||||
"interval_period": "hours",
|
||||
"truncate_before_copy": False,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||
self.assertFalse(serializer.validated_data["payload"]["truncate_before_copy"])
|
||||
|
||||
@@ -54,6 +54,9 @@ class ExchangeConnectionServiceUnitTest(TestCase):
|
||||
"test_connection",
|
||||
return_value="target_alias",
|
||||
) as test_connection_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"prepare_target_structure",
|
||||
) as prepare_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"validate_target_structure",
|
||||
) as validate_mock:
|
||||
@@ -70,13 +73,22 @@ class ExchangeConnectionServiceUnitTest(TestCase):
|
||||
self.assertIsNotNone(connection.last_checked_at)
|
||||
self.assertEqual(connection.last_error, "")
|
||||
test_connection_mock.assert_called_once_with(connection)
|
||||
prepare_mock.assert_called_once_with(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
schema_name="public",
|
||||
models_to_copy=None,
|
||||
)
|
||||
validate_mock.assert_called_once_with(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
schema_name="public",
|
||||
models_to_copy=None,
|
||||
)
|
||||
|
||||
def test_test_connection_payload_does_not_persist_connection(self):
|
||||
def test_validate_saved_connection_updates_timestamp_and_cleans_alias(self):
|
||||
connection = ExchangeConnectionFactory(last_error="old error")
|
||||
|
||||
with patch.object(
|
||||
ExchangeConnectionService,
|
||||
"test_connection",
|
||||
@@ -84,10 +96,85 @@ class ExchangeConnectionServiceUnitTest(TestCase):
|
||||
) as test_connection_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"validate_target_structure",
|
||||
) as validate_mock, patch(
|
||||
"apps.exchange.services.connections"
|
||||
) as connections_mock:
|
||||
connections_mock.databases = {"target_alias": {}}
|
||||
) as validate_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"_cleanup_alias",
|
||||
) as cleanup_mock:
|
||||
result = ExchangeConnectionService.validate_saved_connection(connection)
|
||||
|
||||
self.assertEqual(result, connection)
|
||||
connection.refresh_from_db()
|
||||
self.assertEqual(connection.last_error, "")
|
||||
self.assertIsNotNone(connection.last_checked_at)
|
||||
test_connection_mock.assert_called_once_with(connection)
|
||||
validate_mock.assert_called_once_with(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
schema_name=connection.schema_name,
|
||||
models_to_copy=None,
|
||||
)
|
||||
cleanup_mock.assert_called_once_with("target_alias")
|
||||
|
||||
def test_validate_saved_connection_prepares_target_when_requested(self):
|
||||
connection = ExchangeConnectionFactory(last_error="old error")
|
||||
|
||||
with patch.object(
|
||||
ExchangeConnectionService,
|
||||
"test_connection",
|
||||
return_value="target_alias",
|
||||
) as test_connection_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"prepare_target_structure",
|
||||
) as prepare_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"validate_target_structure",
|
||||
) as validate_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"_cleanup_alias",
|
||||
) as cleanup_mock:
|
||||
ExchangeConnectionService.validate_saved_connection(
|
||||
connection,
|
||||
prepare_target=True,
|
||||
)
|
||||
|
||||
test_connection_mock.assert_called_once_with(connection)
|
||||
prepare_mock.assert_called_once_with(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
schema_name=connection.schema_name,
|
||||
models_to_copy=None,
|
||||
)
|
||||
validate_mock.assert_called_once_with(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
schema_name=connection.schema_name,
|
||||
models_to_copy=None,
|
||||
)
|
||||
cleanup_mock.assert_called_once_with("target_alias")
|
||||
|
||||
def test_activate_connection_validates_and_switches_active_flag(self):
|
||||
old_active = ExchangeConnectionFactory(is_active=True)
|
||||
new_connection = ExchangeConnectionFactory(is_active=False)
|
||||
|
||||
with patch.object(
|
||||
ExchangeConnectionService,
|
||||
"validate_saved_connection",
|
||||
return_value=new_connection,
|
||||
) as validate_mock:
|
||||
result = ExchangeConnectionService.activate_connection(new_connection)
|
||||
|
||||
self.assertEqual(result, new_connection)
|
||||
validate_mock.assert_called_once_with(new_connection, prepare_target=True)
|
||||
old_active.refresh_from_db()
|
||||
new_connection.refresh_from_db()
|
||||
self.assertFalse(old_active.is_active)
|
||||
self.assertTrue(new_connection.is_active)
|
||||
|
||||
def test_test_connection_payload_does_not_persist_connection(self):
|
||||
with patch.object(
|
||||
ExchangeConnectionService,
|
||||
"validate_saved_connection",
|
||||
) as validate_mock:
|
||||
result = ExchangeConnectionService.test_connection_payload(
|
||||
server="127.0.0.1",
|
||||
port=5432,
|
||||
@@ -103,7 +190,6 @@ class ExchangeConnectionServiceUnitTest(TestCase):
|
||||
"Подключение проверено. Соединение и структура БД валидны.",
|
||||
)
|
||||
self.assertEqual(ExchangeConnection.objects.count(), 0)
|
||||
test_connection_mock.assert_called_once()
|
||||
validate_mock.assert_called_once()
|
||||
|
||||
def test_get_active_connection_raises_when_missing(self):
|
||||
@@ -272,6 +358,30 @@ class ExchangeConnectionServiceUnitTest(TestCase):
|
||||
connection.refresh_from_db()
|
||||
self.assertEqual(connection.last_error, "unexpected")
|
||||
|
||||
def test_prepare_target_structure_creates_schema_and_missing_tables(self):
|
||||
connection = ExchangeConnectionFactory(schema_name="target_schema")
|
||||
db_connection = MagicMock()
|
||||
db_connection.ops.quote_name.return_value = '"target_schema"'
|
||||
cursor = MagicMock()
|
||||
cursor.fetchall.return_value = [("fake_table",)]
|
||||
db_connection.cursor.return_value.__enter__.return_value = cursor
|
||||
schema_editor = MagicMock()
|
||||
db_connection.schema_editor.return_value.__enter__.return_value = schema_editor
|
||||
connections_mock = MagicMock()
|
||||
connections_mock.__getitem__.return_value = db_connection
|
||||
|
||||
with patch("apps.exchange.services.connections", connections_mock):
|
||||
ExchangeConnectionService.prepare_target_structure(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
schema_name="target_schema",
|
||||
models_to_copy=[_FakeModel, _AnotherFakeModel],
|
||||
)
|
||||
|
||||
db_connection.ensure_connection.assert_called_once_with()
|
||||
cursor.execute.assert_any_call('CREATE SCHEMA IF NOT EXISTS "target_schema"')
|
||||
schema_editor.create_model.assert_called_once_with(_AnotherFakeModel)
|
||||
|
||||
def test_copy_parsers_data_success(self):
|
||||
connection = ExchangeConnectionFactory(schema_name="target_schema")
|
||||
db_connection = MagicMock()
|
||||
@@ -293,6 +403,9 @@ class ExchangeConnectionServiceUnitTest(TestCase):
|
||||
"_extend_models_with_dependencies",
|
||||
return_value=[_FakeModel, _AnotherFakeModel],
|
||||
), patch.object(
|
||||
ExchangeConnectionService,
|
||||
"prepare_target_structure",
|
||||
) as prepare_mock, patch.object(
|
||||
ExchangeConnectionService,
|
||||
"validate_target_structure",
|
||||
) as validate_mock, patch.object(
|
||||
@@ -312,6 +425,12 @@ class ExchangeConnectionServiceUnitTest(TestCase):
|
||||
self.assertEqual(result["rows_by_table"], {"fake_table": 2, "another_table": 3})
|
||||
self.assertEqual(result["total_rows"], 5)
|
||||
self.assertFalse(result["truncate_before_copy"])
|
||||
prepare_mock.assert_called_once_with(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
schema_name="target_schema",
|
||||
models_to_copy=[_FakeModel, _AnotherFakeModel],
|
||||
)
|
||||
validate_mock.assert_called_once_with(
|
||||
connection=connection,
|
||||
alias="target_alias",
|
||||
|
||||
@@ -39,8 +39,14 @@ class ExchangeViewsTest(APITestCase):
|
||||
self.assertIsInstance(response.data["results"], list)
|
||||
|
||||
@patch("apps.exchange.services.ExchangeConnectionService.validate_target_structure")
|
||||
@patch("apps.exchange.services.ExchangeConnectionService.prepare_target_structure")
|
||||
@patch("apps.exchange.services.ExchangeConnectionService.test_connection")
|
||||
def test_create_connection_success(self, connection_mock, validate_mock):
|
||||
def test_create_connection_success(
|
||||
self,
|
||||
connection_mock,
|
||||
prepare_mock,
|
||||
validate_mock,
|
||||
):
|
||||
old_active = ExchangeConnectionFactory(is_active=True)
|
||||
|
||||
payload = {
|
||||
@@ -79,6 +85,7 @@ class ExchangeViewsTest(APITestCase):
|
||||
self.assertFalse(old_active.is_active)
|
||||
|
||||
connection_mock.assert_called_once()
|
||||
prepare_mock.assert_called_once()
|
||||
validate_mock.assert_called_once()
|
||||
|
||||
@patch("apps.exchange.services.ExchangeConnectionService.test_connection_payload")
|
||||
|
||||
@@ -149,6 +149,19 @@ class ParsersAdminTest(TestCase):
|
||||
self.assertIn("Обновить список прокси", content)
|
||||
self.assertIn("mx-object-tool-form", content)
|
||||
|
||||
def test_financial_report_changelist_renders_toolbar_buttons(self):
|
||||
admin = FinancialReportAdmin(FinancialReport, self.site)
|
||||
response = admin.changelist_view(
|
||||
self._request("/admin/parsers/financialreport/")
|
||||
)
|
||||
response.render()
|
||||
content = response.content.decode("utf-8")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Загрузить Excel бухгалтерской отчетности", content)
|
||||
self.assertIn("Загрузить ZIP бухгалтерской отчетности", content)
|
||||
self.assertIn("mx-admin-action-bar", content)
|
||||
|
||||
@patch("apps.parsers.admin.ProxyToolsSyncService.sync_ru_proxies")
|
||||
def test_proxy_admin_sync_view_calls_service(self, sync_mock):
|
||||
sync_mock.return_value = {
|
||||
@@ -178,9 +191,15 @@ class ParsersAdminTest(TestCase):
|
||||
|
||||
def test_parser_load_log_admin_status_badge(self):
|
||||
admin = ParserLoadLogAdmin(ParserLoadLog, self.site)
|
||||
log = ParserLoadLogFactory(status="success")
|
||||
log = ParserLoadLogFactory(
|
||||
source=ParserLoadLog.Source.FNS_REPORTS,
|
||||
status=ParserLoadLog.Status.SUCCESS,
|
||||
)
|
||||
badge = admin.status_badge(log)
|
||||
self.assertIn("span", str(badge))
|
||||
self.assertEqual(admin.source_title(log), "Бухгалтерская отчетность ФНС")
|
||||
self.assertIn("source_title", admin.list_display)
|
||||
self.assertNotIn("batch_id", admin.list_display)
|
||||
request = self._request()
|
||||
self.assertFalse(admin.has_add_permission(request))
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ class RegistersAdminTest(TestCase):
|
||||
self.factory = RequestFactory()
|
||||
self.user = UserFactory.create_superuser()
|
||||
|
||||
def _request(self):
|
||||
request = self.factory.get("/admin/registers/registerupload/upload-excel/")
|
||||
def _request(self, path="/admin/registers/registerupload/upload-excel/"):
|
||||
request = self.factory.get(path)
|
||||
request.user = self.user
|
||||
request.session = {}
|
||||
request._messages = FallbackStorage(request)
|
||||
@@ -78,6 +78,20 @@ class RegistersAdminTest(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('type="file"', content)
|
||||
self.assertIn("mx-upload-file", content)
|
||||
self.assertIn("multiple", content)
|
||||
|
||||
def test_register_upload_changelist_renders_toolbar_buttons(self):
|
||||
admin = RegisterUploadAdmin(RegisterUpload, self.site)
|
||||
response = admin.changelist_view(
|
||||
self._request("/admin/registers/registerupload/")
|
||||
)
|
||||
response.render()
|
||||
content = response.content.decode("utf-8")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Загрузить справочники из Excel", content)
|
||||
self.assertIn("Добавить загрузку реестра", content)
|
||||
self.assertIn("mx-admin-action-bar", content)
|
||||
|
||||
def test_register_upload_admin_upload_excel_success(self):
|
||||
admin = RegisterUploadAdmin(RegisterUpload, self.site)
|
||||
@@ -144,3 +158,38 @@ class RegistersAdminTest(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
sync_mock.assert_called_once()
|
||||
|
||||
def test_register_upload_admin_processes_multiple_files(self):
|
||||
admin = RegisterUploadAdmin(RegisterUpload, self.site)
|
||||
registry = RegisterFactory()
|
||||
first_upload = _build_register_excel_upload("registry_1.xlsx")
|
||||
second_upload = _build_register_excel_upload("registry_2.xlsx")
|
||||
request = self._post_request(
|
||||
{
|
||||
"registry": str(registry.id),
|
||||
"actual_date": "2026-03-20",
|
||||
"files": [first_upload, second_upload],
|
||||
}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"apps.registers.admin.RegisterImportService.sync_registry_memberships",
|
||||
side_effect=[
|
||||
{
|
||||
"registry_name": registry.name,
|
||||
"rows_in_file": 1,
|
||||
"organizations_created": 1,
|
||||
"organizations_updated": 0,
|
||||
},
|
||||
{
|
||||
"registry_name": registry.name,
|
||||
"rows_in_file": 1,
|
||||
"organizations_created": 0,
|
||||
"organizations_updated": 1,
|
||||
},
|
||||
],
|
||||
) as sync_mock:
|
||||
response = admin.upload_excel_view(request)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(sync_mock.call_count, 2)
|
||||
|
||||
Reference in New Issue
Block a user