229 lines
8.3 KiB
Python
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"
|