skillhub/scripts/suite-bundle-smoke-test.sh

407 lines
15 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${1:-http://localhost:8080}"
ADMIN_USERNAME="${SMOKE_ADMIN_USERNAME:-}"
ADMIN_PASSWORD="${SMOKE_ADMIN_PASSWORD:-}"
WORK_DIR="$(mktemp -d)"
ADMIN_COOKIE="$(mktemp)"
RECOVERY_COOKIE="$(mktemp)"
USER_COOKIE="$(mktemp)"
TOKEN="$(date +%s)${RANDOM}"
ENTRY_SLUG="bundle-entry-${TOKEN}"
REFERENCE_SLUG="bundle-reference-${TOKEN}"
SUITE_SLUG="bundle-suite-${TOKEN}"
LABEL_SLUG="bundle-label-${TOKEN}"
USER_NAME="bundle_user_${TOKEN}"
USER_PASSWORD="BundleUser${TOKEN}!Aa9"
SUITE_ID=""
ENTRY_SKILL_ID=""
REFERENCE_SKILL_ID=""
LABEL_CREATED=false
json_field() {
JSON_INPUT="$1" python3 - "$2" <<'PY'
import json
import os
import sys
value = json.loads(os.environ["JSON_INPUT"])
for part in sys.argv[1].split("."):
value = value[int(part)] if part.isdigit() else value[part]
print(json.dumps(value, ensure_ascii=False) if isinstance(value, (dict, list)) else value)
PY
}
assert_code() {
local description="$1"
local response="$2"
local expected="${3:-0}"
local actual
actual="$(json_field "$response" code)"
if [[ "$actual" != "$expected" ]]; then
echo "FAIL: $description (expected code $expected, got $actual)" >&2
exit 1
fi
echo "PASS: $description"
}
csrf_token() {
awk '$6 == "XSRF-TOKEN" { print $7 }' "$1" | tail -n 1
}
bootstrap_cookie() {
curl -sS -c "$1" "$BASE_URL/api/v1/auth/me" >/dev/null
}
login_admin() {
local cookie_file="$1"
bootstrap_cookie "$cookie_file"
local csrf
csrf="$(csrf_token "$cookie_file")"
local response
response="$(curl -fsS -b "$cookie_file" -c "$cookie_file" \
-H "X-XSRF-TOKEN: $csrf" -H "Content-Type: application/json" \
-X POST "$BASE_URL/api/v1/auth/local/login" \
-d "{\"username\":\"$ADMIN_USERNAME\",\"password\":\"$ADMIN_PASSWORD\"}")"
assert_code "authenticate local administrator" "$response"
}
cleanup() {
local csrf=""
csrf="$(csrf_token "$ADMIN_COOKIE" || true)"
if [[ -n "$csrf" ]]; then
if [[ -n "$SUITE_ID" ]]; then
curl -sS -o /dev/null -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $csrf" \
-X DELETE "$BASE_URL/api/web/suites/$SUITE_ID" || true
fi
if [[ -n "$ENTRY_SKILL_ID" ]]; then
curl -sS -o /dev/null -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $csrf" \
-X DELETE "$BASE_URL/api/v1/skills/id/$ENTRY_SKILL_ID" || true
fi
if [[ -n "$REFERENCE_SKILL_ID" ]]; then
curl -sS -o /dev/null -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $csrf" \
-X DELETE "$BASE_URL/api/v1/skills/id/$REFERENCE_SKILL_ID" || true
fi
if [[ "$LABEL_CREATED" == true ]]; then
curl -sS -o /dev/null -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $csrf" \
-X DELETE "$BASE_URL/api/v1/admin/labels/$LABEL_SLUG" || true
fi
fi
rm -f "$ADMIN_COOKIE" "$RECOVERY_COOKIE" "$USER_COOKIE"
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
if [[ -z "$ADMIN_USERNAME" || -z "$ADMIN_PASSWORD" ]]; then
echo "FAIL: SMOKE_ADMIN_USERNAME and SMOKE_ADMIN_PASSWORD are required" >&2
exit 1
fi
make_skill_zip() {
local slug="$1"
local version="$2"
local output="$3"
SLUG="$slug" VERSION="$version" OUTPUT="$output" python3 - "$WORK_DIR" <<'PY'
from pathlib import Path
import os
import sys
import zipfile
root = Path(sys.argv[1])
skill_md = root / f"{os.environ['SLUG']}-{os.environ['VERSION']}.md"
skill_md.write_text(
"---\n"
f"name: {os.environ['SLUG']}\n"
f"description: Release Compose Bundle smoke member {os.environ['SLUG']}\n"
f"version: {os.environ['VERSION']}\n"
"---\n\n# Bundle smoke member\n",
encoding="utf-8",
)
with zipfile.ZipFile(os.environ["OUTPUT"], "w", zipfile.ZIP_DEFLATED) as archive:
archive.write(skill_md, "SKILL.md")
PY
}
make_bundle_zip() {
local mode="$1"
local version="$2"
local base_version="$3"
local output="$4"
MODE="$mode" VERSION="$version" BASE_VERSION="$base_version" OUTPUT="$output" \
SUITE_SLUG="$SUITE_SLUG" ENTRY_SLUG="$ENTRY_SLUG" REFERENCE_SLUG="$REFERENCE_SLUG" \
python3 - "$WORK_DIR" <<'PY'
from pathlib import Path
import os
import sys
import zipfile
root = Path(sys.argv[1])
member = root / f"entry-{os.environ['VERSION']}.md"
member.write_text(
"---\n"
f"name: {os.environ['ENTRY_SLUG']}\n"
"description: Entry member created and updated through Suite Bundle smoke\n"
f"version: {os.environ['VERSION']}\n"
"---\n\n# Bundle entry\n",
encoding="utf-8",
)
base = "" if not os.environ["BASE_VERSION"] else f" baseVersion: {os.environ['BASE_VERSION']}\n"
manifest = (
"apiVersion: skillhub.iflytek.com/v1alpha1\n"
"kind: SkillSuiteBundle\n"
"metadata:\n"
" namespace: global\n"
f" slug: {os.environ['SUITE_SLUG']}\n"
"spec:\n"
f" mode: {os.environ['MODE']}\n"
f" version: {os.environ['VERSION']}\n"
f"{base}"
" displayName: Release Compose Bundle smoke\n"
" summary: Authenticated release Compose Bundle smoke workflow\n"
" overview: |\n"
" # Release Compose Bundle smoke\n\n"
" Creates and updates a Suite with one package and one exact reference.\n"
" visibility: PUBLIC\n"
f" entry: \"@global/{os.environ['ENTRY_SLUG']}\"\n"
" members:\n"
f" - skill: \"@global/{os.environ['ENTRY_SLUG']}\"\n"
" package:\n"
" path: skills/entry\n"
" visibility: PUBLIC\n"
f" - skill: \"@global/{os.environ['REFERENCE_SLUG']}\"\n"
" reference:\n"
" version: 1.0.0\n"
)
with zipfile.ZipFile(os.environ["OUTPUT"], "w", zipfile.ZIP_DEFLATED) as archive:
archive.writestr("SUITE.yaml", manifest)
archive.write(member, "skills/entry/SKILL.md")
PY
}
poll_skill_status() {
local cookie_file="$1"
local slug="$2"
local expected="$3"
local response=""
for _ in $(seq 1 120); do
response="$(curl -fsS -b "$cookie_file" "$BASE_URL/api/web/skills/global/$slug")"
if JSON_INPUT="$response" EXPECTED="$expected" python3 - <<'PY'
import json
import os
data = json.loads(os.environ["JSON_INPUT"]).get("data") or {}
versions = [data.get("headlineVersion") or {}, data.get("ownerPreviewVersion") or {}, data.get("publishedVersion") or {}]
raise SystemExit(0 if any(item.get("status") == os.environ["EXPECTED"] for item in versions) else 1)
PY
then
printf '%s' "$response"
return 0
fi
sleep 1
done
echo "FAIL: $slug did not reach $expected" >&2
return 1
}
poll_operation() {
local cookie_file="$1"
local operation_id="$2"
local response=""
for _ in $(seq 1 120); do
response="$(curl -fsS -b "$cookie_file" "$BASE_URL/api/web/suite-bundles/operations/$operation_id")"
if [[ "$(json_field "$response" data.status)" == "SUITE_DRAFT_CREATED" ]]; then
printf '%s' "$response"
return 0
fi
sleep 1
done
echo "FAIL: Bundle operation $operation_id did not create a Suite draft" >&2
return 1
}
echo "=== Suite Bundle Release Compose Smoke Test ==="
echo "Target: $BASE_URL"
echo "Suite: @global/$SUITE_SLUG"
login_admin "$ADMIN_COOKIE"
ADMIN_CSRF="$(csrf_token "$ADMIN_COOKIE")"
bootstrap_cookie "$USER_COOKIE"
USER_CSRF="$(csrf_token "$USER_COOKIE")"
REGISTER_RESPONSE="$(curl -fsS -b "$USER_COOKIE" -c "$USER_COOKIE" \
-H "X-XSRF-TOKEN: $USER_CSRF" -H "Content-Type: application/json" \
-X POST "$BASE_URL/api/v1/auth/local/register" \
-d "{\"username\":\"$USER_NAME\",\"password\":\"$USER_PASSWORD\",\"email\":\"$USER_NAME@example.test\"}")"
assert_code "register a non-admin Skill owner" "$REGISTER_RESPONSE"
USER_CSRF="$(csrf_token "$USER_COOKIE")"
make_skill_zip "$REFERENCE_SLUG" 1.0.0 "$WORK_DIR/reference.zip"
REFERENCE_PUBLISH="$(curl -fsS -b "$USER_COOKIE" -c "$USER_COOKIE" \
-H "X-XSRF-TOKEN: $USER_CSRF" \
-F "file=@$WORK_DIR/reference.zip;type=application/zip" -F "visibility=PUBLIC" \
"$BASE_URL/api/web/skills/global/publish")"
assert_code "non-admin publishes reference Skill for review" "$REFERENCE_PUBLISH"
REFERENCE_SKILL_ID="$(json_field "$REFERENCE_PUBLISH" data.skillId)"
poll_skill_status "$USER_COOKIE" "$REFERENCE_SLUG" PENDING_REVIEW >/dev/null
echo "PASS: reference Skill reaches PENDING_REVIEW"
GLOBAL_NAMESPACE="$(curl -fsS -b "$ADMIN_COOKIE" "$BASE_URL/api/web/namespaces/global")"
GLOBAL_NAMESPACE_ID="$(json_field "$GLOBAL_NAMESPACE" data.id)"
REVIEWS="$(curl -fsS -b "$ADMIN_COOKIE" "$BASE_URL/api/web/reviews?status=PENDING&namespaceId=$GLOBAL_NAMESPACE_ID")"
REVIEW_ID="$(JSON_INPUT="$REVIEWS" SLUG="$REFERENCE_SLUG" python3 - <<'PY'
import json
import os
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
match = next((item for item in items if item["skillSlug"] == os.environ["SLUG"]), None)
print(match["id"] if match else "")
PY
)"
[[ -n "$REVIEW_ID" ]] || { echo "FAIL: pending review was not found" >&2; exit 1; }
APPROVE="$(curl -fsS -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $ADMIN_CSRF" \
-H "Content-Type: application/json" -X POST \
"$BASE_URL/api/web/reviews/$REVIEW_ID/approve" -d '{"comment":"release compose Bundle smoke"}')"
assert_code "administrator approves the foreign-owned reference Skill" "$APPROVE"
poll_skill_status "$USER_COOKIE" "$REFERENCE_SLUG" PUBLISHED >/dev/null
echo "PASS: foreign-owned reference Skill is PUBLISHED"
run_bundle() {
local mode="$1"
local version="$2"
local base_version="$3"
local archive="$WORK_DIR/bundle-${version}.zip"
make_bundle_zip "$mode" "$version" "$base_version" "$archive"
local preview
preview="$(curl -fsS -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $ADMIN_CSRF" \
-F "file=@$archive;type=application/zip" "$BASE_URL/api/web/suite-bundles/preview")"
assert_code "$mode Bundle preview" "$preview"
JSON_INPUT="$preview" MODE="$mode" VERSION="$version" ENTRY="$ENTRY_SLUG" REFERENCE="$REFERENCE_SLUG" python3 - <<'PY'
import json
import os
data = json.loads(os.environ["JSON_INPUT"])["data"]
members = {item["coordinate"]: item for item in data["members"]}
assert data["confirmable"] is True
assert data["target"]["mode"] == os.environ["MODE"]
assert data["target"]["targetVersion"] == os.environ["VERSION"]
entry = members[f"@global/{os.environ['ENTRY']}"]
reference = members[f"@global/{os.environ['REFERENCE']}"]
assert entry["sourceType"] == "PACKAGE"
assert entry["packagePath"] == "skills/entry"
assert entry["publishAction"] in {"CREATE_SKILL", "CREATE_VERSION"}
assert reference["sourceType"] == "REFERENCE"
assert reference["publishAction"] == "REFERENCE_VERSION"
PY
echo "PASS: $mode preview exposes package path, actions, and exact foreign reference"
local token digest confirmation operation_id
token="$(json_field "$preview" data.previewToken)"
digest="$(json_field "$preview" data.warningDigest)"
confirmation="$(curl -fsS -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $ADMIN_CSRF" \
-H "Content-Type: application/json" -H "Idempotency-Key: $mode-$TOKEN" \
-X POST "$BASE_URL/api/web/suite-bundles/previews/$token/confirm" \
-d "{\"warningDigest\":\"$digest\"}")"
assert_code "$mode Bundle confirmation" "$confirmation"
operation_id="$(json_field "$confirmation" data.operationId)"
login_admin "$RECOVERY_COOKIE"
local recovered
recovered="$(poll_operation "$RECOVERY_COOKIE" "$operation_id")"
assert_code "$mode operation is recoverable after a fresh login" "$recovered"
echo "PASS: $mode operation reaches SUITE_DRAFT_CREATED"
}
publish_public_suite() {
local version_id="$1"
local submit reviews review_id approve
submit="$(curl -fsS -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $ADMIN_CSRF" \
-X POST "$BASE_URL/api/web/suites/$SUITE_ID/versions/$version_id/submit")"
assert_code "submit public Suite version for review" "$submit"
reviews="$(curl -fsS -b "$ADMIN_COOKIE" \
"$BASE_URL/api/web/reviews?status=PENDING&namespaceId=$GLOBAL_NAMESPACE_ID")"
review_id="$(JSON_INPUT="$reviews" VERSION_ID="$version_id" python3 - <<'PY'
import json
import os
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
match = next((item for item in items
if item.get("subjectType") == "SUITE_VERSION"
and str(item.get("subjectVersionId")) == os.environ["VERSION_ID"]), None)
print(match["id"] if match else "")
PY
)"
[[ -n "$review_id" ]] || { echo "FAIL: pending Suite review was not found" >&2; exit 1; }
approve="$(curl -fsS -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $ADMIN_CSRF" \
-H "Content-Type: application/json" -X POST \
"$BASE_URL/api/web/suites/reviews/$review_id/approve" \
-d '{"comment":"release compose Bundle smoke"}')"
assert_code "approve public Suite version review" "$approve"
}
run_bundle CREATE 1.0.0 ""
CREATE_DETAIL="$(curl -fsS -b "$ADMIN_COOKIE" "$BASE_URL/api/web/suites/global/$SUITE_SLUG?version=1.0.0")"
assert_code "load created Suite draft" "$CREATE_DETAIL"
SUITE_ID="$(json_field "$CREATE_DETAIL" data.id)"
SUITE_VERSION_ID="$(json_field "$CREATE_DETAIL" data.versionId)"
ENTRY_SKILL_ID="$(json_field "$CREATE_DETAIL" data.members.0.skillId)"
LABEL_CREATE="$(curl -fsS -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $ADMIN_CSRF" \
-H "Content-Type: application/json" -X POST "$BASE_URL/api/v1/admin/labels" \
-d "{\"slug\":\"$LABEL_SLUG\",\"type\":\"RECOMMENDED\",\"visibleInFilter\":true,\"sortOrder\":10,\"translations\":[{\"locale\":\"en\",\"displayName\":\"Bundle smoke\"}]}")"
assert_code "create Suite smoke label definition" "$LABEL_CREATE"
LABEL_CREATED=true
LABEL_ATTACH="$(curl -fsS -b "$ADMIN_COOKIE" -H "X-XSRF-TOKEN: $ADMIN_CSRF" \
-X PUT "$BASE_URL/api/web/suites/global/$SUITE_SLUG/labels/$LABEL_SLUG")"
assert_code "attach direct Suite label" "$LABEL_ATTACH"
publish_public_suite "$SUITE_VERSION_ID"
ENTRY_DETAIL="$(curl -fsS -b "$ADMIN_COOKIE" "$BASE_URL/api/web/skills/global/$ENTRY_SLUG")"
JSON_INPUT="$ENTRY_DETAIL" SUITE="$SUITE_SLUG" python3 - <<'PY'
import json
import os
items = json.loads(os.environ["JSON_INPUT"])["data"]["memberOfSuites"]["items"]
match = next(item for item in items if item["slug"] == os.environ["SUITE"])
assert match["currentSkillEntry"] is True
PY
echo "PASS: Entry Skill reverse discovery identifies the published Suite"
REFERENCE_DETAIL="$(curl -fsS -b "$USER_COOKIE" "$BASE_URL/api/web/skills/global/$REFERENCE_SLUG")"
JSON_INPUT="$REFERENCE_DETAIL" SUITE="$SUITE_SLUG" python3 - <<'PY'
import json
import os
items = json.loads(os.environ["JSON_INPUT"])["data"]["memberOfSuites"]["items"]
match = next(item for item in items if item["slug"] == os.environ["SUITE"])
assert match["currentSkillEntry"] is False
PY
echo "PASS: non-entry foreign Skill reverse discovery identifies the published Suite"
ENTRY_FILE="$(curl -fsS -b "$ADMIN_COOKIE" \
"$BASE_URL/api/web/skills/global/$ENTRY_SLUG/versions/1.0.0/file?path=SKILL.md")"
if [[ "$ENTRY_FILE" != *"# Bundle entry"* ]]; then
echo "FAIL: pinned Entry Skill instructions do not contain the expected content" >&2
exit 1
fi
echo "PASS: read the pinned Entry Skill instructions"
run_bundle UPDATE 1.1.0 1.0.0
UPDATE_DETAIL="$(curl -fsS -b "$ADMIN_COOKIE" "$BASE_URL/api/web/suites/global/$SUITE_SLUG?version=1.1.0")"
assert_code "load updated Suite draft" "$UPDATE_DETAIL"
UPDATE_VERSION_ID="$(json_field "$UPDATE_DETAIL" data.versionId)"
publish_public_suite "$UPDATE_VERSION_ID"
LABELS="$(curl -fsS -b "$ADMIN_COOKIE" "$BASE_URL/api/web/suites/global/$SUITE_SLUG/labels")"
JSON_INPUT="$LABELS" LABEL="$LABEL_SLUG" python3 - <<'PY'
import json
import os
items = json.loads(os.environ["JSON_INPUT"])["data"]
assert any(item["slug"] == os.environ["LABEL"] for item in items)
PY
echo "PASS: Suite label persists after Bundle version update"
echo "=== Suite Bundle Release Compose Smoke Test Passed ==="