"""Deep-handler wire realism: seed non-empty, mutations, tasks, REST↔SOAP.""" from __future__ import annotations import os import pytest from httpx import ASGITransport, AsyncClient from app.config import Settings from app.main import create_app from app.vsphere.seed import seed_vsphere_inventory pytestmark = pytest.mark.integration @pytest.fixture async def client() -> AsyncClient: database_url = os.getenv("TEST_DATABASE_URL") or os.getenv("DATABASE_URL") if not database_url: pytest.skip("TEST_DATABASE_URL / DATABASE_URL required") settings = Settings( database_url=database_url, # type: ignore[arg-type] contract_snapshot=None, enable_pve_stub=False, ) app = create_app(settings=settings, worker_factories=()) async with app.router.lifespan_context(app): await seed_vsphere_inventory(app.state.database, force=True, profile="small") async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as http: yield http async def _session(client: AsyncClient) -> dict[str, str]: login = await client.post( "/api/session", auth=("administrator@vsphere.local", "VMware1!"), ) assert login.status_code == 201 return {"vmware-api-session-id": login.json()} async def test_deep_inventory_lists_and_details(client: AsyncClient) -> None: headers = await _session(client) hosts = await client.get("/api/vcenter/host", headers=headers) assert hosts.status_code == 200 assert len(hosts.json()) >= 3 host_id = hosts.json()[0]["host"] host = await client.get(f"/api/vcenter/host/{host_id}", headers=headers) assert host.status_code == 200 assert host.json()["name"] stores = await client.get("/api/vcenter/datastore", headers=headers) assert stores.status_code == 200 ds_id = stores.json()[0]["datastore"] detail = await client.get(f"/api/vcenter/datastore/{ds_id}", headers=headers) assert detail.status_code == 200 files = await client.get(f"/api/vcenter/datastore/{ds_id}/files", headers=headers) assert files.status_code == 200 assert len(files.json()) >= 1 async def test_content_library_deep_get_not_stub(client: AsyncClient) -> None: headers = await _session(client) libs = await client.get("/api/content/library", headers=headers) assert libs.status_code == 200 assert "lib-local-1" in libs.json() local = await client.get("/api/content/local-library", headers=headers) assert local.status_code == 200 assert "lib-local-1" in local.json() info = await client.get("/api/content/library/lib-local-1", headers=headers) assert info.status_code == 200 body = info.json() assert body["id"] == "lib-local-1" assert body["name"] == "Local Content" assert body["type"] == "LOCAL" assert "path" not in body # stub placeholder must not leak local_info = await client.get("/api/content/local-library/lib-local-1", headers=headers) assert local_info.status_code == 200 assert local_info.json()["id"] == "lib-local-1" items = await client.get( "/api/content/library/item", params={"library_id": "lib-local-1"}, headers=headers, ) assert items.status_code == 200 assert "item-ubuntu" in items.json() item = await client.get("/api/content/library/item/item-ubuntu", headers=headers) assert item.status_code == 200 assert item.json()["name"] == "ubuntu-22.04" assert item.json()["library_id"] == "lib-local-1" async def test_power_task_poll_and_soap_consistency(client: AsyncClient) -> None: headers = await _session(client) power = await client.post( "/api/vcenter/vm/vm-104/power", params={"action": "start"}, headers=headers, ) assert power.status_code == 200 task_id = power.json()["task"] assert task_id.startswith("task-") task = await client.get(f"/api/cis/tasks/{task_id}", headers=headers) assert task.status_code == 200 payload = task.json() assert payload["status"] == "SUCCEEDED" assert payload["state"] == "SUCCEEDED" assert isinstance(payload["description"], dict) assert payload["description"]["default_message"] assert isinstance(payload["progress"], dict) assert payload["progress"]["completed"] == 100 assert payload["result"]["vm"] == "vm-104" listed = await client.post( "/api/cis/tasks", params={"action": "list"}, headers=headers, json={"filter_spec": {"tasks": [task_id]}}, ) assert listed.status_code == 200 assert task_id in listed.json() state = await client.get("/api/vcenter/vm/vm-104/power", headers=headers) assert state.json()["state"] == "POWERED_ON" soap = await client.post( "/sdk", content=""" <_this type="SessionManager">SessionManager administrator@vsphere.local VMware1! """, headers={"Content-Type": "text/xml"}, ) assert soap.status_code == 200 cookie = (soap.headers.get("set-cookie") or "").split(";")[0] power_soap = await client.post( "/sdk", content=""" <_this type="VirtualMachine">vm-104 """, headers={"Content-Type": "text/xml", "Cookie": cookie}, ) assert power_soap.status_code == 200 assert "task-" in power_soap.text assert 'type="Task"' in power_soap.text async def test_tagging_association_query_action(client: AsyncClient) -> None: headers = await _session(client) tags = await client.get("/api/cis/tagging/tag", headers=headers) assert tags.status_code == 200 tag_id = tags.json()[0] attach = await client.post( "/api/cis/tagging/tag-association", params={"action": "attach"}, headers=headers, json={"tag_id": tag_id, "object_id": {"type": "VirtualMachine", "id": "vm-103"}}, ) assert attach.status_code == 204 listed = await client.post( "/api/cis/tagging/tag-association", params={"action": "list-attached-tags"}, headers=headers, json={"object_id": {"type": "VirtualMachine", "id": "vm-103"}}, ) assert listed.status_code == 200 assert tag_id in listed.json() async def test_folder_create_and_authz_permissions(client: AsyncClient) -> None: headers = await _session(client) folder = await client.post( "/api/vcenter/folder", headers=headers, json={"name": "deep-repro-folder", "type": "VIRTUAL_MACHINE", "parent": "group-v23"}, ) assert folder.status_code == 200 assert folder.json().startswith("group-") roles = await client.get("/api/vcenter/authorization/roles", headers=headers) assert roles.status_code == 200 assert any(r["role"] == "Administrator" for r in roles.json()) perms = await client.get("/api/vcenter/authorization/permissions", headers=headers) assert perms.status_code == 200 assert len(perms.json()) >= 1 async def test_ovf_deploy_accepts_official_target_fields(client: AsyncClient) -> None: headers = await _session(client) deploy = await client.post( "/api/vcenter/ovf/library-item/item-ubuntu", headers=headers, json={ "target": { "resource_pool_id": "resgroup-22", "folder_id": "group-v23", "host_id": "host-11", "datastore_id": "datastore-31", }, "deployment_spec": {"name": "ovf-deep-repro", "accept_all_EULA": True}, }, ) assert deploy.status_code == 200 body = deploy.json() assert body["resource_id"]["type"] == "VirtualMachine" assert body["resource_id"]["id"].startswith("vm-") assert body["task"].startswith("task-") vm = await client.get(f"/api/vcenter/vm/{body['resource_id']['id']}", headers=headers) assert vm.status_code == 200 assert vm.json()["name"] == "ovf-deep-repro"