fix(e2e): hard-delete disposable accounts after each test (BUNYIP-246) #268
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/bunyip-246-e2e-account-hard-delete"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Disposable e2e test accounts now physically leave the database after each test, instead of accumulating on staging (several hundred had piled up). Implements BUNYIP-246.
Root cause (two layers, both worse than the issue assumed)
deleteMeissuedDELETE /v1/users/mewith no request body, but the endpoint requires aDeleteAccountRequest(password), so it returned 400 and never deleted the account - not even soft. Disposable accounts were live rows that nothing ever removed.DELETE /v1/users/meonly SOFT-deletes (UPDATE users SET deleted_at = NOW()); the row persists forever. Admin delete is also soft. The only hard delete was the host-run bootstrap CLI (two fixed emails).Change
DELETE /v1/users/meaccepts?purge=1, honoured ONLY when the environment is non-production ANDBUNYIP_E2E_BOOTSTRAP_ALLOW=true(Config::e2e_purge_enabled, mirroring thebunyip-e2e-bootstrapguards). Production ignores the flag and soft-deletes exactly as before. The purge still requires the same password / TOTP ownership proof as a normal delete, then short-circuits the downstream cascade (disposables have no Stripe customer / OIDC sessions / connected apps).UserRepository::hard_delete/hard_delete_by_email/hard_delete_stale_disposable. Each drops the actor'saudit_logsrows in the same transaction first -audit_logs.actor_idreferencesusers(id)with NOON DELETEcascade, so a bareDELETE FROM usersFK-violates once the account has any history (register / login / reset all write audit rows). This is the FK reality the issue's "the bootstrap deletes by email successfully, so the FK path resolves" assumption missed.bunyip-e2e-bootstrap --cleanupnow routes throughhard_delete_by_email(no more inlineDELETE FROM usersin the binary).e2e_purge_enabled) hourly hard-deletes any disposable row (email matching the+e2e-subaddress marker) older than 6h, a safety net for a crashed run whosefinallynever ran. Never spawned in production.deleteMe(ctx, password)sends?purge=1plus the required password body; the three disposable specs (password-reset,magic-link,change-email) pass the account password.dev-docs/e2e.md+e2e/README.mddocument the flag, the reaper, the non-prod gating, and that production keeps soft-delete.Safety
Five gates before any hard delete: authenticated user, password verified, TOTP (when enabled), non-production environment, and
BUNYIP_E2E_BOOTSTRAP_ALLOW=true. Production never sets the flag, so?purgeis ignored and the reaper is never spawned - prod behavior is unchanged (soft delete + account recovery preserved).Operator step (required for the purge + reaper to activate on staging)
Set
BUNYIP_E2E_BOOTSTRAP_ALLOW=truein the running staging bunyip-api container env (not just for the one-offjust e2e-bootstrapinvocation).Out of scope
The existing several-hundred backlog (prevent-future-only per the issue decision). An operator can clear it via
bunyip-e2e-bootstrapor manual SQL on staging.Verification
just check-containergreen: fmt + clippy-D warnings+cargo test --workspace --lib(incl. the newe2e_env_allows_purgegate test asserting production / empty environments forbid the purge).tsc --noEmitclean on the e2e suite. End-to-end hard-delete is exercised by the three disposable specs themselves once staging has the flag set.#BUNYIP-246
🤖 Generated with Claude Code