Update features and dependencies

This commit is contained in:
Arnaud
2026-07-01 11:10:37 +02:00
parent 9d9dfb46c6
commit 8b57a3fd87
107 changed files with 14654 additions and 458 deletions
@@ -0,0 +1,96 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { NextRequest } from "next/server";
const mockSession = {
user: { id: "user-1", name: "Test", email: "t@t.com", tier: "free" },
};
const { mockRequireSession } = vi.hoisted(() => ({
mockRequireSession: vi.fn(),
}));
vi.mock("@/lib/api-auth", () => ({
requireSession: mockRequireSession,
}));
vi.mock("@epicure/db", () => ({
db: {
delete: vi.fn(() => ({ where: vi.fn().mockResolvedValue(undefined) })),
update: vi.fn(() => ({
set: vi.fn(() => ({ where: vi.fn().mockResolvedValue(undefined) })),
})),
},
recipes: { id: "id", authorId: "author_id", visibility: "visibility" },
eq: vi.fn(),
and: vi.fn(),
inArray: vi.fn(),
}));
import { DELETE, PATCH } from "../route";
function makeRequest(method: string, body: unknown) {
return new NextRequest(`http://localhost/api/v1/recipes/bulk`, {
method,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
beforeEach(() => {
vi.clearAllMocks();
mockRequireSession.mockResolvedValue({ session: mockSession, response: null });
});
describe("DELETE /api/v1/recipes/bulk", () => {
it("returns 200 on valid ids", async () => {
const res = await DELETE(makeRequest("DELETE", { ids: ["r1", "r2"] }));
expect(res.status).toBe(200);
const body = await res.json() as { ok: boolean };
expect(body.ok).toBe(true);
});
it("returns 400 when ids is empty array", async () => {
const res = await DELETE(makeRequest("DELETE", { ids: [] }));
expect(res.status).toBe(400);
});
it("returns 400 when body is invalid", async () => {
const res = await DELETE(makeRequest("DELETE", { notIds: "wrong" }));
expect(res.status).toBe(400);
});
it("returns 401 when not authenticated", async () => {
mockRequireSession.mockResolvedValue({
session: null,
response: new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 }),
});
const res = await DELETE(makeRequest("DELETE", { ids: ["r1"] }));
expect(res.status).toBe(401);
});
});
describe("PATCH /api/v1/recipes/bulk", () => {
it("returns 200 on valid visibility update", async () => {
const res = await PATCH(makeRequest("PATCH", { ids: ["r1"], visibility: "public" }));
expect(res.status).toBe(200);
});
it("returns 400 when visibility is missing", async () => {
const res = await PATCH(makeRequest("PATCH", { ids: ["r1"] }));
expect(res.status).toBe(400);
});
it("returns 400 when ids is empty", async () => {
const res = await PATCH(makeRequest("PATCH", { ids: [], visibility: "public" }));
expect(res.status).toBe(400);
});
it("returns 401 when not authenticated", async () => {
mockRequireSession.mockResolvedValue({
session: null,
response: new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 }),
});
const res = await PATCH(makeRequest("PATCH", { ids: ["r1"], visibility: "public" }));
expect(res.status).toBe(401);
});
});
+6 -6
View File
@@ -13,22 +13,22 @@ const BulkUpdateSchema = z.object({
});
export async function DELETE(req: NextRequest) {
const session = await requireSession();
if (session instanceof NextResponse) return session;
const { session, response } = await requireSession();
if (response) return response;
const body = BulkDeleteSchema.safeParse(await req.json());
if (!body.success) return NextResponse.json({ error: "Invalid request" }, { status: 400 });
await db
.delete(recipes)
.where(and(inArray(recipes.id, body.data.ids), eq(recipes.authorId, session.user.id)));
.where(and(inArray(recipes.id, body.data.ids), eq(recipes.authorId, session!.user.id)));
return NextResponse.json({ ok: true });
}
export async function PATCH(req: NextRequest) {
const session = await requireSession();
if (session instanceof NextResponse) return session;
const { session, response } = await requireSession();
if (response) return response;
const body = BulkUpdateSchema.safeParse(await req.json());
if (!body.success) return NextResponse.json({ error: "Invalid request" }, { status: 400 });
@@ -39,7 +39,7 @@ export async function PATCH(req: NextRequest) {
await db
.update(recipes)
.set({ visibility, updatedAt: new Date() })
.where(and(inArray(recipes.id, ids), eq(recipes.authorId, session.user.id)));
.where(and(inArray(recipes.id, ids), eq(recipes.authorId, session!.user.id)));
return NextResponse.json({ ok: true });
}