f8d3cbdd59
Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API contracts, docs, client examples, and the unit/integration/compatibility test suite for local client and tooling labs without a real vCenter.
70 lines
2.9 KiB
Python
70 lines
2.9 KiB
Python
"""Version matrix and hot-swap gating."""
|
|
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from app.config import Settings
|
|
from app.main import create_app
|
|
from app.vsphere.contracts.matrix import (
|
|
VERSIONS,
|
|
available_for_request,
|
|
catalog_entries_for_major,
|
|
methods_for_major,
|
|
)
|
|
from tests.unit.test_health import FakeDatabase
|
|
|
|
|
|
def test_major_6_smaller_than_major_9() -> None:
|
|
assert len(methods_for_major(6)) < len(methods_for_major(9))
|
|
assert len(methods_for_major(7)) <= len(methods_for_major(8))
|
|
assert len(methods_for_major(8)) <= len(methods_for_major(9))
|
|
|
|
|
|
def test_runtime_serves_all_registered_regardless_of_major() -> None:
|
|
"""Catalog floors remain for browse; runtime never 501s known paths."""
|
|
|
|
assert available_for_request("POST", "/api/cis/tagging/category", 6) is True
|
|
assert available_for_request("GET", "/api/content/library", 6) is True
|
|
assert available_for_request("POST", "/api/appliance/networking/dns/hostname", 6) is True
|
|
|
|
|
|
def test_catalog_floor_still_shrinks_browse_list() -> None:
|
|
from app.vsphere.contracts.matrix import methods_for_major
|
|
|
|
assert ("POST", "/api/cis/tagging/category") not in methods_for_major(6)
|
|
assert ("POST", "/api/cis/tagging/category") in methods_for_major(7)
|
|
assert ("GET", "/api/content/library") not in methods_for_major(7)
|
|
assert ("GET", "/api/content/library") in methods_for_major(8)
|
|
|
|
|
|
def test_literal_path_beats_param_template() -> None:
|
|
"""`/api/content/library/item` must not resolve as `/{library_id}`."""
|
|
from app.vsphere.contracts.matrix import resolve_template
|
|
|
|
assert resolve_template("GET", "/api/content/library/item") == "/api/content/library/item"
|
|
assert available_for_request("GET", "/api/content/library/item", 8) is True
|
|
|
|
|
|
def test_catalog_entries_match_version() -> None:
|
|
for major in VERSIONS:
|
|
entries = catalog_entries_for_major(major)
|
|
assert all(e["status"] in {"implemented", "stub"} for e in entries)
|
|
assert len(entries) == len(methods_for_major(major))
|
|
|
|
|
|
async def test_hot_swap_does_not_501_registered_paths() -> None:
|
|
app = create_app(
|
|
settings=Settings(enable_pve_stub=False),
|
|
database_factory=lambda _settings: FakeDatabase(True),
|
|
worker_factories=(),
|
|
)
|
|
async with app.router.lifespan_context(app):
|
|
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
|
|
apply = await client.post("/ui/api/contract/apply", params={"major": 6})
|
|
assert apply.status_code == 200
|
|
assert apply.json()["runtime_version"] == "7.0.0"
|
|
# Session required for library — but must not be version-gated 501.
|
|
library = await client.get("/api/content/library")
|
|
assert library.status_code != 501
|
|
restore = await client.post("/ui/api/contract/apply", params={"major": 9})
|
|
assert restore.status_code == 200
|