"""Persistence tests for remaining gap handlers (nodes_extra / cluster_extra).""" from __future__ import annotations import json from typing import Any, cast import pytest from fastapi import FastAPI, Request from app.api.registry import HandlerRegistry from app.db.pool import AsyncpgDatabase from app.handlers.cluster_extra import register_cluster_extra_handlers from app.handlers.nodes_extra import register_nodes_extra_handlers class GapRemainingPool: def __init__(self) -> None: self.cluster_metadata: dict[str, Any] = {} self.node_metadata: dict[str, Any] = {} async def fetchrow(self, query: str, *arguments: object) -> dict[str, Any] | None: if "SELECT metadata FROM clusters" in query: return {"metadata": json.dumps(self.cluster_metadata)} if "SELECT metadata FROM nodes" in query: name = str(arguments[0]) return {"metadata": json.dumps(self.node_metadata.get(name, {}))} raise AssertionError(query) async def fetchval(self, query: str, *arguments: object) -> Any: del arguments if "EXISTS(SELECT 1 FROM nodes" in query: return True if "SELECT name FROM nodes ORDER BY name LIMIT 1" in query: return "pve01" raise AssertionError(query) async def execute(self, query: str, *arguments: object) -> str: if "UPDATE clusters SET metadata" in query: self.cluster_metadata = json.loads(str(arguments[1])) return "UPDATE 1" if "UPDATE nodes SET metadata" in query: self.node_metadata[str(arguments[0])] = json.loads(str(arguments[1])) return "UPDATE 1" raise AssertionError(query) class FakeDatabase: def __init__(self, pool: GapRemainingPool) -> None: self.pool = pool def _request(pool: GapRemainingPool, *, method: str = "GET") -> Request: app = FastAPI() app.state.database = cast(AsyncpgDatabase, FakeDatabase(pool)) result = Request( { "type": "http", "app": app, "method": method, "path": "/", "headers": [], "query_string": b"", "server": ("test", 80), "client": ("test", 123), "scheme": "http", } ) result.state.principal = "root@pam" return result @pytest.mark.asyncio async def test_disks_directory_create_persists(monkeypatch: pytest.MonkeyPatch) -> None: registry = HandlerRegistry() register_nodes_extra_handlers(registry) pool = GapRemainingPool() class FakeTaskRepository: async def create(self, **kwargs: Any) -> Any: return type("Task", (), {"upid": kwargs["upid"]})() monkeypatch.setattr("app.handlers.nodes.TaskRepository", lambda _pool: FakeTaskRepository()) create = registry.get("/nodes/{node}/disks/directory", "POST") assert create is not None created = await create( _request(pool, method="POST"), { "values": {"node": "pve01", "name": "tank", "device": "/dev/sdb"}, "provided": frozenset(), }, ) assert isinstance(created, str) and created.startswith("UPID:") ops = pool.node_metadata["pve01"]["ops"] assert any(item["name"] == "tank" for item in ops["disks"]["directory"]) @pytest.mark.asyncio async def test_certificates_custom_create_does_not_echo_key() -> None: registry = HandlerRegistry() register_nodes_extra_handlers(registry) pool = GapRemainingPool() create = registry.get("/nodes/{node}/certificates/custom", "POST") info = registry.get("/nodes/{node}/certificates/info", "GET") assert create is not None and info is not None await create( _request(pool, method="POST"), { "values": { "node": "pve01", "certificates": "-----BEGIN CERTIFICATE-----\nSIM\n-----END CERTIFICATE-----", "key": "-----BEGIN PRIVATE KEY-----\nSECRET\n-----END PRIVATE KEY-----", }, "provided": frozenset(), }, ) stored = pool.node_metadata["pve01"]["ops"]["certificates"]["custom"] assert stored["key"].startswith("-----BEGIN PRIVATE KEY-----") listing = await info( _request(pool), {"values": {"node": "pve01"}, "provided": frozenset()}, ) blob = json.dumps(listing) assert "PRIVATE KEY" not in blob assert "SECRET" not in blob @pytest.mark.asyncio async def test_realm_sync_job_create_persists() -> None: registry = HandlerRegistry() register_cluster_extra_handlers(registry) pool = GapRemainingPool() create = registry.get("/cluster/jobs/realm-sync/{id}", "POST") listing = registry.get("/cluster/jobs/realm-sync", "GET") assert create is not None and listing is not None created = await create( _request(pool, method="POST"), { "values": {"id": "pam-nightly", "realm": "pam", "schedule": "0 2 * * *"}, "provided": frozenset(), }, ) assert created["id"] == "pam-nightly" assert pool.cluster_metadata["jobs"]["realm_sync"]["pam-nightly"]["realm"] == "pam" items = await listing(_request(pool), {"values": {}, "provided": frozenset()}) assert items[0]["id"] == "pam-nightly" @pytest.mark.asyncio async def test_metrics_server_create_persists() -> None: registry = HandlerRegistry() register_cluster_extra_handlers(registry) pool = GapRemainingPool() create = registry.get("/cluster/metrics/server/{id}", "POST") listing = registry.get("/cluster/metrics/server", "GET") assert create is not None and listing is not None created = await create( _request(pool, method="POST"), { "values": { "id": "influx1", "type": "influxdb", "server": "10.0.0.20", "port": 8089, }, "provided": frozenset(), }, ) assert created["id"] == "influx1" assert pool.cluster_metadata["metrics"]["servers"]["influx1"]["server"] == "10.0.0.20" items = await listing(_request(pool), {"values": {}, "provided": frozenset()}) assert items[0]["id"] == "influx1"