Add Pulse Cloud public signup smoke

This commit is contained in:
rcourtman 2026-04-23 23:09:10 +01:00
parent 4fb67cd547
commit 88683cc32f
5 changed files with 191 additions and 22 deletions

View file

@ -96,6 +96,7 @@ Companion drill:
`go test ./internal/hosted/... -count=1` `go test ./internal/hosted/... -count=1`
`go test ./internal/api -run 'TestHostedLifecycle|TestHostedOrgAdminHandlers|TestHostedSignupSuccess|TestHostedSignupValidationFailures|TestHostedSignupHostedModeGate|TestHostedSignupRateLimit|TestHostedSignupRateLimit_NoProvisioningSideEffects|TestHostedSignupCleanupOnRBACFailure|TestHostedSignupFailsClosedWithoutPublicURL|TestStripeWebhook_' -count=1` `go test ./internal/api -run 'TestHostedLifecycle|TestHostedOrgAdminHandlers|TestHostedSignupSuccess|TestHostedSignupValidationFailures|TestHostedSignupHostedModeGate|TestHostedSignupRateLimit|TestHostedSignupRateLimit_NoProvisioningSideEffects|TestHostedSignupCleanupOnRBACFailure|TestHostedSignupFailsClosedWithoutPublicURL|TestStripeWebhook_' -count=1`
`cd frontend-modern && npx vitest run src/pages/__tests__/HostedSignup.test.tsx src/components/Settings/__tests__/BillingAdminPanel.test.tsx src/components/Settings/__tests__/OrganizationBillingPanel.test.tsx` `cd frontend-modern && npx vitest run src/pages/__tests__/HostedSignup.test.tsx src/components/Settings/__tests__/BillingAdminPanel.test.tsx src/components/Settings/__tests__/OrganizationBillingPanel.test.tsx`
`EXPECT_PUBLIC_SIGNUP_ENABLED=false scripts/run_cloud_public_signup_smoke.sh` while public signup is intentionally closed, or `EXPECT_PUBLIC_SIGNUP_ENABLED=true scripts/run_cloud_public_signup_smoke.sh` during the GA launch window when public signup is expected to be live.
- Manual scenario: - Manual scenario:
1. Start from a real hosted Pulse signup or an existing hosted tenant. 1. Start from a real hosted Pulse signup or an existing hosted tenant.
2. Confirm the user can authenticate into the hosted Pulse app and reach a 2. Confirm the user can authenticate into the hosted Pulse app and reach a

View file

