Files
SnarfCode/tests/unit/test_provider_block.py
2026-05-22 00:19:30 -04:00

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