fix: resolve TODO.md security/perf/test-coverage backlog
Fixes the 13-item codebase health scan backlog: wraps meal-plan generation in a transaction, adds missing userId/GIN indexes, fixes an IPv6-parsing gap in the webhook SSRF guard (and an identical duplicated bug in the AI URL-import path, now consolidated onto one implementation), paginates the collections list, dedupes the AI recipe Zod schemas, wires up Stripe tier sync, rate-limits AI key rotation, gets `pnpm typecheck` actually working, and adds test coverage for the previously-untested admin/webhooks routes. Two flagged issues (collection removeRecipeId IDOR, tier-limit race) turned out to already be fixed/non-issues on inspection — noted in TODO.md rather than silently dropped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
const mockAdminSession = { user: { id: "admin-1", role: "admin" } };
|
||||
|
||||
vi.mock("@/lib/api-auth", () => ({
|
||||
requireAdmin: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/email", () => ({
|
||||
sendEmail: vi.fn().mockResolvedValue(undefined),
|
||||
verifyEmailHtml: vi.fn((url: string) => `<a href="${url}">verify</a>`),
|
||||
}));
|
||||
|
||||
const { requireAdmin } = await import("@/lib/api-auth");
|
||||
const { sendEmail } = await import("@/lib/email");
|
||||
import { POST } from "../route";
|
||||
|
||||
function makeRequest(body: unknown) {
|
||||
return new NextRequest("http://localhost/api/v1/admin/test-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
const ORIGINAL_ENV = process.env["BETTER_AUTH_URL"];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requireAdmin).mockResolvedValue({ session: mockAdminSession as never, response: null });
|
||||
process.env["BETTER_AUTH_URL"] = "https://epicure.example.com";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (ORIGINAL_ENV === undefined) delete process.env["BETTER_AUTH_URL"];
|
||||
else process.env["BETTER_AUTH_URL"] = ORIGINAL_ENV;
|
||||
});
|
||||
|
||||
describe("POST /api/v1/admin/test-email", () => {
|
||||
it("returns 403 when caller is not an admin", async () => {
|
||||
vi.mocked(requireAdmin).mockResolvedValue({
|
||||
session: null,
|
||||
response: new Response(JSON.stringify({ error: "Forbidden" }), { status: 403 }),
|
||||
} as never);
|
||||
|
||||
const res = await POST(makeRequest({ to: "user@example.com" }));
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 400 when 'to' is missing", async () => {
|
||||
const res = await POST(makeRequest({}));
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("returns 500 when BETTER_AUTH_URL is not configured", async () => {
|
||||
delete process.env["BETTER_AUTH_URL"];
|
||||
const res = await POST(makeRequest({ to: "user@example.com" }));
|
||||
expect(res.status).toBe(500);
|
||||
expect(sendEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends the test email using the configured base URL", async () => {
|
||||
const res = await POST(makeRequest({ to: "user@example.com" }));
|
||||
expect(res.status).toBe(200);
|
||||
expect(sendEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ to: "user@example.com" })
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -9,11 +9,16 @@ export async function POST(req: NextRequest) {
|
||||
const { to } = await req.json() as { to: string };
|
||||
if (!to) return NextResponse.json({ error: "Missing 'to'" }, { status: 400 });
|
||||
|
||||
const baseUrl = process.env["BETTER_AUTH_URL"];
|
||||
if (!baseUrl) {
|
||||
return NextResponse.json({ error: "BETTER_AUTH_URL is not configured" }, { status: 500 });
|
||||
}
|
||||
|
||||
try {
|
||||
await sendEmail({
|
||||
to,
|
||||
subject: "Epicure — test email",
|
||||
html: verifyEmailHtml(`${process.env["BETTER_AUTH_URL"] ?? "http://localhost:3001"}/verify-email?token=test`),
|
||||
html: verifyEmailHtml(`${baseUrl}/verify-email?token=test`),
|
||||
});
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user