test(e2e): §22-current Tier-1 harness + metadata E2E (SLICE-3/4/5)

Modernize the Tier-1 stack to the three-tier app and add browser coverage for
the §22.4a metadata UI, closing the E2E gap deferred across SLICE-3/4/5:
- seed-gitea.sh: create a REGISTRY_REPO with projects.yaml + a faceted named
  collection (.collection.yaml fields: priority enum + tags) seeded with three
  metadata-bearing entries; register content+registry webhooks; self-guarding
  (skip if a prior token still works) so a dependency-triggered re-run can't
  remint and invalidate the backend's token.
- .env.tier1: REGISTRY_REPO/DEFAULT_PROJECT_ID; disable OTC cooldown + lift the
  per-IP auth limiter for the single-IP test runner.
- docker-compose: pin backend image; backend-seed inserts a granted owner the
  OTC path can sign in as (write paths need contributor+).
- Makefile: two-phase tier1-up (seed to completion, then create backend so it
  reads the populated token env); robust down; e2e-fresh = down+up+e2e (the
  canonical run, since the edit/bulk specs mutate the seeded corpus).
- metadata.spec.js: SLICE-3 faceted filter (anon), SLICE-4 edit panel (owner),
  SLICE-5 bulk bar (owner). 4 passed against a fresh stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-07 22:29:11 -07:00
