diff --git a/app/tests/test_secrets.py b/app/tests/test_secrets.py new file mode 100644 index 0000000..94eac0c --- /dev/null +++ b/app/tests/test_secrets.py @@ -0,0 +1,139 @@ +"""Tests for secrets interpolation.""" + +import os +import pytest +from unittest.mock import patch + +from src.confgen.secrets import SecretsResolver + + +class TestSecretsResolver: + """Tests for SecretsResolver.""" + + def setup_method(self): + """Set up test fixtures.""" + self.resolver = SecretsResolver() + + def test_resolve_env_var(self): + """Test resolving environment variable placeholders.""" + os.environ["TEST_SECRET"] = "my_secret_value" + content = "password: {{env.TEST_SECRET}}" + + result = self.resolver.resolve(content) + + assert "password: my_secret_value" in result + del os.environ["TEST_SECRET"] + + def test_resolve_multiple_env_vars(self): + """Test resolving multiple environment variables.""" + os.environ["DB_HOST"] = "localhost" + os.environ["DB_PORT"] = "5432" + content = "host: {{env.DB_HOST}}\nport: {{env.DB_PORT}}" + + result = self.resolver.resolve(content) + + assert "host: localhost" in result + assert "port: 5432" in result + del os.environ["DB_HOST"] + del os.environ["DB_PORT"] + + def test_resolve_missing_env_var_raises_error(self): + """Test that missing environment variable raises an error.""" + content = "password: {{env.NONEXISTENT_SECRET}}" + + with pytest.raises(ValueError, match="Environment variable 'NONEXISTENT_SECRET' not found"): + self.resolver.resolve(content) + + def test_resolve_with_prefixed_env_var(self): + """Test resolving environment variables with CONFGEN_ prefix.""" + os.environ["CONFGEN_MY_SECRET"] = "prefixed_secret" + content = "password: {{env.MY_SECRET}}" + + result = self.resolver.resolve(content) + + assert "password: prefixed_secret" in result + del os.environ["CONFGEN_MY_SECRET"] + + def test_get_secret_names(self): + """Test extracting secret names from content.""" + content = """ +password: {{env.DB_PASSWORD}} +api_key: {{env.API_KEY}} +token: {{vault.SECRET_TOKEN}} +""" + names = self.resolver.get_secret_names(content) + + assert "DB_PASSWORD" in names + assert "API_KEY" in names + assert "SECRET_TOKEN" in names + + def test_is_secret_placeholder(self): + """Test detecting secret placeholders.""" + assert self.resolver.is_secret_placeholder("{{env.SECRET_KEY}}") is True + assert self.resolver.is_secret_placeholder("{{vault.SECRET_KEY}}") is True + assert self.resolver.is_secret_placeholder("{{APP_NAME}}") is False + + def test_check_secret_availability(self): + """Test checking which secrets are available.""" + os.environ["AVAILABLE_SECRET"] = "value" + content = "p1: {{env.AVAILABLE_SECRET}}\np2: {{env.UNAVAILABLE_SECRET}}" + + availability = self.resolver.check_secret_availability(content) + + assert availability["AVAILABLE_SECRET"] is True + assert availability["UNAVAILABLE_SECRET"] is False + del os.environ["AVAILABLE_SECRET"] + + def test_resolve_with_no_secrets(self): + """Test resolving content without secrets.""" + content = "app_name: {{APP_NAME}}\nport: {{PORT}}" + variables = {"APP_NAME": "myapp", "PORT": "8080"} + + from src.confgen.template import TemplateEngine + + engine = TemplateEngine() + result = engine.render(content, variables) + + assert result == "app_name: myapp\nport: 8080" + + def test_resolve_vault_secret_without_config(self): + """Test that vault secrets without config raise an error.""" + content = "api_key: {{vault.SECRET_API_KEY}}" + + with pytest.raises(ValueError, match="Vault not configured"): + self.resolver.resolve(content) + + @patch("src.confgen.secrets.SecretsResolver._get_from_vault") + def test_resolve_vault_secret_with_config(self, mock_get): + """Test resolving vault secrets when configured.""" + os.environ["CONFGEN_VAULT_URL"] = "http://localhost:8200" + os.environ["CONFGEN_VAULT_TOKEN"] = "test-token" + mock_get.return_value = "vault_secret_value" + content = "api_key: {{vault.SECRET_API_KEY}}" + + result = self.resolver.resolve(content) + + assert "api_key: vault_secret_value" in result + del os.environ["CONFGEN_VAULT_URL"] + del os.environ["CONFGEN_VAULT_TOKEN"] + + def test_resolve_mixed_placeholders(self): + """Test resolving mixed variable and secret placeholders.""" + os.environ["DB_PASSWORD"] = "secret123" + content = """ +app_name: {{APP_NAME}} +database: + host: {{DB_HOST}} + password: {{env.DB_PASSWORD}} +""" + + from src.confgen.template import TemplateEngine + + engine = TemplateEngine() + resolved = self.resolver.resolve(content) + rendered = engine.render(resolved, {"APP_NAME": "myapp", "DB_HOST": "localhost"}) + + assert "app_name: myapp" in rendered + assert "host: localhost" in rendered + assert "password: secret123" in rendered + del os.environ["DB_PASSWORD"]