from pathlib import Path
import pytest
import yaml
from server import Server
def write_yaml(path: Path, content: dict):
"""Helper to write YAML files."""
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
yaml.dump(content, f)
@pytest.fixture
def kustomize_structure(tmp_path):
"""
Creates a Kustomize structure with one base and two overlays.
Structure:
base/
kustomization.yaml
deployment.yaml
overlay1/
kustomization.yaml
overlay2/
kustomization.yaml
"""
# Create base directory with deployment
base_dir = tmp_path / "base"
base_dir.mkdir()
# Base deployment
deployment = {
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "myapp",
"namespace": "default",
},
"spec": {
"replicas": 3,
"selector": {"matchLabels": {"app": "myapp"}},
"template": {
"metadata": {"labels": {"app": "myapp"}},
"spec": {
"containers": [
{
"name": "myapp",
"image": "myapp:v1.0",
"ports": [{"containerPort": 8080}],
}
]
},
},
},
}
write_yaml(base_dir / "deployment.yaml", deployment)
# Base kustomization
base_kustomization = {
"apiVersion": "kustomize.config.k8s.io/v1beta1",
"kind": "Kustomization",
"resources": ["deployment.yaml"],
}
write_yaml(base_dir / "kustomization.yaml", base_kustomization)
# Create overlay1
overlay1_dir = tmp_path / "overlay1"
overlay1_dir.mkdir()
overlay1_kustomization = {
"apiVersion": "kustomize.config.k8s.io/v1beta1",
"kind": "Kustomization",
"resources": ["../base"],
"namePrefix": "overlay1-",
}
write_yaml(overlay1_dir / "kustomization.yaml", overlay1_kustomization)
# Create overlay2
overlay2_dir = tmp_path / "overlay2"
overlay2_dir.mkdir()
overlay2_kustomization = {
"apiVersion": "kustomize.config.k8s.io/v1beta1",
"kind": "Kustomization",
"resources": ["../base"],
"namePrefix": "overlay2-",
}
write_yaml(overlay2_dir / "kustomization.yaml", overlay2_kustomization)
return tmp_path
def test_checkpoint_workflow(kustomize_structure):
"""
Integration test for the checkpoint workflow:
1. Create a checkpoint
2. Render two directories
3. Diff the two directories at the same checkpoint
4. Change the config in one directory
5. Create a new checkpoint
6. Render the directory with the new checkpoint
7. Diff the two checkpoints
"""
server = Server(kustomize_structure)
# Stage 1: Create checkpoint and render both overlays
checkpoint1_id = server.create_checkpoint()
# Change to the kustomize structure directory for rendering
overlay1_path = Path("overlay1")
overlay2_path = Path("overlay2")
# Render overlay1 and overlay2 into checkpoint 1
server.render(checkpoint1_id, overlay1_path)
server.render(checkpoint1_id, overlay2_path)
# Stage 2: Diff the two overlays at the same checkpoint
diff_result = server.diff_paths(
checkpoint1_id,
overlay1_path,
overlay2_path,
)
# Verify: The overlays should be different due to namePrefix
# overlay1 has "overlay1-myapp" and overlay2 has "overlay2-myapp"
assert (
len(diff_result.added) == 0
and len(diff_result.deleted) == 0
and len(diff_result.modified) == 0
and len(diff_result.replaced) == 1
)
assert diff_result.replaced[0].name == "overlay1-myapp"
# Stage 3: Modify config in overlay1
# Add a patch to change replicas
patch = {
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {"name": "myapp"},
"spec": {
"replicas": 5 # Changed from 3 to 5
},
}
patch_path = kustomize_structure / "overlay1" / "patch.yaml"
write_yaml(patch_path, patch)
# Update overlay1 kustomization to include the patch
overlay1_kustomization = {
"apiVersion": "kustomize.config.k8s.io/v1beta1",
"kind": "Kustomization",
"resources": ["../base"],
"namePrefix": "overlay1-",
"patches": [
{
"path": "patch.yaml",
"target": {"kind": "Deployment", "name": "myapp"},
}
],
}
write_yaml(
kustomize_structure / "overlay1" / "kustomization.yaml",
overlay1_kustomization,
)
# Stage 4: Create new checkpoint and render modified overlay
checkpoint2_id = server.create_checkpoint()
server.render(checkpoint2_id, overlay1_path)
server.render(checkpoint2_id, overlay2_path)
# Stage 5: Diff the two checkpoints
checkpoint_diff = server.diff_checkpoints(checkpoint1_id, checkpoint2_id)
# Verify: overlay1 should show modifications (replica count change)
# We expect at least one modified manifest
assert len(checkpoint_diff.modified) > 0
# Verify the detailed diff contains evidence of the change
assert checkpoint_diff.diff_path is not None
diff_path = Path(checkpoint_diff.diff_path)
assert not diff_path.is_absolute()
diff_path = kustomize_structure / diff_path
with open(diff_path) as f:
diff = f.read()
assert "replicas" in diff or "5" in diff
# Verify: The modified manifest should be the Deployment in overlay1
modified_deployments = [
m
for m in checkpoint_diff.modified
if m.kind == "Deployment" and m.source_path == "overlay1"
]
assert len(modified_deployments) > 0
assert modified_deployments[0].name == "overlay1-myapp"