feat: add imported PVE contract and core endpoints

This commit is contained in:
Sergey Antropoff
2026-07-13 00:27:07 +03:00
parent adf9fb9108
commit d141994540
16 changed files with 71594 additions and 6 deletions
+17
View File
@@ -68,6 +68,23 @@ def test_unknown_schema_fields_are_retained() -> None:
assert snapshot.paths[0].methods[0].returns.extra["futureKeyword"] == {"x": 1}
def test_nullable_source_collections_normalize_as_empty() -> None:
raw = (
b'[{"path":"/nullable","info":{"GET":{"parameters":{"properties":null},'
b'"returns":{"type":"string","enum":null}}}}]'
)
snapshot, _ = normalize_snapshot(
ApiViewerParser().parse(raw),
raw=raw,
source_version="test",
retrieved_at=RETRIEVED_AT,
)
method = snapshot.paths[0].methods[0]
assert method.parameters == ()
assert method.returns.enum == ()
@given(st.dictionaries(st.text(min_size=1), st.integers(), max_size=10))
def test_canonical_json_is_independent_of_mapping_order(values: dict[str, int]) -> None:
reversed_values = dict(reversed(tuple(values.items())))
+120
View File
@@ -0,0 +1,120 @@
"""First vertical read/login handler tests."""
from datetime import UTC, datetime
from pathlib import Path
from httpx import ASGITransport, AsyncClient
from app.config import Settings
from app.contracts.model import Method, Parameter, PathContract, Schema, Snapshot
from app.main import create_app
from app.security.auth import hash_secret
class FakePool:
async def fetchrow(self, sql: str, *args: object) -> dict[str, object] | None:
if "principals" in sql and args[0] == "root@pam":
return {
"name": "root@pam",
"password_hash": hash_secret("secret", salt=b"pve-simulator-v1"),
}
if "FROM nodes" in sql and args[0] == "pve1":
return {"name": "pve1", "status": "online"}
return None
async def fetch(self, sql: str, *args: object) -> list[dict[str, object]]:
del args
if "FROM nodes" in sql:
return [{"node": "pve1", "status": "online"}]
return [
{
"type": "qemu",
"external_id": "100",
"state": '{"status":"stopped"}',
"node": "pve1",
}
]
class FakeDatabase:
pool = FakePool()
async def connect(self) -> None:
pass
async def close(self) -> None:
pass
async def is_ready(self) -> bool:
return True
def method(verb: str, name: str, parameters: tuple[Parameter, ...] = ()) -> Method:
return Method(
verb=verb,
name=name,
parameters=parameters,
returns=Schema(type="object"),
checksum=(name[0] * 64),
)
def write_snapshot(path: Path) -> None:
string = Schema(type="string")
paths = (
PathContract(path="/version", methods=(method("GET", "version"),)),
PathContract(
path="/access/ticket",
methods=(
method(
"POST",
"ticket",
(
Parameter(name="username", definition=string),
Parameter(name="password", definition=string),
),
),
),
),
PathContract(path="/nodes", methods=(method("GET", "nodes"),)),
PathContract(
path="/nodes/{node}/status",
methods=(method("GET", "status", (Parameter(name="node", definition=string),)),),
),
PathContract(path="/cluster/resources", methods=(method("GET", "resources"),)),
)
snapshot = Snapshot(
source_version="test",
retrieved_at=datetime(2026, 1, 1, tzinfo=UTC),
raw_sha256="0" * 64,
paths=paths,
path_count=len(paths),
method_count=5,
)
path.write_bytes(snapshot.canonical_bytes())
async def test_core_login_and_read_endpoints(tmp_path: Path) -> None:
snapshot_path = tmp_path / "snapshot.json"
write_snapshot(snapshot_path)
database = FakeDatabase()
app = create_app(Settings(contract_snapshot=snapshot_path), lambda _settings: database)
async with app.router.lifespan_context(app):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
login = await client.post(
"/api2/json/access/ticket",
content="username=root%40pam&password=secret",
headers={"content-type": "application/x-www-form-urlencoded"},
)
version = await client.get("/api2/json/version")
nodes = await client.get("/api2/json/nodes")
status = await client.get("/api2/json/nodes/pve1/status")
resources = await client.get("/api2/json/cluster/resources")
assert login.status_code == 200
assert login.json()["data"]["username"] == "root@pam"
assert "ticket" in login.json()["data"]
assert version.json()["data"]["release"] == "9.2"
assert nodes.json()["data"][0]["node"] == "pve1"
assert status.json()["data"]["status"] == "online"
assert resources.json()["data"][0]["type"] == "qemu"
+5 -1
View File
@@ -44,7 +44,11 @@ async def request_app(
snapshot_path.write_bytes(contract_snapshot(get_method()).canonical_bytes())
settings = Settings(contract_snapshot=snapshot_path, contract_fallback=fallback)
database = FakeDatabase(True)
app = create_app(settings, lambda _settings: database, handlers)
app = create_app(
settings,
lambda _settings: database,
handlers if handlers is not None else HandlerRegistry(),
)
async with app.router.lifespan_context(app):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
json_response = await client.get("/api2/json/version")