Files
vmware-api-simulator/tests/integration/test_vsphere_deep_realism.py
T

229 lines
8.3 KiB
Python

"""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="""<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<Login xmlns="urn:vim25">
<_this type="SessionManager">SessionManager</_this>
<userName>administrator@vsphere.local</userName>
<password>VMware1!</password>
</Login>
</soapenv:Body>
</soapenv:Envelope>""",
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="""<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<PowerOffVM_Task xmlns="urn:vim25">
<_this type="VirtualMachine">vm-104</_this>
</PowerOffVM_Task>
</soapenv:Body>
</soapenv:Envelope>""",
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"