@ -68,26 +68,27 @@ server-side update execution surfaces.
46. `scripts/release_control/release_promotion_policy_support.py` 46. `scripts/release_control/release_promotion_policy_support.py`
47. `scripts/release_control/resolve_release_promotion.py` 47. `scripts/release_control/resolve_release_promotion.py`
48. `scripts/release_ldflags.sh` 48. `scripts/release_ldflags.sh`
49. `scripts/run_demo_public_browser_smoke.sh` 49. `scripts/run_cloud_public_signup_smoke.sh`
50. `scripts/demo_public_browser_smoke.cjs` 50. `scripts/run_demo_public_browser_smoke.sh`
51. `scripts/run_hosted_staging_smoke.sh` 51. `scripts/demo_public_browser_smoke.cjs`
52. `scripts/trigger-release-dry-run.sh` 52. `scripts/run_hosted_staging_smoke.sh`
53. `scripts/trigger-release.sh` 53. `scripts/trigger-release-dry-run.sh`
54. `scripts/toggle-mock.sh` 54. `scripts/trigger-release.sh`
55. `deploy/helm/pulse/` 55. `scripts/toggle-mock.sh`
56. `tests/integration/playwright.config.ts` 56. `deploy/helm/pulse/`
57. `tests/integration/QUICK_START.md` 57. `tests/integration/playwright.config.ts`
58. `tests/integration/README.md` 58. `tests/integration/QUICK_START.md`
59. `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs` 59. `tests/integration/README.md`
60. `tests/integration/scripts/hosted-mobile-token-runtime.mjs` 60. `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs`
61. `tests/integration/scripts/hosted-tenant-runtime.mjs` 61. `tests/integration/scripts/hosted-mobile-token-runtime.mjs`
62. `tests/integration/scripts/managed-dev-runtime.mjs` 62. `tests/integration/scripts/hosted-tenant-runtime.mjs`
63. `tests/integration/scripts/relay-mobile-token-helper.go` 63. `tests/integration/scripts/managed-dev-runtime.mjs`
64. `tests/integration/tests/helpers.ts` 64. `tests/integration/scripts/relay-mobile-token-helper.go`
65. `tests/integration/tests/runtime-defaults.ts` 65. `tests/integration/tests/helpers.ts`
66. `docker-compose.yml` 66. `tests/integration/tests/runtime-defaults.ts`
67. `scripts/install-docker.sh` 67. `docker-compose.yml`
68. `scripts/validate-published-release.sh` 68. `scripts/install-docker.sh`
69. `scripts/validate-published-release.sh`
69. `scripts/validate-release.sh` 69. `scripts/validate-release.sh`
70. `scripts/release_asset_common.sh` 70. `scripts/release_asset_common.sh`
71. `scripts/backfill-release-assets.sh` 71. `scripts/backfill-release-assets.sh`
@ -174,8 +175,13 @@ server-side update execution surfaces.
External helper binaries fetched by governed release workflows are part of External helper binaries fetched by governed release workflows are part of
the same supply-chain boundary and must be checksum-verified before they are the same supply-chain boundary and must be checksum-verified before they are
executed. executed.
8. Add or change operator-facing hosted tenant runtime canary rollout, batch runtime contract reconciliation, canonical hosted route/public URL generation, or control-plane runtime-registry reconciliation through `cmd/pulse-control-plane/main.go`, `internal/cloudcp/docker/manager.go`, `internal/cloudcp/docker/labels.go`, and `internal/cloudcp/tenant_runtime_rollout.go` 8. Add or change the non-secret Pulse Cloud public signup route smoke through
9. Add or change the canonical hosted staging smoke operator path through `scripts/run_hosted_staging_smoke.sh`, `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs`, `tests/integration/scripts/hosted-mobile-token-runtime.mjs`, `tests/integration/scripts/hosted-tenant-runtime.mjs`, and `tests/integration/scripts/relay-mobile-token-helper.go` `scripts/run_cloud_public_signup_smoke.sh`. That smoke must prove either
the open signup route contract or the intentionally closed redirect contract,
and valid magic-link probes must remain opt-in so routine public checks do
not send email accidentally.
9. Add or change operator-facing hosted tenant runtime canary rollout, batch runtime contract reconciliation, canonical hosted route/public URL generation, or control-plane runtime-registry reconciliation through `cmd/pulse-control-plane/main.go`, `internal/cloudcp/docker/manager.go`, `internal/cloudcp/docker/labels.go`, and `internal/cloudcp/tenant_runtime_rollout.go`
10. Add or change the canonical hosted staging smoke operator path through `scripts/run_hosted_staging_smoke.sh`, `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs`, `tests/integration/scripts/hosted-mobile-token-runtime.mjs`, `tests/integration/scripts/hosted-tenant-runtime.mjs`, and `tests/integration/scripts/relay-mobile-token-helper.go`
## Forbidden Paths ## Forbidden Paths

View file

