"""Unit tests for the ProviderBlockGenerator.""" import pytest from iac_reverse.models import ProviderType, ScanProfile from iac_reverse.generator import ProviderBlockGenerator # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def make_profile( provider: ProviderType, credentials: dict[str, str] | None = None, ) -> ScanProfile: """Create a ScanProfile with sensible defaults for testing.""" default_creds: dict[ProviderType, dict[str, str]] = { ProviderType.KUBERNETES: { "host": "https://k8s-api.local:6443", "cluster_ca_certificate": "/path/to/ca.crt", "token": "my-token-123", }, ProviderType.DOCKER_SWARM: { "host": "tcp://swarm-manager:2376", "cert_path": "/home/user/.docker/certs", }, ProviderType.SYNOLOGY: { "url": "https://nas.local:5001", "username": "admin", "password": "secret123", }, ProviderType.HARVESTER: { "kubeconfig": "/home/user/.kube/harvester.yaml", }, ProviderType.BARE_METAL: { "endpoint": "https://bmc.local/redfish/v1", "username": "root", "password": "calvin", }, ProviderType.WINDOWS: { "host": "win-server-01.local", "username": "Administrator", "password": "P@ssw0rd", "winrm_port": "5986", "winrm_use_ssl": "true", }, } creds = credentials if credentials is not None else default_creds[provider] return ScanProfile(provider=provider, credentials=creds) # --------------------------------------------------------------------------- # Tests: Single provider generates one provider block # --------------------------------------------------------------------------- class TestSingleProvider: """Tests for generating a single provider block.""" def test_kubernetes_generates_one_provider_block(self): """A single Kubernetes provider generates one provider block.""" profile = make_profile(ProviderType.KUBERNETES) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.KUBERNETES}, ) assert result.filename == "providers.tf" assert result.content.count('provider "kubernetes"') == 1 def test_docker_generates_one_provider_block(self): """A single Docker Swarm provider generates one provider block.""" profile = make_profile(ProviderType.DOCKER_SWARM) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.DOCKER_SWARM}, ) assert result.content.count('provider "docker"') == 1 def test_synology_generates_one_provider_block(self): """A single Synology provider generates one provider block.""" profile = make_profile(ProviderType.SYNOLOGY) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.SYNOLOGY}, ) assert result.content.count('provider "synology"') == 1 def test_harvester_generates_one_provider_block(self): """A single Harvester provider generates one provider block.""" profile = make_profile(ProviderType.HARVESTER) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.HARVESTER}, ) assert result.content.count('provider "harvester"') == 1 def test_bare_metal_generates_one_provider_block(self): """A single Bare Metal provider generates one provider block.""" profile = make_profile(ProviderType.BARE_METAL) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.BARE_METAL}, ) assert result.content.count('provider "redfish"') == 1 def test_windows_generates_one_provider_block(self): """A single Windows provider generates one provider block.""" profile = make_profile(ProviderType.WINDOWS) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.WINDOWS}, ) assert result.content.count('provider "windows"') == 1 # --------------------------------------------------------------------------- # Tests: Multiple providers generate multiple blocks # --------------------------------------------------------------------------- class TestMultipleProviders: """Tests for generating multiple provider blocks.""" def test_two_providers_generate_two_blocks(self): """Two distinct providers generate two provider blocks.""" profiles = [ make_profile(ProviderType.KUBERNETES), make_profile(ProviderType.DOCKER_SWARM), ] generator = ProviderBlockGenerator() result = generator.generate( profiles=profiles, provider_types={ProviderType.KUBERNETES, ProviderType.DOCKER_SWARM}, ) assert 'provider "kubernetes"' in result.content assert 'provider "docker"' in result.content def test_three_providers_generate_three_blocks(self): """Three distinct providers generate three provider blocks.""" profiles = [ make_profile(ProviderType.KUBERNETES), make_profile(ProviderType.SYNOLOGY), make_profile(ProviderType.WINDOWS), ] generator = ProviderBlockGenerator() result = generator.generate( profiles=profiles, provider_types={ ProviderType.KUBERNETES, ProviderType.SYNOLOGY, ProviderType.WINDOWS, }, ) assert 'provider "kubernetes"' in result.content assert 'provider "synology"' in result.content assert 'provider "windows"' in result.content def test_all_six_providers(self): """All six provider types generate six provider blocks.""" all_types = set(ProviderType) profiles = [make_profile(pt) for pt in all_types] generator = ProviderBlockGenerator() result = generator.generate( profiles=profiles, provider_types=all_types, ) assert 'provider "kubernetes"' in result.content assert 'provider "docker"' in result.content assert 'provider "synology"' in result.content assert 'provider "harvester"' in result.content assert 'provider "redfish"' in result.content assert 'provider "windows"' in result.content # --------------------------------------------------------------------------- # Tests: Provider-specific configuration is included # --------------------------------------------------------------------------- class TestProviderSpecificConfig: """Tests for provider-specific configuration attributes.""" def test_kubernetes_includes_host_ca_token(self): """Kubernetes provider block includes host, cluster_ca_certificate, token.""" profile = make_profile(ProviderType.KUBERNETES) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.KUBERNETES}, ) assert "https://k8s-api.local:6443" in result.content assert "/path/to/ca.crt" in result.content assert "my-token-123" in result.content assert "host" in result.content assert "cluster_ca_certificate" in result.content assert "token" in result.content def test_docker_includes_host_cert_path(self): """Docker provider block includes host and cert_path.""" profile = make_profile(ProviderType.DOCKER_SWARM) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.DOCKER_SWARM}, ) assert "tcp://swarm-manager:2376" in result.content assert "/home/user/.docker/certs" in result.content assert "host" in result.content assert "cert_path" in result.content def test_synology_includes_url_username_password(self): """Synology provider block includes url, username, password.""" profile = make_profile(ProviderType.SYNOLOGY) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.SYNOLOGY}, ) assert "https://nas.local:5001" in result.content assert "admin" in result.content assert "secret123" in result.content assert "url" in result.content assert "username" in result.content assert "password" in result.content def test_harvester_includes_kubeconfig(self): """Harvester provider block includes kubeconfig.""" profile = make_profile(ProviderType.HARVESTER) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.HARVESTER}, ) assert "/home/user/.kube/harvester.yaml" in result.content assert "kubeconfig" in result.content def test_bare_metal_includes_endpoint_username_password(self): """Bare Metal (redfish) provider block includes endpoint, username, password.""" profile = make_profile(ProviderType.BARE_METAL) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.BARE_METAL}, ) assert "https://bmc.local/redfish/v1" in result.content assert "root" in result.content assert "calvin" in result.content assert "endpoint" in result.content assert "username" in result.content assert "password" in result.content def test_windows_includes_host_username_password_winrm(self): """Windows provider block includes host, username, password, winrm settings.""" profile = make_profile(ProviderType.WINDOWS) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.WINDOWS}, ) assert "win-server-01.local" in result.content assert "Administrator" in result.content assert "P@ssw0rd" in result.content assert "winrm" in result.content assert "5986" in result.content assert "use_ssl" in result.content # --------------------------------------------------------------------------- # Tests: required_providers block is generated # --------------------------------------------------------------------------- class TestRequiredProvidersBlock: """Tests for the terraform { required_providers { ... } } block.""" def test_required_providers_block_present(self): """The output contains a terraform { required_providers { ... } } block.""" profile = make_profile(ProviderType.KUBERNETES) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.KUBERNETES}, ) assert "terraform {" in result.content assert "required_providers {" in result.content def test_required_providers_includes_source(self): """The required_providers block includes source for each provider.""" profile = make_profile(ProviderType.KUBERNETES) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.KUBERNETES}, ) assert 'source = "hashicorp/kubernetes"' in result.content def test_required_providers_includes_version(self): """The required_providers block includes version constraint.""" profile = make_profile(ProviderType.KUBERNETES) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.KUBERNETES}, ) assert 'version = "~> 2.0"' in result.content def test_multiple_providers_all_in_required_providers(self): """Multiple providers are all listed in required_providers.""" profiles = [ make_profile(ProviderType.KUBERNETES), make_profile(ProviderType.DOCKER_SWARM), make_profile(ProviderType.BARE_METAL), ] generator = ProviderBlockGenerator() result = generator.generate( profiles=profiles, provider_types={ ProviderType.KUBERNETES, ProviderType.DOCKER_SWARM, ProviderType.BARE_METAL, }, ) assert 'source = "hashicorp/kubernetes"' in result.content assert 'source = "kreuzwerker/docker"' in result.content assert 'source = "dell/redfish"' in result.content def test_docker_swarm_maps_to_kreuzwerker_docker(self): """Docker Swarm maps to kreuzwerker/docker provider source.""" profile = make_profile(ProviderType.DOCKER_SWARM) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.DOCKER_SWARM}, ) assert "docker = {" in result.content assert 'source = "kreuzwerker/docker"' in result.content def test_harvester_maps_to_harvester_source(self): """Harvester maps to harvester/harvester provider source.""" profile = make_profile(ProviderType.HARVESTER) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.HARVESTER}, ) assert "harvester = {" in result.content assert 'source = "harvester/harvester"' in result.content def test_provider_type_without_matching_profile_gets_placeholder(self): """A provider type with no matching profile gets a placeholder block.""" # Profile is for Kubernetes, but we request Synology too profile = make_profile(ProviderType.KUBERNETES) generator = ProviderBlockGenerator() result = generator.generate( profiles=[profile], provider_types={ProviderType.KUBERNETES, ProviderType.SYNOLOGY}, ) # Synology should still appear in required_providers assert "synology = {" in result.content assert 'source = "synology-community/synology"' in result.content # And should have a placeholder provider block assert '# No profile provided' in result.content