mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-24 00:55:35 +00:00
407 lines
15 KiB
Bash
Executable file
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 ==="
|