Created IAC reverse generator
This commit is contained in:
245
tests/unit/test_snapshot_store.py
Normal file
245
tests/unit/test_snapshot_store.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""Unit tests for the SnapshotStore class."""
|
||||
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from iac_reverse.incremental.snapshot_store import SnapshotStore
|
||||
from iac_reverse.models import (
|
||||
CpuArchitecture,
|
||||
DiscoveredResource,
|
||||
PlatformCategory,
|
||||
ProviderType,
|
||||
ScanResult,
|
||||
)
|
||||
|
||||
|
||||
def _make_scan_result(
|
||||
profile_hash: str = "abc123",
|
||||
resource_name: str = "test-resource",
|
||||
) -> ScanResult:
|
||||
"""Create a sample ScanResult for testing."""
|
||||
resource = DiscoveredResource(
|
||||
resource_type="kubernetes_deployment",
|
||||
unique_id="apps/v1/deployments/default/nginx",
|
||||
name=resource_name,
|
||||
provider=ProviderType.KUBERNETES,
|
||||
platform_category=PlatformCategory.CONTAINER_ORCHESTRATION,
|
||||
architecture=CpuArchitecture.AARCH64,
|
||||
endpoint="https://k8s-api.internal.lab:6443",
|
||||
attributes={"replicas": 3, "image": "nginx:1.25"},
|
||||
raw_references=["default/services/nginx-svc"],
|
||||
)
|
||||
return ScanResult(
|
||||
resources=[resource],
|
||||
warnings=["some warning"],
|
||||
errors=[],
|
||||
scan_timestamp="2024-01-15T10:30:00Z",
|
||||
profile_hash=profile_hash,
|
||||
is_partial=False,
|
||||
)
|
||||
|
||||
|
||||
class TestStoreSnapshot:
|
||||
"""Tests for storing snapshots."""
|
||||
|
||||
def test_store_creates_file_in_correct_directory(self, tmp_path: Path) -> None:
|
||||
"""Storing a snapshot creates a JSON file in the snapshot directory."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
result = _make_scan_result(profile_hash="prof1")
|
||||
|
||||
store.store_snapshot(result, "prof1")
|
||||
|
||||
files = list(snapshot_dir.iterdir())
|
||||
assert len(files) == 1
|
||||
assert files[0].name.startswith("prof1_")
|
||||
assert files[0].name.endswith(".json")
|
||||
|
||||
def test_store_creates_directory_if_not_exists(self, tmp_path: Path) -> None:
|
||||
"""Storing a snapshot creates the snapshot directory if it doesn't exist."""
|
||||
snapshot_dir = tmp_path / "nested" / "deep" / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
result = _make_scan_result()
|
||||
|
||||
store.store_snapshot(result, "abc123")
|
||||
|
||||
assert snapshot_dir.exists()
|
||||
assert len(list(snapshot_dir.iterdir())) == 1
|
||||
|
||||
def test_stored_file_contains_valid_json(self, tmp_path: Path) -> None:
|
||||
"""The stored snapshot file contains valid JSON with expected fields."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
result = _make_scan_result(profile_hash="prof1")
|
||||
|
||||
store.store_snapshot(result, "prof1")
|
||||
|
||||
files = list(snapshot_dir.iterdir())
|
||||
with open(files[0], "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data["profile_hash"] == "prof1"
|
||||
assert data["scan_timestamp"] == "2024-01-15T10:30:00Z"
|
||||
assert len(data["resources"]) == 1
|
||||
assert data["resources"][0]["resource_type"] == "kubernetes_deployment"
|
||||
assert data["resources"][0]["provider"] == "kubernetes"
|
||||
assert data["resources"][0]["architecture"] == "aarch64"
|
||||
|
||||
|
||||
class TestLoadPrevious:
|
||||
"""Tests for loading previous snapshots."""
|
||||
|
||||
def test_load_returns_correct_scan_result(self, tmp_path: Path) -> None:
|
||||
"""Loading a previous snapshot returns the correct ScanResult."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
original = _make_scan_result(profile_hash="prof1", resource_name="nginx")
|
||||
|
||||
store.store_snapshot(original, "prof1")
|
||||
loaded = store.load_previous("prof1")
|
||||
|
||||
assert loaded is not None
|
||||
assert loaded.profile_hash == "prof1"
|
||||
assert loaded.scan_timestamp == "2024-01-15T10:30:00Z"
|
||||
assert loaded.is_partial is False
|
||||
assert loaded.warnings == ["some warning"]
|
||||
assert loaded.errors == []
|
||||
assert len(loaded.resources) == 1
|
||||
|
||||
resource = loaded.resources[0]
|
||||
assert resource.resource_type == "kubernetes_deployment"
|
||||
assert resource.unique_id == "apps/v1/deployments/default/nginx"
|
||||
assert resource.name == "nginx"
|
||||
assert resource.provider == ProviderType.KUBERNETES
|
||||
assert resource.platform_category == PlatformCategory.CONTAINER_ORCHESTRATION
|
||||
assert resource.architecture == CpuArchitecture.AARCH64
|
||||
assert resource.endpoint == "https://k8s-api.internal.lab:6443"
|
||||
assert resource.attributes == {"replicas": 3, "image": "nginx:1.25"}
|
||||
assert resource.raw_references == ["default/services/nginx-svc"]
|
||||
|
||||
def test_load_returns_none_when_no_snapshot_exists(self, tmp_path: Path) -> None:
|
||||
"""Loading when no snapshot exists returns None."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
|
||||
result = store.load_previous("nonexistent")
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_load_returns_none_when_directory_does_not_exist(
|
||||
self, tmp_path: Path
|
||||
) -> None:
|
||||
"""Loading when the snapshot directory doesn't exist returns None."""
|
||||
snapshot_dir = tmp_path / "does_not_exist"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
|
||||
result = store.load_previous("prof1")
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_load_returns_most_recent_snapshot(self, tmp_path: Path) -> None:
|
||||
"""When multiple snapshots exist, load returns the most recent one."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
|
||||
# Store first snapshot
|
||||
result1 = _make_scan_result(profile_hash="prof1", resource_name="first")
|
||||
store.store_snapshot(result1, "prof1")
|
||||
time.sleep(1.1) # Ensure different timestamp
|
||||
|
||||
# Store second snapshot
|
||||
result2 = _make_scan_result(profile_hash="prof1", resource_name="second")
|
||||
store.store_snapshot(result2, "prof1")
|
||||
|
||||
loaded = store.load_previous("prof1")
|
||||
assert loaded is not None
|
||||
assert loaded.resources[0].name == "second"
|
||||
|
||||
|
||||
class TestRetention:
|
||||
"""Tests for snapshot retention/pruning."""
|
||||
|
||||
def test_retains_at_least_two_most_recent_snapshots(self, tmp_path: Path) -> None:
|
||||
"""Only the 2 most recent snapshots are kept per profile."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
|
||||
# Store 4 snapshots with different timestamps
|
||||
for i in range(4):
|
||||
result = _make_scan_result(
|
||||
profile_hash="prof1", resource_name=f"resource-{i}"
|
||||
)
|
||||
store.store_snapshot(result, "prof1")
|
||||
time.sleep(1.1) # Ensure different timestamps
|
||||
|
||||
# Should only have 2 files remaining
|
||||
files = list(snapshot_dir.iterdir())
|
||||
assert len(files) == 2
|
||||
|
||||
# The most recent should be loadable
|
||||
loaded = store.load_previous("prof1")
|
||||
assert loaded is not None
|
||||
assert loaded.resources[0].name == "resource-3"
|
||||
|
||||
def test_two_snapshots_are_not_pruned(self, tmp_path: Path) -> None:
|
||||
"""Exactly 2 snapshots are retained without pruning."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
|
||||
store.store_snapshot(_make_scan_result(profile_hash="prof1"), "prof1")
|
||||
time.sleep(1.1)
|
||||
store.store_snapshot(_make_scan_result(profile_hash="prof1"), "prof1")
|
||||
|
||||
files = list(snapshot_dir.iterdir())
|
||||
assert len(files) == 2
|
||||
|
||||
|
||||
class TestMultipleProfiles:
|
||||
"""Tests for multiple profile isolation."""
|
||||
|
||||
def test_multiple_profiles_do_not_interfere(self, tmp_path: Path) -> None:
|
||||
"""Snapshots from different profiles don't interfere with each other."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
|
||||
result_a = _make_scan_result(profile_hash="profile_a", resource_name="res-a")
|
||||
result_b = _make_scan_result(profile_hash="profile_b", resource_name="res-b")
|
||||
|
||||
store.store_snapshot(result_a, "profile_a")
|
||||
store.store_snapshot(result_b, "profile_b")
|
||||
|
||||
loaded_a = store.load_previous("profile_a")
|
||||
loaded_b = store.load_previous("profile_b")
|
||||
|
||||
assert loaded_a is not None
|
||||
assert loaded_a.resources[0].name == "res-a"
|
||||
assert loaded_b is not None
|
||||
assert loaded_b.resources[0].name == "res-b"
|
||||
|
||||
def test_pruning_only_affects_matching_profile(self, tmp_path: Path) -> None:
|
||||
"""Pruning for one profile does not remove snapshots from another."""
|
||||
snapshot_dir = tmp_path / "snapshots"
|
||||
store = SnapshotStore(base_dir=str(snapshot_dir))
|
||||
|
||||
# Store 4 snapshots for profile_a (should prune to 2)
|
||||
for i in range(4):
|
||||
result = _make_scan_result(
|
||||
profile_hash="profile_a", resource_name=f"a-{i}"
|
||||
)
|
||||
store.store_snapshot(result, "profile_a")
|
||||
time.sleep(1.1)
|
||||
|
||||
# Store 1 snapshot for profile_b
|
||||
result_b = _make_scan_result(profile_hash="profile_b", resource_name="b-0")
|
||||
store.store_snapshot(result_b, "profile_b")
|
||||
|
||||
# profile_a should have 2 files, profile_b should have 1
|
||||
all_files = list(snapshot_dir.iterdir())
|
||||
a_files = [f for f in all_files if f.name.startswith("profile_a_")]
|
||||
b_files = [f for f in all_files if f.name.startswith("profile_b_")]
|
||||
|
||||
assert len(a_files) == 2
|
||||
assert len(b_files) == 1
|
||||
Reference in New Issue
Block a user