Skip to content

Reject fractional testimonial ratings before database updates#473

Merged
ralyodio merged 2 commits into
profullstack:masterfrom
rissrice2105-agent:codex/testimonial-integer-rating
Jun 14, 2026
Merged

Reject fractional testimonial ratings before database updates#473
ralyodio merged 2 commits into
profullstack:masterfrom
rissrice2105-agent:codex/testimonial-integer-rating

Conversation

@rissrice2105-agent

@rissrice2105-agent rissrice2105-agent commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #472.

Changes

  • require testimonial ratings to be integers from 1 through 5 for both creation and edits
  • reject fractional ratings before service/database updates
  • add focused POST and PATCH regression tests

Verification

  • corepack pnpm vitest run 'src/app/api/testimonials/[id]/route.test.ts' src/app/api/testimonials/route.rating.test.ts
  • corepack pnpm tsc --noEmit

@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR tightens testimonial rating validation by replacing the typeof rating !== "number" check with !Number.isInteger(rating), which correctly rejects fractional values (e.g. 4.5) while still accepting valid integer inputs. The change is applied consistently to both the POST (create) and PATCH (update) handlers, and two new unit tests verify that fractional ratings are rejected before any database interaction.

  • route.ts (POST) and [id]/route.ts (PATCH): Swap typeof rating !== "number" guard for !Number.isInteger(rating); update the error message to "Rating must be an integer from 1-5" in both places.
  • New test files: route.rating.test.ts asserts createServiceClient is never invoked for a fractional POST; [id]/route.test.ts asserts mockUpdate is never invoked for a fractional PATCH, confirming the guard fires before any write.

Confidence Score: 5/5

Safe to merge — the change is a small, targeted guard swap with no new database interactions or behavioral side-effects.

Both handlers are updated consistently, Number.isInteger correctly rejects floats while implicitly handling non-number types, and two focused regression tests confirm the guard fires before any write reaches the database.

No files require special attention.

Important Files Changed

Filename Overview
src/app/api/testimonials/[id]/route.ts Rating guard correctly swapped to Number.isInteger; error message updated consistently. Note that a SELECT query is still issued before the validation fires (pre-existing design, unchanged by this PR).
src/app/api/testimonials/route.ts POST handler integer guard added before createServiceClient(); validation order means DB is never touched on a bad rating, confirmed by the new test.
src/app/api/testimonials/[id]/route.test.ts New test file; mocks are correctly wired, the single test covers the fractional-rejection path and verifies no update is attempted.
src/app/api/testimonials/route.rating.test.ts New test file; asserts createServiceClient is never called for a fractional rating, validating early-exit behavior in the POST handler.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PATCH /api/testimonials/:id] --> B{Auth check}
    B -- Unauthorized --> C[401]
    B -- OK --> D[SELECT testimonial from DB]
    D -- Not found --> E[404]
    D -- Found --> F{rating provided?}
    F -- No --> G{status provided?}
    F -- Yes --> H{Number.isInteger AND 1–5?}
    H -- No --> I[400 'Rating must be an integer from 1-5']
    H -- Yes --> J[updateData.rating = rating]
    J --> K[UPDATE testimonials in DB]
    K --> L[200 testimonial]
    G -- Invalid --> M[400]
    G -- approved/rejected --> N[Permission check]
    N -- No permission --> O[403]
    N -- OK --> P[UPDATE status in DB]
    P --> Q[200 testimonial]
Loading

Reviews (2): Last reviewed commit: "Validate testimonial creation ratings" | Re-trigger Greptile

Comment on lines +55 to +64
it("rejects fractional ratings before updating the testimonial", async () => {
const response = await PATCH(makeRequest(4.5), {
params: Promise.resolve({ id: "testimonial-1" }),
});
const body = await response.json();

expect(response.status).toBe(400);
expect(body.error).toBe("Rating must be an integer from 1-5");
expect(mocks.mockUpdate).not.toHaveBeenCalled();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test coverage gaps around the rating validation boundary

The test suite only exercises the fractional-rejection path. Complementary cases that would give stronger confidence in the guard are: a valid integer (e.g. 3) that should reach the database, and out-of-range integers (0, 6) that should also return 400. Without a passing happy-path case there is no test that confirms mockUpdate is called when a valid rating is supplied, so a regression that accidentally broadens the guard (e.g. rejecting all numbers) would go undetected.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@rissrice2105-agent

Copy link
Copy Markdown
Contributor Author

CI is green for PR #473.

Verification:

  • corepack pnpm vitest run 'src/app/api/testimonials/[id]/route.test.ts'
  • corepack pnpm tsc --noEmit

uGig invoice evidence will be sent after the retry window expires.

@rissrice2105-agent

Copy link
Copy Markdown
Contributor Author

CI is green after expanding this PR to cover both testimonial creation and edits.

Verification:

  • corepack pnpm vitest run 'src/app/api/testimonials/[id]/route.test.ts' src/app/api/testimonials/route.rating.test.ts
  • corepack pnpm tsc --noEmit

uGig invoice evidence has been sent for this PR.

@ralyodio ralyodio merged commit 2c7952d into profullstack:master Jun 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Testimonial PATCH accepts fractional ratings rejected by the database

2 participants