Pulse/tests/integration/scripts/trial-signup-contract.sh
2026-04-10 17:48:20 +01:00

198 lines
6.9 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# Live trial-signup initiation contract probe for self-hosted Pulse + control-plane.
# Intended to run inside the target environment (for example inside an LXC).
PULSE_BASE_URL="${PULSE_BASE_URL:-http://127.0.0.1:7655}"
PULSE_E2E_USERNAME="${PULSE_E2E_USERNAME:-admin}"
PULSE_E2E_PASSWORD="${PULSE_E2E_PASSWORD:-admin}"
WAIT_TIMEOUT_SECONDS="${WAIT_TIMEOUT_SECONDS:-60}"
MAX_DUPLICATE_TRIAL_ATTEMPTS="${MAX_DUPLICATE_TRIAL_ATTEMPTS:-10}"
if ! command -v curl >/dev/null 2>&1; then
echo "ERROR: curl is required"
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "ERROR: jq is required"
exit 1
fi
tmp_dir="$(mktemp -d)"
trap 'rm -rf "${tmp_dir}"' EXIT
cookies_file="${tmp_dir}/cookies.txt"
login_body="${tmp_dir}/login.json"
entitlements_body="${tmp_dir}/entitlements.json"
start_body="${tmp_dir}/trial_start.json"
second_start_body="${tmp_dir}/trial_start_second.json"
second_start_headers="${tmp_dir}/trial_start_second.headers"
wait_for_http() {
local url="$1"
local elapsed=0
while [ "${elapsed}" -lt "${WAIT_TIMEOUT_SECONDS}" ]; do
code="$(curl -sS -o /dev/null -w '%{http_code}' "${url}" || true)"
if [ "${code}" != "000" ]; then
return 0
fi
sleep 1
elapsed=$((elapsed + 1))
done
echo "ERROR: timed out waiting for ${url}"
return 1
}
assert_code() {
local expected="$1"
local actual="$2"
local context="$3"
if [ "${actual}" != "${expected}" ]; then
echo "ERROR: ${context} expected HTTP ${expected}, got ${actual}"
exit 1
fi
}
echo "[1/6] Waiting for Pulse API readiness at ${PULSE_BASE_URL}"
wait_for_http "${PULSE_BASE_URL}/api/login"
echo "[2/6] Login with configured test credentials"
login_code="$(
curl -sS -o "${login_body}" -w '%{http_code}' \
-c "${cookies_file}" \
-H 'Content-Type: application/json' \
--data "{\"username\":\"${PULSE_E2E_USERNAME}\",\"password\":\"${PULSE_E2E_PASSWORD}\"}" \
"${PULSE_BASE_URL}/api/login"
)"
assert_code "200" "${login_code}" "POST /api/login"
csrf_token="$(awk '$6=="pulse_csrf" {print $7}' "${cookies_file}" | tail -n1)"
if [ -z "${csrf_token}" ]; then
echo "ERROR: login did not return pulse_csrf cookie"
exit 1
fi
echo "[3/6] Check pre-trial entitlements"
entitlements_code="$(
curl -sS -o "${entitlements_body}" -w '%{http_code}' \
-b "${cookies_file}" \
"${PULSE_BASE_URL}/api/license/entitlements"
)"
assert_code "200" "${entitlements_code}" "GET /api/license/entitlements"
trial_eligible="$(jq -r '.trial_eligible // empty' "${entitlements_body}")"
if [ "${trial_eligible}" != "true" ]; then
echo "ERROR: expected trial_eligible=true before trial start"
exit 1
fi
echo "[4/6] Verify hosted trial signup initiation returns a control-plane redirect"
start_code="$(
curl -sS -o "${start_body}" -w '%{http_code}' \
-X POST \
-b "${cookies_file}" \
-H "X-CSRF-Token: ${csrf_token}" \
"${PULSE_BASE_URL}/api/license/trial/start"
)"
assert_code "409" "${start_code}" "POST /api/license/trial/start"
start_code_name="$(jq -r '.code // empty' "${start_body}")"
if [ "${start_code_name}" != "trial_signup_required" ]; then
echo "ERROR: expected code=trial_signup_required, got ${start_code_name:-<empty>}"
exit 1
fi
action_url="$(jq -r '.details.action_url // empty' "${start_body}")"
if [ -z "${action_url}" ]; then
echo "ERROR: expected action_url in trial start response"
exit 1
fi
if ! printf '%s' "${action_url}" | grep -q '/start-pro-trial'; then
echo "ERROR: expected action_url to target hosted start-pro-trial page, got ${action_url}"
exit 1
fi
echo "[5/6] Verify local entitlements remain unchanged before hosted activation"
post_entitlements_code="$(
curl -sS -o "${entitlements_body}" -w '%{http_code}' \
-b "${cookies_file}" \
"${PULSE_BASE_URL}/api/license/entitlements"
)"
assert_code "200" "${post_entitlements_code}" "GET /api/license/entitlements (post-trial)"
post_trial_eligible="$(jq -r '.trial_eligible // empty' "${entitlements_body}")"
if [ "${post_trial_eligible}" != "true" ]; then
echo "ERROR: expected trial_eligible=true before hosted activation completes"
exit 1
fi
post_sub_state="$(jq -r '.subscription_state // empty' "${entitlements_body}")"
if [ "${post_sub_state}" != "expired" ]; then
echo "ERROR: expected subscription_state=expired before hosted activation, got ${post_sub_state:-<empty>}"
exit 1
fi
echo "[6/6] Verify duplicate initiation stays on the retry-burst contract until the limiter engages"
rate_limited_attempt=""
second_start_code=""
second_code_name=""
retry_after_header=""
retry_after_seconds=""
for attempt in $(seq 2 "${MAX_DUPLICATE_TRIAL_ATTEMPTS}"); do
second_start_code="$(
curl -sS -D "${second_start_headers}" -o "${second_start_body}" -w '%{http_code}' \
-X POST \
-b "${cookies_file}" \
-H "X-CSRF-Token: ${csrf_token}" \
"${PULSE_BASE_URL}/api/license/trial/start"
)"
second_code_name="$(jq -r '.code // empty' "${second_start_body}")"
if [ "${second_start_code}" = "409" ]; then
if [ "${second_code_name}" != "trial_signup_required" ]; then
echo "ERROR: expected code=trial_signup_required within retry burst, got ${second_code_name:-<empty>}"
exit 1
fi
action_url="$(jq -r '.details.action_url // empty' "${second_start_body}")"
if [ -z "${action_url}" ]; then
echo "ERROR: expected action_url while retry burst still allows hosted signup"
exit 1
fi
continue
fi
if [ "${second_start_code}" = "429" ]; then
if [ "${second_code_name}" != "trial_rate_limited" ]; then
echo "ERROR: expected code=trial_rate_limited once retry burst is exceeded, got ${second_code_name:-<empty>}"
exit 1
fi
retry_after_header="$(
awk 'tolower($1)=="retry-after:" {print $2}' "${second_start_headers}" | tr -d '\r' | tail -n1
)"
retry_after_seconds="$(jq -r '.details.retry_after_seconds // empty' "${second_start_body}")"
if [ -z "${retry_after_header}" ]; then
echo "ERROR: expected Retry-After header once retry burst is exceeded"
exit 1
fi
if [ "${retry_after_seconds}" != "${retry_after_header}" ]; then
echo "ERROR: expected details.retry_after_seconds=${retry_after_header}, got ${retry_after_seconds:-<empty>}"
exit 1
fi
rate_limited_attempt="${attempt}"
break
fi
echo "ERROR: expected HTTP 409 within retry burst or HTTP 429 once the limiter engages, got ${second_start_code}"
exit 1
done
if [ -z "${rate_limited_attempt}" ]; then
echo "ERROR: duplicate trial initiation never hit the retry limiter within ${MAX_DUPLICATE_TRIAL_ATTEMPTS} attempts"
exit 1
fi
echo "PASS: hosted trial signup initiation contract validated"
echo " login_code=${login_code}"
echo " entitlements_before_code=${entitlements_code}"
echo " trial_start_code=${start_code} (code=${start_code_name})"
echo " post_trial_entitlements_code=${post_entitlements_code}"
echo " retry_limiter_attempt=${rate_limited_attempt}"
echo " final_trial_start_code=${second_start_code} (code=${second_code_name}, retry_after=${retry_after_header})"