Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/app/api/testimonials/[id]/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { NextRequest } from "next/server";
import { PATCH } from "./route";

const mocks = vi.hoisted(() => ({
mockGetAuthContext: vi.fn(),
mockFrom: vi.fn(),
mockUpdate: vi.fn(),
}));

vi.mock("@/lib/auth/get-user", () => ({
getAuthContext: mocks.mockGetAuthContext,
}));

vi.mock("@/lib/supabase/service", () => ({
createServiceClient: () => ({
from: mocks.mockFrom,
}),
}));

function makeRequest(rating: number) {
return new NextRequest("http://localhost/api/testimonials/testimonial-1", {
method: "PATCH",
body: JSON.stringify({ rating }),
headers: { "Content-Type": "application/json" },
});
}

describe("PATCH /api/testimonials/[id]", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.mockGetAuthContext.mockResolvedValue({
user: { id: "author-1" },
supabase: {},
});
mocks.mockFrom.mockReturnValue({
select: () => ({
eq: () => ({
single: () =>
Promise.resolve({
data: {
id: "testimonial-1",
profile_id: "profile-1",
gig_id: null,
author_id: "author-1",
},
error: null,
}),
}),
}),
update: mocks.mockUpdate,
});
});

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();
});
Comment on lines +55 to +64

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!

});
4 changes: 2 additions & 2 deletions src/app/api/testimonials/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) {
updateData.content = content.trim();
}
if (rating !== undefined) {
if (typeof rating !== "number" || rating < 1 || rating > 5) {
return NextResponse.json({ error: "Rating must be 1-5" }, { status: 400 });
if (!Number.isInteger(rating) || rating < 1 || rating > 5) {
return NextResponse.json({ error: "Rating must be an integer from 1-5" }, { status: 400 });
}
updateData.rating = rating;
}
Expand Down
Loading