"""Unit test module.""" from unittest.mock import ANY import boto3 import pytest from moto import mock_aws import app as index import config class MockValidator(object): """Validator class for use in assertions.""" def __init__(self, validator): """Create validator.""" self.validator = validator def __eq__(self, other): """Evaluate the condition.""" return bool(self.validator(other)) @pytest.fixture() def mock_account_id(): """Return an AWS account id.""" return '123456789000' @pytest.fixture() @mock_aws def mock_rds_client(): """Return a mock RDS client.""" return boto3.client('rds', region_name=config.AWS_DEFAULT_REGION) @pytest.fixture() @mock_aws def mock_lambda_client(): """Return a mock Lambda client.""" return boto3.client('lambda', region_name=config.AWS_DEFAULT_REGION) @mock_aws def test_assume_target_account_role(monkeypatch): """Test assume_source_account_role function.""" aws_credentials = ['access_key', 'secret_key', 'token', 'expiry_time'] monkeypatch.setattr(index.config, 'EXTERNAL_ID', 'abc1234') result = index.assume_target_account_role() for credential in aws_credentials: assert result[credential] @mock_aws def test_restore_clone(mocker, mock_rds_client, mock_lambda_client, monkeypatch): # noqa """Test the restore function when CLONE_RESTORE is true.""" monkeypatch.setattr(index.config, 'EXTERNAL_ID', 'abc1234') mocker.patch('config.CLONE_RESTORE', True) mocker.patch('config.SOURCE_ACCOUNT_ID', '1234567890') mocker.patch( 'common.logic.database.get_config', return_value={ 'Engine': 'aurora-mysql', 'InstanceEventSubscriptions': ['my-event-subscription'] }) mock_rename_database = mocker.patch( 'common.logic.database.rename_database') mock_clone_database = mocker.patch( 'common.logic.database.clone_database') mock_sanitise_data = mocker.patch('common.logic.sanitiser.sanitise') mock_configure_event_subscriptions = mocker.patch( 'common.logic.database.configure_event_subscriptions') mock_delete_database = mocker.patch( 'common.logic.database.delete_database_if_exists') index.restore( db_name='my-db', source_db_name='my-db-source', db_type='cluster', snapshot_name='my-snapshot', rds_client=mock_rds_client ) mock_clone_database.assert_called_once_with( 'arn:aws:rds:us-east-1:1234567890:cluster:my-db-source', MockValidator(lambda x: x.startswith('my-db')), 'cluster', { 'Engine': 'aurora-mysql', 'InstanceEventSubscriptions': ['my-event-subscription'] }, ANY ) mock_sanitise_data.assert_called_once_with( config.SANITISE_DATA_FUNCTION_NAME, 'my-db', MockValidator(lambda x: x.startswith('my-db')), 'cluster', 'scripts/my-db', 'aurora-mysql', ANY) assert mock_rename_database.call_count == 2 mock_rename_database.assert_any_call( 'my-db', 'my-db-old', 'cluster', ANY) mock_rename_database.assert_any_call( MockValidator(lambda x: x.startswith('my-db')), 'my-db', 'cluster', ANY) mock_configure_event_subscriptions.assert_called_once_with( 'my-db', 'cluster', { 'Engine': 'aurora-mysql', 'InstanceEventSubscriptions': ['my-event-subscription'] }, ANY) assert mock_delete_database.call_count == 2 mock_delete_database.assert_any_call('my-db-old', 'cluster', ANY, wait=False) mock_delete_database.assert_any_call( MockValidator(lambda x: x.startswith('my-db-tmp-')), 'cluster', ANY, wait=False ) @mock_aws def test_restore_snapshot( mocker, mock_rds_client, mock_lambda_client, mock_account_id, monkeypatch): # noqa """Test the restore function when CLONE_RESTORE is false.""" monkeypatch.setattr(index.config, 'EXTERNAL_ID', 'abc1234') mocker.patch('config.CLONE_RESTORE', False) mocker.patch('config.SOURCE_ACCOUNT_ID', '1234567890') mocker.patch( 'common.logic.database.get_config', return_value={ 'Engine': 'aurora-postgresql', 'InstanceEventSubscriptions': ['my-event-subscription'] }) mock_rename_database = mocker.patch( 'common.logic.database.rename_database') mock_restore_database = mocker.patch( 'common.logic.database.restore_database') mock_sanitise_data = mocker.patch('common.logic.sanitiser.sanitise') mock_configure_event_subscriptions = mocker.patch( 'common.logic.database.configure_event_subscriptions') mock_delete_database = mocker.patch( 'common.logic.database.delete_database_if_exists') index.restore( db_name='my-db', source_db_name='my-db-source', db_type='standalone', snapshot_name='my-snapshot', rds_client=mock_rds_client ) mock_restore_database.assert_called_once_with( 'arn:aws:rds:us-east-1:1234567890:snapshot:my-snapshot', MockValidator(lambda x: x.startswith('my-db')), 'standalone', { 'Engine': 'aurora-postgresql', 'InstanceEventSubscriptions': ['my-event-subscription'] }, ANY ) mock_sanitise_data.assert_called_once_with( config.SANITISE_DATA_FUNCTION_NAME, 'my-db', MockValidator(lambda x: x.startswith('my-db')), 'standalone', 'scripts/my-db', 'aurora-postgresql', ANY) assert mock_rename_database.call_count == 2 mock_rename_database.assert_any_call( 'my-db', 'my-db-old', 'standalone', ANY) mock_rename_database.assert_any_call( MockValidator(lambda x: x.startswith('my-db')), 'my-db', 'standalone', ANY) mock_configure_event_subscriptions.assert_called_once_with( 'my-db', 'standalone', { 'Engine': 'aurora-postgresql', 'InstanceEventSubscriptions': ['my-event-subscription'] }, ANY) assert mock_delete_database.call_count == 2 mock_delete_database.assert_any_call('my-db-old', 'standalone', ANY, wait=False) mock_delete_database.assert_any_call( MockValidator(lambda x: x.startswith('my-db-tmp-')), 'standalone', ANY, wait=False ) @mock_aws def test_restore_failure_preserves_old_database( mocker, mock_rds_client, mock_lambda_client, monkeypatch): """A failed refresh must not delete the -old database. If the refresh fails after the live database was renamed to -old, the -old database holds the only copy of the previous data, so only the intermediate database may be cleaned up. """ monkeypatch.setattr(index.config, 'EXTERNAL_ID', 'abc1234') mocker.patch('config.CLONE_RESTORE', True) mocker.patch('config.SOURCE_ACCOUNT_ID', '1234567890') mocker.patch( 'common.logic.database.get_config', return_value={ 'Engine': 'aurora-mysql', 'InstanceEventSubscriptions': ['my-event-subscription'] }) mocker.patch('common.logic.database.clone_database') mocker.patch('common.logic.sanitiser.sanitise') mocker.patch( 'common.logic.database.rename_database', side_effect=[None, Exception('rename failed')]) mock_delete_database = mocker.patch( 'common.logic.database.delete_database_if_exists') with pytest.raises(Exception, match='rename failed'): index.restore( db_name='my-db', source_db_name='my-db-source', db_type='cluster', snapshot_name='my-snapshot', rds_client=mock_rds_client ) mock_delete_database.assert_called_once_with( MockValidator(lambda x: x.startswith('my-db-tmp-')), 'cluster', ANY, wait=False ) def test_snapshot_copy_not_required_when_same_account(): """Test snapshot copy is not required when accounts are the same.""" snapshot_copy_required = index.snapshot_copy_required( source_account_id='1234567890', target_account_id='1234567890', db_type='standalone' ) assert not snapshot_copy_required def test_snapshot_copy_not_required_for_clusters(): """Test snapshot copy is not required for clusters.""" snapshot_copy_required = index.snapshot_copy_required( source_account_id='1234567890', target_account_id='1234567891', db_type='cluster' ) assert not snapshot_copy_required def test_snapshot_required(): """Test snapshot copy is required when all necessary conditions hold.""" snapshot_copy_required = index.snapshot_copy_required( source_account_id='1234567890', target_account_id='1234567891', db_type='standalone' ) assert snapshot_copy_required @mock_aws def test_copy_snapshot_strips_shared_suffix(mocker, monkeypatch): """A '-shared' suffix is removed to derive the local snapshot name.""" monkeypatch.setattr('config.SNAPSHOT_NAME', 'prod-db-2026-shared') mock_copy = mocker.patch('common.logic.database.copy_snapshot') result = index.copy_snapshot(mocker.Mock()) assert result == 'prod-db-2026' assert mock_copy.call_args.args[3] == 'prod-db-2026' @mock_aws def test_copy_snapshot_preserves_arbitrary_id(mocker, monkeypatch): """An arbitrary supplied snapshot id is not mangled when it has no suffix. A trailing character that happens to be in '-shared' (e.g. 'd') must not be stripped, which is why removesuffix is used rather than rstrip. """ monkeypatch.setattr('config.SNAPSHOT_NAME', 'known-good-2026-shared-data') mock_copy = mocker.patch('common.logic.database.copy_snapshot') result = index.copy_snapshot(mocker.Mock()) assert result == 'known-good-2026-shared-data' assert mock_copy.call_args.args[3] == 'known-good-2026-shared-data'