"""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"]