"""Tests for Secrets Manager access.""" import json import pytest from src.logic import secrets class _ResourceNotFoundException(Exception): """Stand-in for the boto3 ResourceNotFoundException.""" @pytest.fixture def mock_sm(mocker): """Patch the Secrets Manager client used by the secrets module.""" client = mocker.patch('src.logic.secrets.sm') client.exceptions.ResourceNotFoundException = _ResourceNotFoundException return client @pytest.fixture def configured_prefix(mocker): """Configure a role-passwords secret name prefix.""" mocker.patch.object( secrets.config, 'ROLE_PASSWORDS_SECRET_NAME_PREFIX', 'qa/sanitise-rds-data/') def test_get_role_passwords_returns_parsed_map(mock_sm, configured_prefix): """The per-database secret is read and parsed into a role-password map.""" mock_sm.get_secret_value.return_value = { 'SecretString': json.dumps({'app_login': 's3cret', 'app_owner': 'x'}) } passwords = secrets.get_role_passwords('qa-songwhip') assert passwords == {'app_login': 's3cret', 'app_owner': 'x'} mock_sm.get_secret_value.assert_called_once_with( SecretId='qa/sanitise-rds-data/qa-songwhip/postgres-user-passwords') def test_get_role_passwords_strips_tmp_suffix(mock_sm, configured_prefix): """The intermediate -tmp- suffix is stripped before lookup. During a refresh sanitisation runs against the temporary restored cluster (e.g. qa-songwhip-api-tmp-20260604093040), but the secret is keyed on the stable db name. """ mock_sm.get_secret_value.return_value = { 'SecretString': json.dumps({'app_login': 's3cret'}) } passwords = secrets.get_role_passwords( 'qa-songwhip-api-tmp-20260604093040') assert passwords == {'app_login': 's3cret'} mock_sm.get_secret_value.assert_called_once_with( SecretId='qa/sanitise-rds-data/qa-songwhip-api/' 'postgres-user-passwords') def test_get_role_passwords_no_prefix_returns_empty(mock_sm, mocker): """No configured prefix yields an empty map without an API call.""" mocker.patch.object( secrets.config, 'ROLE_PASSWORDS_SECRET_NAME_PREFIX', None) assert secrets.get_role_passwords('qa-songwhip') == {} mock_sm.get_secret_value.assert_not_called() def test_get_role_passwords_missing_secret_returns_empty( mock_sm, configured_prefix): """A missing secret yields an empty map (passwordless fallback).""" mock_sm.get_secret_value.side_effect = _ResourceNotFoundException() assert secrets.get_role_passwords('qa-no-secret') == {} def test_get_role_passwords_does_not_log_values( mock_sm, configured_prefix, mocker): """Password values are never passed to the logger.""" mock_info = mocker.patch('src.logic.secrets.logger.info') mock_sm.get_secret_value.return_value = { 'SecretString': json.dumps({'app_login': 'topsecret'}) } secrets.get_role_passwords('qa-songwhip') for log_call in mock_info.call_args_list: assert 'topsecret' not in log_call.args[0]