From c7934069a341052d3c94280d22e376561dfe0d68 Mon Sep 17 00:00:00 2001 From: jsdavid278-cyber Date: Sat, 13 Jun 2026 18:15:10 -0600 Subject: [PATCH] Handle malformed referral JSON --- .../api/referrals/route.invalid-json.test.ts | 50 +++++++++++++++++++ src/app/api/referrals/route.ts | 20 +++++++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/app/api/referrals/route.invalid-json.test.ts diff --git a/src/app/api/referrals/route.invalid-json.test.ts b/src/app/api/referrals/route.invalid-json.test.ts new file mode 100644 index 00000000..2942cd6c --- /dev/null +++ b/src/app/api/referrals/route.invalid-json.test.ts @@ -0,0 +1,50 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { NextRequest } from "next/server"; +import { POST } from "./route"; + +const mocks = vi.hoisted(() => ({ + mockGetAuthContext: vi.fn(), + mockCreateServiceClient: vi.fn(), + mockSendEmail: vi.fn(), +})); + +vi.mock("@/lib/auth/get-user", () => ({ + getAuthContext: mocks.mockGetAuthContext, +})); + +vi.mock("@/lib/supabase/service", () => ({ + createServiceClient: mocks.mockCreateServiceClient, +})); + +vi.mock("@/lib/email", () => ({ + referralInviteEmail: vi.fn(), + sendEmail: mocks.mockSendEmail, +})); + +function makeRawPostRequest(body: BodyInit) { + return new NextRequest("http://localhost/api/referrals", { + method: "POST", + body, + headers: { "Content-Type": "application/json" }, + }); +} + +describe("POST /api/referrals invalid JSON handling", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.mockGetAuthContext.mockResolvedValue({ + user: { id: "user1" }, + supabase: { from: vi.fn() }, + }); + }); + + it("returns 400 for malformed JSON before referral side effects", async () => { + const res = await POST(makeRawPostRequest("{not valid json")); + const body = await res.json(); + + expect(res.status).toBe(400); + expect(body.error).toBe("Invalid JSON body"); + expect(mocks.mockCreateServiceClient).not.toHaveBeenCalled(); + expect(mocks.mockSendEmail).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/api/referrals/route.ts b/src/app/api/referrals/route.ts index 6a27788a..5017e0e0 100644 --- a/src/app/api/referrals/route.ts +++ b/src/app/api/referrals/route.ts @@ -7,6 +7,18 @@ type AnySupabase = any; const MAX_EMAIL_ENTRIES_PER_REQUEST = 200; const MAX_INVITES_PER_REQUEST = 20; +async function readJsonObject(request: NextRequest) { + try { + const body = await request.json(); + if (!body || typeof body !== "object" || Array.isArray(body)) { + return null; + } + return body as Record; + } catch { + return null; + } +} + // GET /api/referrals - List my referrals export async function GET(request: NextRequest) { try { @@ -54,7 +66,13 @@ export async function POST(request: NextRequest) { } const { user, supabase } = auth; - const body = await request.json(); + const body = await readJsonObject(request); + if (!body) { + return NextResponse.json( + { error: "Invalid JSON body" }, + { status: 400 } + ); + } const { emails } = body; if (!emails || !Array.isArray(emails) || emails.length === 0) {