Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesDaily ProgrammingDaily programming — QA plan

Daily programming — QA plan

Pre-requisites

  • Pro-tier org with:
    • Schedule-mode program “Open Box” containing two class types: “WOD” and “Open Gym”.
    • Library workouts: “Fran”, “Easy Row”, “Skills Day”.
  • Personas: Coach Casey, Owner Olga, Member Mia.
  • Mia has a booking on the WOD session 2026-06-03.
  • Org timezone Asia/Jerusalem.

Test scenarios

Set / replace

  1. Set a slot with one workout

    • Trigger: Casey PUT /daily-programming { classTypeId:<wod>, date:'2026-06-03', workouts:[{workoutId:<fran>, sortOrder:0}] }.
    • Expected: 1 row in daily_programming with status='draft', created_by=Casey. Response is the grouped slot with that one workout.
  2. Set two workouts on the same slot

    • Trigger: same PUT with two workouts at sortOrder 0 and 1.
    • Expected: 2 rows. UNIQUE constraint not violated (different workout_id).
  3. Replace existing slot

    • Trigger: after step 2, PUT again with one different workout.
    • Expected: the previous 2 rows are physically deleted; only the new 1 row remains.
  4. Set with empty workouts[]

    • Trigger: PUT { ..., workouts: [] }.
    • Expected: existing rows deleted; slot effectively empty.
  5. Set with cross-org workoutId

    • Trigger: workoutId from a different org.
    • Expected: 400 “One or more workouts not found in this organization”.
  6. Set with soft-deleted workoutId

    • Trigger: workoutId of a workout with deleted_at set.
    • Expected: 400.
  7. Set with cross-org classTypeId

    • Trigger: classTypeId from another org.
    • Expected: 400 “Class type not found in this organization”.
  8. Duplicate workoutId in the same payload

    • Trigger: workouts:[{wid:X, sortOrder:0}, {wid:X, sortOrder:1}].
    • Expected: UNIQUE (class_type_id, date, workout_id) rejects the second insert. TODO: verify whether service deduplicates first or the DB raises.

Status

  1. Publish a slot

    • Trigger: PATCH /daily-programming/status { classTypeId:<wod>, date:'2026-06-03', status:'published' }.
    • Expected: all rows for the slot become published.
  2. Unpublish back to draft

    • Trigger: same with status:'draft'.
    • Expected: all rows back to draft.
  3. Status flip on empty slot

    • Trigger: PATCH /status on a slot with no rows.
    • Expected: 200, no rows updated, response shows empty slot.

Reads

  1. Week read as coach

    • Trigger: GET /daily-programming?weekStart=2026-06-01.
    • Expected: rows for the whole week, including drafts. Grouped per (classType, date).
  2. Week read as member with drafts present

    • Trigger: same as 12 but as Mia.
    • Expected: drafts filtered out; only published rows visible.
  3. Date read with explicit classType filter

    • Trigger: GET /daily-programming?date=2026-06-03&classTypeId=<wod> (passed via service findByDate with classTypeId).
    • Note: the HTTP GET / does NOT accept classTypeId; the route uses findByDate(orgId, clerkId, date). The classTypeId-scoped read is internal. TODO: verify which is preferred.
  4. Week read returns no rows for empty week

    • Trigger: a week with no programming.
    • Expected: [].
  5. Member sees workout sections

    • Trigger: Mia GET /daily-programming?date=2026-06-03 for a slot that’s published.
    • Expected: response carries the workout with sections + movements + exercise metadata.

Remove

  1. Remove a slot

    • Trigger: DELETE /daily-programming?classTypeId=<wod>&date=2026-06-03.
    • Expected: rows physically deleted. Subsequent GET returns the slot absent (no draft tombstone).
  2. Remove a non-existent slot

    • Trigger: same on an empty slot.
    • Expected: 200, no-op.

Template apply integration

  1. Apply schedule template writes daily-programming
    • Trigger: POST /program-templates/:id/apply with mode schedule, startDate.
    • Expected: new daily_programming rows (status='draft') for each cell that lands on an active day. Coach must publish manually.

Permissions

  1. Member tries to PUT

    • Trigger: Mia PUT /daily-programming.
    • Expected: 403.
  2. Member tries to PATCH status

    • Trigger: Mia PATCH /daily-programming/status.
    • Expected: 403.
  3. Cross-org read

    • Trigger: Coach Bob (org B) calls org A’s endpoint.
    • Expected: 403 from requireMembership.

Class-session integration

  1. Class session card shows the day’s published workouts

    • Trigger: Mia opens the WOD session for 2026-06-03 on her schedule.
    • Expected: the session detail loads via ClassSessionsService.findByDateRange and renders Fran. If the slot is draft, Mia sees no workout.
  2. Class session card shows nothing in draft

    • Trigger: same after unpublishing.
    • Expected: workout disappears from member’s view.

i18n

  1. Whiteboard in Hebrew

    • Trigger: Mia in he views the WOD board.
    • Expected: section labels, movement names (canonical with Hebrew override), and time-cap labels all RTL/translated.
  2. Coach UI in Russian

    • Trigger: Casey in ru sets the day’s programming.
    • Expected: status chips (“Draft”/“Published”) and form labels translated.

What “broken” looks like

  • A previously-removed workout reappears after a status flip (because PATCH /status doesn’t restore physical deletes — but if a bug re-inserted rows on flip, would show this symptom).
  • Member sees draft rows (member visibility filter regressed).
  • Two coaches editing the same slot simultaneously each end up with their own row set (race in the delete-then-insert transaction).
  • Cross-org workout slips into the slot (workoutId org validation missing).
  • Daily programming for a deleted class type persists and renders ghost entries (the class type FK is set NULL’d? — verify CASCADE behavior; current schema does not specify ON DELETE on class_type_id).
  • Member sees programming for a class type that doesn’t belong to their org (org scoping via getOrgClassTypeIds regressed).
  • Status enum drift — service accepts arbitrary strings instead of draft/published (currently the IsIn validator on the DTO enforces this).