Initial commit: VMware vSphere API simulator scaffold.
Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API contracts, docs, client examples, and the unit/integration/compatibility test suite for local client and tooling labs without a real vCenter.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
"""Unit tests for SOAP PropertyCollector helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from app.vsphere.inventory import ManagedObject
|
||||
from app.vsphere.soap.property_collector import (
|
||||
_descendants,
|
||||
_one_level_traverse,
|
||||
_type_matches,
|
||||
_wants_parent_traversal,
|
||||
build_prop_map,
|
||||
clear_pc_state,
|
||||
resolve_inventory_path,
|
||||
store_page_token,
|
||||
take_page_token,
|
||||
take_page_token_full,
|
||||
view_moids_from_object,
|
||||
wait_updates_xml,
|
||||
)
|
||||
|
||||
|
||||
def _obj(
|
||||
moid: str,
|
||||
type_name: str,
|
||||
name: str,
|
||||
parent: str | None,
|
||||
props: dict | None = None,
|
||||
) -> ManagedObject:
|
||||
return ManagedObject(
|
||||
moid=moid,
|
||||
type=type_name,
|
||||
name=name,
|
||||
parent_moid=parent,
|
||||
props=props or {},
|
||||
)
|
||||
|
||||
|
||||
def test_type_matches_and_parent_traversal() -> None:
|
||||
assert _type_matches("VirtualMachine", {"ManagedEntity"})
|
||||
assert _wants_parent_traversal("<path>parent</path>")
|
||||
|
||||
|
||||
def test_descendants() -> None:
|
||||
objects = [
|
||||
_obj("group-d1", "Folder", "Datacenters", None),
|
||||
_obj("datacenter-21", "Datacenter", "DC", "group-d1"),
|
||||
_obj("group-v23", "Folder", "vm", "datacenter-21"),
|
||||
_obj("vm-101", "VirtualMachine", "web-01", "group-v23"),
|
||||
]
|
||||
expanded = _descendants([objects[0]], objects)
|
||||
assert {o.moid for o in expanded} >= {"group-d1", "datacenter-21", "group-v23", "vm-101"}
|
||||
|
||||
|
||||
def test_view_moids_from_object_props() -> None:
|
||||
view = _obj("view-1", "ContainerView", "view-1", None, {"view_moids": ["vm-101", "vm-102"]})
|
||||
assert view_moids_from_object(view) == ["vm-101", "vm-102"]
|
||||
props = build_prop_map(
|
||||
view,
|
||||
children=[],
|
||||
all_by_moid={
|
||||
"vm-101": _obj("vm-101", "VirtualMachine", "a", "group-v23"),
|
||||
"vm-102": _obj("vm-102", "VirtualMachine", "b", "group-v23"),
|
||||
},
|
||||
)
|
||||
assert "view" in props
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_page_token_roundtrip_db() -> None:
|
||||
database_url = os.getenv("TEST_DATABASE_URL") or os.getenv("DATABASE_URL")
|
||||
if not database_url:
|
||||
pytest.skip("TEST_DATABASE_URL / DATABASE_URL required")
|
||||
from app.config import Settings
|
||||
from app.db.pool import AsyncpgDatabase
|
||||
|
||||
database = AsyncpgDatabase(Settings(database_url=database_url)) # type: ignore[arg-type]
|
||||
await database.connect()
|
||||
try:
|
||||
await clear_pc_state(database)
|
||||
token = await store_page_token(database, ["vm-1", "vm-2"], path_sets=["name"])
|
||||
assert await take_page_token_full(database, token) == (["vm-1", "vm-2"], ["name"])
|
||||
token2 = await store_page_token(database, ["vm-3"])
|
||||
assert await take_page_token(database, token2) == ["vm-3"]
|
||||
assert await take_page_token(database, token2) is None
|
||||
finally:
|
||||
await database.close()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_updates_versions_db() -> None:
|
||||
database_url = os.getenv("TEST_DATABASE_URL") or os.getenv("DATABASE_URL")
|
||||
if not database_url:
|
||||
pytest.skip("TEST_DATABASE_URL / DATABASE_URL required")
|
||||
from app.config import Settings
|
||||
from app.db.pool import AsyncpgDatabase
|
||||
|
||||
database = AsyncpgDatabase(Settings(database_url=database_url)) # type: ignore[arg-type]
|
||||
await database.connect()
|
||||
try:
|
||||
await clear_pc_state(database)
|
||||
objects = [_obj("vm-101", "VirtualMachine", "web-01", "group-v23")]
|
||||
first = await wait_updates_xml(
|
||||
database, session_key="sess-1", body="<version></version>", objects=objects
|
||||
)
|
||||
assert "<version>1</version>" in first
|
||||
idle = await wait_updates_xml(
|
||||
database, session_key="sess-1", body="<version>1</version>", objects=objects
|
||||
)
|
||||
assert "<version>1</version>" in idle
|
||||
assert "objectSet" not in idle
|
||||
finally:
|
||||
await database.close()
|
||||
|
||||
|
||||
def test_resolve_inventory_path_and_one_level() -> None:
|
||||
objects = [
|
||||
_obj("group-d1", "Folder", "Datacenters", None),
|
||||
_obj("datacenter-21", "Datacenter", "DC1", "group-d1", {"vm_folder": "group-v23"}),
|
||||
_obj("group-v23", "Folder", "vm", "datacenter-21"),
|
||||
_obj("vm-101", "VirtualMachine", "web-01", "group-v23"),
|
||||
]
|
||||
hit = resolve_inventory_path("/DC1/vm/web-01", objects)
|
||||
assert hit is not None and hit.moid == "vm-101"
|
||||
kids = _one_level_traverse([objects[2]], objects, "<path>childEntity</path>")
|
||||
assert any(o.moid == "vm-101" for o in kids)
|
||||
Reference in New Issue
Block a user