parent 9e1b7ce34f
commit 2fc7029bd9
7 changed files with 282 additions and 29 deletions
+10 -1
View File
@@ -3,7 +3,8 @@ GITEA_BOT_USER=rfc-bot
GITEA_BOT_TOKEN=tier1-bot-token-PLACEHOLDER
GITEA_ORG=wiggleverse
META_REPO=ohm-content
REGISTRY_REPO=
REGISTRY_REPO=rfc-registry
DEFAULT_PROJECT_ID=ohm
OAUTH_CLIENT_ID=tier1-oauth-client-PLACEHOLDER
OAUTH_CLIENT_SECRET=tier1-oauth-secret-PLACEHOLDER
APP_URL=http://localhost:8080
@@ -19,3 +20,11 @@ EMAIL_FROM=rfc@example.test
EMAIL_FROM_NAME=RFC Tier1
EMAIL_ENABLED=true
TURNSTILE_REQUIRED=false
# Tier-1/e2e: disable the per-email OTC request cooldown so a test can sign the
# same account in more than once across specs without 429s.
OTC_REQUEST_COOLDOWN_SECONDS=0
# Tier-1/e2e drives the auth endpoints repeatedly from one IP; lift the per-IP
# sliding-window budgets well above a single suite run (prod leaves these unset
# and keeps the secure defaults).
RATELIMIT_OTC_REQUEST_MAX=1000
RATELIMIT_VERIFY_MAX=1000
+25
View File
@@ -57,6 +57,7 @@ services:
restart: "no"
backend:
image: rfc-tier1-backend
build:
context: ..
dockerfile: testing/backend.Dockerfile
@@ -74,6 +75,30 @@ services:
timeout: 3s
retries: 30
# Insert a granted deployment-owner user keyed by a known e2e email, so the
# OTC sign-in path (which provisions only `pending` contributors) yields an
# owner who can exercise the metadata edit/bulk write paths (SLICE-4/5). The
# owner identity (gitea_login='owner') matches OWNER_GITEA_LOGIN. Idempotent.
backend-seed:
image: rfc-tier1-backend
depends_on:
backend:
condition: service_healthy
volumes:
- backend-data:/data
entrypoint: ["python", "-c"]
command:
- |
import sqlite3
c = sqlite3.connect("/data/rfc-app.db")
c.execute("""INSERT INTO users
(gitea_id, gitea_login, email, display_name, avatar_url, role, permission_state)
SELECT 9001, 'owner', 'e2e-owner@example.test', 'E2E Owner', '', 'owner', 'granted'
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email='e2e-owner@example.test' COLLATE NOCASE)""")
c.commit(); c.close()
print("backend-seed: owner user ready")
restart: "no"
web:
build:
context: ..
+123 -27
View File
@@ -1,14 +1,21 @@
#!/usr/bin/env sh
set -eu
# Tier-1 seed (§22-current). Stands up Gitea content + registry so the current
# three-tier app boots, plus a faceted **named collection** (a `.collection.yaml`
# with a `fields:` schema + entries carrying metadata) so the §22.4a metadata
# UI — faceted filter (SLICE-3), edit panel (SLICE-4), bulk bar (SLICE-5) — can
# be exercised end-to-end in a real browser.
GITEA="${GITEA_URL:-http://gitea:3000}"
ADMIN_USER="${GITEA_ADMIN_USER:-giteaadmin}"
ADMIN_PASS="${GITEA_ADMIN_PASSWORD:-giteaadmin-pass}"
ADMIN_EMAIL="${GITEA_ADMIN_EMAIL:-admin@example.test}"
ORG="${GITEA_ORG:-wiggleverse}"
BOT_USER="${GITEA_BOT_USER:-rfc-bot}"
BOT_PASS="${GITEA_BOT_PASSWORD:-rfc-bot-pass}"
CONTENT_REPO="${META_REPO:-ohm-content}"
REGISTRY_REPO="${REGISTRY_REPO:-rfc-registry}"
DEFAULT_PROJECT_ID="${DEFAULT_PROJECT_ID:-ohm}"
APP_URL="${APP_URL:-http://localhost:8080}"
WEBHOOK_SECRET="${GITEA_WEBHOOK_SECRET:-tier1-webhook-secret}"
OUT="${SEED_OUT:-/seed/.env.tier1.generated}"
@@ -22,6 +29,21 @@ done
auth_admin() { curl -sf -u "$ADMIN_USER:$ADMIN_PASS" "$@"; }
# Idempotency guard: if a prior run already wrote a bot token that still works
# against THIS gitea (the registry is readable), the stack is already seeded —
# skip entirely. This makes a second invocation a true no-op, so a later
# dependency-triggered re-run can't delete/remint the token the backend is
# already using (that mismatch 401s the registry mirror). A fresh `down -v`
# brings up a new gitea where the stale token fails, so the seed re-runs.
if [ -f "$OUT" ]; then
EXIST_TOK=$(sed -n 's/^GITEA_BOT_TOKEN=//p' "$OUT")
if [ -n "$EXIST_TOK" ] && curl -sf -H "Authorization: token $EXIST_TOK" \
"$GITEA/api/v1/repos/$ORG/$REGISTRY_REPO/contents/projects.yaml?ref=main" >/dev/null 2>&1; then
echo "seed: existing token valid and registry present — already seeded, skipping"
exit 0
fi
fi
echo "seed: ensuring bot user"
auth_admin -X POST "$GITEA/api/v1/admin/users" \
-H 'Content-Type: application/json' \
@@ -34,55 +56,129 @@ auth_admin -X POST "$GITEA/api/v1/admin/users" \
-d "{\"username\":\"owner\",\"email\":\"owner@example.test\",\"password\":\"owner-pass\",\"must_change_password\":false}" \
|| echo "seed: owner exists, continuing"
echo "seed: minting bot access token"
echo "seed: minting bot access token (drop any prior 'tier1-bot' first — idempotent)"
curl -s -u "$BOT_USER:$BOT_PASS" -X DELETE "$GITEA/api/v1/users/$BOT_USER/tokens/tier1-bot" >/dev/null 2>&1 || true
TOKEN=$(curl -sf -u "$BOT_USER:$BOT_PASS" -X POST "$GITEA/api/v1/users/$BOT_USER/tokens" \
-H 'Content-Type: application/json' \
-d '{"name":"tier1-bot","scopes":["write:repository","write:organization","write:user","write:admin"]}' \
| sed -n 's/.*"sha1":"\([^"]*\)".*/\1/p')
[ -n "$TOKEN" ] || { echo "seed: failed to mint bot token" ; exit 1; }
api() { curl -s -H "Authorization: token $TOKEN" "$@"; }
echo "seed: ensuring org $ORG (owned by bot)"
curl -sf -H "Authorization: token $TOKEN" -X POST "$GITEA/api/v1/orgs" \
-H 'Content-Type: application/json' \
-d "{\"username\":\"$ORG\"}" || echo "seed: org exists, continuing"
api -X POST "$GITEA/api/v1/orgs" -H 'Content-Type: application/json' \
-d "{\"username\":\"$ORG\"}" >/dev/null || echo "seed: org exists, continuing"
echo "seed: ensuring content repo $ORG/$CONTENT_REPO"
curl -sf -H "Authorization: token $TOKEN" -X POST "$GITEA/api/v1/orgs/$ORG/repos" \
-H 'Content-Type: application/json' \
-d "{\"name\":\"$CONTENT_REPO\",\"auto_init\":true,\"default_branch\":\"main\"}" \
|| echo "seed: content repo exists, continuing"
ensure_repo() {
api -X POST "$GITEA/api/v1/orgs/$ORG/repos" -H 'Content-Type: application/json' \
-d "{\"name\":\"$1\",\"auto_init\":true,\"default_branch\":\"main\"}" >/dev/null \
|| echo "seed: repo $1 exists, continuing"
}
echo "seed: seeding one entry under rfcs/ so the catalog is non-empty"
B64=$(printf '%s' '---
# put_file <repo> <path> <plaintext>
put_file() {
_b64=$(printf '%s' "$3" | base64 | tr -d '\n')
api -X POST "$GITEA/api/v1/repos/$ORG/$1/contents/$2" \
-H 'Content-Type: application/json' \
-d "{\"message\":\"seed $2\",\"content\":\"$_b64\",\"branch\":\"main\"}" >/dev/null \
|| echo "seed: $1/$2 exists, continuing"
}
register_webhook() {
api -X POST "$GITEA/api/v1/repos/$ORG/$1/hooks" \
-H 'Content-Type: application/json' \
-d "{\"type\":\"gitea\",\"active\":true,\"events\":[\"push\",\"pull_request\"],\"config\":{\"url\":\"http://backend:8000/api/webhooks/gitea\",\"content_type\":\"json\",\"secret\":\"$WEBHOOK_SECRET\"}}" >/dev/null \
|| echo "seed: webhook on $1 exists, continuing"
}
echo "seed: ensuring content repo $ORG/$CONTENT_REPO and registry $ORG/$REGISTRY_REPO"
ensure_repo "$CONTENT_REPO"
ensure_repo "$REGISTRY_REPO"
echo "seed: registry projects.yaml (default project '$DEFAULT_PROJECT_ID')"
put_file "$REGISTRY_REPO" "projects.yaml" "deployment:
name: Tier1 RFC
tagline: Tier-1 end-to-end deployment
projects:
- id: $DEFAULT_PROJECT_ID
name: OHM
type: document
content_repo: $CONTENT_REPO
visibility: public
"
echo "seed: default-collection entry under rfcs/ (no fields — legacy/document path)"
put_file "$CONTENT_REPO" "rfcs/intro.md" "---
slug: intro
title: Intro
status: graduated
state: active
id: RFC-0001
owners: [owner]
---
# Intro
Seed entry for Tier-1 e2e.
' | base64 | tr -d '\n')
curl -s -H "Authorization: token $TOKEN" -X POST \
"$GITEA/api/v1/repos/$ORG/$CONTENT_REPO/contents/rfcs/intro.md" \
-H 'Content-Type: application/json' \
-d "{\"message\":\"seed intro\",\"content\":\"$B64\",\"branch\":\"main\"}" \
|| echo "seed: intro.md exists, continuing"
Seed entry for the default (document) collection.
"
echo "seed: 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.
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.
"
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.
"
put_file "$CONTENT_REPO" "bdd/rfcs/search-facets.md" "---
slug: search-facets
title: Faceted search
state: active
priority: P0
tags: [search]
---
Faceted search scenario.
"
echo "seed: registering OAuth application"
OAUTH_JSON=$(curl -sf -u "$ADMIN_USER:$ADMIN_PASS" -X POST "$GITEA/api/v1/user/applications/oauth2" \
OAUTH_JSON=$(auth_admin -X POST "$GITEA/api/v1/user/applications/oauth2" \
-H 'Content-Type: application/json' \
-d "{\"name\":\"rfc-app-tier1\",\"redirect_uris\":[\"$APP_URL/auth/callback\"],\"confidential_client\":true}")
CLIENT_ID=$(printf '%s' "$OAUTH_JSON" | sed -n 's/.*"client_id":"\([^"]*\)".*/\1/p')
CLIENT_SECRET=$(printf '%s' "$OAUTH_JSON" | sed -n 's/.*"client_secret":"\([^"]*\)".*/\1/p')
echo "seed: registering webhook on content repo -> backend"
curl -s -H "Authorization: token $TOKEN" -X POST \
"$GITEA/api/v1/repos/$ORG/$CONTENT_REPO/hooks" \
-H 'Content-Type: application/json' \
-d "{\"type\":\"gitea\",\"active\":true,\"events\":[\"push\",\"pull_request\"],\"config\":{\"url\":\"http://backend:8000/api/webhooks/gitea\",\"content_type\":\"json\",\"secret\":\"$WEBHOOK_SECRET\"}}" \
|| echo "seed: webhook exists, continuing"
echo "seed: registering webhooks (content + registry) -> backend"
register_webhook "$CONTENT_REPO"
register_webhook "$REGISTRY_REPO"
echo "seed: writing generated env to $OUT"
cat > "$OUT" <<EOF