Add a stateful Proxmox API console and broad handler coverage beyond the
initial QEMU slice, backed by imported contracts for majors 6–9. - Implement durable handlers for access/auth, cluster, LXC, storage, HA, firewall, Ceph, SDN, ACME, notifications, pools, mapping, and node ops - Serve an interactive Web UI with catalog browsing, demo seed controls, and OpenAPI/help surfaces - Bundle PVE 6.4-15, 7.4-16, and 8.4.5 contract revisions alongside 9.2.3 - Support in-memory runtime contract Apply (POST /ui/api/contract/apply) so /version and /api2 routes follow the selected major until restart - Expand seed profiles (including demo-cluster), migrations 007–008, TLS gateway config, Compose/Makefile tooling, and compatibility evidence - Tighten .gitignore for macOS, hidden directories (.*/), and local secrets
This commit is contained in:
+76
-2
@@ -2,7 +2,13 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from app.simulation.seed import build_profile, large_profile, small_profile, stable_id
|
||||
from app.simulation.seed import (
|
||||
build_profile,
|
||||
clear_simulation_state,
|
||||
large_profile,
|
||||
small_profile,
|
||||
stable_id,
|
||||
)
|
||||
|
||||
|
||||
def test_small_profile_matches_required_logical_shape() -> None:
|
||||
@@ -12,7 +18,7 @@ def test_small_profile_matches_required_logical_shape() -> None:
|
||||
assert first == second
|
||||
state = first.logical_state()
|
||||
assert state == second.logical_state()
|
||||
assert state["nodes"] == [{"name": "pve1", "status": "online"}]
|
||||
assert state["nodes"] == [{"name": "pve01", "status": "online"}]
|
||||
resources = state["resources"]
|
||||
assert isinstance(resources, list)
|
||||
assert [resource["kind"] for resource in resources].count("qemu") == 2
|
||||
@@ -48,6 +54,74 @@ def test_profile_validation() -> None:
|
||||
large_profile(node_count=0, resource_count=1)
|
||||
|
||||
|
||||
def test_demo_cluster_profile_shape() -> None:
|
||||
profile = build_profile("demo-cluster")
|
||||
assert profile.name == "demo-cluster"
|
||||
assert len(profile.nodes) == 20
|
||||
assert sum(resource.kind == "qemu" for resource in profile.resources) == 850
|
||||
assert sum(resource.kind == "lxc" for resource in profile.resources) == 150
|
||||
assert sum(resource.kind == "ceph-osd" for resource in profile.resources) == 300
|
||||
assert sum(resource.kind == "storage" for resource in profile.resources) >= 62
|
||||
assert len(profile.tasks) == 250
|
||||
external_ids = {
|
||||
resource.external_id for resource in profile.resources if resource.kind in {"qemu", "lxc"}
|
||||
}
|
||||
assert len(external_ids) == 1000
|
||||
|
||||
|
||||
def test_demo_cluster_spreads_guests_evenly_across_nodes() -> None:
|
||||
profile = build_profile("demo-cluster")
|
||||
names = {node.id: node.name for node in profile.nodes}
|
||||
|
||||
def counts(kind: str) -> list[int]:
|
||||
counter: dict[str, int] = {name: 0 for name in names.values()}
|
||||
for resource in profile.resources:
|
||||
if resource.kind == kind:
|
||||
counter[names[resource.node_id]] += 1
|
||||
return list(counter.values())
|
||||
|
||||
for kind, expected_total in (("qemu", 850), ("lxc", 150), ("ceph-osd", 300)):
|
||||
values = counts(kind)
|
||||
assert sum(values) == expected_total
|
||||
assert max(values) - min(values) <= 1
|
||||
|
||||
guest_counts = counts("qemu")
|
||||
guest_counts = [a + b for a, b in zip(guest_counts, counts("lxc"), strict=True)]
|
||||
assert max(guest_counts) - min(guest_counts) <= 2
|
||||
|
||||
|
||||
def test_minimal_profile() -> None:
|
||||
profile = build_profile("minimal")
|
||||
assert len(profile.nodes) == 1
|
||||
assert not any(resource.kind in {"qemu", "lxc"} for resource in profile.resources)
|
||||
|
||||
|
||||
def test_stable_ids_are_namespaced_and_repeatable() -> None:
|
||||
assert stable_id("qemu:100") == stable_id("qemu:100")
|
||||
assert stable_id("qemu:100") != stable_id("qemu:101")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_simulation_state_wipes_api_created_identity() -> None:
|
||||
executed: list[str] = []
|
||||
|
||||
class FakeConnection:
|
||||
async def execute(self, sql: str, *args: object) -> str:
|
||||
del args
|
||||
executed.append(" ".join(sql.split()))
|
||||
return "DELETE 0"
|
||||
|
||||
await clear_simulation_state(FakeConnection())
|
||||
joined = "\n".join(executed)
|
||||
for table in (
|
||||
"resources",
|
||||
"nodes",
|
||||
"principals",
|
||||
"identity_groups",
|
||||
"roles",
|
||||
"storage_contents",
|
||||
"api_tokens",
|
||||
):
|
||||
assert f"DELETE FROM {table}" in joined # noqa: S608 - asserting SQL text
|
||||
assert "DELETE FROM realms WHERE name NOT IN" in joined
|
||||
assert any(sql.startswith("UPDATE clusters") for sql in executed)
|
||||
|
||||
Reference in New Issue
Block a user