"""Tests for the Proxmox compute client.""" from __future__ import annotations import json import pytest import respx from httpx import Response from conduit_client import AsyncConduitClient, ConduitClient from conduit_client.models.compute import ( CloneVmRequest, ComputeTicketRequest, CreateContainerRequest, CreateSnapshotRequest, CreateVmRequest, MigrateVmRequest, NodeTasksQuery, StorageQuery, UploadStorageRequest, ) @respx.mock def test_auth_ticket(api_key: str) -> None: route = respx.post("https://api.example.com/compute/auth/ticket").mock( return_value=Response( 200, json={ "ticket": "tck", "CSRFPreventionToken": "csrf", "username": "root", }, ) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: result = client.compute.auth_ticket( ComputeTicketRequest(username="root", password="pw") ) assert result.ticket == "tck" assert result.csrfpreventiontoken == "csrf" assert json.loads(route.calls[0].request.content) == { "username": "root", "password": "pw", } @respx.mock def test_list_resources_and_nodes(api_key: str) -> None: respx.get("https://api.example.com/compute/resources").mock( return_value=Response(200, json=[{"id": "qemu/100"}]) ) respx.get("https://api.example.com/compute/resources").mock( return_value=Response(200, json=[{"id": "qemu/100"}]) ) respx.get("https://api.example.com/compute/cluster/status").mock( return_value=Response(200, json={"quorate": True}) ) respx.get("https://api.example.com/compute/nodes").mock( return_value=Response(200, json=[{"node": "pve"}]) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: assert client.compute.list_resources() == [{"id": "qemu/100"}] assert client.compute.list_resources(type_="vm") == [{"id": "qemu/100"}] assert client.compute.cluster_status() == {"quorate": True} assert client.compute.list_nodes() == [{"node": "pve"}] @respx.mock def test_node_status_and_tasks(api_key: str) -> None: respx.get("https://api.example.com/compute/nodes/pve/status").mock( return_value=Response(200, json={"cpu": 0.1}) ) route = respx.get("https://api.example.com/compute/nodes/pve/tasks").mock( return_value=Response(200, json=[]) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: assert client.compute.get_node_status("pve") == {"cpu": 0.1} assert client.compute.list_node_tasks("pve", NodeTasksQuery(limit=10)) == [] assert route.calls[0].request.url.params["limit"] == "10" @respx.mock def test_vm_lifecycle(api_key: str) -> None: respx.get("https://api.example.com/compute/vms").mock( return_value=Response(200, json=[{"vmid": 100}]) ) create_route = respx.post("https://api.example.com/compute/vms").mock( return_value=Response(200, json={"task": "UPID:abc"}) ) respx.get("https://api.example.com/compute/vms/100").mock( return_value=Response(200, json={"vmid": 100, "name": "vm1"}) ) patch_route = respx.patch("https://api.example.com/compute/vms/100").mock( return_value=Response(200, json={"task": "UPID:patch"}) ) respx.delete("https://api.example.com/compute/vms/100").mock( return_value=Response(204) ) respx.post("https://api.example.com/compute/vms/100/start").mock( return_value=Response(200, json={"task": "UPID:start"}) ) respx.post("https://api.example.com/compute/vms/100/stop").mock( return_value=Response(200, json={"task": "UPID:stop"}) ) respx.post("https://api.example.com/compute/vms/100/shutdown").mock( return_value=Response(200, json={"task": "UPID:shutdown"}) ) respx.post("https://api.example.com/compute/vms/100/reboot").mock( return_value=Response(200, json={"task": "UPID:reboot"}) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: assert client.compute.list_vms() == [{"vmid": 100}] result = client.compute.create_vm( CreateVmRequest(node="pve", vmid=100, cores=2, memory=1024) ) assert result.task == "UPID:abc" assert client.compute.get_vm(100)["name"] == "vm1" assert client.compute.patch_vm(100, {"memory": 2048}).task == "UPID:patch" client.compute.delete_vm(100, confirm=100) assert client.compute.start_vm(100).task == "UPID:start" assert client.compute.stop_vm(100).task == "UPID:stop" assert client.compute.shutdown_vm(100).task == "UPID:shutdown" assert client.compute.reboot_vm(100).task == "UPID:reboot" body = json.loads(create_route.calls[0].request.content) assert body["node"] == "pve" assert body["cores"] == 2 assert json.loads(patch_route.calls[0].request.content) == {"memory": 2048} @respx.mock def test_vm_clone_migrate_snapshots_agent(api_key: str) -> None: respx.post("https://api.example.com/compute/vms/100/clone").mock( return_value=Response(200, json={"task": "UPID:clone"}) ) respx.post("https://api.example.com/compute/vms/100/migrate").mock( return_value=Response(200, json={"task": "UPID:migrate"}) ) respx.get("https://api.example.com/compute/vms/100/snapshots").mock( return_value=Response(200, json=[{"name": "snap1"}]) ) respx.post("https://api.example.com/compute/vms/100/snapshots").mock( return_value=Response(200, json={"task": "UPID:snap"}) ) respx.post("https://api.example.com/compute/vms/100/agent/exec").mock( return_value=Response(200, json={"out-data": "ok"}) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: assert client.compute.clone_vm(100, CloneVmRequest(newid=200)).task == "UPID:clone" assert client.compute.migrate_vm( 100, MigrateVmRequest(target="pve2", online=True) ).task == "UPID:migrate" assert client.compute.list_snapshots(100) == [{"name": "snap1"}] assert client.compute.create_snapshot( 100, CreateSnapshotRequest(snapname="snap2") ).task == "UPID:snap" assert client.compute.agent_action(100, "exec", {"command": "ls"}) == {"out-data": "ok"} @respx.mock def test_containers(api_key: str) -> None: create_route = respx.post("https://api.example.com/compute/containers").mock( return_value=Response(200, json={"task": "UPID:ct"}) ) respx.post("https://api.example.com/compute/containers/200/start").mock( return_value=Response(200, json={"task": "UPID:start"}) ) respx.post("https://api.example.com/compute/containers/200/stop").mock( return_value=Response(200, json={"task": "UPID:stop"}) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: result = client.compute.create_container( CreateContainerRequest(node="pve", ostemplate="local:vztmpl/debian.tar.gz", vmid=200) ) assert result.task == "UPID:ct" assert client.compute.start_container(200).task == "UPID:start" assert client.compute.stop_container(200).task == "UPID:stop" body = json.loads(create_route.calls[0].request.content) assert body["node"] == "pve" assert body["ostemplate"] == "local:vztmpl/debian.tar.gz" @respx.mock def test_storage(api_key: str) -> None: respx.get("https://api.example.com/compute/storage").mock( return_value=Response(200, json=[{"storage": "local"}]) ) respx.get("https://api.example.com/compute/storage/local/content").mock( return_value=Response(200, json=[{"volid": "local:iso/img.iso"}]) ) upload_route = respx.post("https://api.example.com/compute/storage/local/upload").mock( return_value=Response(200, json={"task": "UPID:upload"}) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: assert client.compute.list_storage(StorageQuery(node="pve")) == [{"storage": "local"}] assert client.compute.list_storage_content("local", StorageQuery(node="pve")) == [ {"volid": "local:iso/img.iso"} ] result = client.compute.upload_to_storage( "local", UploadStorageRequest(content="abc", filename="img.iso"), node="pve", ) assert result.task == "UPID:upload" assert upload_route.calls[0].request.url.params["node"] == "pve" @respx.mock def test_backup_jobs_and_misc(api_key: str) -> None: respx.get("https://api.example.com/compute/backups/jobs").mock( return_value=Response(200, json=[]) ) respx.post("https://api.example.com/compute/backups/jobs").mock( return_value=Response(200, json={"task": "UPID:backup"}) ) respx.get("https://api.example.com/compute/ha/resources").mock( return_value=Response(200, json=[]) ) respx.get("https://api.example.com/compute/access/users").mock( return_value=Response(200, json=[]) ) respx.get("https://api.example.com/compute/access/acl").mock( return_value=Response(200, json=[]) ) with ConduitClient(api_key, base_url="https://api.example.com") as client: assert client.compute.list_backup_jobs() == [] assert client.compute.create_backup_job({"id": "job1"}).task == "UPID:backup" assert client.compute.list_ha_resources() == [] assert client.compute.list_access_users() == [] assert client.compute.list_access_acl() == [] @pytest.mark.asyncio @respx.mock async def test_async_start_vm(api_key: str) -> None: respx.post("https://api.example.com/compute/vms/100/start").mock( return_value=Response(200, json={"task": "UPID:start"}) ) async with AsyncConduitClient(api_key, base_url="https://api.example.com") as client: result = await client.compute.start_vm(100) assert result.task == "UPID:start"