Created IAC reverse generator
This commit is contained in:
403
tests/unit/test_models.py
Normal file
403
tests/unit/test_models.py
Normal file
@@ -0,0 +1,403 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user