Announcements — QA Plan
1. Create + fan-out
| # | Scenario | Expected |
|---|---|---|
| 1.1 | Coach creates with title + content | 201; WS event to org:{id} room; push enqueued to N-1 members; PostHog event captured (prod) |
| 1.2 | Member tries to create | 403 |
| 1.3 | Create with empty title | 400 from DTO |
| 1.4 | 500-member org | Push fan-out completes; verify queue handles chunking; no throttling errors |
| 1.6 | Author opts out of announcement push pref | Author still excluded; no push to author regardless |
| 1.7 | One member opts out of announcement pref | Push to that member skipped |
2. List + counts
| # | Scenario | Expected |
|---|---|---|
| 2.1 | Member fetches list | Returns rows with per-member readAt |
| 2.2 | Unread count | Matches DB truth (count where no announcement_reads row exists for me) |
| 2.3 | Cursor pagination | Stable |
3. Read receipts
| # | Scenario | Expected |
|---|---|---|
| 3.1 | Member marks read | Row inserted; counts decrement |
| 3.2 | Member marks read twice | Idempotent; same row |
| 3.3 | Member of org A marks read for an announcement in org B | Should 403/404 — confirm in service |
4. WebSocket
| # | Scenario | Expected |
|---|---|---|
| 4.1 | Member connected → coach creates | Member’s UI receives announcement:new |
| 4.2 | Member offline → comes online later | Picks up via REST list — no replay over WS |
5. Detail view (staff)
| # | Scenario | Expected |
|---|---|---|
| 5.1 | Staff opens detail | Sees read receipts |
| 5.2 | Member opens detail endpoint | 403 (staff-only) — verify in code |
5b. Announcement email
| # | Scenario | Expected |
|---|---|---|
| 5b.1 | Email switch is off (default) | No audience picker; no email; create request has no emailAudience |
| 5b.2 | Switch on in an org with 120 members | List shows 50, “Showing 50 of 120”, “Load more” reaches 120. The recipient count is the server’s, not the loaded rows |
| 5b.3 | Filter to not_paying, send | Only matching members get the email; the count in the dialog equals the emails queued |
| 5b.4 | Untick two members, send | They get no email; the recipient count drops by two |
| 5b.5 | Pick mode with nobody ticked | Send is disabled |
| 5b.6 | Pick three people across two searches | Exactly those three get the email |
| 5b.7 | A member switched announcement email off | Skipped; “Skipped (announcement emails off)” shows their number |
| 5b.8 | Author matches the filter | Author gets no email |
| 5b.9 | Audience over 2000 | Over-limit message; send disabled; the API refuses with 400 and publishes nothing |
| 5b.10 | Hebrew org, Russian-speaking member | Email in Russian, from the org’s name, Reply-To the org’s address |
| 5b.11 | Title with <script> or a line break | Escaped in the body; no header injection in the subject |
| 5b.12 | Stop Redis, send with email | Announcement published; warning toast says emails could not be queued |
| 5b.13 | Open announcement.emailed in the audit log | One row with counts and no addresses |
| 5b.15 | App off, email on | Emails queued; no list entry in the app, no push, no realtime event; audit row has inApp: false |
| 5b.16 | App off, email off | Send is disabled and the dialog says to choose a channel; the API returns 400 |
| 5b.17 | Attach a 1 MB PDF, send | Every email carries the PDF under the chosen name; the member can open it |
| 5b.18 | Choose a 6 MB file | Refused at once: “larger than 5 MB… add a link”; no upload request; Send disabled; Remove re-enables Send |
| 5b.19 | Choose an .exe | Refused as an unsupported type |
| 5b.20 | Rename virus.exe to plan.pdf and attach | The server checks the real type from the upload; a non-allowed type is refused |
| 5b.21 | Attach, then switch Email off | The file is dropped; nothing is sent with it |
| 5b.22 | Attach with App on and Email on | Email has the file; the in-app announcement does not |
| 5b.23 | Send to 500 members with a 5 MB file | Emails queue quickly; the API returns at once; R2 is read by the email provider, not by our workers |
| 5b.14 | Coach (not owner) sends | Allowed — same rule as creating an announcement |
6. RTL + i18n
| # | Scenario | Expected |
|---|---|---|
| 6.1 | Hebrew content rendered | RTL preserved |
| 6.2 | Mixed Hebrew + English in content | Correct bidi |
7. Smoke
- Create announcement → push arrives on mobile.
- Bell badge updates after mark-read.
- Member from another org doesn’t see anything.