Class Types — QA Plan
Pre-requisites
- Test org with at least one program (
scheduledelivery mode). - A second org for cross-org isolation.
- Owner, admin, coach, member personas seeded.
Golden path
G1 — Owner creates a class type
| Step | Action | Expected |
|---|---|---|
| 1 | Owner navigates to /dashboard/schedule → class types tab. | List loads. |
| 2 | Click “Add class type”, pick program, name “CrossFit WOD”, color, defaults 60min/20 cap, hasWorkout=true. | Form valid. |
| 3 | Submit. | New row appears; response includes embedded program metadata. |
G2 — Coach lists class types when creating a session
| Step | Action | Expected |
|---|---|---|
| 1 | Coach navigates to “Create session”. | Dropdown lists active class types only. |
| 2 | Pick “CrossFit WOD”; submit with no explicit capacity. | Session inherits default_capacity=20. |
G3 — Owner deactivates a class type
| Step | Action | Expected |
|---|---|---|
| 1 | Owner clicks delete on a class type. | API: PATCH? No — DELETE /class-types/:id. |
| 2 | Re-list. | Class type no longer in list. |
| 3 | Open a historical session that used it. | Class type name still renders (relational fetch ignores is_active). |
G4 — Update defaults
| Step | Action | Expected |
|---|---|---|
| 1 | PATCH /class-types/:id with defaultCapacity:30. | Row updated, updated_at bumped. |
| 2 | New session created with no explicit capacity. | Inherits 30 (not 20). Existing sessions unchanged. |
G5 — hasWorkout=false (“Open Gym”)
| Step | Action | Expected |
|---|---|---|
| 1 | Create class type with hasWorkout=false. | Row saved. |
| 2 | Create a session against it. | Workout-attachment controls hidden in UI; class_session_workouts allowed to remain empty; workoutSource='none' in the response. |
Edge cases
E1 — Cross-program prevention
| Step | Action | Expected |
|---|---|---|
| 1 | Owner of org A POSTs class type with programId belonging to org B. | 400 ‘Program not found in this organization’. |
E2 — Coach attempts mutation
| Step | Action | Expected |
|---|---|---|
| 1 | Coach POSTs/PATCHes/DELETEs. | 403 ‘Only owners and admins can …’. |
E3 — Non-member access
| Step | Action | Expected |
|---|---|---|
| 1 | User with no membership in org GETs class types. | 403 from requireMembership. |
E4 — Cross-org get-by-id
| Step | Action | Expected |
|---|---|---|
| 1 | Owner of org A: GET /organizations/A/class-types/{idFromB}. | 404 ‘Class type not found’. |
E5 — Deactivated class type in active session listing
| Step | Action | Expected |
|---|---|---|
| 1 | Deactivate a class type that has 3 published future sessions. | Sessions still appear on the schedule. List of class types omits it. |
E6 — Reactivation
| Step | Action | Expected |
|---|---|---|
| 1 | Re-PATCH the row with isActive:true. Note: UpdateClassTypeDto does not accept isActive; reactivation through PATCH may not work. | TODO: verify whether DB-direct reactivation or a dedicated endpoint is the supported path. |
Cross-persona
- Member never sees mutation endpoints; only reads.
- Coach can read; cannot mutate.
i18n
| Lang | Strings to verify |
|---|---|
| en | schedule.classTypeSheet.hasWorkoutHelper reads “Off for class types like Open Gym that don’t carry programmed workouts — only sessions are generated.” |
| he | Same key present, Hebrew translation. |
| ru | Same key present, Russian translation. |
Expected vs actual checks
- After deactivation, query DB:
is_active=false,deleted_atstill NULL. - After session create with inherited capacity, query session row:
capacitymatches class type’sdefault_capacityat the time of creation. - After class type rename, existing sessions show the new name (no snapshot on session).