"""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("parent") 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="", objects=objects ) assert "1" in first idle = await wait_updates_xml( database, session_key="sess-1", body="1", objects=objects ) assert "1" 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, "childEntity") assert any(o.moid == "vm-101" for o in kids)