from app.config import Settings
from app.pending_changes import PendingChangeError, PendingChangeRepository, ProposedChangeRequest
def test_creates_and_reads_pending_change(tmp_path) -> None:
repo = PendingChangeRepository(Settings(tmp_path))
request = ProposedChangeRequest(
kind="inventory-item",
target="virtual-machines/gnauth",
summary="Update gnauth VM metadata",
reason="Document newly confirmed service mapping.",
payload={"runs_services": ["gnexus-auth"]},
)
created = repo.create(request)
fetched = repo.get(created["id"])
changes = repo.list()
assert created["status"] == "pending"
assert fetched["summary"] == "Update gnauth VM metadata"
assert changes[0]["id"] == created["id"]
assert (tmp_path / "90-maintenance" / "pending-changes" / f"{created['id']}.json").exists()
def _copy_schema_files(tmp_path) -> None:
(tmp_path / "schemas").mkdir()
source_schema = Settings().repo_root / "schemas"
for schema in source_schema.glob("*.json"):
(tmp_path / "schemas" / schema.name).write_text(schema.read_text(), encoding="utf-8")
def _create_empty_inventory(tmp_path) -> None:
(tmp_path / "40-inventory").mkdir()
for name in [
"backups",
"databases",
"domains",
"hardware",
"hosts",
"networks",
"services",
"traffic-routes",
"virtual-machines",
]:
(tmp_path / "40-inventory" / f"{name}.yml").write_text("---\n[]\n", encoding="utf-8")
def test_applies_doc_change(tmp_path) -> None:
_copy_schema_files(tmp_path)
_create_empty_inventory(tmp_path)
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="doc",
target="10-systems/example.md",
summary="Add example doc",
payload={
"content": (
"---\n"
"owner: gmikcon\n"
"status: active\n"
"last_reviewed: 2026-05-09\n"
"review_interval: 90d\n"
"confidence: medium\n"
"source_of_truth: test\n"
"---\n\n"
"# Example\n"
)
},
)
)
applied = repo.apply(created["id"])
assert applied["status"] == "applied"
assert applied["validation"]["status"] == "ok"
assert (tmp_path / "10-systems" / "example.md").exists()
def test_rolls_back_doc_change_when_validation_fails(tmp_path) -> None:
_copy_schema_files(tmp_path)
_create_empty_inventory(tmp_path)
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="doc",
target="10-systems/bad.md",
summary="Add invalid doc",
payload={"content": "# Missing Frontmatter\n"},
)
)
try:
repo.apply(created["id"])
except PendingChangeError as exc:
assert "failed validation" in str(exc)
else:
raise AssertionError("Expected PendingChangeError")
assert not (tmp_path / "10-systems" / "bad.md").exists()
assert repo.get(created["id"])["status"] == "pending"
def test_rejects_non_doc_apply(tmp_path) -> None:
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="inventory",
target="virtual-machines",
summary="Update inventory",
payload={"items": []},
)
)
try:
repo.apply(created["id"])
except PendingChangeError as exc:
assert "Only doc and inventory-item changes" in str(exc)
else:
raise AssertionError("Expected PendingChangeError")
def test_applies_inventory_item_change(tmp_path) -> None:
_copy_schema_files(tmp_path)
_create_empty_inventory(tmp_path)
inventory = tmp_path / "40-inventory" / "virtual-machines.yml"
inventory.write_text(
"---\n"
"- id: gnauth\n"
" name: gnauth\n"
" status: running\n"
" hypervisor_host: hp-proliant-dl380-g6\n"
" virtualization_stack: kvm-libvirt\n"
" docs: ../10-systems/virtualization/libvirt-vms.md\n"
" last_reviewed: 2026-05-09\n",
encoding="utf-8",
)
docs = tmp_path / "10-systems" / "virtualization"
docs.mkdir(parents=True)
(docs / "libvirt-vms.md").write_text(
"---\n"
"owner: gmikcon\n"
"status: active\n"
"last_reviewed: 2026-05-09\n"
"review_interval: 90d\n"
"confidence: medium\n"
"source_of_truth: test\n"
"---\n\n"
"# Libvirt VMs\n",
encoding="utf-8",
)
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="inventory-item",
target="virtual-machines/gnauth",
summary="Add gnauth service mapping",
payload={"patch": {"runs_services": ["gnexus-auth"]}},
)
)
applied = repo.apply(created["id"])
assert applied["status"] == "applied"
assert "gnexus-auth" in inventory.read_text(encoding="utf-8")
def test_inventory_item_change_rolls_back_on_validation_error(tmp_path) -> None:
_copy_schema_files(tmp_path)
_create_empty_inventory(tmp_path)
inventory = tmp_path / "40-inventory" / "virtual-machines.yml"
original = (
"---\n"
"- id: gnauth\n"
" name: gnauth\n"
" status: running\n"
" hypervisor_host: hp-proliant-dl380-g6\n"
" virtualization_stack: kvm-libvirt\n"
" docs: ../10-systems/virtualization/libvirt-vms.md\n"
" last_reviewed: 2026-05-09\n"
)
inventory.write_text(original, encoding="utf-8")
docs = tmp_path / "10-systems" / "virtualization"
docs.mkdir(parents=True)
(docs / "libvirt-vms.md").write_text(
"---\n"
"owner: gmikcon\n"
"status: active\n"
"last_reviewed: 2026-05-09\n"
"review_interval: 90d\n"
"confidence: medium\n"
"source_of_truth: test\n"
"---\n\n"
"# Libvirt VMs\n",
encoding="utf-8",
)
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="inventory-item",
target="virtual-machines/gnauth",
summary="Break VM status",
payload={"patch": {"status": "invalid-status"}},
)
)
try:
repo.apply(created["id"])
except PendingChangeError as exc:
assert "failed validation" in str(exc)
else:
raise AssertionError("Expected PendingChangeError")
assert inventory.read_text(encoding="utf-8") == original
def test_creates_inventory_item(tmp_path) -> None:
_copy_schema_files(tmp_path)
_create_empty_inventory(tmp_path)
docs = tmp_path / "10-systems" / "virtualization"
docs.mkdir(parents=True)
(docs / "libvirt-vms.md").write_text(
"---\n"
"owner: gmikcon\n"
"status: active\n"
"last_reviewed: 2026-05-09\n"
"review_interval: 90d\n"
"confidence: medium\n"
"source_of_truth: test\n"
"---\n\n"
"# Libvirt VMs\n",
encoding="utf-8",
)
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="inventory-item",
target="virtual-machines/new-vm",
summary="Add new VM",
payload={
"mode": "create",
"patch": {
"name": "new-vm",
"status": "running",
"hypervisor_host": "hp-proliant-dl380-g6",
"virtualization_stack": "kvm-libvirt",
"docs": "../10-systems/virtualization/libvirt-vms.md",
"last_reviewed": "2026-05-09",
},
},
)
)
applied = repo.apply(created["id"])
assert applied["status"] == "applied"
assert "new-vm" in (tmp_path / "40-inventory" / "virtual-machines.yml").read_text(
encoding="utf-8"
)
def test_create_inventory_item_rejects_duplicate(tmp_path) -> None:
_copy_schema_files(tmp_path)
_create_empty_inventory(tmp_path)
inventory = tmp_path / "40-inventory" / "virtual-machines.yml"
inventory.write_text(
"---\n"
"- id: gnauth\n"
" name: gnauth\n"
" status: running\n"
" hypervisor_host: hp-proliant-dl380-g6\n"
" virtualization_stack: kvm-libvirt\n"
" docs: ../10-systems/virtualization/libvirt-vms.md\n"
" last_reviewed: 2026-05-09\n",
encoding="utf-8",
)
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="inventory-item",
target="virtual-machines/gnauth",
summary="Duplicate VM",
payload={
"mode": "create",
"patch": {
"name": "gnauth",
"status": "running",
"hypervisor_host": "hp-proliant-dl380-g6",
"virtualization_stack": "kvm-libvirt",
"docs": "../10-systems/virtualization/libvirt-vms.md",
"last_reviewed": "2026-05-09",
},
},
)
)
try:
repo.apply(created["id"])
except PendingChangeError as exc:
assert "already exists" in str(exc)
else:
raise AssertionError("Expected PendingChangeError")
def test_create_inventory_item_rolls_back_on_validation_error(tmp_path) -> None:
_copy_schema_files(tmp_path)
_create_empty_inventory(tmp_path)
inventory = tmp_path / "40-inventory" / "virtual-machines.yml"
original = inventory.read_text(encoding="utf-8")
repo = PendingChangeRepository(Settings(tmp_path))
created = repo.create(
ProposedChangeRequest(
kind="inventory-item",
target="virtual-machines/bad-vm",
summary="Add invalid VM",
payload={
"mode": "create",
"patch": {
"name": "bad-vm",
"status": "invalid-status",
},
},
)
)
try:
repo.apply(created["id"])
except PendingChangeError as exc:
assert "failed validation" in str(exc)
else:
raise AssertionError("Expected PendingChangeError")
assert inventory.read_text(encoding="utf-8") == original