Files
proxmox-api-simulator/tests/unit/test_qemu_handlers.py
T
2026-07-13 01:54:04 +03:00

188 lines
5.9 KiB
Python

"""Persistent QEMU CRUD semantic handler tests."""
import uuid
from typing import Any, cast
import pytest
from fastapi import FastAPI, Request
from app.api.errors import ApiError
from app.api.registry import HandlerRegistry
from app.db.pool import AsyncpgDatabase
from app.db.primitives import ConflictError
from app.handlers.qemu import register_qemu_handlers
from app.tasks.repository import Task
class QemuPool:
def __init__(self) -> None:
self.resource_exists = False
self.missing = False
self.running = False
self.commands: list[str] = []
self.resource_id = uuid.uuid4()
async def fetchval(self, sql: str, *args: object) -> bool | int:
del args
if "pg_backend_pid" in sql:
return 123
if "extract(epoch" in sql:
return 1_700_000_000
if "FROM nodes" in sql:
return True
if "FROM resources" in sql:
return self.resource_exists
raise AssertionError(sql)
async def fetchrow(self, sql: str, *args: object) -> dict[str, object] | None:
del args
if self.missing:
return None
if "SELECT r.id, r.version" in sql:
return {
"id": self.resource_id,
"version": 1,
"state": '{"name":"old","status":"stopped"}',
"config": '{"name":"old"}',
}
if "SELECT r.id, r.state" in sql:
status = "running" if self.running else "stopped"
return {"id": self.resource_id, "state": f'{{"status":"{status}"}}'}
raise AssertionError(sql)
async def execute(self, sql: str, *args: object) -> str:
del args
self.commands.append(sql)
return "UPDATE 1"
class FakeDatabase:
def __init__(self, pool: QemuPool) -> None:
self.pool = pool
def request(pool: QemuPool) -> Request:
app = FastAPI()
app.state.database = cast(AsyncpgDatabase, FakeDatabase(pool))
result = Request(
{
"type": "http",
"app": app,
"method": "POST",
"path": "/",
"headers": [],
"query_string": b"",
"server": ("test", 80),
"client": ("test", 123),
"scheme": "http",
}
)
result.state.principal = "root@pam"
return result
def inputs(**values: object) -> dict[str, Any]:
return {"values": values, "provided": tuple(values)}
async def test_qemu_create_sync_async_update_and_delete(
monkeypatch: pytest.MonkeyPatch,
) -> None:
created_payloads: list[dict[str, Any]] = []
class FakeTaskRepository:
def __init__(self, pool: object) -> None:
del pool
async def create(self, **kwargs: Any) -> Task:
created_payloads.append(kwargs)
return Task(
uuid.uuid4(),
str(kwargs["upid"]),
str(kwargs["task_type"]),
"queued",
dict(kwargs["payload"]),
0,
False,
0,
)
monkeypatch.setattr("app.handlers.qemu.TaskRepository", FakeTaskRepository)
registry = HandlerRegistry()
register_qemu_handlers(registry)
pool = QemuPool()
http_request = request(pool)
create = registry.get("/nodes/{node}/qemu", "POST")
update_sync = registry.get("/nodes/{node}/qemu/{vmid}/config", "PUT")
update_async = registry.get("/nodes/{node}/qemu/{vmid}/config", "POST")
delete = registry.get("/nodes/{node}/qemu/{vmid}", "DELETE")
assert create and update_sync and update_async and delete
create_upid = await create(
http_request,
inputs(node="pve1", vmid=150, name="new", cores=2),
)
assert create_upid.startswith("UPID:pve1:")
assert created_payloads[-1]["task_type"] == "qemu-create"
assert (
await update_sync(
http_request,
inputs(node="pve1", vmid=150, name="sync", delete="unused"),
)
is None
)
assert len(pool.commands) == 2
update_upid = await update_async(
http_request,
inputs(node="pve1", vmid=150, memory="2048"),
)
assert update_upid.startswith("UPID:pve1:")
assert created_payloads[-1]["task_type"] == "qemu-update"
delete_upid = await delete(http_request, inputs(node="pve1", vmid=150))
assert delete_upid.startswith("UPID:pve1:")
assert created_payloads[-1]["task_type"] == "qemu-delete"
async def test_qemu_crud_conflicts_and_missing_resources(
monkeypatch: pytest.MonkeyPatch,
) -> None:
class ConflictingRepository:
def __init__(self, pool: object) -> None:
del pool
async def create(self, **_kwargs: object) -> Task:
raise ConflictError("resource is locked")
monkeypatch.setattr("app.handlers.qemu.TaskRepository", ConflictingRepository)
registry = HandlerRegistry()
register_qemu_handlers(registry)
pool = QemuPool()
http_request = request(pool)
create = registry.get("/nodes/{node}/qemu", "POST")
update = registry.get("/nodes/{node}/qemu/{vmid}/config", "PUT")
delete = registry.get("/nodes/{node}/qemu/{vmid}", "DELETE")
assert create and update and delete
with pytest.raises(ApiError) as locked:
await create(http_request, inputs(node="pve1", vmid=150))
assert locked.value.status_code == 409
pool.resource_exists = True
with pytest.raises(ApiError) as duplicate:
await create(http_request, inputs(node="pve1", vmid=150))
assert duplicate.value.status_code == 409
pool.missing = True
with pytest.raises(ApiError) as missing:
await update(http_request, inputs(node="pve1", vmid=150, name="missing"))
assert missing.value.status_code == 404
pool.missing = False
pool.running = True
with pytest.raises(ApiError) as running:
await delete(http_request, inputs(node="pve1", vmid=150))
assert running.value.status_code == 409