Initial commit: stateful OpenStack API laboratory simulator.
Ship Keystone auth, multi-service handlers (Yoga→Dalmatian), Compose/Helm packaging, API contract packs, and pytest/Pulumi coverage labs.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Helpers for series activation and non-empty validation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
|
||||
def activate_series(host: str, series: str) -> None:
|
||||
body = json.dumps({"series": series}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{host.rstrip('/')}/ui/api/openstack/contracts/activate",
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as res:
|
||||
if res.status >= 400:
|
||||
raise RuntimeError(f"activate {series}: HTTP {res.status}")
|
||||
except urllib.error.HTTPError as exc:
|
||||
raw = exc.read().decode()
|
||||
raise RuntimeError(f"activate {series}: HTTP {exc.code} {raw[:300]}") from exc
|
||||
|
||||
|
||||
def is_nonempty(value: Any) -> bool:
|
||||
if value is None:
|
||||
return False
|
||||
if isinstance(value, str) and not value.strip():
|
||||
return False
|
||||
if isinstance(value, (list, tuple, set, dict)) and len(value) == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def assert_outputs_nonempty(outputs: dict[str, Any], *, min_count: int = 15) -> list[str]:
|
||||
"""Return list of failing export names (empty if all good)."""
|
||||
failures: list[str] = []
|
||||
if len(outputs) < min_count:
|
||||
failures.append(f"__export_count__={len(outputs)}<{min_count}")
|
||||
for key, value in sorted(outputs.items()):
|
||||
if not is_nonempty(value):
|
||||
failures.append(f"{key}={value!r}")
|
||||
return failures
|
||||
|
||||
|
||||
def payload_nonempty(
|
||||
payload: Any, *, collection_key: str | None = None, method: str = "GET"
|
||||
) -> bool:
|
||||
"""True when a successful response body has meaningful content.
|
||||
|
||||
For GET list/show and POST create we require real data — empty ``[]`` /
|
||||
``{}`` / blank strings fail. DELETE/204-style empties are not checked here.
|
||||
"""
|
||||
if payload is None:
|
||||
return False
|
||||
if isinstance(payload, str):
|
||||
return bool(payload.strip())
|
||||
if isinstance(payload, list):
|
||||
return len(payload) > 0
|
||||
if not isinstance(payload, dict):
|
||||
return True
|
||||
if collection_key and collection_key in payload:
|
||||
value = payload[collection_key]
|
||||
if isinstance(value, list):
|
||||
return len(value) > 0
|
||||
return is_nonempty(value)
|
||||
# Common OpenStack envelopes
|
||||
for key, value in payload.items():
|
||||
if key in {"versions", "version", "id", "token", "links", "status", "name"}:
|
||||
if is_nonempty(value):
|
||||
return True
|
||||
if isinstance(value, list) and value:
|
||||
return True
|
||||
if isinstance(value, dict) and (value.get("id") or value.get("name") or value.get("uuid")):
|
||||
return True
|
||||
if isinstance(value, str) and value.strip():
|
||||
return True
|
||||
if isinstance(value, (int, float, bool)):
|
||||
return True
|
||||
# Non-empty dict with any nested content
|
||||
return any(is_nonempty(v) for v in payload.values())
|
||||
Reference in New Issue
Block a user