mirror of
https://github.com/iflytek/skillhub.git
synced 2026-11-15 09:11:18 +00:00
596 lines
22 KiB
Bash
Executable file
596 lines
22 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${1:-http://localhost:8080}"
|
|
PASS=0
|
|
FAIL=0
|
|
USER_COOKIE="$(mktemp)"
|
|
ADMIN_COOKIE="$(mktemp)"
|
|
WORK_DIR="$(mktemp -d)"
|
|
SLUG="psmoke$(date +%s)${RANDOM}"
|
|
|
|
cleanup() {
|
|
rm -f "$USER_COOKIE" "$ADMIN_COOKIE"
|
|
rm -rf "$WORK_DIR"
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
pass() {
|
|
echo "PASS: $1"
|
|
PASS=$((PASS + 1))
|
|
}
|
|
|
|
fail() {
|
|
echo "FAIL: $1"
|
|
FAIL=$((FAIL + 1))
|
|
}
|
|
|
|
csrf_token() {
|
|
local cookie_file="$1"
|
|
awk '$6 == "XSRF-TOKEN" { print $7 }' "$cookie_file" | tail -n 1
|
|
}
|
|
|
|
bootstrap_csrf() {
|
|
local cookie_file="$1"
|
|
local user_id="$2"
|
|
curl -s -c "$cookie_file" -H "X-Mock-User-Id: $user_id" "$BASE_URL/api/v1/auth/providers" >/dev/null
|
|
}
|
|
|
|
json_field() {
|
|
local json="$1"
|
|
local expr="$2"
|
|
JSON_INPUT="$json" python3 - "$expr" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
expr = sys.argv[1]
|
|
value = json.loads(os.environ["JSON_INPUT"])
|
|
for part in expr.split("."):
|
|
if part.isdigit():
|
|
value = value[int(part)]
|
|
else:
|
|
value = value[part]
|
|
if isinstance(value, (dict, list)):
|
|
print(json.dumps(value, ensure_ascii=False))
|
|
else:
|
|
print(value)
|
|
PY
|
|
}
|
|
|
|
assert_code() {
|
|
local description="$1"
|
|
local json="$2"
|
|
local expected="$3"
|
|
local actual
|
|
actual="$(json_field "$json" "code")"
|
|
if [[ "$actual" == "$expected" ]]; then
|
|
pass "$description"
|
|
else
|
|
fail "$description (expected code $expected, got $actual)"
|
|
fi
|
|
}
|
|
|
|
echo "=== Promotion Workflow Smoke Test ==="
|
|
echo "Target: $BASE_URL"
|
|
echo "Slug: $SLUG"
|
|
echo
|
|
|
|
bootstrap_csrf "$USER_COOKIE" "local-user"
|
|
bootstrap_csrf "$ADMIN_COOKIE" "local-admin"
|
|
|
|
USER_CSRF="$(csrf_token "$USER_COOKIE")"
|
|
ADMIN_CSRF="$(csrf_token "$ADMIN_COOKIE")"
|
|
|
|
if [[ -z "$USER_CSRF" || -z "$ADMIN_CSRF" ]]; then
|
|
echo "Could not bootstrap CSRF tokens"
|
|
exit 1
|
|
fi
|
|
|
|
GLOBAL_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
"$BASE_URL/api/web/namespaces/global")"
|
|
assert_code "Global namespace detail is available" "$GLOBAL_RESPONSE" "0"
|
|
GLOBAL_NAMESPACE_ID="$(json_field "$GLOBAL_RESPONSE" "data.id")"
|
|
|
|
CREATE_NAMESPACE_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" \
|
|
-H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/namespaces" \
|
|
-d "{\"slug\":\"$SLUG\",\"displayName\":\"Promotion Smoke $SLUG\",\"description\":\"promotion smoke test\"}")"
|
|
assert_code "Admin can create promotion smoke namespace" "$CREATE_NAMESPACE_RESPONSE" "0"
|
|
NAMESPACE_ID="$(json_field "$CREATE_NAMESPACE_RESPONSE" "data.id")"
|
|
|
|
ADD_MEMBER_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" \
|
|
-H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/namespaces/$SLUG/members" \
|
|
-d '{"userId":"local-user","role":"MEMBER"}')"
|
|
assert_code "Admin can add the regular user as a namespace member" "$ADD_MEMBER_RESPONSE" "0"
|
|
|
|
cat > "$WORK_DIR/SKILL.md" <<EOF
|
|
---
|
|
name: Promotion Smoke $SLUG
|
|
description: Promotion smoke test
|
|
version: 1.0.0
|
|
---
|
|
Body
|
|
EOF
|
|
python3 - "$WORK_DIR" <<'PY'
|
|
from pathlib import Path
|
|
import sys
|
|
import zipfile
|
|
|
|
work_dir = Path(sys.argv[1])
|
|
with zipfile.ZipFile(work_dir / "skill.zip", "w", zipfile.ZIP_DEFLATED) as archive:
|
|
archive.write(work_dir / "SKILL.md", "SKILL.md")
|
|
PY
|
|
|
|
PUBLISH_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" \
|
|
-F "file=@$WORK_DIR/skill.zip;type=application/zip" \
|
|
-F "visibility=PUBLIC" \
|
|
"$BASE_URL/api/web/skills/$SLUG/publish")"
|
|
assert_code "Regular user can publish a team skill for review" "$PUBLISH_RESPONSE" "0"
|
|
SKILL_ID="$(json_field "$PUBLISH_RESPONSE" "data.skillId")"
|
|
SKILL_SLUG="$(json_field "$PUBLISH_RESPONSE" "data.slug")"
|
|
|
|
REVIEW_READY=false
|
|
SKILL_DETAIL_RESPONSE=""
|
|
for _ in $(seq 1 60); do
|
|
SKILL_DETAIL_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
"$BASE_URL/api/web/skills/$SLUG/$SKILL_SLUG")"
|
|
if JSON_INPUT="$SKILL_DETAIL_RESPONSE" python3 - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
data = json.loads(os.environ["JSON_INPUT"]).get("data") or {}
|
|
versions = [data.get("ownerPreviewVersion") or {}, data.get("headlineVersion") or {}]
|
|
raise SystemExit(0 if any(version.get("status") == "PENDING_REVIEW" for version in versions) else 1)
|
|
PY
|
|
then
|
|
REVIEW_READY=true
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
assert_code "Regular user can load the submitted team skill" "$SKILL_DETAIL_RESPONSE" "0"
|
|
if [[ "$REVIEW_READY" != "true" ]]; then
|
|
fail "Published team skill did not finish scanning within 60 seconds"
|
|
exit 1
|
|
fi
|
|
pass "Published team skill is ready for review"
|
|
|
|
PENDING_REVIEWS_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
"$BASE_URL/api/web/reviews?status=PENDING&namespaceId=$NAMESPACE_ID")"
|
|
assert_code "Admin can list pending skill reviews" "$PENDING_REVIEWS_RESPONSE" "0"
|
|
REVIEW_ID="$(JSON_INPUT="$PENDING_REVIEWS_RESPONSE" python3 - "$SKILL_SLUG" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
skill_slug = sys.argv[1]
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
match = next((item for item in items if item["skillSlug"] == skill_slug), None)
|
|
print(match["id"] if match else "")
|
|
PY
|
|
)"
|
|
if [[ -z "$REVIEW_ID" ]]; then
|
|
fail "Pending skill reviews should contain the published team skill"
|
|
exit 1
|
|
fi
|
|
pass "Pending skill reviews contain the published team skill"
|
|
|
|
APPROVE_REVIEW_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$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":"approved by promotion smoke"}')"
|
|
assert_code "Admin can approve the regular user's team skill" "$APPROVE_REVIEW_RESPONSE" "0"
|
|
|
|
SKILL_DETAIL_RESPONSE=""
|
|
PROMOTION_READY=false
|
|
for _ in $(seq 1 60); do
|
|
SKILL_DETAIL_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
"$BASE_URL/api/web/skills/$SLUG/$SKILL_SLUG")"
|
|
if JSON_INPUT="$SKILL_DETAIL_RESPONSE" python3 - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
response = json.loads(os.environ["JSON_INPUT"])
|
|
data = response.get("data") or {}
|
|
headline_version = data.get("headlineVersion") or {}
|
|
raise SystemExit(0 if headline_version.get("id") and data.get("canSubmitPromotion") else 1)
|
|
PY
|
|
then
|
|
PROMOTION_READY=true
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
assert_code "Regular user can load team skill detail" "$SKILL_DETAIL_RESPONSE" "0"
|
|
if [[ "$PROMOTION_READY" != "true" ]]; then
|
|
fail "Published team skill did not become promotable within 60 seconds"
|
|
exit 1
|
|
fi
|
|
VERSION_ID="$(json_field "$SKILL_DETAIL_RESPONSE" "data.headlineVersion.id")"
|
|
CAN_SUBMIT_PROMOTION="$(json_field "$SKILL_DETAIL_RESPONSE" "data.canSubmitPromotion")"
|
|
if [[ "$CAN_SUBMIT_PROMOTION" == "True" || "$CAN_SUBMIT_PROMOTION" == "true" ]]; then
|
|
pass "Approved team skill is marked promotable"
|
|
else
|
|
fail "Approved team skill should expose canSubmitPromotion=true"
|
|
fi
|
|
|
|
MY_SKILLS_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
"$BASE_URL/api/web/me/skills")"
|
|
assert_code "Regular user can list my skills with promotion metadata" "$MY_SKILLS_RESPONSE" "0"
|
|
if JSON_INPUT="$MY_SKILLS_RESPONSE" python3 - "$SKILL_ID" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
skill_id = int(sys.argv[1])
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
match = next(item for item in items if item["id"] == skill_id)
|
|
headline_version = match.get("headlineVersion") or {}
|
|
raise SystemExit(0 if match["canSubmitPromotion"] and headline_version.get("id") else 1)
|
|
PY
|
|
then
|
|
pass "My skills response exposes promotion submission fields"
|
|
else
|
|
fail "My skills response should expose headlineVersion and canSubmitPromotion"
|
|
fi
|
|
|
|
SUBMIT_PROMOTION_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" \
|
|
-H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotions" \
|
|
-d "{\"sourceSkillId\":$SKILL_ID,\"sourceVersionId\":$VERSION_ID,\"targetNamespaceId\":$GLOBAL_NAMESPACE_ID}")"
|
|
assert_code "Regular user can submit promotion to global namespace" "$SUBMIT_PROMOTION_RESPONSE" "0"
|
|
|
|
PENDING_PROMOTIONS_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
"$BASE_URL/api/web/promotions?status=PENDING")"
|
|
assert_code "Admin can list pending promotions" "$PENDING_PROMOTIONS_RESPONSE" "0"
|
|
if JSON_INPUT="$PENDING_PROMOTIONS_RESPONSE" python3 - "$SKILL_ID" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
skill_id = int(sys.argv[1])
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
raise SystemExit(0 if any(item["sourceSkillId"] == skill_id for item in items) else 1)
|
|
PY
|
|
then
|
|
pass "Pending promotions list contains the submitted team skill"
|
|
else
|
|
fail "Pending promotions list should include submitted team skill"
|
|
fi
|
|
|
|
PROMOTION_ID="$(JSON_INPUT="$PENDING_PROMOTIONS_RESPONSE" python3 - "$SKILL_ID" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
skill_id = int(sys.argv[1])
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
match = next((item for item in items if item["sourceSkillId"] == skill_id), None)
|
|
print(match["id"] if match else "")
|
|
PY
|
|
)"
|
|
if [[ -z "$PROMOTION_ID" ]]; then
|
|
fail "Pending promotions should contain the submitted team skill"
|
|
exit 1
|
|
fi
|
|
|
|
UNAUTHORIZED_APPROVAL_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" \
|
|
-H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotions/$PROMOTION_ID/approve" \
|
|
-d '{"comment":"unauthorized"}')"
|
|
assert_code "Regular user cannot approve a promotion" "$UNAUTHORIZED_APPROVAL_RESPONSE" "403"
|
|
|
|
PROMOTION_AFTER_DENIAL_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
"$BASE_URL/api/web/promotions?status=PENDING")"
|
|
assert_code "Admin can reload pending promotions after denied approval" "$PROMOTION_AFTER_DENIAL_RESPONSE" "0"
|
|
if JSON_INPUT="$PROMOTION_AFTER_DENIAL_RESPONSE" python3 - "$PROMOTION_ID" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
promotion_id = int(sys.argv[1])
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
match = next((item for item in items if item["id"] == promotion_id), None)
|
|
raise SystemExit(0 if match and match["status"] == "PENDING" and match.get("targetSkillId") is None else 1)
|
|
PY
|
|
then
|
|
pass "Denied approval keeps the promotion pending without a target"
|
|
else
|
|
fail "Denied approval should keep the promotion pending without a target"
|
|
fi
|
|
|
|
APPROVE_PROMOTION_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" \
|
|
-H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotions/$PROMOTION_ID/approve" \
|
|
-d '{"comment":"approved by promotion smoke"}')"
|
|
assert_code "Admin can approve promotion to global namespace" "$APPROVE_PROMOTION_RESPONSE" "0"
|
|
TARGET_SKILL_ID="$(json_field "$APPROVE_PROMOTION_RESPONSE" "data.targetSkillId")"
|
|
if [[ -n "$TARGET_SKILL_ID" && "$TARGET_SKILL_ID" != "None" && "$TARGET_SKILL_ID" != "null" ]]; then
|
|
pass "Approved promotion exposes target skill id"
|
|
else
|
|
fail "Approved promotion should expose target skill id"
|
|
fi
|
|
|
|
GLOBAL_VERSIONS_RESPONSE="$(curl -sS \
|
|
"$BASE_URL/api/web/skills/global/$SKILL_SLUG/versions")"
|
|
assert_code "Promoted global skill versions are visible" "$GLOBAL_VERSIONS_RESPONSE" "0"
|
|
if JSON_INPUT="$GLOBAL_VERSIONS_RESPONSE" python3 - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
raise SystemExit(0 if any(
|
|
item["version"] == "1.0.0"
|
|
and item["status"] == "PUBLISHED"
|
|
and item["downloadAvailable"]
|
|
for item in items
|
|
) else 1)
|
|
PY
|
|
then
|
|
pass "Promoted global version is published and downloadable"
|
|
else
|
|
fail "Promoted global version should be PUBLISHED with downloadAvailable=true"
|
|
fi
|
|
|
|
GLOBAL_BUNDLE="$WORK_DIR/global-skill.zip"
|
|
GLOBAL_DOWNLOAD_STATUS="$(curl -sS -L -o "$GLOBAL_BUNDLE" -w '%{http_code}' \
|
|
"$BASE_URL/api/web/skills/global/$SKILL_SLUG/versions/1.0.0/download")"
|
|
if [[ "$GLOBAL_DOWNLOAD_STATUS" == "200" ]]; then
|
|
pass "Promoted global version download returns HTTP 200"
|
|
else
|
|
fail "Promoted global version download should return HTTP 200 (got $GLOBAL_DOWNLOAD_STATUS)"
|
|
fi
|
|
|
|
if python3 - "$GLOBAL_BUNDLE" <<'PY'
|
|
import sys
|
|
import zipfile
|
|
|
|
with zipfile.ZipFile(sys.argv[1]) as archive:
|
|
content = archive.read("SKILL.md").decode("utf-8")
|
|
raise SystemExit(0 if "Body" in content.splitlines() else 1)
|
|
PY
|
|
then
|
|
pass "Promoted global bundle contains the source SKILL.md"
|
|
else
|
|
fail "Promoted global bundle should contain the source SKILL.md"
|
|
fi
|
|
|
|
# A published team update goes through a second, separate global review.
|
|
cat > "$WORK_DIR/SKILL.md" <<EOF
|
|
---
|
|
name: Promotion Smoke $SLUG
|
|
description: Promotion smoke test updated
|
|
version: 1.1.0
|
|
---
|
|
Updated Body
|
|
EOF
|
|
python3 - "$WORK_DIR" <<'PY'
|
|
from pathlib import Path
|
|
import sys
|
|
import zipfile
|
|
|
|
work_dir = Path(sys.argv[1])
|
|
with zipfile.ZipFile(work_dir / "skill-update.zip", "w", zipfile.ZIP_DEFLATED) as archive:
|
|
archive.write(work_dir / "SKILL.md", "SKILL.md")
|
|
PY
|
|
|
|
UPDATE_PUBLISH_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" \
|
|
-F "file=@$WORK_DIR/skill-update.zip;type=application/zip" \
|
|
-F "visibility=PUBLIC" \
|
|
"$BASE_URL/api/web/skills/$SLUG/publish")"
|
|
assert_code "Owner can submit a new team version" "$UPDATE_PUBLISH_RESPONSE" "0"
|
|
|
|
UPDATE_REVIEW_READY=false
|
|
for _ in $(seq 1 60); do
|
|
SKILL_DETAIL_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
"$BASE_URL/api/web/skills/$SLUG/$SKILL_SLUG")"
|
|
if JSON_INPUT="$SKILL_DETAIL_RESPONSE" python3 - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
data = json.loads(os.environ["JSON_INPUT"]).get("data") or {}
|
|
version = data.get("ownerPreviewVersion") or {}
|
|
raise SystemExit(0 if version.get("version") == "1.1.0" and version.get("status") == "PENDING_REVIEW" else 1)
|
|
PY
|
|
then
|
|
UPDATE_REVIEW_READY=true
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if [[ "$UPDATE_REVIEW_READY" != "true" ]]; then
|
|
fail "New team version did not finish scanning within 60 seconds"
|
|
exit 1
|
|
fi
|
|
pass "New team version is ready for review"
|
|
|
|
UPDATE_REVIEW_ID=""
|
|
for _ in $(seq 1 60); do
|
|
PENDING_REVIEWS_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
"$BASE_URL/api/web/reviews?status=PENDING&namespaceId=$NAMESPACE_ID")"
|
|
UPDATE_REVIEW_ID="$(JSON_INPUT="$PENDING_REVIEWS_RESPONSE" python3 - "$SKILL_SLUG" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
match = next((item for item in items if item["skillSlug"] == sys.argv[1]), None)
|
|
print(match["id"] if match else "")
|
|
PY
|
|
)"
|
|
if [[ -n "$UPDATE_REVIEW_ID" ]]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if [[ -z "$UPDATE_REVIEW_ID" ]]; then
|
|
fail "New team version should become reviewable"
|
|
exit 1
|
|
fi
|
|
|
|
APPROVE_UPDATE_REVIEW_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/reviews/$UPDATE_REVIEW_ID/approve" \
|
|
-d '{"comment":"approved team update"}')"
|
|
assert_code "Admin approves the team update first" "$APPROVE_UPDATE_REVIEW_RESPONSE" "0"
|
|
|
|
TEAM_VERSIONS_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
"$BASE_URL/api/web/skills/$SLUG/$SKILL_SLUG/versions")"
|
|
TEAM_UPDATE_VERSION_ID="$(JSON_INPUT="$TEAM_VERSIONS_RESPONSE" python3 - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
match = next((item for item in items if item["version"] == "1.1.0" and item["status"] == "PUBLISHED"), None)
|
|
print(match["id"] if match else "")
|
|
PY
|
|
)"
|
|
if [[ -z "$TEAM_UPDATE_VERSION_ID" ]]; then
|
|
fail "Team update must be PUBLISHED before global submission"
|
|
exit 1
|
|
fi
|
|
pass "Team update is published before global submission"
|
|
|
|
SUBMIT_UPDATE_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotions" \
|
|
-d "{\"sourceSkillId\":$SKILL_ID,\"sourceVersionId\":$TEAM_UPDATE_VERSION_ID,\"targetNamespaceId\":$GLOBAL_NAMESPACE_ID}")"
|
|
assert_code "Owner submits the published team version to existing global skill" "$SUBMIT_UPDATE_RESPONSE" "0"
|
|
UPDATE_PROMOTION_ID="$(json_field "$SUBMIT_UPDATE_RESPONSE" "data.id")"
|
|
if JSON_INPUT="$SUBMIT_UPDATE_RESPONSE" python3 - "$TARGET_SKILL_ID" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
data = json.loads(os.environ["JSON_INPUT"])["data"]
|
|
raise SystemExit(0 if data["requestKind"] == "UPDATE" and data["targetSkillId"] == int(sys.argv[1]) else 1)
|
|
PY
|
|
then
|
|
pass "Update request remains linked to the original global skill"
|
|
else
|
|
fail "Update request should target the original global skill"
|
|
fi
|
|
|
|
APPROVE_UPDATE_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotions/$UPDATE_PROMOTION_ID/approve" \
|
|
-d '{"comment":"approved global update"}')"
|
|
assert_code "Admin approves update to existing global skill" "$APPROVE_UPDATE_RESPONSE" "0"
|
|
|
|
GLOBAL_VERSIONS_RESPONSE="$(curl -sS "$BASE_URL/api/web/skills/global/$SKILL_SLUG/versions")"
|
|
if JSON_INPUT="$GLOBAL_VERSIONS_RESPONSE" python3 - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
published = {item["version"] for item in items if item["status"] == "PUBLISHED"}
|
|
raise SystemExit(0 if {"1.0.0", "1.1.0"}.issubset(published) else 1)
|
|
PY
|
|
then
|
|
pass "Global skill retains both published versions"
|
|
else
|
|
fail "Global skill should retain both published versions"
|
|
fi
|
|
|
|
SUBMIT_REVOCATION_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotion-revocations" \
|
|
-d "{\"sourceSkillId\":$SKILL_ID,\"reason\":\"promotion smoke cleanup\"}")"
|
|
assert_code "Owner requests revocation of the global derivative" "$SUBMIT_REVOCATION_RESPONSE" "0"
|
|
REVOCATION_ID="$(json_field "$SUBMIT_REVOCATION_RESPONSE" "data.id")"
|
|
|
|
UNAUTHORIZED_REVOCATION_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotion-revocations/$REVOCATION_ID/approve" \
|
|
-d '{"comment":"unauthorized"}')"
|
|
assert_code "Owner cannot approve their own revocation request" "$UNAUTHORIZED_REVOCATION_RESPONSE" "403"
|
|
|
|
APPROVE_REVOCATION_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotion-revocations/$REVOCATION_ID/approve" \
|
|
-d '{"comment":"approved revocation"}')"
|
|
assert_code "Admin approves global revocation" "$APPROVE_REVOCATION_RESPONSE" "0"
|
|
|
|
REVOKED_GLOBAL_STATUS="$(curl -sS -o "$WORK_DIR/revoked-global.json" -w '%{http_code}' \
|
|
"$BASE_URL/api/web/skills/global/$SKILL_SLUG")"
|
|
if [[ "$REVOKED_GLOBAL_STATUS" == "400" || "$REVOKED_GLOBAL_STATUS" == "404" ]] \
|
|
&& JSON_INPUT="$(cat "$WORK_DIR/revoked-global.json")" python3 - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
response = json.loads(os.environ["JSON_INPUT"])
|
|
raise SystemExit(0 if response.get("code") in (400, 404)
|
|
and response.get("data") is None
|
|
and "not found" in response.get("msg", "").lower() else 1)
|
|
PY
|
|
then
|
|
pass "Revoked global skill detail is unavailable"
|
|
else
|
|
fail "Revoked global skill detail should be unavailable (got HTTP $REVOKED_GLOBAL_STATUS)"
|
|
fi
|
|
|
|
TEAM_BUNDLE="$WORK_DIR/team-update.zip"
|
|
TEAM_DOWNLOAD_STATUS="$(curl -sS -L -o "$TEAM_BUNDLE" -w '%{http_code}' \
|
|
-H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" \
|
|
"$BASE_URL/api/web/skills/$SLUG/$SKILL_SLUG/versions/1.1.0/download")"
|
|
if [[ "$TEAM_DOWNLOAD_STATUS" == "200" ]] && python3 - "$TEAM_BUNDLE" <<'PY'
|
|
import sys
|
|
import zipfile
|
|
|
|
with zipfile.ZipFile(sys.argv[1]) as archive:
|
|
content = archive.read("SKILL.md").decode("utf-8")
|
|
raise SystemExit(0 if "Updated Body" in content else 1)
|
|
PY
|
|
then
|
|
pass "Team source files remain downloadable after revocation"
|
|
else
|
|
fail "Team source files must remain readable after global revocation"
|
|
fi
|
|
|
|
REPROMOTION_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-user" -b "$USER_COOKIE" -c "$USER_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $USER_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotions" \
|
|
-d "{\"sourceSkillId\":$SKILL_ID,\"sourceVersionId\":$TEAM_UPDATE_VERSION_ID,\"targetNamespaceId\":$GLOBAL_NAMESPACE_ID}")"
|
|
assert_code "Owner can submit a new initial promotion after revocation" "$REPROMOTION_RESPONSE" "0"
|
|
REPROMOTION_ID="$(json_field "$REPROMOTION_RESPONSE" "data.id")"
|
|
if [[ "$(json_field "$REPROMOTION_RESPONSE" "data.requestKind")" == "INITIAL" ]]; then
|
|
pass "Re-promotion starts a new application instead of restoring the old target"
|
|
else
|
|
fail "Re-promotion should start a new initial application"
|
|
fi
|
|
|
|
APPROVE_REPROMOTION_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotions/$REPROMOTION_ID/approve" \
|
|
-d '{"comment":"approved re-promotion"}')"
|
|
assert_code "Admin can approve re-promotion using the released address" "$APPROVE_REPROMOTION_RESPONSE" "0"
|
|
NEW_TARGET_SKILL_ID="$(json_field "$APPROVE_REPROMOTION_RESPONSE" "data.targetSkillId")"
|
|
if [[ "$NEW_TARGET_SKILL_ID" != "$TARGET_SKILL_ID" ]]; then
|
|
pass "Re-promotion creates a new global skill ID"
|
|
else
|
|
fail "Re-promotion must not restore the deleted global skill ID"
|
|
fi
|
|
|
|
DIRECT_REVOKE_RESPONSE="$(curl -sS -H "X-Mock-User-Id: local-admin" -b "$ADMIN_COOKIE" -c "$ADMIN_COOKIE" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" -H "Content-Type: application/json" \
|
|
-X POST "$BASE_URL/api/web/promotion-revocations/source/$SKILL_ID/direct" \
|
|
-d '{"reason":"promotion smoke cleanup after re-promotion"}')"
|
|
assert_code "Admin can directly revoke the new global derivative" "$DIRECT_REVOKE_RESPONSE" "0"
|
|
|
|
echo
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
if [[ "$FAIL" -ne 0 ]]; then
|
|
exit 1
|
|
fi
|