TC-FUND-003: Cover Image, Aggregates & Public Campaign Page (Detailed) — #661 / #662
isAnonymous IS API-ONLY. There is no frontend checkbox for anonymous donations yet. TC-FUND-PUBLIC-02 below requires calling the donate endpoint directly (e.g. via
curl or the browser devtools Network tab replay) with isAnonymous: true in the body — you cannot exercise this from the donate UI alone.
TC-FUND-COVER-01: Cover Image Upload — Size & Type Guards (#661)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Log in as the finance admin. Open a campaign's edit page (/admin/campaigns/:id → Edit, or the create form). |
Edit form loads with a cover-image field. | admin@rcme.membervu.com / Admin123! |
| 2 | Attempt to upload an image file > 5MB. | POST /api/billing/admin/campaigns/:id/cover-image returns 400 with message Image too large. Please upload under 5 MB. |
a 6-8MB JPG/PNG |
| 3 | Attempt to upload a non-allowed image type (e.g. a GIF, BMP, or SVG data URI). | 400 with message Invalid image type. Use JPG, PNG, or WEBP. |
data:image/gif;base64,... |
| 4 | Upload a valid PNG/JPG/JPEG/WEBP under 5MB. | 200. Response is the fully-serialized campaign object with the new cover image URL/data set on coverImage. |
a valid ~500KB PNG |
| 5 | Reload the campaign detail page. | The new cover image renders. | — |
TC-FUND-COVER-02: Edit-Flow Isolation on Upload Failure
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Open the campaign edit form. Change the campaign title/description to a new value AND attach an oversized (>5MB) cover image in the same submit. | The form submits. | Title: "Isolation Test Campaign Title" |
| 2 | Observe the response/toast. | The cover-image portion fails with the same 400 from TC-FUND-COVER-01 step 2. |
— |
| 3 | Reload the campaign detail page (fresh GET, not cached). | Assert on data: the title/description SURVIVED and shows the new value from step 1, even though the cover image was NOT updated (still the old image or none). The two are isolated — not an all-or-nothing transaction. | — |
TC-FUND-AGG-01: List/Detail Aggregates (#662)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Ensure the target campaign has: 1+ APPROVED donation, 1+ PENDING donation (with proof, unapproved). | Known baseline for the sum checks below. | e.g. APPROVED=500+300=800 total, PENDING=1 |
| 2 | Call GET /api/billing/admin/campaigns (list) and find this campaign in the response. |
raisedAmount = 800 (sum of APPROVED only); pendingProofCount = 1. |
— |
| 3 | Call GET /api/billing/admin/campaigns/:id (detail) for the same campaign. |
Same raisedAmount/pendingProofCount values as the list response. |
— |
| 4 | Call the create, update (non-list), status-change, or archive endpoints for a campaign. | These responses may OMIT raisedAmount/pendingProofCount entirely — treat as optional/not-guaranteed on non-list/detail responses; do not fail a test solely because they're absent there. |
— |
| 5 | Open the admin campaign detail page in the browser. | A progress bar renders showing raised (800) vs the campaign's goal amount. | — |
KNOWN CAVEAT — refund does not reduce raisedAmount. If you refund an APPROVED donation's underlying payment through another flow (e.g. billing refund tooling), re-check
raisedAmount here: it will NOT decrease, because Donation has no un-approve/refund transition. This is a documented gap, not a bug to file — do not assert a decrease in any test.
TC-FUND-PUBLIC-01: Public Donor List — No PII Leak
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Log out completely (clear session/localStorage). Navigate to the public campaign page. | Page loads without auth. | https://stg-rcme.membervu.com/rcme/campaigns/:slug |
| 2 | Inspect the network response for GET /api/public/:tenantSlug/campaigns/:slug. |
raisedAmount/donationCount reflect APPROVED donations only. A donor list array is present. |
— |
| 3 | Inspect each donor list item's fields. | Shape is exactly {donorName, amount, createdAt}. Assert donorEmail and donorMemberId are NOT present anywhere in the response — not just hidden in the UI, actually absent from the JSON payload. |
— |
TC-FUND-PUBLIC-02: Anonymous Donations Null Out donorName
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | As donor, get a valid access token (log in normally, then grab the token from devtools/localStorage oneledger_session*). |
Token captured for a direct API call. | testmember@rcme.membervu.com / Member123! |
| 2 | Call POST /api/campaigns/:campaignId/donations directly (curl/Postman/devtools) with a real donorName AND isAnonymous: true in the body. |
201. Donation row created with isAnonymous: true and the submitted donorName stored internally. |
{"amountCents":10000,"currency":"PHP","donorName":"Maria Cruz","isAnonymous":true} |
| 3 | Upload proof for the invoice, then admin-approve the donation (as in TC-FUND-001). | Donation reaches APPROVED. |
— |
| 4 | Fetch the public campaign page / API response. | Assert on data: the donor list entry for this donation has donorName: null — REGARDLESS of "Maria Cruz" being on the underlying row. The amount and timestamp are still shown. |
— |
TC-FUND-PUBLIC-03: Members-Only Visibility Soft-Gate
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Set a campaign's visibility to members-only (via admin edit form or direct API if no UI toggle exists). |
Campaign saved with the restricted visibility. | — |
| 2 | Logged out, call GET /api/public/:tenantSlug/campaigns/:slug for this campaign. |
200 (NOT 401/403). Response shape: {campaign:{gated:true, ...}}. No donor list or raisedAmount detail leaked to the anonymous caller. |
— |
| 3 | Log in as a member and repeat the call (or view the page while logged in). | Full payload returned, including the donor list (per TC-FUND-PUBLIC-01 shape). | testmember@rcme.membervu.com / Member123! |
TC-FUND-PUBLIC-04: DRAFT / ARCHIVED Campaigns — No Existence Leak
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Create a campaign and leave it in DRAFT status (do not activate). Note its slug. |
Campaign exists in DRAFT. | — |
| 2 | Logged out, call GET /api/public/:tenantSlug/campaigns/:slug for the DRAFT slug. |
404 with body {code:"CAMPAIGN_NOT_FOUND"}. |
— |
| 3 | Archive a previously-CLOSED campaign (or use one already ARCHIVED). Call the same public endpoint for its slug. | Same 404 {code:"CAMPAIGN_NOT_FOUND"} — identical shape to a nonexistent slug, so no information is leaked about the campaign's existence/state to an unauthenticated caller. |
— |
| 4 | Call the same endpoint for a slug that never existed at all. | Identical 404 {code:"CAMPAIGN_NOT_FOUND"} response — confirms indistinguishability between DRAFT/ARCHIVED/nonexistent. |
/rcme/campaigns/definitely-not-a-real-slug-12345 |
CLEANUP
Data created during this test:
- Cover images uploaded to test campaigns — safe to leave, or reset to the original image if the campaign is used elsewhere.
- One anonymous APPROVED donation ("Maria Cruz", 100.00 PHP,
isAnonymous:true) — useful to keep for future public-page regression; note it is intentionally NOT visible by name on the public page. - One members-only-visibility campaign — revert
visibilityif it should be public again for other test cases. - One DRAFT campaign left un-activated — safe to leave or delete.