Initial commit: stateful OpenStack API laboratory simulator.
Ship Keystone auth, multi-service handlers (Yoga→Dalmatian), Compose/Helm packaging, API contract packs, and pytest/Pulumi coverage labs.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""Persistent LXC semantic handler tests."""
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from fastapi import Request
|
||||
|
||||
from app.api.errors import ApiError
|
||||
from app.api.registry import HandlerRegistry
|
||||
from app.handlers.lxc import register_lxc_handlers
|
||||
|
||||
|
||||
class LxcPool:
|
||||
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
|
||||
if "FROM snapshots" in sql:
|
||||
return False
|
||||
raise AssertionError(sql)
|
||||
|
||||
async def fetch(self, sql: str, *args: object) -> list[dict[str, object]]:
|
||||
del args
|
||||
if "FROM resources" in sql and "kind='lxc'" in sql:
|
||||
return [{"vmid": 200, "state": '{"status":"stopped","name":"service"}'}]
|
||||
assert "FROM snapshots" in sql
|
||||
return [
|
||||
{
|
||||
"name": "baseline",
|
||||
"parent_name": None,
|
||||
"description": "stable",
|
||||
"created_at": datetime(2026, 1, 1, tzinfo=UTC),
|
||||
}
|
||||
]
|
||||
|
||||
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, c.config" in sql:
|
||||
return {"id": self.resource_id, "state": '{"status":"stopped"}', "config": "{}"}
|
||||
if "SELECT r.id, r.state FROM resources" in sql:
|
||||
status = "running" if self.running else "stopped"
|
||||
return {"id": self.resource_id, "state": f'{{"status":"{status}"}}'}
|
||||
if "SELECT s.* FROM snapshots" in sql:
|
||||
return {
|
||||
"id": uuid.uuid4(),
|
||||
"name": "baseline",
|
||||
"parent_name": None,
|
||||
"description": "stable",
|
||||
"created_at": datetime(2026, 1, 1, tzinfo=UTC),
|
||||
"state": "{}",
|
||||
}
|
||||
raise AssertionError(sql)
|
||||
|
||||
async def execute(self, sql: str, *args: object) -> str:
|
||||
del sql, args
|
||||
self.commands.append("execute")
|
||||
return "UPDATE 1"
|
||||
|
||||
|
||||
class FakeDatabase:
|
||||
def __init__(self, pool: LxcPool) -> None:
|
||||
self.pool = pool
|
||||
|
||||
|
||||
class FakeTaskRepository:
|
||||
def __init__(self, pool: LxcPool) -> None:
|
||||
self.pool = pool
|
||||
self.created: list[dict[str, Any]] = []
|
||||
|
||||
async def create(self, **kwargs: Any) -> Any:
|
||||
self.created.append(kwargs)
|
||||
return type(
|
||||
"Task", (), {"upid": "UPID:pve1:00000001:00000001:1700000000:pctcreate:201:root@pam:"}
|
||||
)()
|
||||
|
||||
|
||||
def _request(pool: LxcPool) -> Request:
|
||||
app = type("App", (), {"state": type("State", (), {"database": FakeDatabase(pool)})()})()
|
||||
request = Request({"type": "http", "headers": [], "method": "POST", "path": "/"})
|
||||
request.scope["app"] = app
|
||||
request.state.principal = "root@pam"
|
||||
return request
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def registry() -> HandlerRegistry:
|
||||
handler_registry = HandlerRegistry()
|
||||
register_lxc_handlers(handler_registry)
|
||||
return handler_registry
|
||||
|
||||
|
||||
async def test_lxc_list_returns_seeded_containers(registry: HandlerRegistry) -> None:
|
||||
pool = LxcPool()
|
||||
handler = registry.get("/nodes/{node}/lxc", "GET")
|
||||
assert handler is not None
|
||||
result = await handler(_request(pool), {"values": {"node": "pve1"}})
|
||||
assert result == [{"vmid": 200, "status": "stopped", "name": "service"}]
|
||||
|
||||
|
||||
async def test_lxc_create_rejects_duplicate_vmid(registry: HandlerRegistry) -> None:
|
||||
pool = LxcPool()
|
||||
pool.resource_exists = True
|
||||
handler = registry.get("/nodes/{node}/lxc", "POST")
|
||||
assert handler is not None
|
||||
with pytest.raises(ApiError, match="VMID already exists"):
|
||||
await handler(
|
||||
_request(pool),
|
||||
{"values": {"node": "pve1", "vmid": 201, "hostname": "app"}},
|
||||
)
|
||||
|
||||
|
||||
async def test_lxc_delete_requires_stopped_container(registry: HandlerRegistry) -> None:
|
||||
pool = LxcPool()
|
||||
pool.running = True
|
||||
handler = registry.get("/nodes/{node}/lxc/{vmid}", "DELETE")
|
||||
assert handler is not None
|
||||
with pytest.raises(ApiError, match="cannot delete a running container"):
|
||||
await handler(_request(pool), {"values": {"node": "pve1", "vmid": "200"}})
|
||||
|
||||
|
||||
async def test_lxc_start_creates_task(
|
||||
monkeypatch: pytest.MonkeyPatch, registry: HandlerRegistry
|
||||
) -> None:
|
||||
pool = LxcPool()
|
||||
repository = FakeTaskRepository(pool)
|
||||
monkeypatch.setattr("app.handlers.lxc.TaskRepository", lambda _pool: repository)
|
||||
handler = registry.get("/nodes/{node}/lxc/{vmid}/status/start", "POST")
|
||||
assert handler is not None
|
||||
upid = await handler(_request(pool), {"values": {"node": "pve1", "vmid": "200"}})
|
||||
assert upid.startswith("UPID:")
|
||||
assert repository.created[0]["task_type"] == "lxc-start"
|
||||
Reference in New Issue
Block a user