@ -2569,6 +2569,7 @@
"scripts/release_control/render_release_body.py", "scripts/release_control/render_release_body.py",
"scripts/release_control/resolve_release_promotion.py", "scripts/release_control/resolve_release_promotion.py",
"scripts/release_ldflags.sh", "scripts/release_ldflags.sh",
"scripts/run_cloud_public_signup_smoke.sh",
"scripts/run_demo_public_browser_smoke.sh", "scripts/run_demo_public_browser_smoke.sh",
"scripts/run_hosted_staging_smoke.sh", "scripts/run_hosted_staging_smoke.sh",
"scripts/toggle-mock.sh", "scripts/toggle-mock.sh",
@ -2698,6 +2699,19 @@
"scripts/installtests/release_ldflags_test.go" "scripts/installtests/release_ldflags_test.go"
] ]
}, },
{
"id": "cloud-public-signup-smoke-runtime",
"label": "Cloud public signup smoke proof",
"match_prefixes": [],
"match_files": [
"scripts/run_cloud_public_signup_smoke.sh"
],
"allow_same_subsystem_tests": false,
"test_prefixes": [],
"exact_files": [
"scripts/tests/test-cloud-public-signup-smoke.sh"
]
},
{ {
"id": "demo-public-browser-smoke-runtime", "id": "demo-public-browser-smoke-runtime",
"label": "demo public browser smoke proof", "label": "demo public browser smoke proof",

View file

@ -0,0 +1,133 @@
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="${DOMAIN:-cloud.pulserelay.pro}"
PULSE_CLOUD_BASE_URL="${PULSE_CLOUD_BASE_URL:-https://${DOMAIN}}"
EXPECT_PUBLIC_SIGNUP_ENABLED="${EXPECT_PUBLIC_SIGNUP_ENABLED:-true}"
PUBLIC_SIGNUP_CLOSED_REDIRECT_URL="${PUBLIC_SIGNUP_CLOSED_REDIRECT_URL:-https://pulserelay.pro/}"
CHECK_MAGIC_LINK_VALID_PROBE="${CHECK_MAGIC_LINK_VALID_PROBE:-false}"
CURL_TIMEOUT="${CURL_TIMEOUT:-15}"
BASE_URL="${PULSE_CLOUD_BASE_URL%/}"
FAILURES=0
pass() {
echo "[PASS] $*"
}
fail() {
echo "[FAIL] $*"
FAILURES=$((FAILURES + 1))
}
info() {
echo "[INFO] $*"
}
http_code() {
local method="$1"
local path="$2"
shift 2
curl -sS --max-time "${CURL_TIMEOUT}" -o /dev/null -w "%{http_code}" -X "${method}" "$@" "${BASE_URL}${path}"
}
http_status_and_redirect() {
local method="$1"
local path="$2"
shift 2
curl -sS --max-time "${CURL_TIMEOUT}" -o /dev/null -w "%{http_code} %{redirect_url}" -X "${method}" "$@" "${BASE_URL}${path}"
}
expect_code() {
local method="$1"
local path="$2"
local expected="$3"
shift 3
local code
code="$(http_code "${method}" "${path}" "$@")"
if [[ "${code}" == "${expected}" ]]; then
pass "${method} ${path} returns ${expected}"
else
fail "${method} ${path} expected ${expected}, got ${code}"
fi
}
expect_redirect() {
local method="$1"
local path="$2"
local expected_code="$3"
shift 3
local code redirect_url
read -r code redirect_url <<<"$(http_status_and_redirect "${method}" "${path}" "$@")"
if [[ "${code}" == "${expected_code}" && "${redirect_url}" == "${PUBLIC_SIGNUP_CLOSED_REDIRECT_URL}" ]]; then
pass "${method} ${path} returns ${expected_code} to ${PUBLIC_SIGNUP_CLOSED_REDIRECT_URL}"
else
fail "${method} ${path} expected ${expected_code} to ${PUBLIC_SIGNUP_CLOSED_REDIRECT_URL}, got ${code} to ${redirect_url:-<none>}"
fi
}
check_shared_public_floor() {
info "Checking shared public control-plane floor at ${BASE_URL}"
expect_code GET /healthz 200
expect_code GET /readyz 200
expect_code POST /api/stripe/webhook 400
}
check_closed_signup_contract() {
info "Checking intentionally closed public signup contract"
expect_redirect GET /signup 302
expect_redirect GET /cloud/signup 302
expect_redirect GET /signup/complete 302
expect_redirect GET /cloud/signup/complete 302
expect_redirect GET /api/public/signup 302
expect_redirect POST /api/public/signup 307 -H "Content-Type: application/json" --data '{"email":"not-an-email","org_name":"Acme"}'
expect_code POST /api/public/magic-link/request 400 -H "Content-Type: application/json" --data '{"email":"not-an-email"}'
}
check_open_signup_contract() {
info "Checking live public signup contract"
expect_code GET /signup 200
expect_code GET /cloud/signup 200
expect_code GET /signup/complete 200
expect_code GET /cloud/signup/complete 200
expect_code GET /api/public/signup 405
expect_code POST /api/public/signup 400 -H "Content-Type: application/json" --data '{"email":"not-an-email","org_name":"Acme"}'
expect_code POST /api/public/magic-link/request 400 -H "Content-Type: application/json" --data '{"email":"not-an-email"}'
if [[ "${CHECK_MAGIC_LINK_VALID_PROBE}" == "true" ]]; then
expect_code POST /api/public/magic-link/request 200 -H "Content-Type: application/json" --data '{"email":"owner@example.com"}'
else
info "Skipping valid magic-link probe (CHECK_MAGIC_LINK_VALID_PROBE=${CHECK_MAGIC_LINK_VALID_PROBE})"
fi
}
main() {
if ! command -v curl >/dev/null 2>&1; then
echo "curl is required for Pulse Cloud public signup smoke" >&2
exit 1
fi
local expected_state
expected_state="$(echo "${EXPECT_PUBLIC_SIGNUP_ENABLED}" | tr '[:upper:]' '[:lower:]')"
check_shared_public_floor
case "${expected_state}" in
true)
check_open_signup_contract
;;
false)
check_closed_signup_contract
;;
*)
fail "EXPECT_PUBLIC_SIGNUP_ENABLED must be true or false (got '${EXPECT_PUBLIC_SIGNUP_ENABLED}')"
;;
esac
echo
echo "Summary: failures=${FAILURES}"
if (( FAILURES > 0 )); then
exit 1
fi
}
main "$@"

View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SMOKE="${ROOT_DIR}/scripts/run_cloud_public_signup_smoke.sh"
bash -n "${SMOKE}"
grep -q 'EXPECT_PUBLIC_SIGNUP_ENABLED' "${SMOKE}"
grep -q 'PUBLIC_SIGNUP_CLOSED_REDIRECT_URL' "${SMOKE}"
grep -q 'CHECK_MAGIC_LINK_VALID_PROBE' "${SMOKE}"
grep -q 'check_closed_signup_contract' "${SMOKE}"
grep -q 'check_open_signup_contract' "${SMOKE}"
echo "cloud public signup smoke checks passed"