Skip to content

Commit 5949d0f

Browse files
test(e2e): functional deploy-failure debug-path live UI spec + factory withFailedDeploy (#195)
The CEO's core ask: CI must FUNCTIONALLY prove the debug path works — "deploy an app and get its logs; fail a deployment and get its logs/events" — not just that llms.txt redirects. Adds e2e/live-ui-failure-diag.spec.ts (real backend, minted cohort, reaped) with three legs: - Happy deploy → logs (schedule lane): mint pro → create deploy (202) → the build-logs endpoint authorizes the owner and returns a recognized state (200 streaming / 409 not_ready-still-building / 404 GC'd, NOT 401/5xx) AND the LiveBuild SSE panel reaches a rendered stream state in the UI. - Failed deploy → /events (API, @pr-smoke): mint with_failed_deploy → GET /api/v1/deployments/:id = status=failed + non-empty error_message; GET /api/v1/deployments/:id/events = autopsy event with reason (OOMKilled) + non-empty last_lines + hint; + auth-negative (401) + cross-team (404). This is the agent auto-debug surface, asserted end to end. - Failed deploy → UI (FailureAutopsyPanel, @pr-smoke): load /app/deployments/:id in the browser (minted session) → the panel renders the reason heading + hint + last_lines (log toggle) against a real backend. Extends e2e/factory.ts with mintUserWithFailedDeploy() (with_failed_deploy → failed_deploy_id, api 021bb7e). The two failed-deploy legs are @pr-smoke (fast: seeded reads + panel render, no real Kaniko build) so the debug surface can't silently regress on a web PR; the happy real-deploy leg stays on the schedule. Cohort-tagged + reaped (rule 24); gated on the e2e token (skip-clean). Verified live against prod api 021bb7e: all 4 legs pass, reap clean (attempted=8 deleted=5 alreadyGone=3 failed=0, ledger cleared). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1a11604 commit 5949d0f

2 files changed

Lines changed: 513 additions & 1 deletion

File tree

e2e/factory.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ export interface MintedUser {
7272
sessionJWT: string
7373
/** Pre-seeded resource tokens (empty unless mintUserWithResources was used). */
7474
seededTokens: string[]
75+
/**
76+
* The app_id of the seeded FAILED deployment (status=failed + a
77+
* failure_autopsy event with reason/last_lines/hint), present only when minted
78+
* via mintUserWithFailedDeploy(). Empty string otherwise. The failure-diag
79+
* journey navigates to /app/deployments/<failedDeployID> and reads
80+
* GET /api/v1/deployments/<id>/events with this id.
81+
*/
82+
failedDeployID: string
7583
}
7684

7785
interface MintResponseBody {
@@ -82,13 +90,23 @@ interface MintResponseBody {
8290
session_jwt: string
8391
seeded_tokens?: string[]
8492
seeded_count?: number
93+
failed_deploy_id?: string
8594
}
8695

8796
// ── Mint ─────────────────────────────────────────────────────────────────────
8897

8998
interface MintOpts {
9099
tier?: MintableTier
91100
withResources?: boolean
101+
/**
102+
* Pre-seed ONE failed deployment + its failure_autopsy event (OOMKilled, exit
103+
* 137, JS-heap last_lines, a memory hint) on the minted team. Surfaces the
104+
* deployment's app_id as failed_deploy_id on the response. Pure DB rows on the
105+
* api side (no k8s) — synchronous + sub-ms. Used by the failure-diagnosis
106+
* journey to render the FailureAutopsyPanel + read /events against a real
107+
* backend. api: internal_e2e_account.go with_failed_deploy (#70, 021bb7e).
108+
*/
109+
withFailedDeploy?: boolean
92110
}
93111

94112
/**
@@ -111,7 +129,11 @@ export async function mintUser(
111129
const resp = await request.fetch(`${base}/internal/e2e/account`, {
112130
method: 'POST',
113131
headers: { 'Content-Type': 'application/json', [E2E_ACCOUNT_TOKEN_HEADER]: accountToken() },
114-
data: JSON.stringify({ tier, with_resources: !!opts.withResources }),
132+
data: JSON.stringify({
133+
tier,
134+
with_resources: !!opts.withResources,
135+
with_failed_deploy: !!opts.withFailedDeploy,
136+
}),
115137
failOnStatusCode: false,
116138
})
117139
// Inert-by-default 404: the token is wrong or the endpoint isn't armed on
@@ -141,6 +163,7 @@ export async function mintUser(
141163
tier: body.tier,
142164
sessionJWT: body.session_jwt,
143165
seededTokens: body.seeded_tokens ?? [],
166+
failedDeployID: body.failed_deploy_id ?? '',
144167
}
145168
}
146169

@@ -152,6 +175,21 @@ export function mintUserWithResources(
152175
return mintUser(request, { ...opts, withResources: true })
153176
}
154177

178+
/**
179+
* Mint an account pre-seeded with ONE failed deployment + its failure_autopsy
180+
* event (default tier pro — deployments_apps headroom). The returned
181+
* MintedUser.failedDeployID is the seeded deployment's app_id; the
182+
* failure-diagnosis journey navigates to /app/deployments/<failedDeployID> and
183+
* reads GET /api/v1/deployments/<id>/events with it. Returns null when the
184+
* factory is unarmed (caller SKIPs). See mintUser + the withFailedDeploy opt.
185+
*/
186+
export function mintUserWithFailedDeploy(
187+
request: APIRequestContext,
188+
opts: Omit<MintOpts, 'withFailedDeploy'> = {},
189+
): Promise<MintedUser | null> {
190+
return mintUser(request, { tier: 'pro', ...opts, withFailedDeploy: true })
191+
}
192+
155193
/**
156194
* Mint at the deployments_apps cap: a hobby account (deployments_apps=1). The
157195
* delete-when-exhausted→replace journey (#4) fills the single slot, asserts the

0 commit comments

Comments
 (0)