feat: add basic QEMU and task vertical slice

This commit is contained in:
Sergey Antropoff
2026-07-13 00:41:13 +03:00
parent d141994540
commit 4b923e63f2
15 changed files with 452 additions and 14 deletions
+81 -3
View File
@@ -1,14 +1,17 @@
"""First vertical read/login handler tests."""
import uuid
from datetime import UTC, datetime
from pathlib import Path
import pytest
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
from app.tasks.repository import Task
class FakePool:
@@ -20,12 +23,21 @@ class FakePool:
}
if "FROM nodes" in sql and args[0] == "pve1":
return {"name": "pve1", "status": "online"}
if "FROM resources r" in sql and args == ("pve1", "100"):
if "SELECT r.id" in sql:
return {
"id": uuid.UUID("00000000-0000-0000-0000-000000000100"),
"state": '{"name":"demo","status":"stopped"}',
}
return {"state": '{"name":"demo","status":"stopped"}'}
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"}]
if "r.kind='qemu'" in sql:
return [{"vmid": 100, "state": '{"name":"demo","status":"stopped"}'}]
return [
{
"type": "qemu",
@@ -35,6 +47,9 @@ class FakePool:
}
]
async def fetchval(self, sql: str) -> int:
return 100 if "pg_backend_pid" in sql else 1_700_000_000
class FakeDatabase:
pool = FakePool()
@@ -82,6 +97,36 @@ def write_snapshot(path: Path) -> None:
methods=(method("GET", "status", (Parameter(name="node", definition=string),)),),
),
PathContract(path="/cluster/resources", methods=(method("GET", "resources"),)),
PathContract(
path="/nodes/{node}/qemu",
methods=(method("GET", "qemu", (Parameter(name="node", definition=string),)),),
),
PathContract(
path="/nodes/{node}/qemu/{vmid}/config",
methods=(
method(
"GET",
"config",
(
Parameter(name="node", definition=string),
Parameter(name="vmid", definition=Schema(type="integer")),
),
),
),
),
PathContract(
path="/nodes/{node}/qemu/{vmid}/status/start",
methods=(
method(
"POST",
"start",
(
Parameter(name="node", definition=string),
Parameter(name="vmid", definition=Schema(type="integer")),
),
),
),
),
)
snapshot = Snapshot(
source_version="test",
@@ -89,16 +134,39 @@ def write_snapshot(path: Path) -> None:
raw_sha256="0" * 64,
paths=paths,
path_count=len(paths),
method_count=5,
method_count=sum(len(item.methods) for item in paths),
)
path.write_bytes(snapshot.canonical_bytes())
async def test_core_login_and_read_endpoints(tmp_path: Path) -> None:
async def test_core_login_and_read_endpoints(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
class FakeTaskRepository:
def __init__(self, pool: object) -> None:
del pool
async def create(self, **kwargs: object) -> Task:
return Task(
uuid.uuid4(),
str(kwargs["upid"]),
str(kwargs["task_type"]),
"queued",
{},
0,
False,
0,
)
monkeypatch.setattr("app.handlers.qemu.TaskRepository", FakeTaskRepository)
snapshot_path = tmp_path / "snapshot.json"
write_snapshot(snapshot_path)
database = FakeDatabase()
app = create_app(Settings(contract_snapshot=snapshot_path), lambda _settings: database)
app = create_app(
Settings(contract_snapshot=snapshot_path),
lambda _settings: database,
worker_factories=(),
)
async with app.router.lifespan_context(app):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
login = await client.post(
@@ -106,10 +174,17 @@ async def test_core_login_and_read_endpoints(tmp_path: Path) -> None:
content="username=root%40pam&password=secret",
headers={"content-type": "application/x-www-form-urlencoded"},
)
csrf = login.json()["data"]["CSRFPreventionToken"]
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")
qemu = await client.get("/api2/json/nodes/pve1/qemu")
config = await client.get("/api2/json/nodes/pve1/qemu/100/config")
start = await client.post(
"/api2/json/nodes/pve1/qemu/100/status/start",
headers={"CSRFPreventionToken": csrf},
)
assert login.status_code == 200
assert login.json()["data"]["username"] == "root@pam"
@@ -118,3 +193,6 @@ async def test_core_login_and_read_endpoints(tmp_path: Path) -> None:
assert nodes.json()["data"][0]["node"] == "pve1"
assert status.json()["data"]["status"] == "online"
assert resources.json()["data"][0]["type"] == "qemu"
assert qemu.json()["data"][0]["vmid"] == 100
assert config.json()["data"]["name"] == "demo"
assert start.json()["data"].startswith("UPID:pve1:")