415 lines
15 KiB
Python
415 lines
15 KiB
Python
"""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
|