feat(e2e): deployed-environment E2E harness + gated test-auth (v0.52.0)
The §9 pipeline's PPE+E2E stage was unreachable: e2e/metadata.spec.js was bound to Tier-1-only scaffolding (docker-seeded faceted collection, SQLite-injected owner, Mailpit OTC sink). This makes the same suite run against a deployed host. - backend: POST /auth/test/login — fail-closed, secret-gated, single- identity owner test-login (404 unless E2E_TEST_AUTH_SECRET + E2E_TEST_AUTH_EMAIL both set; constant-time compare; loud startup warn). 6 vertical tests; backend 665 green. Documented in backend/.env.example. - e2e/lib/auth.js: branch on E2E_TEST_AUTH_SECRET (deployed test-login vs local Mailpit OTC); OWNER_EMAIL from E2E_OWNER_EMAIL. Spec unchanged so the localhost Tier-1 path keeps working. - testing/seed-ppe.sh: seed a dedicated, prod-untouching PPE registry + content repo (faceted bdd collection) — real OHM content never touched. - docs/design/2026-06-07-deployed-env-e2e-harness.md; CHANGELOG; VERSION + frontend/package.json -> 0.52.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+171
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
# PPE E2E content seed (§9 deployed-environment harness).
|
||||
#
|
||||
# The Tier-1 docker seed (seed-gitea.sh) stands up a throwaway Gitea. PPE
|
||||
# instead runs against the REAL Gitea (git.wiggleverse.org) — which it
|
||||
# shares with prod. To exercise the §22.4a metadata UI (faceted filter
|
||||
# SLICE-3, edit panel SLICE-4, bulk bar SLICE-5) on PPE WITHOUT touching
|
||||
# real OHM content, this script seeds a DEDICATED, PPE-only registry +
|
||||
# content repo:
|
||||
#
|
||||
# wiggleverse/rfc-registry-ppe — PPE's own project registry (prod
|
||||
# keeps using wiggleverse/rfc-registry,
|
||||
# so prod is never affected).
|
||||
# wiggleverse/rfc-app-ppe-content — content for the one project the PPE
|
||||
# registry describes: a default
|
||||
# (document) collection + a faceted
|
||||
# `bdd` named collection with a
|
||||
# fields: schema + three entries.
|
||||
#
|
||||
# Point PPE at the PPE registry with:
|
||||
# flotilla-core overlay set rfc-app-ppe REGISTRY_REPO=rfc-registry-ppe
|
||||
# The app's startup reconciler sweep loads this content into cached_rfcs
|
||||
# on the next deploy/restart (no webhook needed for the initial load).
|
||||
#
|
||||
# Auth: pass a Gitea token with org repo-create + content-write scope via
|
||||
# GITEA_TOKEN (the wiggleverse admin token — see the wgl-gitea-admin
|
||||
# skill; never echo it). The collection path the E2E suite hits is
|
||||
# /p/ohm/c/bdd, so the seeded project id is `ohm` (matching the Tier-1
|
||||
# seed and DEFAULT_PROJECT_ID=ohm) and the collection is `bdd`.
|
||||
#
|
||||
# Idempotent: repos/files that already exist are left as-is. Pass
|
||||
# RESEED=1 to force-overwrite the three entry files back to their seed
|
||||
# values (so a re-run restores SLICE-4/5's expected preconditions).
|
||||
|
||||
GITEA="${GITEA_URL:-https://git.wiggleverse.org}"
|
||||
ORG="${GITEA_ORG:-wiggleverse}"
|
||||
REGISTRY_REPO="${REGISTRY_REPO:-rfc-registry-ppe}"
|
||||
CONTENT_REPO="${CONTENT_REPO:-rfc-app-ppe-content}"
|
||||
PROJECT_ID="${DEFAULT_PROJECT_ID:-ohm}"
|
||||
TOKEN="${GITEA_TOKEN:?set GITEA_TOKEN to a wiggleverse-org admin/bot token (do not echo it)}"
|
||||
RESEED="${RESEED:-0}"
|
||||
|
||||
api() { curl -s -H "Authorization: token $TOKEN" "$@"; }
|
||||
|
||||
echo "seed-ppe: target $GITEA org=$ORG registry=$REGISTRY_REPO content=$CONTENT_REPO project=$PROJECT_ID"
|
||||
|
||||
ensure_repo() {
|
||||
if api -o /dev/null -w '%{http_code}' "$GITEA/api/v1/repos/$ORG/$1" | grep -q '^200$'; then
|
||||
echo "seed-ppe: repo $ORG/$1 exists"
|
||||
return 0
|
||||
fi
|
||||
echo "seed-ppe: creating repo $ORG/$1 (private)"
|
||||
api -X POST "$GITEA/api/v1/orgs/$ORG/repos" -H 'Content-Type: application/json' \
|
||||
-d "{\"name\":\"$1\",\"auto_init\":true,\"default_branch\":\"main\",\"private\":true}" >/dev/null \
|
||||
|| { echo "seed-ppe: failed to create $1" ; exit 1; }
|
||||
}
|
||||
|
||||
# file_sha <repo> <path> -> prints the blob sha if the file exists, else empty
|
||||
file_sha() {
|
||||
api "$GITEA/api/v1/repos/$ORG/$1/contents/$2?ref=main" \
|
||||
| sed -n 's/.*"sha":"\([0-9a-f]*\)".*/\1/p' | head -1
|
||||
}
|
||||
|
||||
# put_file <repo> <path> <plaintext> [force]
|
||||
# Creates the file if absent. If it exists: skipped, unless force=1, in
|
||||
# which case it is updated in place (PUT with the current sha).
|
||||
put_file() {
|
||||
_repo="$1"; _path="$2"; _content="$3"; _force="${4:-0}"
|
||||
_b64=$(printf '%s' "$_content" | base64 | tr -d '\n')
|
||||
_sha=$(file_sha "$_repo" "$_path")
|
||||
if [ -n "$_sha" ]; then
|
||||
if [ "$_force" = "1" ]; then
|
||||
echo "seed-ppe: updating $_repo/$_path"
|
||||
api -X PUT "$GITEA/api/v1/repos/$ORG/$_repo/contents/$_path" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"message\":\"reseed $_path\",\"content\":\"$_b64\",\"sha\":\"$_sha\",\"branch\":\"main\"}" >/dev/null \
|
||||
|| echo "seed-ppe: update $_repo/$_path failed, continuing"
|
||||
else
|
||||
echo "seed-ppe: $_repo/$_path exists, leaving as-is"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
echo "seed-ppe: creating $_repo/$_path"
|
||||
api -X POST "$GITEA/api/v1/repos/$ORG/$_repo/contents/$_path" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"message\":\"seed $_path\",\"content\":\"$_b64\",\"branch\":\"main\"}" >/dev/null \
|
||||
|| { echo "seed-ppe: create $_repo/$_path failed" ; exit 1; }
|
||||
}
|
||||
|
||||
ensure_repo "$REGISTRY_REPO"
|
||||
ensure_repo "$CONTENT_REPO"
|
||||
|
||||
# The PPE registry describes a single project `ohm` whose content lives in
|
||||
# the dedicated PPE content repo.
|
||||
put_file "$REGISTRY_REPO" "projects.yaml" "deployment:
|
||||
name: RFC PPE
|
||||
tagline: rfc-app pre-prod (E2E fixtures)
|
||||
projects:
|
||||
- id: $PROJECT_ID
|
||||
name: OHM
|
||||
type: document
|
||||
content_repo: $CONTENT_REPO
|
||||
visibility: public
|
||||
"
|
||||
|
||||
# Default (document) collection — one entry under rfcs/.
|
||||
put_file "$CONTENT_REPO" "rfcs/intro.md" "---
|
||||
slug: intro
|
||||
title: Intro
|
||||
state: active
|
||||
id: RFC-0001
|
||||
owners: [ben.stull]
|
||||
---
|
||||
|
||||
# Intro
|
||||
|
||||
Seed entry for the default (document) collection on PPE.
|
||||
"
|
||||
|
||||
# Faceted named collection 'bdd' with a fields: schema (§22.4a).
|
||||
put_file "$CONTENT_REPO" "bdd/.collection.yaml" "type: bdd
|
||||
visibility: public
|
||||
name: BDD Scenarios
|
||||
fields:
|
||||
priority:
|
||||
type: enum
|
||||
values: [P0, P1, P2]
|
||||
label: Priority
|
||||
tags:
|
||||
type: tags
|
||||
label: Tags
|
||||
"
|
||||
|
||||
# Three entries with varied priority/tags so facets have counts and the
|
||||
# bulk bar has multiple selectable rows. Force-overwritten when RESEED=1
|
||||
# so a re-run restores SLICE-4 (checkout-returning starts P1) and SLICE-5
|
||||
# (two P0 entries) preconditions.
|
||||
put_file "$CONTENT_REPO" "bdd/rfcs/checkout-guest.md" "---
|
||||
slug: checkout-guest
|
||||
title: Guest checkout
|
||||
state: active
|
||||
priority: P0
|
||||
tags: [checkout, payments]
|
||||
---
|
||||
|
||||
Guest checkout scenario.
|
||||
" "$RESEED"
|
||||
put_file "$CONTENT_REPO" "bdd/rfcs/checkout-returning.md" "---
|
||||
slug: checkout-returning
|
||||
title: Returning-customer checkout
|
||||
state: active
|
||||
priority: P1
|
||||
tags: [checkout]
|
||||
---
|
||||
|
||||
Returning-customer checkout scenario.
|
||||
" "$RESEED"
|
||||
put_file "$CONTENT_REPO" "bdd/rfcs/search-facets.md" "---
|
||||
slug: search-facets
|
||||
title: Faceted search
|
||||
state: active
|
||||
priority: P0
|
||||
tags: [search]
|
||||
---
|
||||
|
||||
Faceted search scenario.
|
||||
" "$RESEED"
|
||||
|
||||
echo "seed-ppe: done. Set REGISTRY_REPO=$REGISTRY_REPO on rfc-app-ppe and redeploy."
|
||||
Reference in New Issue
Block a user