Validate affiliate application notes - #146
Conversation
Greptile SummaryThis PR adds input validation to the affiliate apply endpoint's
Confidence Score: 4/5Safe to merge — the validation logic in route.ts is correct and the happy-path and normalization behaviour are well tested. The route change is small and correct: the type guard fires before any DB write and the trim/null normalization is straightforward. The only gap is in the test file — the rejection test does not assert that the database insert was never called, so a future regression that accidentally reached the insert would not be caught by this test alone. route.test.ts — the first test case would benefit from asserting the insert spy was not called. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant POST as POST /apply
participant DB as Supabase
Client->>POST: "POST { note: ... }"
POST->>DB: fetch affiliate_offer
DB-->>POST: offer data
POST->>DB: check existing application
DB-->>POST: null (not applied)
POST->>DB: fetch profile (username)
DB-->>POST: profile
alt note is non-string (object / array / number)
POST-->>Client: "400 { error: note must be a string }"
else note is string
Note over POST: trim → blank → null
POST->>DB: insert affiliate_application (normalizedNote)
DB-->>POST: application record
POST->>DB: increment total_affiliates
POST->>DB: insert notification
POST-->>Client: "201 { application, tracking_code, tracking_url }"
end
Reviews (1): Last reviewed commit: "Validate affiliate application notes" | Re-trigger Greptile |
| insert: vi.fn(), | ||
| }; |
There was a problem hiding this comment.
Test title claims "before creating" but never verifies the insert was skipped
The test is named "rejects non-string notes before creating an application", yet the insert spy is created anonymously with vi.fn() and no reference is kept, making it impossible to assert insert was not called. Contrast this with the second test (line 113) where insert is captured and asserted. If the validation guard were accidentally removed, this test would still pass as long as the 400 status was somehow returned — the DB write side-effect would go undetected.
| insert: vi.fn(), | |
| }; | |
| if (table === "affiliate_applications") { | |
| const insertSpy = vi.fn(); | |
| return { | |
| select: () => ({ | |
| eq: () => ({ | |
| eq: () => ({ | |
| single: () => Promise.resolve({ data: null, error: null }), | |
| }), | |
| }), | |
| }), | |
| insert: insertSpy, | |
| }; | |
| } |
Summary
nullWhy
The affiliate apply endpoint passed
body.note || nullstraight into the insert payload. Malformed clients could send objects, arrays, or numbers and reach the database write path instead of getting a clear validation error.Validation
pnpm test:run src/app/api/affiliates/offers/[id]/apply/route.test.tspnpm exec eslint src/app/api/affiliates/offers/[id]/apply/route.ts src/app/api/affiliates/offers/[id]/apply/route.test.tspnpm type-checkgit diff --checkPayment for the active uGig affiliate testing bounty can go to SOL:
27sdMYXofqoM9qR13bZhccRNYeEgYn5EoHXTSJn4QWKP.Payment fallback: PayPal cultofrozen@gmail.com