Add versioned event registry and transactional feature rules
Docker test deployment / test-build-deploy (push) Successful in 4m44s
Docker test deployment / test-build-deploy (push) Successful in 4m44s
This commit is contained in:
@@ -30,7 +30,7 @@ cleanup() {
|
||||
local result=$?
|
||||
trap - EXIT
|
||||
if ! $successful && $legacy_stopped; then
|
||||
docker compose -f compose.yml stop api worker >/dev/null || true
|
||||
docker compose -f compose.yml stop api worker event-worker >/dev/null || true
|
||||
docker start adcloud-server-test >/dev/null || true
|
||||
echo 'Compose deployment failed; legacy SQLite service restored where possible.'
|
||||
fi
|
||||
@@ -64,7 +64,7 @@ if docker container inspect adcloud-server-test >/dev/null 2>&1; then
|
||||
legacy_stopped=true
|
||||
fi
|
||||
docker compose -f compose.yml up -d --wait --wait-timeout 180 api
|
||||
docker compose -f compose.yml up -d worker
|
||||
docker compose -f compose.yml up -d worker event-worker
|
||||
docker compose -f compose.yml --profile checks run --rm smoke
|
||||
# Verify the published host port too; config stays read-only in this probe.
|
||||
docker run --rm --network host --volume "$CONFIG_VOLUME:/config:ro" --entrypoint python "$DEPLOY_IMAGE" /app/scripts/smoke_api.py --base-url "http://127.0.0.1:$DEPLOY_PORT" --require-projection
|
||||
|
||||
@@ -4,6 +4,7 @@ import argparse
|
||||
import http.cookiejar
|
||||
import json
|
||||
import urllib.request
|
||||
from datetime import UTC, datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from adcloud_server.settings import Settings
|
||||
@@ -19,10 +20,11 @@ opener = urllib.request.build_opener(
|
||||
)
|
||||
|
||||
|
||||
def api(path, body=None, trusted=False, method=None):
|
||||
def api(path, body=None, trusted=False, method=None, extra_headers=None):
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if trusted:
|
||||
headers["Authorization"] = "Bearer " + settings.service_token
|
||||
headers.update(extra_headers or {})
|
||||
request = urllib.request.Request(
|
||||
args.base_url.rstrip("/") + path,
|
||||
data=None if body is None else json.dumps(body).encode(),
|
||||
@@ -34,7 +36,31 @@ def api(path, body=None, trusted=False, method=None):
|
||||
|
||||
|
||||
anon = api("/v1/identity/anonymous?rotate=true", {})
|
||||
api("/v1/events", {"name": "product.view", "properties": {"sku": "demo-running-shoe"}})
|
||||
app_id = "demo-" + uuid4().hex
|
||||
application = api(
|
||||
"/v1/registry/apps", {"app_id": app_id, "name": "Identity demo"}, trusted=True
|
||||
)
|
||||
registration = "/v1/registry/apps/" + app_id + "/events/product.view/versions/1"
|
||||
management = {"X-Adcloud-Management-Key": application["management_key"]}
|
||||
api(
|
||||
registration,
|
||||
{"standard_event": "commerce.product.view", "description": "Demo product view"},
|
||||
method="PUT",
|
||||
extra_headers=management,
|
||||
)
|
||||
api(registration + "/publish", {}, extra_headers=management)
|
||||
api(
|
||||
"/v1/events",
|
||||
{
|
||||
"name": "product.view",
|
||||
"app_id": app_id,
|
||||
"event_version": 1,
|
||||
"event_id": str(uuid4()),
|
||||
"occurred_at": datetime.now(UTC).isoformat(),
|
||||
"properties": {"product_id": "demo-running-shoe"},
|
||||
},
|
||||
extra_headers={"X-Adcloud-Key": application["collection_key"]},
|
||||
)
|
||||
api(
|
||||
"/v1/users/" + anon["master_id"],
|
||||
{"attributes": {"interest": "running", "unused": None}, "tags": ["runner"]},
|
||||
|
||||
+57
-9
@@ -7,6 +7,7 @@ import os
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -17,10 +18,13 @@ 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):
|
||||
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(),
|
||||
@@ -46,14 +50,54 @@ def check(base_url: str, service_token: str, require_projection=False):
|
||||
assert any(cookie.name == "adcloud_anonymous" for cookie in jar), (
|
||||
"Missing anonymous cookie"
|
||||
)
|
||||
event = api(
|
||||
"/v1/events",
|
||||
{
|
||||
"event_id": str(uuid4()),
|
||||
"name": "deployment.smoke",
|
||||
"properties": {"source": "gitea-actions"},
|
||||
},
|
||||
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)
|
||||
@@ -86,13 +130,17 @@ def check(base_url: str, service_token: str, require_projection=False):
|
||||
assert storage["profile_backend"] == "cassandra"
|
||||
for _ in range(60):
|
||||
profile = api("/v1/users/" + linked["master_id"], trusted=True)
|
||||
if profile["projection_status"] == "current":
|
||||
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 "")
|
||||
|
||||
Reference in New Issue
Block a user