"""Test AWS utility functions.""" from unittest.mock import Mock from unittest.mock import patch from botocore.client import BaseClient import pytest from dbdeploy.util.aws import helpers from dbdeploy.base import config @pytest.fixture def mock_boto3(): """Mock boto3.""" boto3_path = 'dbdeploy.util.aws.helpers.boto3' with patch(boto3_path) as boto3: boto3.client = Mock(spec=BaseClient) yield boto3 @pytest.fixture def mock_requests(): """Mock requests.""" requests_path = 'dbdeploy.util.aws.helpers.requests' with patch(requests_path) as requests: yield requests class TestSecretsManagerHelpers(): """Test Secrets helpers.""" def test_get_secret_name(self): """Test correct secret name is returned.""" expected = 'test/kafka-db-deploy/CREDENTIALS' secret_name = helpers.get_secret_name( environment=config.ENVIRONMENT, service_name=config.SERVICE_NAME, secret_key='CREDENTIALS') assert expected == secret_name def test_get_secret(self, mock_get_secret): """Test secretsmanager is requested.""" expected = {'secret': 'name'} mock_get_secret.return_value = expected secret = helpers.get_secret( aws_region=config.AWS_REGION, environment=config.ENVIRONMENT, service_name=config.SERVICE_NAME, secret_key='CREDENTIALS') assert expected == secret def test_get_secret_raises(self, mock_get_secret): """Test exception is raised if not JSON.""" mock_get_secret.return_value = 'hello' with pytest.raises(ValueError): helpers.get_secret( aws_region=config.AWS_REGION, environment=config.ENVIRONMENT, service_name=config.SERVICE_NAME, secret_key='CREDENTIALS') def test_get_secret_raises_empty_json(self, mock_get_secret): """Test exception is raised if empty JSON object.""" mock_get_secret.return_value = {} with pytest.raises(ValueError): helpers.get_secret( aws_region=config.AWS_REGION, environment=config.ENVIRONMENT, service_name=config.SERVICE_NAME, secret_key='CREDENTIALS') class TestMSKHelpers(): """Test helper used to get MSK clusters.""" def test_get_msk_bootstrap_brokers(self, mock_boto3): """Test broker list string is returned.""" client = mock_boto3.client() expected_broker_string = 'b-3:9094,b-1:9094,b-2:9094' test_cluster_name = 'test-managed-kafka-db-deploy' test_arn = 'arn:aws:kafka:*:*:test-cluster-arn' client.list_clusters.return_value = { 'ResponseMetadata': {...}, 'ClusterInfoList': [ {'BrokerNodeGroupInfo': {...}, 'ClusterArn': 'arn:aws:kafka:*:*:test-cluster-arn', 'ClusterName': 'test-managed-kafka-db-deploy', ...: ...}] } client.get_bootstrap_brokers.return_value = { 'ResponseMetadata': {...}, 'BootstrapBrokerStringTls': 'b-3:9094,b-1:9094,b-2:9094' } test_result = helpers.get_msk_bootstrap_brokers(test_cluster_name) assert client.get_bootstrap_brokers.called_with(ClusterArn=test_arn) assert expected_broker_string == test_result class TestECSHelpers(): """Test ECS helper functions.""" def test_get_ecs_cluster_arn(self, mock_boto3): """Test ecs cluster ARN is returned.""" expected_arn = 'arn:aws:ecs:*:*:cluster/test-kafka-connect-neo-sink-db' client = mock_boto3.client() test_cluster_name = 'test-kafka-connect-neo-sink-db' client.describe_clusters.return_value = { 'clusters': [ {'clusterArn': expected_arn, 'clusterName': 'test-kafka-connect-neo-sink-db', 'status': 'ACTIVE', ...: ...}], 'failures': [], 'ResponseMetadata': {...} } test_result = helpers.get_ecs_cluster_arn(client, test_cluster_name) assert expected_arn == test_result def test_get_ecs_cluster_arn_returns_none(self, mock_boto3): """Test ecs cluster ARN is not found.""" client = mock_boto3.client() test_cluster_name = 'test-kafka-connect-neo' client.describe_clusters.return_value = { 'clusters': [], 'failures': [ {'arn': 'arn:aws:ecs:*:*:cluster/test-kafka-connect-neo', 'reason': 'MISSING'}], 'ResponseMetadata': {...} } test_result = helpers.get_ecs_cluster_arn(client, test_cluster_name) assert test_result is None def test_check_running_task_count(self, mock_boto3, mock_requests): """Test check returns True.""" client = mock_boto3.client() response = Mock() response.status_code = 200 mock_requests.get.return_value = response test_desired_count = 1 test_backoff = config.FARGATE_TASK_HEALTH_CHECK_BACKOFF_TIMEOUT / 10000 test_grace_period = config.FARGATE_TASK_HEALTH_CHECK_GRACE_PERIOD / 10000 # noqa: E501 test_url = 'http://test_domain.io' test_cluster_arn = 'arn:aws:ecs:*:*:cluster/test-kafka-connect-neo' client.list_tasks.return_value = { 'taskArns': [ 'arn:aws:ecs:*:*:task/0cc43cdb-3bee-4407-9c26-c0e6ea5bee84', ], 'ResponseMetadata': {...} } test_result = helpers.check_running_task_count( ecs_client=client, desired_count=test_desired_count, backoff_timeout=test_backoff, grace_period=test_grace_period, cluster_arn=test_cluster_arn, cluster_url=test_url) assert test_result is True def test_check_running_task_count_do_not_match( self, mock_boto3, mock_requests): """Test task count do not match desired and returns False.""" client = mock_boto3.client() response = Mock() response.status_code = 200 mock_requests.get.return_value = response test_desired_count = 1 test_backoff = config.FARGATE_TASK_HEALTH_CHECK_BACKOFF_TIMEOUT / 10000 test_grace_period = config.FARGATE_TASK_HEALTH_CHECK_GRACE_PERIOD / 10000 # noqa: E501 test_url = 'http://test_domain.io' test_cluster_arn = 'arn:aws:ecs:*:*:cluster/test-kafka-connect-neo' client.list_tasks.return_value = { 'taskArns': [], 'ResponseMetadata': {...} } test_result = helpers.check_running_task_count( ecs_client=client, desired_count=test_desired_count, backoff_timeout=test_backoff, grace_period=test_grace_period, cluster_arn=test_cluster_arn, cluster_url=test_url) assert test_result is False def test_check_running_task_count_connector_not_healthy( self, mock_boto3, mock_requests): """Test task count when connector isn't healthy returns False.""" client = mock_boto3.client() response = Mock() response.status_code = 400 mock_requests.get.return_value = response test_desired_count = 1 test_backoff = config.FARGATE_TASK_HEALTH_CHECK_BACKOFF_TIMEOUT / 10000 test_grace_period = config.FARGATE_TASK_HEALTH_CHECK_GRACE_PERIOD / 10000 # noqa: E501 test_url = 'http://test_domain.io' test_cluster_arn = 'arn:aws:ecs:*:*:cluster/test-kafka-connect-neo' client.list_tasks.return_value = { 'taskArns': [ 'arn:aws:ecs:*:*:task/0cc43cdb-3bee-4407-9c26-c0e6ea5bee84', ], 'ResponseMetadata': {...} } test_result = helpers.check_running_task_count( ecs_client=client, desired_count=test_desired_count, backoff_timeout=test_backoff, grace_period=test_grace_period, cluster_arn=test_cluster_arn, cluster_url=test_url) assert test_result is False def test_check_running_task_count_connector_not_healthy_stop_tasks( self, mock_boto3, mock_requests): """Test task count when connector isn't healthy, but tasks are to be stopped anyways returns True. """ client = mock_boto3.client() response = Mock() response.status_code = 400 mock_requests.get.return_value = response test_desired_count = 0 test_backoff = config.FARGATE_TASK_HEALTH_CHECK_BACKOFF_TIMEOUT / 10000 test_grace_period = config.FARGATE_TASK_HEALTH_CHECK_GRACE_PERIOD / 10000 # noqa: E501 test_url = 'http://test_domain.io' test_cluster_arn = 'arn:aws:ecs:*:*:cluster/test-kafka-connect-neo' client.list_tasks.return_value = { 'taskArns': [], 'ResponseMetadata': {...} } test_result = helpers.check_running_task_count( ecs_client=client, desired_count=test_desired_count, backoff_timeout=test_backoff, grace_period=test_grace_period, cluster_arn=test_cluster_arn, cluster_url=test_url) assert test_result is True class TestDynamoDBHelpers(): """Test DynamoDB helper functions.""" def test_get_table_item(self, mock_boto3): """Test DynamoDB get returns correct value.""" mock_id = 'mock_dynamo_item_id' mock_table = 'mock_table' expected = {'changeset_id': mock_id} client = mock_boto3.client() client.get_item.return_value = { 'Item': expected, 'failures': [], 'ResponseMetadata': {...} } test_result = helpers.get_table_item(client, mock_id, mock_table) assert expected == test_result def test_put_table_item(self, mock_boto3): """Test DynamoDB put returns correct value.""" mock_id = 'mock_dynamo_item_id' mock_table = 'mock_table' mock_cypher_hash = 'mock_cypher_hash' mock_sql_hash = 'mock_sql_hash' expected = { 'changeset_id': mock_id, 'cypher_hash': mock_cypher_hash, 'sql_hash': mock_sql_hash, } client = mock_boto3.client() client.update_item.return_value = { 'Attributes': expected, 'failures': [], 'ResponseMetadata': {...} } test_result = helpers.put_table_item( dynamodb_client=client, changeset_id=mock_id, cypher_hash=mock_cypher_hash, sql_hash=mock_sql_hash, table_name=mock_table) assert expected == test_result def test_put_table_lock_item_not_locked(self, mock_boto3): """Test put lock helper returns true if unlocked""" mock_table = 'mock_table' client = mock_boto3.client() client.update_item.return_value = { 'failures': [], 'ResponseMetadata': {...} } test_result_is_true = helpers.put_table_lock_item( dynamodb_client=client, table_name=mock_table) assert test_result_is_true def test_put_table_lock_item_locked(self, mock_boto3): """Test put lock helper returns false if locked""" mock_table = 'mock_table' mock_attrs = { 'changeset_id': '__DB_DEPLOY_LOCK__', } client = mock_boto3.client() client.update_item.return_value = { 'Attributes': mock_attrs, 'failures': [], 'ResponseMetadata': {...} } test_result_is_false = helpers.put_table_lock_item( dynamodb_client=client, table_name=mock_table) assert not test_result_is_false def test_delete_table_lock_item_success(self, mock_boto3): """Test delete lock helper returns true if lock released.""" mock_table = 'mock_table' mock_attrs = { 'changeset_id': '__DB_DEPLOY_LOCK__', } client = mock_boto3.client() client.delete_item.return_value = { 'Attributes': mock_attrs, 'failures': [], 'ResponseMetadata': {...} } test_result_is_true = helpers.delete_table_lock_item( dynamodb_client=client, table_name=mock_table) assert test_result_is_true def test_delete_table_lock_item_failed(self, mock_boto3): """Test delete lock helper returns false if lock release failed.""" mock_table = 'mock_table' client = mock_boto3.client() client.delete_item.return_value = { 'failures': [], 'ResponseMetadata': {...} } test_result_is_false = helpers.delete_table_lock_item( dynamodb_client=client, table_name=mock_table) assert not test_result_is_false