Skip to content

Commit 33457bc

Browse files
test(e2e): Batch C LIVE prod specs (W-STACKS/STACKS-ADV/LIFECYCLE/AUTH2) (#186)
Adds e2e/live-stacks-lifecycle.spec.ts driving the stack, resource- lifecycle, and secondary-auth user-flows against PROD via the minted cohort account. Closes Batch C of the prod-coverage matrix (waves 11-14). W-STACKS: POST /stacks/new (single-service minimal manifest -> 202 accepted contract) -> GET /api/v1/stacks (list) + GET /api/v1/stacks/:slug + GET /stacks/:slug (get) -> PATCH /stacks/:slug/env (merge -> 200, merged key present; incremental + invalid-key 400) -> missing-slug 404 -> two-step DELETE /stacks/:slug (paid 202 pending OR 200) + confirm-deletion cancel -> skip-email reap -> gone. Full multi-service Kaniko build DEFERRED (202-accepted contract only, mirrors live-writes deploy 202). W-STACKS-ADV: GET /api/v1/stacks/:slug/family (PRO 200 + private Cache-Control), POST /api/v1/stacks/:slug/promote (non-destructive contract: 412 needs-build / 402 / 400 / 202, never 5xx; reaps any accidentally-created sibling), DELETE confirm-deletion cancel arm (404 with no pending row). W-LIFECYCLE: on a FAST cache resource (no dedicated DB) -- pause -> resume (Pro+), rotate-credentials, backup CONTRACT (cache -> 400 unsupported_resource_type, postgres-only; never waits for a real backup), backups/restores list, reap. W-AUTH2: POST /auth/email/start accepted contract (Brevo-gated, delivery NOT asserted), POST /auth/github body-flow no-credential contract (400 missing_code / 401 oauth_failed / 503 not-configured), and a DISPOSABLE claimed-session logout round-trip (/auth/me 200 -> logout -> SAME bearer 401). The logout leg NEVER revokes the shared minted JWT (Batch B lesson) -- it claims a throwaway team and revokes THAT session. cleanup-ledger.ts: add a `stack` entity kind (DELETE /stacks/:slug with X-Skip-Email-Confirmation so a paid team's two-step flow doesn't strand the reap behind an undeliverable confirmation email; account cascade is the backstop). Safety machinery mirrors live-writes/live-reads EXACTLY (rule 24): E2E_LIVE=1 gating, assertSafeApiTarget, ledger-before-assert + inline reap + afterAll backstop, 503 per-service skips. Brevo/Razorpay contract-only; no team-tier minting; slow services pinned off (cache hot-pool). npm run gate green (tsc --noEmit + build + 1115 vitest pass). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2efb086 commit 33457bc

2 files changed

Lines changed: 919 additions & 0 deletions

File tree

e2e/cleanup-ledger.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ export type CohortEntityKind =
2626
| 'deployment'
2727
| 'team'
2828
| 'storage-prefix'
29+
// A multi-service stack created via POST /stacks/new (Batch C W-STACKS /
30+
// W-STACKS-ADV). Reaped via DELETE /stacks/:slug — but a PAID team's plain
31+
// DELETE enters the two-step email-confirmed flow (202 pending), which on
32+
// prod never resolves because the Brevo sender is unvalidated. So the reaper
33+
// sends the X-Skip-Email-Confirmation header for this kind to force immediate
34+
// destruction (the SAME header the spec's inline reap uses). The account
35+
// cascade (DELETE /internal/e2e/account) is the backstop.
36+
| 'stack'
2937
// A SECONDARY ephemeral cohort account minted via POST /internal/e2e/account
3038
// (Batch B W-TEAM member-mgmt isolation). It is NOT reapable by a team-scoped
3139
// bearer DELETE — there is no DELETE /api/v1/team/:id route, and DELETE
@@ -115,6 +123,11 @@ function deletePath(entity: CohortEntity): string {
115123
return `/api/v1/resources/${entity.id}`
116124
case 'deployment':
117125
return `/api/v1/deployments/${entity.id}`
126+
case 'stack':
127+
// Stacks delete by slug (NOT under /api/v1). The skip-email header is
128+
// attached in reapEntities so a paid team's two-step flow doesn't strand
129+
// the row behind an undeliverable confirmation email.
130+
return `/stacks/${entity.id}`
118131
case 'team':
119132
return `/api/v1/team/${entity.id}`
120133
case 'e2e-account':
@@ -157,6 +170,15 @@ export async function reapEntities(
157170
} else if (entity.token) {
158171
headers.Authorization = `Bearer ${entity.token}`
159172
}
173+
if (entity.kind === 'stack') {
174+
// Force immediate destruction — a paid team's DELETE /stacks/:slug would
175+
// otherwise enter the two-step email-confirmed flow (202 pending), which
176+
// never resolves on prod (Brevo sender unvalidated). The 202 would then be
177+
// mis-counted as a failed reap (not 2xx/404). The skip-email header makes
178+
// teardown synchronous + idempotent. Constant per the no-hardcoded-strings
179+
// rule (mirrors deletion_confirm.go SkipEmailConfirmationHeader).
180+
headers['X-Skip-Email-Confirmation'] = 'yes'
181+
}
160182
try {
161183
const resp = await request.fetch(`${entity.apiUrl.replace(/\/$/, '')}${path}`, {
162184
method: 'DELETE',

0 commit comments

Comments
 (0)