mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-28 11:25:00 +00:00
344 lines
12 KiB
Bash
Executable file
344 lines
12 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${1:-http://localhost:8080}"
|
|
PASS=0
|
|
FAIL=0
|
|
COOKIE_JAR="$(mktemp)"
|
|
REGISTER_RESPONSE_FILE="$(mktemp)"
|
|
AUTH_METHODS_RESPONSE_FILE="$(mktemp)"
|
|
AUTH_PROVIDERS_RESPONSE_FILE="$(mktemp)"
|
|
USERNAME="smoketest_$(date +%s)"
|
|
EMAIL="${USERNAME}@example.com"
|
|
PASSWORD="Smoke@2026"
|
|
NEW_PASSWORD="Smoke@2027"
|
|
|
|
cleanup() {
|
|
rm -f \
|
|
"$COOKIE_JAR" \
|
|
"$REGISTER_RESPONSE_FILE" \
|
|
"$AUTH_METHODS_RESPONSE_FILE" \
|
|
"$AUTH_PROVIDERS_RESPONSE_FILE"
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
check() {
|
|
local desc="$1"
|
|
local url="$2"
|
|
local expected="$3"
|
|
local status
|
|
status="$(curl --retry 3 --retry-delay 1 --max-time 10 -s -o /dev/null -w "%{http_code}" "$url" || true)"
|
|
if [[ "$status" == "$expected" ]]; then
|
|
echo "PASS: $desc (HTTP $status)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: $desc (expected $expected, got $status)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
echo "=== SkillHub Smoke Test ==="
|
|
echo "Target: $BASE_URL"
|
|
echo
|
|
|
|
check "Health endpoint" "$BASE_URL/actuator/health" "200"
|
|
check "Prometheus metrics requires auth" "$BASE_URL/actuator/prometheus" "401"
|
|
check "Namespaces API requires auth" "$BASE_URL/api/v1/namespaces" "401"
|
|
check "Auth required" "$BASE_URL/api/v1/auth/me" "401"
|
|
check "Unknown OAuth provider fails before upstream redirect" \
|
|
"$BASE_URL/oauth2/authorization/__missing_provider__" "403"
|
|
|
|
AUTH_METHODS_STATUS="$(curl --max-time 10 -s \
|
|
-o "$AUTH_METHODS_RESPONSE_FILE" \
|
|
-w "%{http_code}" \
|
|
"$BASE_URL/api/v1/auth/methods" || true)"
|
|
AUTH_PROVIDERS_STATUS="$(curl --max-time 10 -s \
|
|
-o "$AUTH_PROVIDERS_RESPONSE_FILE" \
|
|
-w "%{http_code}" \
|
|
"$BASE_URL/api/v1/auth/providers" || true)"
|
|
if [[ "$AUTH_METHODS_STATUS" == "200" \
|
|
&& "$AUTH_PROVIDERS_STATUS" == "200" ]] \
|
|
&& python3 - \
|
|
"$AUTH_METHODS_RESPONSE_FILE" \
|
|
"$AUTH_PROVIDERS_RESPONSE_FILE" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
with open(sys.argv[1], encoding="utf-8") as response:
|
|
methods = json.load(response)["data"]
|
|
with open(sys.argv[2], encoding="utf-8") as response:
|
|
providers = json.load(response)["data"]
|
|
|
|
method_ids = {method["id"] for method in methods}
|
|
oauth_methods = {
|
|
method["provider"]: method
|
|
for method in methods
|
|
if method["methodType"] == "OAUTH_REDIRECT"
|
|
}
|
|
oauth_providers = {provider["id"]: provider for provider in providers}
|
|
|
|
valid = (
|
|
"local-password" in method_ids
|
|
and oauth_methods.keys() == oauth_providers.keys()
|
|
and all(
|
|
method["actionUrl"].startswith(
|
|
f"/oauth2/authorization/{provider_code}"
|
|
)
|
|
for provider_code, method in oauth_methods.items()
|
|
)
|
|
)
|
|
raise SystemExit(0 if valid else 1)
|
|
PY
|
|
then
|
|
echo "PASS: Authentication catalog exposes only reconciled OAuth providers"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Authentication catalog is inconsistent (methods HTTP $AUTH_METHODS_STATUS, providers HTTP $AUTH_PROVIDERS_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
curl -s -c "$COOKIE_JAR" "$BASE_URL/api/v1/auth/me" >/dev/null
|
|
CSRF_TOKEN="$(awk '$6 == "XSRF-TOKEN" { print $7 }' "$COOKIE_JAR" | tail -n 1)"
|
|
|
|
REGISTER_STATUS="$(curl --max-time 10 -s -o "$REGISTER_RESPONSE_FILE" -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/auth/local/register" \
|
|
-b "$COOKIE_JAR" \
|
|
-c "$COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $CSRF_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\",\"email\":\"$EMAIL\"}" || true)"
|
|
if [[ "$REGISTER_STATUS" == "200" ]]; then
|
|
echo "PASS: Register (HTTP $REGISTER_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Register (got $REGISTER_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
REGISTERED_USER_ID="$(python3 - "$REGISTER_RESPONSE_FILE" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
try:
|
|
with open(sys.argv[1], encoding="utf-8") as response:
|
|
print(json.load(response)["data"]["userId"])
|
|
except (KeyError, TypeError, json.JSONDecodeError):
|
|
pass
|
|
PY
|
|
)"
|
|
|
|
AUTH_ME_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR" "$BASE_URL/api/v1/auth/me" || true)"
|
|
if [[ "$AUTH_ME_STATUS" == "200" ]]; then
|
|
echo "PASS: Auth me with session (HTTP $AUTH_ME_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Auth me with session (got $AUTH_ME_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
NAMESPACES_AUTH_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR" "$BASE_URL/api/v1/namespaces" || true)"
|
|
if [[ "$NAMESPACES_AUTH_STATUS" == "200" ]]; then
|
|
echo "PASS: Namespaces API with session (HTTP $NAMESPACES_AUTH_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Namespaces API with session (got $NAMESPACES_AUTH_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
CHANGE_PASSWORD_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/auth/local/change-password" \
|
|
-b "$COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $CSRF_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"currentPassword\":\"$PASSWORD\",\"newPassword\":\"$NEW_PASSWORD\"}" || true)"
|
|
if [[ "$CHANGE_PASSWORD_STATUS" == "200" ]]; then
|
|
echo "PASS: Change password (HTTP $CHANGE_PASSWORD_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Change password (got $CHANGE_PASSWORD_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
LOGOUT_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/auth/logout" \
|
|
-b "$COOKIE_JAR" \
|
|
-c "$COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $CSRF_TOKEN" || true)"
|
|
if [[ "$LOGOUT_STATUS" == "302" || "$LOGOUT_STATUS" == "200" || "$LOGOUT_STATUS" == "204" ]]; then
|
|
echo "PASS: Logout (HTTP $LOGOUT_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Logout (got $LOGOUT_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
POST_LOGOUT_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR" "$BASE_URL/api/v1/auth/me" || true)"
|
|
if [[ "$POST_LOGOUT_STATUS" == "401" ]]; then
|
|
echo "PASS: Auth me after logout (HTTP $POST_LOGOUT_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Auth me after logout (got $POST_LOGOUT_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
# ---- Label Management (requires admin) ----
|
|
ADMIN_USERNAME="${BOOTSTRAP_ADMIN_USERNAME:-admin}"
|
|
ADMIN_PASSWORD="${BOOTSTRAP_ADMIN_PASSWORD:-ChangeMe!2026}"
|
|
ADMIN_COOKIE_JAR="$(mktemp)"
|
|
LABEL_SLUG="smoke-label-$(date +%s)"
|
|
|
|
cleanup_admin() {
|
|
rm -f "$ADMIN_COOKIE_JAR"
|
|
}
|
|
trap 'cleanup; cleanup_admin' EXIT
|
|
|
|
# Get CSRF token for admin session
|
|
curl -s -c "$ADMIN_COOKIE_JAR" "$BASE_URL/api/v1/auth/me" >/dev/null
|
|
ADMIN_CSRF="$(awk '$6 == "XSRF-TOKEN" { print $7 }' "$ADMIN_COOKIE_JAR" | tail -n 1)"
|
|
|
|
# Login as admin
|
|
ADMIN_LOGIN_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/auth/local/login" \
|
|
-b "$ADMIN_COOKIE_JAR" \
|
|
-c "$ADMIN_COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"username\":\"$ADMIN_USERNAME\",\"password\":\"$ADMIN_PASSWORD\"}" || true)"
|
|
if [[ "$ADMIN_LOGIN_STATUS" == "200" ]]; then
|
|
echo "PASS: Admin login (HTTP $ADMIN_LOGIN_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Admin login (got $ADMIN_LOGIN_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
# Refresh CSRF after login
|
|
ADMIN_CSRF="$(awk '$6 == "XSRF-TOKEN" { print $7 }' "$ADMIN_COOKIE_JAR" | tail -n 1)"
|
|
|
|
UNKNOWN_RECOVERY_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/admin/identity-providers/__missing_provider__/authority/recover" \
|
|
-b "$ADMIN_COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" || true)"
|
|
if [[ "$UNKNOWN_RECOVERY_STATUS" == "404" ]]; then
|
|
echo "PASS: Provider authority recovery rejects unknown provider without mutation (HTTP $UNKNOWN_RECOVERY_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Provider authority recovery unknown-provider guard (expected 404, got $UNKNOWN_RECOVERY_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
# Exercise the administrator activation workflow over HTTP. The transactional
|
|
# integration test covers the missing-membership precondition; this smoke path
|
|
# verifies the deployed controller, security, persistence, and read model.
|
|
if [[ -n "$REGISTERED_USER_ID" && "$ADMIN_LOGIN_STATUS" == "200" ]]; then
|
|
DISABLE_USER_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/admin/users/$REGISTERED_USER_ID/disable" \
|
|
-b "$ADMIN_COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" || true)"
|
|
if [[ "$DISABLE_USER_STATUS" == "200" ]]; then
|
|
echo "PASS: Admin disables smoke user (HTTP $DISABLE_USER_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Admin disables smoke user (got $DISABLE_USER_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
APPROVE_USER_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/admin/users/$REGISTERED_USER_ID/approve" \
|
|
-b "$ADMIN_COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" || true)"
|
|
if [[ "$APPROVE_USER_STATUS" == "200" ]]; then
|
|
echo "PASS: Admin activates smoke user (HTTP $APPROVE_USER_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Admin activates smoke user (got $APPROVE_USER_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
GLOBAL_MEMBERS_RESPONSE="$(curl --max-time 10 -s \
|
|
-b "$ADMIN_COOKIE_JAR" \
|
|
"$BASE_URL/api/web/namespaces/global/members?size=1000" || true)"
|
|
if JSON_INPUT="$GLOBAL_MEMBERS_RESPONSE" python3 - "$REGISTERED_USER_ID" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
user_id = sys.argv[1]
|
|
try:
|
|
items = json.loads(os.environ["JSON_INPUT"])["data"]["items"]
|
|
except (KeyError, TypeError, json.JSONDecodeError):
|
|
raise SystemExit(1)
|
|
|
|
raise SystemExit(0 if any(
|
|
item.get("userId") == user_id and item.get("role") == "MEMBER"
|
|
for item in items
|
|
) else 1)
|
|
PY
|
|
then
|
|
echo "PASS: Activated user has @global MEMBER membership"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Activated user is missing @global MEMBER membership"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
else
|
|
echo "FAIL: Cannot exercise admin activation workflow without registered user and admin session"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
# Create label definition
|
|
CREATE_LABEL_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/v1/admin/labels" \
|
|
-b "$ADMIN_COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"slug\":\"$LABEL_SLUG\",\"type\":\"RECOMMENDED\",\"visibleInFilter\":true,\"sortOrder\":99,\"translations\":[{\"locale\":\"en\",\"displayName\":\"Smoke Label\"}]}" || true)"
|
|
if [[ "$CREATE_LABEL_STATUS" == "200" ]]; then
|
|
echo "PASS: Create label definition (HTTP $CREATE_LABEL_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Create label definition (got $CREATE_LABEL_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
# List admin label definitions
|
|
LIST_LABELS_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-b "$ADMIN_COOKIE_JAR" "$BASE_URL/api/v1/admin/labels" || true)"
|
|
if [[ "$LIST_LABELS_STATUS" == "200" ]]; then
|
|
echo "PASS: List admin label definitions (HTTP $LIST_LABELS_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: List admin label definitions (got $LIST_LABELS_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
# List visible labels (public)
|
|
VISIBLE_LABELS_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
"$BASE_URL/api/v1/labels" || true)"
|
|
if [[ "$VISIBLE_LABELS_STATUS" == "200" ]]; then
|
|
echo "PASS: List visible labels (HTTP $VISIBLE_LABELS_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: List visible labels (got $VISIBLE_LABELS_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
# Delete label definition (cleanup)
|
|
DELETE_LABEL_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \
|
|
-X DELETE "$BASE_URL/api/v1/admin/labels/$LABEL_SLUG" \
|
|
-b "$ADMIN_COOKIE_JAR" \
|
|
-H "X-XSRF-TOKEN: $ADMIN_CSRF" || true)"
|
|
if [[ "$DELETE_LABEL_STATUS" == "200" || "$DELETE_LABEL_STATUS" == "204" ]]; then
|
|
echo "PASS: Delete label definition (HTTP $DELETE_LABEL_STATUS)"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: Delete label definition (got $DELETE_LABEL_STATUS)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
|
|
echo
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
if [[ "$FAIL" -ne 0 ]]; then
|
|
exit 1
|
|
fi
|