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

This commit is contained in:
2026-03-24 13:58:24 +01:00
parent 559b9bc5ef
commit c98ba76081
33 changed files with 2915 additions and 209 deletions

View File

@@ -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",