Push Notifications — QA Plan
1. Token lifecycle
| # | Scenario | Expected |
|---|---|---|
| 1.1 | Mobile registers a token on first sign-in | 201; row exists with platform; last_seen_at = now |
| 1.2 | Same device re-registers (re-launch) | last_seen_at updated; same row |
| 1.3 | User A signs out; User B signs in on same device | Token row’s user_id flips to B; deleted_at cleared |
| 1.4 | Sign-out flow calls revoke | deleted_at set |
| 1.5 | Caller tries to revoke a token they don’t own | 403 |
2. Send paths
| # | Scenario | Expected |
|---|---|---|
| 2.1 | notifyUser(memberId) with category newMessage | Job enqueued; push arrives on device |
| 2.2 | User opted out of newMessage push | No enqueue |
| 2.3 | User has no active tokens | No enqueue |
| 2.4 | User has 3 devices | Job carries 3 tokens; all receive |
| 2.5 | Bulk fan-out (announcement to 500 members) | Chunked across 5+ Expo HTTP calls; all eventually delivered |
| 2.6 | Expo returns DeviceNotRegistered for one token | Token soft-deleted in DB; next send excludes it |
| 2.7 | Expo HTTP 500 | BullMQ retries 3× with exponential backoff; eventual log only |
3. Receipt processing
| # | Scenario | Expected |
|---|---|---|
| 3.1 | Push send succeeds → 15min later, receipts fetched | Jobs run; DeviceNotRegistered tokens cleaned |
| 3.2 | Receipt fetch times out | Logged; retry per BullMQ options |
4. Scheduled push
| # | Scenario | Expected |
|---|---|---|
| 4.1 | scheduleNotifyUser with sendAt=+30min | BullMQ job with delay |
| 4.2 | Caller cancels job by id before delay elapses | Push not sent |
| 4.3 | sendAt in the past | Synchronous notifyUser call (delay=0) |
5. Preferences
| # | Scenario | Expected |
|---|---|---|
| 5.1 | First PATCH for a user | Row inserted |
| 5.2 | Toggle announcement.push = false | Future announcement sends skip this user |
| 5.3 | Missing key in JSONB | Treated as opt-in |
| 5.4 | Add a new category to the catalog | Backward-compatible (opt-in default) |
6. Integration QA across features
| # | Scenario | Expected |
|---|---|---|
| 6.1 | Coach assigns a workout | workoutAssigned push arrives |
| 6.2 | Coach posts a message | newMessage push arrives |
| 6.3 | Member opted out of newMessage | No push; web/mobile in-app still updates |
| 6.4 | Coach publishes announcement | announcement push to every member except author |
| 6.5 | Booking class reminder fires at T-30min | classReminder push arrives |
7. Smoke
- Real device with Expo dev client receives a test push.
- DeviceNotRegistered receipt soft-deletes token; verified in DB.
- Pref toggle visible immediately in send behavior.