Trials — QA Plan
Pre-requisites
- Org with a non-UTC timezone; owner, admin, coach A, coach B, a Sales rep (coach with an
owngrant), a Sales manager, members. - A published session S coached by A, starting in ~50 minutes, capacity 3, waitlist on; a second published session S2 tomorrow.
- A lead with a phone number assigned to the rep.
- A Trial workout plan “Trial class” (created as Trial workout in the plan editor), and member M1 holding it; member M2 on a regular plan; member M3 with no plan.
- A published
health_declarationcompliance form (and an org without one for thenullcase). - The mobile app signed in as coach A, notifications allowed.
Golden paths
G1 — Rep books a trial into a class
| Step | Action | Expected |
|---|---|---|
| 1 | Sales → lead → Log result → Trial scheduled. | Session picker lists published future sessions for 14 days with time, class, coach and spots left; full ones disabled; “Other (no class)” keeps the date-time input. |
| 2 | Pick S, save. | Appointment starts_at = S start, type = class name, class_session_id = S. Lead sheet shows class, time, coach. |
| 3 | Open S on the web schedule. | bookingCount/capacityRemaining include the trial; roster lists the lead in “Trials · N” with a Trial badge and the intake note (also as coach A); no source, no lead source anywhere. |
G2 — Coach sees and checks in the trialist
| Step | Action | Expected |
|---|---|---|
| 1 | Coach A opens the mobile home. | ”Trials today” card lists S with the lead (Trial badge, no source). Coach B does not see S; the owner sees it without “mine”. |
| 2 | Wait for the brief job (S starts within 60 min). | One push “1 trial at HH:MM · |
| 3 | Open S → Roster → Check in on the trial. | Entry shows attended (checked in); seat still taken; lead activity “checked in” by coach A. |
| 4 | Rep opens the lead. | ”Checked in HH:MM by A”; outcome editor preselects attended. |
| 5 | Rep confirms attended. | Outcome attended; seat still held (no waitlist promotion); entry stays on the roster. |
G3 — Plan and staff trials
| Step | Action | Expected |
|---|---|---|
| 1 | M1 books S with the trial plan. | Roster: M1 in “Trials · N” with a Trial badge (no plan name, no source); no Mark/Unmark action. |
| 2 | M2 books S. | Not a trial; “Mark as trial” offered (canMarkTrial). |
| 3 | Coach A marks M2 as trial. | M2 moves into the trials block (no “Marked by”); “Unmark” offered (canUnmarkTrial); trialCount +1; the brief and the Trials today card include M2. |
| 4 | Unmark M2. | Back to “Everyone else”. |
| 4b | Mark M2, M2 cancels and books S again. | A plain booking: not a trial, “Mark as trial” offered, revocable by the entitlement sweep. |
| 4c | A cancelled or no-show booking. | Neither Mark nor Unmark offered; the route answers 409 trial_not_markable. |
| 4d | M2 opens their bookings in the app / web, and the booking response when booking. | No trialMarkedByUserId / trialAppointmentId keys. |
| 5 | Plain drop-in (not flagged) booking. | Not a trial. |
G3b — Trial workout plan
| Step | Action | Expected |
|---|---|---|
| 1 | Plans → New plan. | Type picker: Subscription, Class Pack, Drop-in, then Trial workout nested under Drop-in (“A single trial class for newcomers”). No “Trial plan” switch anywhere. |
| 2 | Pick Trial workout. | Name pre-filled “Trial workout” (kept if already typed); price shows “Set 0 for a free trial”; Use within (days/weeks), Show in shop, One per person (on), payment link. No interval, credits, presale/intro, purchase cap, plan group, booking limits, overlap. |
| 3 | Price 0, Use within 2 weeks, save. | Plan saved drop_in + isTrial, trialOncePerPerson: true, expiresInDays: 14. List type cell and plan header: “Trial workout”. |
| 4 | Edit it. | Kind shown locked as Trial workout; One per person and Use within loaded. |
| 5 | M3 buys it in the shop. | Active at once; usable for 14 days (expired after). |
| 6 | M3 buys any trial plan again (shop, join link with that plan, minisite CTA). | Shop: “A trial was already used here…” toast. Join: “You’ve already had a trial” with Browse plans; no failed-checkout task for the gym. |
| 7 | Guardian buys the trial for a child twice. | Second purchase refused with the same message (add-child step / shop). |
| 8 | Owner sells M3 another trial at the desk / enrolls M3. | Allowed. |
| 9 | Turn One per person off; M3 buys again. | Allowed. Plain drop-ins and other plans are never affected. |
| 10 | M4 starts a paid checkout of trial A, leaves it unpaid, then buys trial B. | Refused trial_already_used. Resuming the trial A checkout works. |
| 11 | Un-flag a trial plan someone bought (or abandoned a checkout of). | 400 trial_plan_in_use; plan stays a trial. Un-flag an unsold one with a Use within window: 400 saying to clear expiresInDays; with it cleared: saved, One per person reset. |
G4 — Lead converts before class
| Step | Action | Expected |
|---|---|---|
| 1 | S full (capacity reached with the lead’s trial), a member waitlisted. Convert the lead (Sales convert, Sales link, CRM convert, CRM link, or a self-signup with the lead’s email). | Appointment converted (lead sheet: “Converted — booked as member”); the new member has a confirmed booking in S with trial_appointment_id; waitlisted member still waitlisted; seat counts unchanged. |
| 2 | Open the roster. | One entry for the person (kind booking), a Trial badge (no source), lead note kept; no lead-trial row; no Mark/Unmark. |
| 3 | Member has exactly one plan that can pay. | Booking carries that plan, credit spent once. With no plan: booking without a plan. |
| 4 | End the member’s plan (cancel / plan change). | The converted booking is not revoked by the entitlement sweep. |
| 5 | Member already booked in S before converting. | Their booking kept and linked; the trial’s seat goes to the waitlist. |
| 6 | Lead checked in before converting. | Trial ends attended, no booking created. |
| 7 | Class already started, not checked in. | Every path (Sales and CRM convert/link, self-signup): trial cancelled and superseded; no booking created. |
G5 — Notes carry over
| Step | Action | Expected |
|---|---|---|
| 1 | Coach adds an important note on the trial from the roster. | Note on the lead; visible in the Sales lead sheet. |
| 2 | Convert the lead into a member. | Member detail lists the note (no copy made); roster shows it as important on their booking. |
| 3 | Coach B (not author) opens it. | No edit/delete (canEdit = false); owner can edit/delete. |
G6 — Book an existing member as a trial
| Step | Action | Expected |
|---|---|---|
| 1 | Members → M2 → Overview. | Trial classes card: “No upcoming trial classes” and Book trial class. |
| 2 | Book trial class → pick S2 (picker lists the next 14 days with time, class, coach, spots left; full classes selectable) → Book trial. | Toast “Trial class booked”; the card lists S2 (class, date/time, coach, Booked) with Unmark. Booking row: trial_marked_by_user_id and booked_by_user_id = the caller, subscription_id = M2’s plan; a credit plan loses one credit like a normal booking; M2 gets the usual confirmation. Roster of S2: M2 in the trials block, “Marked by …“. |
| 3 | Give M2 a second plan that can pay, book another class. | The dialog asks which plan pays (server’s candidate list, first preselected); resubmitting books on the chosen plan. |
| 4 | Book M2 into a full class with a waitlist. | Toast “Added to the waitlist as a trial”; card shows Waitlisted; booking marked. |
| 5 | Book M2 into a class M2 already booked. | No second booking; the existing one is marked (no-op if already a trial). |
| 6 | Unmark from the card. | Booking unmarked; it leaves the card and the roster trials block. |
| 7 | M1 (trial plan) and a member converted from a lead with a class trial. | Their trials are listed without Unmark. |
| 8 | Refusals: M3 (no plan, org requires one), a plan out of credits, a class whose registration closed. | The booking copy naming the member (e.g. “M3 needs an active plan…”); nothing booked. |
| 9 | Sales → a converted lead’s sheet. | Trial classes · |
| 10 | As a member, call GET/POST …/members/:membershipId/trial-bookings. | 403. |
Trial seats
| # | Case | Expected |
|---|---|---|
| 1 | Session with all seats taken | Picker shows 0, disabled; API 409 trial_session_full |
| 2 | Draft / deleted / started / other-org session | 400 trial_session_unavailable |
| 3 | Member books the last seat at the same moment as the trial claim | Exactly one wins; the other gets full / waitlisted |
| 4 | Reschedule the trial within the same full session | Allowed (own seat not counted) |
| 5 | Reschedule to S2 | S seat freed and waitlist filled; S2 seat taken |
| 6 | Rep records no-show / cancelled | Seat freed, waitlist filled |
| 7 | Result Not relevant | Trial cancelled, seat freed |
| 8 | Archive the lead (single and bulk) | Trial cancelled and superseded, seat freed, waitlist filled |
| 9 | Convert / link the lead with a pending trial in a future class | Seat moves into the member booking (G4); no waitlist promotion |
| 10 | Move S’s start time | Appointment starts_at, lead trial_date and the open trial follow-up task (start + 1h) move |
| 11 | Trial with “Other (no class)“ | No seat held anywhere |
| 12 | Coach check-in | Seat stays held |
| 13 | Coach checks the lead in, then the rep converts / marks Not relevant / archives, or reschedules | The checked-in trial ends attended, not superseded: still on S’s roster, seat still held, no promotion on S |
| 14 | Cancel S (or delete it, bulk-delete its week with booked classes, overwrite it with a template apply, or Arbox stops reporting an Arbox-owned S) with a pending trial not checked in | Trial cancelled and superseded; lead activity sales_trial session_cancelled; the rep’s open trial task cancelled; no waitlist promotion |
| 14b | S holds only the lead’s trial; bulk delete without “include booked”, or a template apply with overwrite | S is treated as booked: kept (overwrite reports it blocked), trial still pending |
| 15 | Same, with the trial checked in | Trial ends attended; activity written; trial task left open |
Marking
| # | Case | Expected |
|---|---|---|
| 1 | Mark a booking twice by two staff | First marker kept |
| 2 | Mark / unmark a plan trial or a converted-trial booking, or a cancelled / no-show booking | 409 trial_not_markable |
| 3 | Booking of another session | 404 |
| 4 | Member calls the route | 403 |
Check-in
| # | Case | Expected |
|---|---|---|
| 1 | Check in twice | Second is 409 trial_not_pending |
| 2 | Check in after the rep recorded an outcome | 409 trial_not_pending |
| 3 | Undo while pending | Columns cleared, version bumped, activity check_in_undone |
| 4 | Appointment of another session / org | 404 |
| 5 | Session ended with no outcome | Trial stays pending (no auto no-show); appears as outcome work in Sales |
Health declaration
| # | Case | Expected |
|---|---|---|
| 1 | Member signed the current declaration | signed |
| 2 | Member did not sign | not_signed |
| 3 | Org with no health_declaration form | null for all |
| 4 | Lead trial entry | null |
Coach brief
| # | Case | Expected |
|---|---|---|
| 1 | Session in 90 min | No push yet |
| 2 | Session with no trial (members only) | No push, no marker; a trial added later is briefed before start |
| 3 | Session moved after its brief | Briefed again for the new time |
| 4 | notifyUser throws while enqueuing | Marker cleared; retried next tick |
| 4b | Enqueue succeeds but Expo rejects the delivery | Not retried; marker stays |
| 5 | Coach turned trials push off | No push |
| 6 | Session without a coach / inactive coach | No push |
| 7 | Push for an org that is not active in the app | App switches org, then opens the session |
Trials report, history, Action Center
| # | Case | Expected |
|---|---|---|
| 1 | A lead trial checked in, then the lead converts and starts a regular plan 30 days after the class | One trial, source lead: attended, checked in, converted; median 30 days |
| 2 | Same, plan starts on day 31 | Attended, not converted; listed under “Attended, not converted” |
| 3 | A lead converted before the class | Counted once, as the member booking (source lead) |
| 4 | A lead trial past its time with no check-in and no Sales outcome | awaiting_outcome |
| 5 | Staff-marked booking set no-show; trial-plan booking in the future | no_show (source staff); upcoming (source plan) |
| 6 | By coach / by rep | Coach = the class’s coach; rep = the lead’s owner or the staff member who marked it, both as membership ids; trial plans have no rep |
| 7 | Member page and the lead’s Sales sheet for a converted lead | Both list the same trials, newest first, with status, check-in, converted plan and days; no source |
| 8 | Someone attended in the last 14 days with no regular plan | Action Center row trials.attended_not_converted for owner/admin, linking to the Trials report; gone once they convert |
| 9 | Sales cohort report | ”Trial attended” counts checked-in trials and converted trials whose booking was attended |
Telemetry
| # | Case | Expected |
|---|---|---|
| 1 | Schedule, reschedule, release, check in, mark, convert | Matching TrialEvents, organization group set, no name / phone / note text |
| 2 | subscription.started delivered twice for a converting plan | Two captures with the same uuid; PostHog keeps one trial_converted |
| 3 | Refused trial seat / purchase / un-flag / mark | One trial_refused info log with its reason |
| 4 | A brief run | One trial_brief_run log with due, briefed, skipped, failed, durationMs |
Permissions
| Action | Owner / Admin | Coach | Coach with a Sales grant | Member |
|---|---|---|---|---|
| Roster / trials today / mark trial / trial check-in / trial note | ✓ | ✓ (trials today: own sessions) | ✓ | 403 |
| Member notes read/add | ✓ | ✓ | ✓ | 403 |
| Edit/delete someone else’s member note | ✓ | 403 | 403 | — |
| Lead notes, trial-sessions picker | ✓ | 403 | Leads in grant scope | 403 |
| Edit/delete someone else’s lead note | ✓ (implicit manager) | 403 | Manager bit only | — |
| Create/edit a Trial workout plan | ✓ (plan editors) | — | — | — |
| Buy a once-per-person trial a second time | ✓ (desk sale / enrollment allowed) | — | — | 409 trial_already_used |
| Trials report | ✓ | 403 | 403 | 403 |
| Member trial history | ✓ | ✓ | ✓ | 403 |
| Lead trial history | ✓ | 403 | Leads in grant scope | 403 |
Mobile specifics
- Card and roster hidden for members and while acting for a child.
- Notification settings hide
trialsfor non-staff. - Mark, check-in and notes with no connection fail; never queued.
- Roster data is not in the persisted query snapshot after an app restart offline.