404 lines
13 KiB
Python
404 lines
13 KiB
Python
"""Unit tests for core data models."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from iac_reverse.models import (
|
|
ChangeType,
|
|
ChangeSummary,
|
|
CodeGenerationResult,
|
|
CpuArchitecture,
|
|
DependencyGraph,
|
|
DiscoveredResource,
|
|
ExtractedVariable,
|
|
GeneratedFile,
|
|
PlannedChange,
|
|
PlatformCategory,
|
|
PROVIDER_PLATFORM_MAP,
|
|
ProviderType,
|
|
ResourceChange,
|
|
ResourceRelationship,
|
|
ScanProfile,
|
|
ScanProgress,
|
|
ScanResult,
|
|
StateEntry,
|
|
StateFile,
|
|
UnresolvedReference,
|
|
ValidationError,
|
|
ValidationResult,
|
|
)
|
|
|
|
|
|
class TestProviderType:
|
|
def test_all_values(self):
|
|
assert ProviderType.DOCKER_SWARM.value == "docker_swarm"
|
|
assert ProviderType.KUBERNETES.value == "kubernetes"
|
|
assert ProviderType.SYNOLOGY.value == "synology"
|
|
assert ProviderType.HARVESTER.value == "harvester"
|
|
assert ProviderType.BARE_METAL.value == "bare_metal"
|
|
assert ProviderType.WINDOWS.value == "windows"
|
|
|
|
def test_member_count(self):
|
|
assert len(ProviderType) == 6
|
|
|
|
|
|
class TestPlatformCategory:
|
|
def test_all_values(self):
|
|
assert PlatformCategory.CONTAINER_ORCHESTRATION.value == "container"
|
|
assert PlatformCategory.STORAGE_APPLIANCE.value == "storage"
|
|
assert PlatformCategory.HCI.value == "hci"
|
|
assert PlatformCategory.BARE_METAL.value == "bare_metal"
|
|
assert PlatformCategory.WINDOWS.value == "windows"
|
|
|
|
def test_member_count(self):
|
|
assert len(PlatformCategory) == 5
|
|
|
|
|
|
class TestProviderPlatformMap:
|
|
def test_all_providers_mapped(self):
|
|
for provider in ProviderType:
|
|
assert provider in PROVIDER_PLATFORM_MAP
|
|
|
|
def test_container_orchestration_providers(self):
|
|
assert PROVIDER_PLATFORM_MAP[ProviderType.DOCKER_SWARM] == PlatformCategory.CONTAINER_ORCHESTRATION
|
|
assert PROVIDER_PLATFORM_MAP[ProviderType.KUBERNETES] == PlatformCategory.CONTAINER_ORCHESTRATION
|
|
|
|
def test_other_providers(self):
|
|
assert PROVIDER_PLATFORM_MAP[ProviderType.SYNOLOGY] == PlatformCategory.STORAGE_APPLIANCE
|
|
assert PROVIDER_PLATFORM_MAP[ProviderType.HARVESTER] == PlatformCategory.HCI
|
|
assert PROVIDER_PLATFORM_MAP[ProviderType.BARE_METAL] == PlatformCategory.BARE_METAL
|
|
assert PROVIDER_PLATFORM_MAP[ProviderType.WINDOWS] == PlatformCategory.WINDOWS
|
|
|
|
|
|
class TestCpuArchitecture:
|
|
def test_all_values(self):
|
|
assert CpuArchitecture.AMD64.value == "amd64"
|
|
assert CpuArchitecture.ARM.value == "arm"
|
|
assert CpuArchitecture.AARCH64.value == "aarch64"
|
|
|
|
def test_member_count(self):
|
|
assert len(CpuArchitecture) == 3
|
|
|
|
|
|
class TestChangeType:
|
|
def test_all_values(self):
|
|
assert ChangeType.ADDED.value == "added"
|
|
assert ChangeType.REMOVED.value == "removed"
|
|
assert ChangeType.MODIFIED.value == "modified"
|
|
|
|
|
|
class TestScanProfile:
|
|
def test_valid_profile(self):
|
|
profile = ScanProfile(
|
|
provider=ProviderType.KUBERNETES,
|
|
credentials={"kubeconfig_path": "/home/user/.kube/config"},
|
|
)
|
|
assert profile.validate() == []
|
|
|
|
def test_empty_credentials_invalid(self):
|
|
profile = ScanProfile(
|
|
provider=ProviderType.KUBERNETES,
|
|
credentials={},
|
|
)
|
|
errors = profile.validate()
|
|
assert len(errors) > 0
|
|
assert "credentials" in errors[0]
|
|
|
|
def test_platform_category_property(self):
|
|
profile = ScanProfile(
|
|
provider=ProviderType.DOCKER_SWARM,
|
|
credentials={"host": "localhost"},
|
|
)
|
|
assert profile.platform_category == PlatformCategory.CONTAINER_ORCHESTRATION
|
|
|
|
def test_optional_fields_default_none(self):
|
|
profile = ScanProfile(
|
|
provider=ProviderType.SYNOLOGY,
|
|
credentials={"host": "nas01"},
|
|
)
|
|
assert profile.endpoints is None
|
|
assert profile.resource_type_filters is None
|
|
assert profile.authentik_token is None
|
|
|
|
|
|
class TestDiscoveredResource:
|
|
def test_creation(self):
|
|
resource = DiscoveredResource(
|
|
resource_type="kubernetes_deployment",
|
|
unique_id="apps/v1/deployments/default/nginx",
|
|
name="nginx",
|
|
provider=ProviderType.KUBERNETES,
|
|
platform_category=PlatformCategory.CONTAINER_ORCHESTRATION,
|
|
architecture=CpuArchitecture.AARCH64,
|
|
endpoint="https://k8s-api:6443",
|
|
attributes={"replicas": 3, "image": "nginx:1.25"},
|
|
raw_references=["default/services/nginx-svc"],
|
|
)
|
|
assert resource.resource_type == "kubernetes_deployment"
|
|
assert resource.unique_id == "apps/v1/deployments/default/nginx"
|
|
assert resource.provider == ProviderType.KUBERNETES
|
|
|
|
def test_raw_references_default_empty(self):
|
|
resource = DiscoveredResource(
|
|
resource_type="windows_service",
|
|
unique_id="win01/services/nginx",
|
|
name="nginx",
|
|
provider=ProviderType.WINDOWS,
|
|
platform_category=PlatformCategory.WINDOWS,
|
|
architecture=CpuArchitecture.AMD64,
|
|
endpoint="win01.internal.lab",
|
|
attributes={"state": "running"},
|
|
)
|
|
assert resource.raw_references == []
|
|
|
|
|
|
class TestScanResult:
|
|
def test_creation(self):
|
|
result = ScanResult(
|
|
resources=[],
|
|
warnings=["unsupported type: foo"],
|
|
errors=[],
|
|
scan_timestamp="2024-01-15T10:30:00Z",
|
|
profile_hash="abc123",
|
|
)
|
|
assert result.is_partial is False
|
|
assert len(result.warnings) == 1
|
|
|
|
def test_partial_scan(self):
|
|
result = ScanResult(
|
|
resources=[],
|
|
warnings=[],
|
|
errors=["connection lost"],
|
|
scan_timestamp="2024-01-15T10:30:00Z",
|
|
profile_hash="abc123",
|
|
is_partial=True,
|
|
)
|
|
assert result.is_partial is True
|
|
|
|
|
|
class TestScanProgress:
|
|
def test_creation(self):
|
|
progress = ScanProgress(
|
|
current_resource_type="kubernetes_deployment",
|
|
resources_discovered=15,
|
|
resource_types_completed=2,
|
|
total_resource_types=5,
|
|
)
|
|
assert progress.resources_discovered == 15
|
|
assert progress.resource_types_completed == 2
|
|
|
|
|
|
class TestResourceRelationship:
|
|
def test_creation(self):
|
|
rel = ResourceRelationship(
|
|
source_id="resource-a",
|
|
target_id="resource-b",
|
|
relationship_type="dependency",
|
|
source_attribute="network_id",
|
|
)
|
|
assert rel.relationship_type == "dependency"
|
|
|
|
|
|
class TestUnresolvedReference:
|
|
def test_creation(self):
|
|
ref = UnresolvedReference(
|
|
source_resource_id="resource-a",
|
|
source_attribute="vpc_id",
|
|
referenced_id="vpc-unknown",
|
|
suggested_resolution="data_source",
|
|
)
|
|
assert ref.suggested_resolution == "data_source"
|
|
|
|
|
|
class TestDependencyGraph:
|
|
def test_creation(self):
|
|
graph = DependencyGraph(
|
|
resources=[],
|
|
relationships=[],
|
|
topological_order=["a", "b", "c"],
|
|
cycles=[],
|
|
unresolved_references=[],
|
|
)
|
|
assert graph.topological_order == ["a", "b", "c"]
|
|
assert graph.cycles == []
|
|
|
|
|
|
class TestGeneratedFile:
|
|
def test_creation(self):
|
|
gf = GeneratedFile(
|
|
filename="kubernetes_deployment.tf",
|
|
content='resource "kubernetes_deployment" "nginx" {}',
|
|
resource_count=1,
|
|
)
|
|
assert gf.filename == "kubernetes_deployment.tf"
|
|
assert gf.resource_count == 1
|
|
|
|
|
|
class TestExtractedVariable:
|
|
def test_creation(self):
|
|
var = ExtractedVariable(
|
|
name="environment",
|
|
type_expr="string",
|
|
default_value="production",
|
|
description="Deployment environment",
|
|
used_by=["resource-a", "resource-b"],
|
|
)
|
|
assert len(var.used_by) == 2
|
|
|
|
def test_used_by_default_empty(self):
|
|
var = ExtractedVariable(
|
|
name="region",
|
|
type_expr="string",
|
|
default_value="us-east-1",
|
|
description="Region",
|
|
)
|
|
assert var.used_by == []
|
|
|
|
|
|
class TestCodeGenerationResult:
|
|
def test_creation(self):
|
|
result = CodeGenerationResult(
|
|
resource_files=[GeneratedFile("main.tf", "content", 5)],
|
|
variables_file=GeneratedFile("variables.tf", "vars", 0),
|
|
provider_file=GeneratedFile("provider.tf", "provider", 0),
|
|
outputs_file=None,
|
|
skipped_resources=[("res-1", "unsupported type")],
|
|
)
|
|
assert len(result.resource_files) == 1
|
|
assert result.outputs_file is None
|
|
assert len(result.skipped_resources) == 1
|
|
|
|
|
|
class TestStateEntry:
|
|
def test_creation(self):
|
|
entry = StateEntry(
|
|
resource_type="kubernetes_deployment",
|
|
resource_name="nginx",
|
|
provider_id="apps/v1/deployments/default/nginx",
|
|
attributes={"namespace": "default"},
|
|
sensitive_attributes=["password"],
|
|
schema_version=1,
|
|
dependencies=["kubernetes_service.nginx_svc"],
|
|
)
|
|
assert entry.schema_version == 1
|
|
assert len(entry.dependencies) == 1
|
|
|
|
def test_defaults(self):
|
|
entry = StateEntry(
|
|
resource_type="kubernetes_service",
|
|
resource_name="svc",
|
|
provider_id="id-123",
|
|
attributes={},
|
|
)
|
|
assert entry.sensitive_attributes == []
|
|
assert entry.schema_version == 0
|
|
assert entry.dependencies == []
|
|
|
|
|
|
class TestStateFile:
|
|
def test_defaults(self):
|
|
state = StateFile()
|
|
assert state.version == 4
|
|
assert state.terraform_version == ""
|
|
assert state.serial == 1
|
|
assert state.lineage == ""
|
|
assert state.resources == []
|
|
|
|
def test_to_json_structure(self):
|
|
state = StateFile(
|
|
terraform_version="1.7.0",
|
|
resources=[
|
|
StateEntry(
|
|
resource_type="kubernetes_deployment",
|
|
resource_name="nginx",
|
|
provider_id="apps/v1/deployments/default/nginx",
|
|
attributes={"namespace": "default", "replicas": 3},
|
|
schema_version=1,
|
|
dependencies=["kubernetes_service.nginx_svc"],
|
|
)
|
|
],
|
|
)
|
|
parsed = json.loads(state.to_json())
|
|
assert parsed["version"] == 4
|
|
assert parsed["terraform_version"] == "1.7.0"
|
|
assert parsed["serial"] == 1
|
|
assert "lineage" in parsed
|
|
assert len(parsed["resources"]) == 1
|
|
|
|
res = parsed["resources"][0]
|
|
assert res["mode"] == "managed"
|
|
assert res["type"] == "kubernetes_deployment"
|
|
assert res["name"] == "nginx"
|
|
assert res["instances"][0]["schema_version"] == 1
|
|
assert res["instances"][0]["attributes"]["id"] == "apps/v1/deployments/default/nginx"
|
|
|
|
def test_to_json_generates_lineage(self):
|
|
state = StateFile()
|
|
parsed = json.loads(state.to_json())
|
|
assert len(parsed["lineage"]) > 0 # UUID generated
|
|
|
|
|
|
class TestValidationResult:
|
|
def test_creation(self):
|
|
result = ValidationResult(
|
|
init_success=True,
|
|
validate_success=True,
|
|
plan_success=False,
|
|
planned_changes=[
|
|
PlannedChange(
|
|
resource_address="kubernetes_deployment.nginx",
|
|
change_type="modify",
|
|
details="replicas changed",
|
|
)
|
|
],
|
|
errors=[],
|
|
correction_attempts=1,
|
|
)
|
|
assert result.plan_success is False
|
|
assert len(result.planned_changes) == 1
|
|
|
|
|
|
class TestValidationError:
|
|
def test_creation_with_line(self):
|
|
err = ValidationError(file="main.tf", message="invalid block", line=42)
|
|
assert err.line == 42
|
|
|
|
def test_creation_without_line(self):
|
|
err = ValidationError(file="main.tf", message="missing provider")
|
|
assert err.line is None
|
|
|
|
|
|
class TestResourceChange:
|
|
def test_added(self):
|
|
change = ResourceChange(
|
|
resource_id="new-resource",
|
|
resource_type="kubernetes_service",
|
|
resource_name="new-svc",
|
|
change_type=ChangeType.ADDED,
|
|
)
|
|
assert change.changed_attributes is None
|
|
|
|
def test_modified(self):
|
|
change = ResourceChange(
|
|
resource_id="existing-resource",
|
|
resource_type="kubernetes_deployment",
|
|
resource_name="nginx",
|
|
change_type=ChangeType.MODIFIED,
|
|
changed_attributes={"replicas": {"old": 3, "new": 5}},
|
|
)
|
|
assert change.changed_attributes is not None
|
|
|
|
|
|
class TestChangeSummary:
|
|
def test_creation(self):
|
|
summary = ChangeSummary(
|
|
added_count=2,
|
|
removed_count=1,
|
|
modified_count=3,
|
|
changes=[],
|
|
)
|
|
assert summary.added_count == 2
|
|
assert summary.removed_count == 1
|
|
assert summary.modified_count == 3
|