Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesExports ImportsExports & Imports — QA Plan

Exports & Imports — QA Plan

Exports — auth & rate

StepExpected
Member (non-admin/owner) calls POST /export/members403 Only owners and admins can export data.
Owner calls with empty filtersJob enqueued; CSV contains every member row.
11 export requests in 60 seconds from one IP11th returns 429.

Exports — content

StepExpected
Filter roles: ['coach']CSV contains only coach rows.
Filter statuses: ['active', 'invited']CSV contains only those statuses.
Filter fields: ['email', 'firstName']CSV has those two columns only, in the order they’re emitted by the select clause.
CSV opened in Excel with Hebrew namesRenders correctly (UTF-8 BOM present).
Job completesEmail lands with a presigned download URL; URL works for ~1h.
Job 3h after completionGET /jobs/:jobId returns the row with a fresh presigned downloadUrl (r2.getPresignedUrl regenerates).

Exports — failure

StepExpected
Force a DB outage mid-processorJob lands in failed with errorMessage.
Force an R2 upload failureSame.
Email send failsJob still marked completed; sendExportEmail warning logged. Download URL still served via the API endpoint.

Analytics tab CSV (synchronous)

StepExpected
GET /analytics/export?tab=revenue-summary as owner200 with Content-Disposition: attachment; filename="revenue-summary-<date>.csv".
Unknown tab400.
Member calls403.

Imports — Arbox Public V3

StepExpected
Owner posts { apiKey }One encrypted Arbox provider row exists; no email or password field is accepted.
GET /provider-configReturns { ..., hasCredentials: true } with no plaintext.
Start Arbox members + plans importJob snapshot contains no credential; worker loads the encrypted key and calls only Public V3.
Source user has active: falseImported member is recorded inactive when that authorized V3 field is present.
Trigger entities.leads: true400; leads remain unavailable pending official API validation.
Source contains address, ID, emergency, medical, RFID, or child-status dataNone of those fields persist in Taikan profiles.
Concurrent trigger while one is running409 Active import already exists.
Whitelist ["[email protected]", "[email protected]"]Only those two emails create memberships.
Cancel a pending jobStatus flips to cancelled; queue job removed.
Arbox import completes on a box whose transactions carry document linksEach imported member’s receipts appear under “Receipts from Arbox” on their Memberships tab, newest first; “Open receipt” opens the PDF; the toast counts the receipts.
Re-run the same importNo receipt is duplicated.
Arbox fails some transaction windowsImport still completes; a warning toast names how many periods are missing; re-running fills them.
Coach opens a member’s profileNo receipts request is made and no section renders.
Erase a member with imported receiptsTheir imported_receipts rows are gone.

Arbox break-glass

StepExpected
Gate unset or not trueConfigure, revoke, and run are denied.
Clerk actor lacks privateMetadata.arboxBreakGlass === trueConfigure, revoke, and run are denied.
Run lacks customer authorization, internal approval, or valid bounded windowRun is denied before a provider request.
Successful runEncrypted credential remains server-side; run row records actor, references, window, status, and aggregate counts only.
Failed provider requestRun row is marked failed; no secret, token, or response payload is stored.
RevokeThe dedicated encrypted credential row is deleted.

Imports — CSV

StepExpected
Upload a 4MB CSV200 with preview + suggested mapping.
Upload a 6MB CSV400 MaxFileSizeValidator.
Upload a non-CSV (rename .txt to .csv)400 file type validator (skipMagicNumbersValidation note: validator only checks extension; magic bytes skipped).
Confirm a job before upload404 / 409.
Confirm with valid mapping, runJob lands in_progress, then completed.
Confirm a job whose status is already in_progress409.
Upload then cancel before confirmingStale pending CSV job is dropped on next upload via cancelStalePendingCsvJobs.

Imports — idempotency

StepExpected
Run the same Arbox import twiceMembers upserted; no duplicate users rows.
Member exists with a different email cased differentlyTreated as the same row (case-insensitive match on import path).
Plan exists with the same nameUpdate, not duplicate.

Negative / robustness

  • Empty Arbox response (no members) → job completes immediately with 0 created.
  • BullMQ Redis outage → orchestration fails fast; new imports rejected until Redis recovers.
  • One member-fan-out job hits a DB unique violation → BullMQ retries; eventual success after the race resolves.

Observability

  • Pino logs every phase transition with [job:<id>].
  • import_jobs.results is the source of truth for “what landed and what didn’t”.
  • No PostHog event today; gap.