#!/usr/bin/env python3 """Fail if any registered GET returns 501, stub marker, or empty JSON body.""" from __future__ import annotations import json import os import re import ssl import sys import urllib.error import urllib.request from base64 import b64encode from app.vsphere.rest.coverage import IMPLEMENTED BASE = os.getenv("VSPHERE_BASE", "https://localhost") USER = os.getenv("VSPHERE_USER", "administrator@vsphere.local") PASSWORD = os.getenv("VSPHERE_PASSWORD", "VMware1!") _SUBS = { "{vm}": "vm-101", "{host}": "host-11", "{datastore}": "datastore-31", "{task}": "task-1", "{snapshot}": "snapshot-missing", "{category_id}": "cat-lab-1", "{tag_id}": "tag-lab-1", "{item_id}": "item-ubuntu", "{library_id}": "lib-local-1", "{folder}": "group-v23", "{datacenter}": "datacenter-21", "{cluster}": "domain-c21", "{resource_pool}": "resgroup-22", "{permission_id}": "1", "{policy}": "policy-default", "{disk}": "2000", "{nic}": "4000", "{cdrom}": "3000", "{adapter}": "1000", "{network}": "network-41", "{supervisor}": "supervisor-1", "{commit}": "commit-lab-1", "{domain}": "lab.local", "{interface}": "nic0", "{service}": "vpxd", "{session_id}": "session-lab-1", "{download_session_id}": "session-lab-1", "{update_session_id}": "session-lab-1", "{provider}": "vsphere.local", "{role}": "ReadOnly", "{chain}": "chain-1", "{floppy}": "8000", "{port}": "9000", } def _ctx() -> ssl.SSLContext | None: if not BASE.startswith("https://"): return None return ssl._create_unverified_context() # noqa: S323 def _concrete(path: str) -> str: out = path for key, value in _SUBS.items(): out = out.replace(key, value) return re.sub(r"\{([A-Za-z0-9_]+)\}", r"lab-\1", out) def _request(method: str, path: str, *, headers: dict[str, str]) -> tuple[int, str]: req = urllib.request.Request(f"{BASE}{path}", method=method, headers=headers) try: with urllib.request.urlopen(req, context=_ctx(), timeout=60) as resp: # noqa: S310 return int(resp.status), resp.read().decode("utf-8", errors="replace") except urllib.error.HTTPError as error: return int(error.code), error.read().decode("utf-8", errors="replace") def main() -> int: basic = b64encode(f"{USER}:{PASSWORD}".encode()).decode() code, body = _request("POST", "/api/session", headers={"Authorization": f"Basic {basic}"}) if code not in {200, 201}: print(json.dumps({"error": f"session failed {code}", "body": body[:200]})) return 1 session = json.loads(body) headers = {"vmware-api-session-id": session, "Accept": "application/json"} failures: list[dict[str, object]] = [] ok = 0 checked = 0 for (verb, path), _status in sorted(IMPLEMENTED.items()): if verb != "GET" or path in {"/api/session", "/rest/com/vmware/cis/session"}: continue checked += 1 concrete = _concrete(path) if path == "/api/content/library/item": concrete = f"{concrete}?library_id=lib-local-1" status, raw = _request("GET", concrete, headers=headers) if status == 501: failures.append({"path": path, "status": status, "reason": "version gate 501"}) continue if status >= 500: failures.append( {"path": path, "status": status, "reason": "server error", "body": raw[:160]} ) continue if status not in {200, 201}: # Missing probe ids may 404 — still require a JSON error body. if not raw.strip(): failures.append({"path": path, "status": status, "reason": "empty error body"}) continue if not raw.strip(): failures.append({"path": path, "status": status, "reason": "empty body"}) continue if '"stub": true' in raw or '"stub":true' in raw: failures.append({"path": path, "status": status, "reason": "stub marker"}) continue try: payload = json.loads(raw) except json.JSONDecodeError: failures.append( {"path": path, "status": status, "reason": "non-json", "body": raw[:160]} ) continue if payload in ([], {}, None, "") or ( isinstance(payload, (list, dict)) and len(payload) == 0 ): failures.append( {"path": path, "status": status, "reason": "empty json", "body": raw[:160]} ) continue if isinstance(payload, dict): for key in ("data", "value", "messages", "items", "results"): if key in payload and payload[key] in ([], None, {}): failures.append( { "path": path, "status": status, "reason": f"empty nested {key}", "body": raw[:160], } ) break else: ok += 1 continue ok += 1 print( json.dumps( { "checked": checked, "ok": ok, "failure_count": len(failures), "failures": failures[:80], }, indent=2, ) ) return 1 if failures else 0 if __name__ == "__main__": raise SystemExit(main())