Files
adcloud-server/scripts/smoke_api.py
T
codex-bot b43043255c
Docker test deployment / test-build-deploy (push) Successful in 4m44s
Add versioned event registry and transactional feature rules
2026-09-16 04:46:17 +08:00

177 lines
6.6 KiB
Python

"""HTTP deployment checks using only synthetic data; never print credentials."""
import argparse
import http.cookiejar
import json
import os
import time
import urllib.error
import urllib.request
from datetime import UTC, datetime
from pathlib import Path
from uuid import uuid4
from dotenv import dotenv_values
def check(base_url: str, service_token: str, require_projection=False):
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
def api(
path, body=None, *, trusted=False, method=None, expected=200, extra_headers=None
):
headers = {"Content-Type": "application/json"}
if trusted:
headers["Authorization"] = "Bearer " + service_token
headers.update(extra_headers or {})
request = urllib.request.Request(
base_url.rstrip("/") + path,
data=None if body is None else json.dumps(body).encode(),
headers=headers,
method=method,
)
try:
with opener.open(request, timeout=10) as response:
assert response.status == expected, "Unexpected HTTP status"
return json.load(response)
except urllib.error.HTTPError as error:
if error.code == expected:
return None
raise RuntimeError(
f"HTTP smoke request failed: {path} HTTP {error.code}"
) from None
assert api("/health") == {"status": "ok"}
for module in ("events", "users", "ads", "identity"):
assert api(f"/v1/{module}/health") == {"status": "ok", "module": module}
assert "/v1/identity/stitch" in api("/openapi.json")["paths"]
anon = api("/v1/identity/anonymous", {})
assert any(cookie.name == "adcloud_anonymous" for cookie in jar), (
"Missing anonymous cookie"
)
if not service_token:
api("/v1/events", {"name": "unregistered"}, expected=422)
print(
"Public HTTP smoke passed: routes, anonymous cookie, unregistered event rejection"
)
return
app_id = "smoke-" + uuid4().hex
application = api(
"/v1/registry/apps",
{"app_id": app_id, "name": "Deployment smoke"},
trusted=True,
expected=201,
)
management = {"X-Adcloud-Management-Key": application["management_key"]}
registration = "/v1/registry/apps/" + app_id + "/events/add_cart/versions/1"
config = {
"standard_event": "commerce.cart.add",
"description": "Synthetic digital cart",
"rules": [
{
"conditions": [
{
"field": "properties.category_id",
"operator": "eq",
"value": "digital",
}
],
"effects": [{"feature": "intent.purchase.digital", "value": 1}],
}
],
}
api(registration, config, method="PUT", extra_headers=management)
api(registration + "/publish", {}, extra_headers=management)
body = {
"event_id": str(uuid4()),
"app_id": app_id,
"event_version": 1,
"occurred_at": datetime.now(UTC).isoformat(),
"name": "add_cart",
"properties": {
"product_id": "synthetic-camera",
"quantity": 1,
"category_id": "digital",
},
}
headers = {"X-Adcloud-Key": application["collection_key"]}
event = api("/v1/events", body, extra_headers=headers)
assert api("/v1/events", body, extra_headers=headers)["duplicate"] is True
assert event["anonymous_id"] == anon["anonymous_id"], "Anonymous cookie not reused"
if service_token:
api("/v1/users/" + anon["master_id"], expected=401)
api(
"/v1/users/" + anon["master_id"],
{"attributes": {"smoke": True}},
trusted=True,
method="PATCH",
)
linked = api(
"/v1/identity/stitch",
{
"anonymous_token": anon["anonymous_token"],
"identifiers": [
{"kind": "email", "value": "smoke-" + uuid4().hex + "@example.com"}
],
},
trusted=True,
)
profile = api("/v1/users/" + linked["master_id"], trusted=True)
assert profile["event_count"] == 1 and profile["attributes"]["smoke"] is True
history = api("/v1/events?master_id=" + linked["master_id"], trusted=True)
assert history["items"][0]["id"] == event["event_id"]
graph = api("/v1/identity/" + linked["master_id"] + "/graph", trusted=True)
assert len(graph["identifiers"]) == 1
if require_projection:
storage = api("/v1/identity/storage", trusted=True)
assert storage["graph_backend"] == "postgresql"
assert storage["canonical_profile_format"] == "jsonb"
assert storage["profile_backend"] == "cassandra"
for _ in range(60):
profile = api("/v1/users/" + linked["master_id"], trusted=True)
if (
profile["projection_status"] == "current"
and profile["features"].get("intent.purchase.digital") == 1
):
break
time.sleep(1)
assert profile["projection_status"] == "current", (
"Worker projection not current"
)
assert profile["attributes"]["smoke"] is True
assert profile["features"] == {"intent.purchase.digital": 1}
print(
"HTTP smoke passed: routes, anonymous cookie, events"
+ (", auth, stitching, profile and history" if service_token else "")
)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--base-url", default="http://127.0.0.1:8000")
parser.add_argument(
"--public-only",
action="store_true",
help="Do not read or send the service token",
)
parser.add_argument("--require-projection", action="store_true")
args = parser.parse_args()
token = ""
if not args.public_only:
stored = dotenv_values(
Path(os.getenv("IDENTITY_BOOTSTRAP_FILE", "/config/identity.env"))
)
token = os.getenv("SERVICE_TOKEN") or stored.get("SERVICE_TOKEN") or ""
if not token:
raise SystemExit(
"Configure SERVICE_TOKEN or mount the deployment config volume"
)
if args.require_projection and args.public_only:
parser.error("Projection checks require a service token")
check(args.base_url, token, args.require_projection)
if __name__ == "__main__":
main()