From 2c4590f12e4c145b1e881b0240f984607fb59e9b Mon Sep 17 00:00:00 2001 From: Abdu Alim Arlikhozhaev Date: Sat, 11 Jul 2026 19:54:06 -0700 Subject: [PATCH] fix: return 404 for debug pipeline route in production (RL-004) --- app/api/debug/pipeline/[id]/route.test.ts | 76 +++++++++++++++++++++++ app/api/debug/pipeline/[id]/route.ts | 23 +++++-- 2 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 app/api/debug/pipeline/[id]/route.test.ts diff --git a/app/api/debug/pipeline/[id]/route.test.ts b/app/api/debug/pipeline/[id]/route.test.ts new file mode 100644 index 0000000..399dd66 --- /dev/null +++ b/app/api/debug/pipeline/[id]/route.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +const mockRequireAuthUser = vi.fn(); +const mockFindUnique = vi.fn(); + +vi.mock("@/lib/auth-helpers", () => ({ + requireAuthUser: () => mockRequireAuthUser(), +})); + +vi.mock("@/lib/prisma", () => ({ + prisma: { + analysisSession: { + findUnique: (...args: unknown[]) => mockFindUnique(...args), + update: vi.fn(), + }, + analysisResult: { + deleteMany: vi.fn(), + }, + }, +})); + +vi.mock("@/features/analysis", () => ({ + runAnalysisPipeline: vi.fn(), +})); + +import { GET } from "./route"; + +function callGet(slug = "debug-slug", nodeEnv: string) { + vi.stubEnv("NODE_ENV", nodeEnv); + return GET(new Request(`http://localhost/api/debug/pipeline/${slug}`), { + params: { id: slug }, + }); +} + +beforeEach(() => { + vi.clearAllMocks(); + mockRequireAuthUser.mockResolvedValue(null); +}); + +afterEach(() => { + vi.unstubAllEnvs(); +}); + +describe("GET /api/debug/pipeline/[id]", () => { + it("returns 404 in production", async () => { + const res = await callGet("any-slug", "production"); + const body = await res.json(); + + expect(res.status).toBe(404); + expect(body.error).toBe("Not found"); + expect(mockRequireAuthUser).not.toHaveBeenCalled(); + }); + + it("returns 404 in development when unauthenticated", async () => { + const res = await callGet("any-slug", "development"); + const body = await res.json(); + + expect(res.status).toBe(404); + expect(body.error).toBe("Not found"); + }); + + it("returns 404 in development for non-owner", async () => { + mockRequireAuthUser.mockResolvedValue({ userId: "user_a", session: {} }); + mockFindUnique.mockResolvedValue({ + id: "sess_1", + userId: "user_b", + status: "COMPLETED", + }); + + const res = await callGet("owned-by-b", "development"); + const body = await res.json(); + + expect(res.status).toBe(404); + expect(body.error).toBe("Not found"); + }); +}); diff --git a/app/api/debug/pipeline/[id]/route.ts b/app/api/debug/pipeline/[id]/route.ts index 8464c93..c63c523 100644 --- a/app/api/debug/pipeline/[id]/route.ts +++ b/app/api/debug/pipeline/[id]/route.ts @@ -1,25 +1,38 @@ import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; +import { requireAuthUser } from "@/lib/auth-helpers"; import { runAnalysisPipeline } from "@/features/analysis"; +function notFound() { + return NextResponse.json({ error: "Not found" }, { status: 404 }); +} + export async function GET( _req: Request, { params }: { params: { id: string } } ) { - // Only usable in development if (process.env.NODE_ENV !== "development") { - return NextResponse.json({ error: "Not available in production" }, { status: 403 }); + return notFound(); + } + + const authUser = await requireAuthUser(); + if (!authUser) { + return notFound(); } const { id: slug } = params; const session = await prisma.analysisSession.findUnique({ where: { shareableSlug: slug }, - select: { id: true, status: true }, + select: { id: true, userId: true, status: true }, }); if (!session) { - return NextResponse.json({ error: "Session not found" }, { status: 404 }); + return notFound(); + } + + if (session.userId !== authUser.userId) { + return notFound(); } // Force reset to PENDING so pipeline can re-run @@ -41,4 +54,4 @@ export async function GET( const stack = error instanceof Error ? error.stack : undefined; return NextResponse.json({ success: false, error: message, stack }, { status: 500 }); } -} \ No newline at end of file +}