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

406 lines
15 KiB
Python

"""Unit tests for the VariableExtractor."""
import pytest
from iac_reverse.models import (
CpuArchitecture,
DiscoveredResource,
ExtractedVariable,
PlatformCategory,
ProviderType,
)
from iac_reverse.generator import VariableExtractor
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def make_resource(
resource_type: str = "kubernetes_deployment",
unique_id: str = "default/deployments/nginx",
name: str = "nginx",
provider: ProviderType = ProviderType.KUBERNETES,
platform_category: PlatformCategory = PlatformCategory.CONTAINER_ORCHESTRATION,
architecture: CpuArchitecture = CpuArchitecture.AARCH64,
attributes: dict | None = None,
) -> DiscoveredResource:
"""Create a sample DiscoveredResource for testing."""
return DiscoveredResource(
resource_type=resource_type,
unique_id=unique_id,
name=name,
provider=provider,
platform_category=platform_category,
architecture=architecture,
endpoint="https://k8s-api.local:6443",
attributes=attributes or {},
raw_references=[],
)
# ---------------------------------------------------------------------------
# Tests: No shared values produces no variables
# ---------------------------------------------------------------------------
class TestNoSharedValues:
"""Tests that no variables are produced when values are not shared."""
def test_empty_resources_produces_no_variables(self):
"""An empty resource list produces no variables."""
extractor = VariableExtractor()
result = extractor.extract_variables([])
assert result == []
def test_single_resource_produces_no_variables(self):
"""A single resource cannot have shared values."""
resource = make_resource(attributes={"namespace": "default", "replicas": 3})
extractor = VariableExtractor()
result = extractor.extract_variables([resource])
assert result == []
def test_two_resources_no_common_values_produces_no_variables(self):
"""Two resources with completely different attribute values produce no variables."""
r1 = make_resource(
unique_id="r1",
name="app-a",
attributes={"namespace": "alpha", "replicas": 1},
)
r2 = make_resource(
unique_id="r2",
name="app-b",
attributes={"namespace": "beta", "replicas": 2},
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert result == []
def test_two_resources_same_key_different_values_no_variable(self):
"""Two resources with the same key but different values produce no variable."""
r1 = make_resource(
unique_id="r1",
name="app-a",
attributes={"environment": "staging"},
)
r2 = make_resource(
unique_id="r2",
name="app-b",
attributes={"environment": "production"},
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert result == []
# ---------------------------------------------------------------------------
# Tests: Value appearing in 2 resources produces a variable
# ---------------------------------------------------------------------------
class TestSharedValueExtraction:
"""Tests that shared values are correctly extracted as variables."""
def test_same_value_in_two_resources_produces_variable(self):
"""A value appearing in 2 resources for the same key produces a variable."""
r1 = make_resource(
unique_id="r1",
name="app-a",
attributes={"namespace": "production"},
)
r2 = make_resource(
unique_id="r2",
name="app-b",
attributes={"namespace": "production"},
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert len(result) == 1
assert result[0].name == "var_namespace"
def test_same_value_in_three_resources_produces_one_variable(self):
"""A value appearing in 3 resources still produces exactly one variable."""
resources = [
make_resource(
unique_id=f"r{i}",
name=f"app-{i}",
attributes={"region": "us-east-1"},
)
for i in range(3)
]
extractor = VariableExtractor()
result = extractor.extract_variables(resources)
assert len(result) == 1
assert result[0].name == "var_region"
def test_multiple_shared_keys_produce_multiple_variables(self):
"""Multiple shared attribute keys each produce their own variable."""
r1 = make_resource(
unique_id="r1",
name="app-a",
attributes={"namespace": "default", "environment": "prod"},
)
r2 = make_resource(
unique_id="r2",
name="app-b",
attributes={"namespace": "default", "environment": "prod"},
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
var_names = {v.name for v in result}
assert "var_namespace" in var_names
assert "var_environment" in var_names
# ---------------------------------------------------------------------------
# Tests: Default is set to most common value
# ---------------------------------------------------------------------------
class TestDefaultValue:
"""Tests that the default value is set to the most common value."""
def test_default_is_most_common_value(self):
"""When only one value is shared (2+ resources), default is that value."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"namespace": "production"}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"namespace": "production"}
)
r3 = make_resource(
unique_id="r3", name="app-c", attributes={"namespace": "staging"}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2, r3])
# "production" appears in 2 resources, "staging" in 1 (not shared)
# The variable for "production" should have default = "production"
assert len(result) == 1
assert result[0].default_value == '"production"'
def test_default_with_equal_counts_picks_one(self):
"""When values have equal counts, each variable gets its own value as default."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"namespace": "alpha"}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"namespace": "alpha"}
)
r3 = make_resource(
unique_id="r3", name="app-c", attributes={"namespace": "beta"}
)
r4 = make_resource(
unique_id="r4", name="app-d", attributes={"namespace": "beta"}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2, r3, r4])
# Both "alpha" and "beta" appear in 2 resources each
# Both should produce variables; each with its own value as default
assert len(result) == 2
defaults = {v.default_value for v in result}
assert '"alpha"' in defaults
assert '"beta"' in defaults
# ---------------------------------------------------------------------------
# Tests: Type expression matches value type
# ---------------------------------------------------------------------------
class TestTypeExpression:
"""Tests that type expressions match the Python value type."""
def test_string_value_has_string_type(self):
"""A string attribute value produces type = string."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"namespace": "default"}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"namespace": "default"}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert result[0].type_expr == "string"
def test_integer_value_has_number_type(self):
"""An integer attribute value produces type = number."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"replicas": 3}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"replicas": 3}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert result[0].type_expr == "number"
def test_boolean_value_has_bool_type(self):
"""A boolean attribute value produces type = bool."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"enabled": True}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"enabled": True}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert result[0].type_expr == "bool"
def test_number_default_format(self):
"""A number default is formatted without quotes."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"replicas": 3}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"replicas": 3}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert result[0].default_value == "3"
def test_bool_default_format(self):
"""A boolean default is formatted as true/false."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"enabled": False}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"enabled": False}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert result[0].default_value == "false"
# ---------------------------------------------------------------------------
# Tests: used_by lists all resources using the variable
# ---------------------------------------------------------------------------
class TestUsedBy:
"""Tests that used_by correctly lists all resources using the variable."""
def test_used_by_contains_both_resource_ids(self):
"""used_by lists both resource unique_ids that share the value."""
r1 = make_resource(
unique_id="ns/deployments/app-a",
name="app-a",
attributes={"namespace": "production"},
)
r2 = make_resource(
unique_id="ns/deployments/app-b",
name="app-b",
attributes={"namespace": "production"},
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2])
assert len(result) == 1
assert "ns/deployments/app-a" in result[0].used_by
assert "ns/deployments/app-b" in result[0].used_by
def test_used_by_contains_all_three_resource_ids(self):
"""used_by lists all three resource unique_ids when value is shared by 3."""
resources = [
make_resource(
unique_id=f"ns/deployments/app-{i}",
name=f"app-{i}",
attributes={"environment": "prod"},
)
for i in range(3)
]
extractor = VariableExtractor()
result = extractor.extract_variables(resources)
assert len(result[0].used_by) == 3
for i in range(3):
assert f"ns/deployments/app-{i}" in result[0].used_by
def test_used_by_excludes_resources_with_different_value(self):
"""used_by does not include resources that have a different value for the key."""
r1 = make_resource(
unique_id="r1", name="app-a", attributes={"namespace": "production"}
)
r2 = make_resource(
unique_id="r2", name="app-b", attributes={"namespace": "production"}
)
r3 = make_resource(
unique_id="r3", name="app-c", attributes={"namespace": "staging"}
)
extractor = VariableExtractor()
result = extractor.extract_variables([r1, r2, r3])
# Only the "production" variable should exist (2 resources)
assert len(result) == 1
assert "r1" in result[0].used_by
assert "r2" in result[0].used_by
assert "r3" not in result[0].used_by
# ---------------------------------------------------------------------------
# Tests: generate_variables_tf output
# ---------------------------------------------------------------------------
class TestGenerateVariablesTf:
"""Tests for the variables.tf file content generation."""
def test_empty_variables_produces_empty_string(self):
"""No variables produces an empty string."""
extractor = VariableExtractor()
result = extractor.generate_variables_tf([])
assert result == ""
def test_single_variable_produces_valid_block(self):
"""A single variable produces a valid Terraform variable block."""
var = ExtractedVariable(
name="var_namespace",
type_expr="string",
default_value='"production"',
description="Shared namespace value extracted from 2 resources",
used_by=["r1", "r2"],
)
extractor = VariableExtractor()
result = extractor.generate_variables_tf([var])
assert 'variable "var_namespace"' in result
assert "type = string" in result
assert 'description = "Shared namespace value extracted from 2 resources"' in result
assert 'default = "production"' in result
def test_multiple_variables_separated_by_blank_line(self):
"""Multiple variables are separated by blank lines."""
vars_list = [
ExtractedVariable(
name="var_namespace",
type_expr="string",
default_value='"default"',
description="Shared namespace",
used_by=["r1", "r2"],
),
ExtractedVariable(
name="var_replicas",
type_expr="number",
default_value="3",
description="Shared replicas",
used_by=["r1", "r2"],
),
]
extractor = VariableExtractor()
result = extractor.generate_variables_tf(vars_list)
assert 'variable "var_namespace"' in result
assert 'variable "var_replicas"' in result
# Two blocks separated by a blank line
assert "\n\n" in result