185 lines
9.9 KiB
Bash
Executable file
185 lines
9.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -u -o pipefail
|
|
|
|
ENV_FILE="${1:-.vk-smoke.env}"
|
|
failures=0
|
|
skips=0
|
|
TMP_DIR=""
|
|
|
|
result() {
|
|
local name="$1" status="$2" detail="${3:-}"
|
|
printf '%s %s' "$status" "$name"
|
|
[[ -z "$detail" ]] || printf ' — %s' "$detail"
|
|
printf '\n'
|
|
[[ "$status" != FAIL ]] || failures=$((failures + 1))
|
|
[[ "$status" != SKIP && "$status" != MANUAL ]] || skips=$((skips + 1))
|
|
}
|
|
|
|
if [[ ! -f "$ENV_FILE" || -L "$ENV_FILE" ]]; then
|
|
printf 'FAIL config — expected a regular, non-symlink 0600 file: %s\n' "$ENV_FILE"
|
|
exit 2
|
|
fi
|
|
mode="$(stat -c '%a' -- "$ENV_FILE" 2>/dev/null || true)"
|
|
if [[ "$mode" != 600 ]]; then
|
|
printf 'FAIL config — permissions must be 0600 (found %s)\n' "${mode:-unknown}"
|
|
exit 2
|
|
fi
|
|
|
|
declare -A config=()
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
[[ "$line" =~ ^[[:space:]]*(#|$) ]] && continue
|
|
[[ "$line" == *=* ]] || { printf 'FAIL config — malformed line\n'; exit 2; }
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
case "$key" in
|
|
VK_SMOKE_BASE_URL|VK_SMOKE_SESSION_COOKIE|VK_SMOKE_PROXY_AUTH|VK_SMOKE_PUBLIC_POST_URL|VK_SMOKE_PRIVATE_POST_URL|VK_SMOKE_GROUP_ID|VK_SMOKE_CREATE_GIVEAWAY) ;;
|
|
*) printf 'FAIL config — unsupported key %s\n' "$key"; exit 2 ;;
|
|
esac
|
|
[[ -z "${config[$key]+x}" ]] || { printf 'FAIL config — duplicate key %s\n' "$key"; exit 2; }
|
|
[[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || { printf 'FAIL config — multiline values are forbidden\n'; exit 2; }
|
|
config[$key]="$value"
|
|
done < "$ENV_FILE"
|
|
|
|
BASE_URL="${config[VK_SMOKE_BASE_URL]:-}"
|
|
SESSION_COOKIE="${config[VK_SMOKE_SESSION_COOKIE]:-}"
|
|
PUBLIC_POST_URL="${config[VK_SMOKE_PUBLIC_POST_URL]:-}"
|
|
PRIVATE_POST_URL="${config[VK_SMOKE_PRIVATE_POST_URL]:-}"
|
|
GROUP_ID="${config[VK_SMOKE_GROUP_ID]:-}"
|
|
ALLOW_CREATE="${config[VK_SMOKE_CREATE_GIVEAWAY]:-NO}"
|
|
|
|
if [[ ! "$BASE_URL" =~ ^http://(localhost|127\.0\.0\.1)(:[0-9]+)?$ && "$BASE_URL" != 'https://random.ochenstarik.ru' ]]; then
|
|
printf 'FAIL config — base URL must be loopback HTTP or https://random.ochenstarik.ru\n'
|
|
exit 2
|
|
fi
|
|
if [[ -n "$SESSION_COOKIE" && ! "$SESSION_COOKIE" =~ ^randomayzer_session=[A-Za-z0-9._~-]+$ ]]; then
|
|
printf 'FAIL config — session cookie format is invalid\n'
|
|
exit 2
|
|
fi
|
|
PROXY_AUTH="${config[VK_SMOKE_PROXY_AUTH]:-}"
|
|
if [[ -n "$PROXY_AUTH" && ! "$PROXY_AUTH" =~ ^[A-Za-z0-9._~-]+:[A-Za-z0-9._~-]+$ ]]; then
|
|
printf 'FAIL config — proxy auth must use safe username:password characters\n'
|
|
exit 2
|
|
fi
|
|
if [[ "$BASE_URL" == 'https://random.ochenstarik.ru' && -z "$PROXY_AUTH" ]]; then
|
|
printf 'FAIL config — VK_SMOKE_PROXY_AUTH is required for the domain smoke test\n'
|
|
exit 2
|
|
fi
|
|
|
|
umask 077
|
|
TMP_DIR="$(mktemp -d)" || {
|
|
printf 'FAIL setup — could not create a private temporary directory\n'
|
|
exit 2
|
|
}
|
|
trap 'rm -rf -- "$TMP_DIR"' EXIT
|
|
BODY="$TMP_DIR/body.json"
|
|
HEADERS="$TMP_DIR/headers.txt"
|
|
CURL_CONFIG="$TMP_DIR/curl.cfg"
|
|
printf 'silent\nshow-error\n' > "$CURL_CONFIG"
|
|
[[ -z "$SESSION_COOKIE" ]] || printf 'header = "Cookie: %s"\n' "$SESSION_COOKIE" >> "$CURL_CONFIG"
|
|
[[ -z "$PROXY_AUTH" ]] || printf 'user = "%s"\n' "$PROXY_AUTH" >> "$CURL_CONFIG"
|
|
|
|
request() {
|
|
local method="$1" path="$2" payload="${3:-}"
|
|
local args=(--config "$CURL_CONFIG" --connect-timeout 5 --max-time 120 --request "$method" --output "$BODY" --dump-header "$HEADERS" --write-out '%{http_code}')
|
|
if [[ "$method" != GET ]]; then
|
|
args+=(--header "Origin: $BASE_URL" --header 'Content-Type: application/json' --data "$payload")
|
|
fi
|
|
HTTP_CODE="$(curl "${args[@]}" "$BASE_URL$path" 2>/dev/null || true)"
|
|
}
|
|
|
|
json_success() {
|
|
node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.exit(d?.success === true ? 0 : 1)' "$BODY" >/dev/null 2>&1
|
|
}
|
|
|
|
# Marker is emitted by the server for the actual provider instance, not inferred from access mode.
|
|
json_live_success() {
|
|
node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.exit(d?.success === true && d.providerMode === "LIVE" ? 0 : 1)' "$BODY" >/dev/null 2>&1
|
|
}
|
|
json_live_import_success() {
|
|
node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.exit(d?.success === true && d.providerMode === "LIVE" && Number.isSafeInteger(d.totalCount) && d.totalCount > 0 ? 0 : 1)' "$BODY" >/dev/null 2>&1
|
|
}
|
|
|
|
# A: OAuth start creates a VK ID authorize redirect with PKCE S256.
|
|
request GET /api/auth/vk/start
|
|
if [[ "$HTTP_CODE" =~ ^30[1278]$ ]] && grep -Eiq '^location: https://id\.vk\.com/authorize[^\r]*code_challenge_method=S256' "$HEADERS"; then
|
|
result A PASS 'VK ID authorize redirect and PKCE S256 found'
|
|
else
|
|
result A FAIL "expected VK ID 3xx authorize redirect with PKCE S256 (HTTP ${HTTP_CODE:-curl-error})"
|
|
fi
|
|
|
|
# B: consent, redirect completion, and cookie delivery require a human in a browser.
|
|
result B MANUAL 'open /api/auth/vk/start, approve consent in VK, and confirm callback returns to the app with a session cookie'
|
|
|
|
# C: authenticated session lookup. No response body or cookie is printed.
|
|
if [[ -z "$SESSION_COOKIE" ]]; then
|
|
result C SKIP 'set VK_SMOKE_SESSION_COOKIE after completing manual consent'
|
|
else
|
|
request GET /api/auth/me
|
|
if [[ "$HTTP_CODE" == 200 ]] && node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.exit(d?.authenticated === true ? 0 : 1)' "$BODY" >/dev/null 2>&1; then result C PASS 'authenticated session endpoint returned success'; else result C FAIL "session endpoint returned HTTP ${HTTP_CODE:-curl-error}"; fi
|
|
fi
|
|
|
|
POST_JSON=""
|
|
GIVEAWAY_ID=""
|
|
if [[ -z "$SESSION_COOKIE" || -z "$PUBLIC_POST_URL" ]]; then
|
|
result D SKIP 'authenticated session and VK_SMOKE_PUBLIC_POST_URL are required'
|
|
else
|
|
payload="$(node -e 'process.stdout.write(JSON.stringify({url:process.argv[1],platform:"VK"}))' "$PUBLIC_POST_URL")"
|
|
request POST /api/posts/preview "$payload"
|
|
if [[ "$HTTP_CODE" == 200 ]] && json_live_success; then
|
|
access_mode="$(node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(d.effectiveCapabilities?.accessMode||"")' "$BODY")"
|
|
if [[ "$access_mode" != PUBLIC_SERVICE ]]; then
|
|
result D FAIL 'public post preview did not report PUBLIC_SERVICE access mode'
|
|
else
|
|
result D PASS 'public VK post preview succeeded with PUBLIC_SERVICE access mode'
|
|
POST_JSON="$(node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(JSON.stringify(d.post))' "$BODY")"
|
|
fi
|
|
else result D FAIL "public post preview requires LIVE provider mode (HTTP ${HTTP_CODE:-curl-error})"; fi
|
|
fi
|
|
|
|
if [[ -z "$SESSION_COOKIE" || -z "$PRIVATE_POST_URL" ]]; then
|
|
result E SKIP 'authenticated session and VK_SMOKE_PRIVATE_POST_URL are required'
|
|
else
|
|
payload="$(node -e 'process.stdout.write(JSON.stringify({url:process.argv[1],platform:"VK"}))' "$PRIVATE_POST_URL")"
|
|
request POST /api/posts/preview "$payload"
|
|
if [[ "$HTTP_CODE" == 200 ]] && json_live_success; then
|
|
mode="$(node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(d.post?.resolvedAuthType||"")' "$BODY")"
|
|
if [[ "$mode" == USER ]]; then result E PASS 'restricted post preview used organizer authorization'; else result E FAIL 'restricted post did not resolve with USER authorization'; fi
|
|
else result E FAIL "restricted post preview requires LIVE provider mode (HTTP ${HTTP_CODE:-curl-error})"; fi
|
|
fi
|
|
|
|
if [[ "$ALLOW_CREATE" != YES ]]; then
|
|
result F SKIP 'set VK_SMOKE_CREATE_GIVEAWAY=YES to create a test giveaway'
|
|
result G SKIP 'requires a giveaway created by check F'
|
|
result H SKIP 'requires a giveaway and VK_SMOKE_GROUP_ID'
|
|
elif [[ -z "$SESSION_COOKIE" || -z "$POST_JSON" ]]; then
|
|
result F FAIL 'cannot create without a successful public preview and authenticated session'
|
|
result G SKIP 'requires a giveaway created by check F'
|
|
result H SKIP 'requires a giveaway and VK_SMOKE_GROUP_ID'
|
|
else
|
|
create_payload="$(node -e 'const p=JSON.parse(process.argv[1]); process.stdout.write(JSON.stringify({sourceUrl:p.sourceUrl,post:p,filterRules:{requireLike:true,requireComment:false,requireRepost:false,requireSubscription:false,excludeAdmins:false,excludeBlacklistedIds:[],minEligibleParticipants:1}}))' "$POST_JSON")"
|
|
request POST /api/giveaways "$create_payload"
|
|
if [[ "$HTTP_CODE" == 201 ]] && json_success; then
|
|
GIVEAWAY_ID="$(node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(d.giveaway?.id||"")' "$BODY")"
|
|
if [[ -n "$GIVEAWAY_ID" ]]; then result F PASS 'test giveaway created'; else result F FAIL 'create response omitted giveaway id'; fi
|
|
else result F FAIL "giveaway create returned HTTP ${HTTP_CODE:-curl-error}"; fi
|
|
|
|
if [[ -n "$GIVEAWAY_ID" ]]; then
|
|
import_payload='{"filterRules":{"requireLike":true,"requireComment":true,"requireRepost":false,"requireSubscription":false,"excludeAdmins":false,"excludeBlacklistedIds":[],"minEligibleParticipants":1}}'
|
|
request POST "/api/giveaways/$GIVEAWAY_ID/participants" "$import_payload"
|
|
if [[ "$HTTP_CODE" == 200 ]] && json_live_import_success; then result G PASS 'participant import succeeded'; else result G FAIL "participant import requires LIVE mode and nonempty participants (HTTP ${HTTP_CODE:-curl-error})"; fi
|
|
|
|
if [[ -z "$GROUP_ID" ]]; then
|
|
result H SKIP 'set VK_SMOKE_GROUP_ID to run subscription verification'
|
|
else
|
|
subscription_payload="$(node -e 'const p=JSON.parse(process.argv[1]); p.filterRules.requireSubscription=true; p.filterRules.targetGroupId=process.argv[2]; process.stdout.write(JSON.stringify(p))' "$import_payload" "$GROUP_ID")"
|
|
request POST "/api/giveaways/$GIVEAWAY_ID/participants" "$subscription_payload"
|
|
if [[ "$HTTP_CODE" == 200 ]] && json_live_import_success; then result H PASS 'subscription verification returned a complete result'; else result H FAIL "subscription verification requires LIVE mode and nonempty participants (HTTP ${HTTP_CODE:-curl-error})"; fi
|
|
fi
|
|
else
|
|
result G SKIP 'requires a giveaway created by check F'
|
|
result H SKIP 'requires a giveaway and VK_SMOKE_GROUP_ID'
|
|
fi
|
|
fi
|
|
|
|
printf 'Smoke checks: %d failure(s), %d skipped/manual check(s).\n' "$failures" "$skips"
|
|
[[ "$failures" -eq 0 ]]
|