TestTools
The TestTools model — fields, relations, and API.
Transport: REST — POST /api/v1/testing/purge-account · Auth: API key only (no JWT) · Gates: TEST_TOOLS_ENABLED + TEST_TOOLS_API_KEY
PRD
Business problem
QA needs to sign up, exercise a workspace, and tear it down repeatedly against a shared (staging) environment. The real self-deletion flow enforces a 7-day grace window before a workspace is purged, which makes a fast signup→delete→repeat loop impossible. Testers were resorting to hand-run SQL against the database.
User stories
- As a tester, I want to delete an account by email with a single authenticated call, so that I can immediately re-register the same email and run the flow again — as many times as I like — without waiting out a grace period or asking for DB access.
Success criteria
- One call hard-deletes the user and every workspace they own (hyper rows + dedicated DBs), freeing the email for immediate re-signup.
- Reachable with only a shared API key — no JWT, no admin account.
- Impossible to trigger by accident in real production: disabled unless an explicit flag is on and a matching key is supplied.
- Idempotent — deleting a missing account is a
200withdeleted: false, not an error.
How it works
High-level overview — enough to understand the feature without reading the code.
Summary
A test-only endpoint that hard-deletes an account by email, reusing the exact purge primitives the production workspace-deletion cron uses — just keyed by email and looped over every workspace, with the grace window skipped. It is walled off behind a double fail-closed guard so it can only ever run where a tester explicitly enabled it.
Flow
Steps
- The guard checks
TEST_TOOLS_ENABLED === 'true'(else403) then a constant-timeX-Api-KeyvsTEST_TOOLS_API_KEY(else401). No user context is resolved. TestAccountService.purgeByEmaillooks the user up by email; a miss returnsdeleted: false(no error).- For each of the user's workspaces:
- Hyper →
WorkspaceMigrationService.deleteWorkspaceDatadeletes everyworkspace_id-scoped row from the sharedcrm_hyperDB (including theadminrow), then theuser_workspacesrow is removed; the shared tenant stays. - Dedicated →
TenantProvisioningService.dropDatabasedrops the tenant DB, then its main-DB records (memberships → workspace → tenant) are removed.
- Hyper →
- The user's remaining memberships and the
userrow itself are deleted, freeing the email.
Key components
- Controller —
test-account.controller.ts(POST /testing/purge-account,@UseGuards(TestToolsGuard)). - Service —
test-account.service.ts(purgeByEmail, mirrorsWorkspaceDeletionProcessor.purge). - Guard —
guards/test-tools.guard.ts(enable-flag + constant-time key check). - Reused —
WorkspaceMigrationService,HyperTenantService,TenantProvisioningService— the same primitives as production deletion, so RLS / connection-pinning behavior is identical (no hand-rolled SQL).
FRD
The single capability this module exposes:
purge-account— hard-delete a user + all their workspaces by email. Body{ email }; returns{ deleted, email, user_id?, workspaces[] }. Bypasses the self-deletion grace window (test-only). Idempotent on a missing account.
HLD
Frontend
None — this is a backend QA utility. A tester calls it from a script, Postman, or curl
with the shared X-Api-Key.
API
REST, POST /api/v1/testing/purge-account, guarded by TestToolsGuard (API key, no
JWT). See API.
Database changes
Destructive and irreversible: deletes workspace_id rows across the shared crm_hyper
DB, drops dedicated tenant databases, and removes user_workspaces,
user_tenant_memberships, and user rows in the main DB. Creates nothing.
LLD
Configuration
| Env var | Purpose | Fail-closed behavior |
|---|---|---|
TEST_TOOLS_ENABLED | Master on/off. Must equal "true". | Anything else → 403 |
TEST_TOOLS_API_KEY | Shared secret sent as X-Api-Key. | Unset → 401; mismatch → 401 |
Leave TEST_TOOLS_ENABLED=false in real production. Both gates must pass, so a stray
key with the flag off (or the flag on with no key) stays inert.
Services
TestAccountService.purgeByEmail(email)— orchestrates the lookup + per-workspace purge + user removal; returns aPurgeAccountResult.purgeHyperWorkspace/purgeDedicatedWorkspace— private helpers, one per tenant kind, mirroring the production processor.
Validators
PurgeAccountDto—@IsEmail()+@IsNotEmpty(); the email is trimmed + lowercased before lookup.TestToolsGuard— enforces the enable flag and the constant-time key comparison.
Request / response
POST /api/v1/testing/purge-account
X-Api-Key: <TEST_TOOLS_API_KEY>
Content-Type: application/json
{ "email": "tester@example.com" }{
"message": "Account purged",
"data": {
"deleted": true,
"email": "tester@example.com",
"user_id": 262,
"workspaces": [
{ "workspace_id": 56, "type": "hyper", "rows_deleted": 137 }
]
}
}workspaces[].type ∈ hyper | dedicated | orphan; rows_deleted is the hyper row
count (null for a dedicated DB drop). A missing account returns deleted: false with
an empty workspaces array.
ERD
No entities — this module owns no tables. It operates on existing main-DB records
(user, user_workspaces, user_tenant_memberships, tenants) and the shared
crm_hyper workspace-scoped tables, reusing the deletion module's primitives.