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,80 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
const mockAdminSession = { user: { id: "admin-1", role: "admin" } };
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
headers: vi.fn().mockResolvedValue(new Headers()),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/auth/server", () => ({
|
||||
auth: { api: { getSession: vi.fn() } },
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/site-settings", () => ({
|
||||
setSiteSetting: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
const { mockSelectChain, mockInsertValues } = vi.hoisted(() => {
|
||||
const mockSelectChain = {
|
||||
from: vi.fn().mockReturnThis(),
|
||||
where: vi.fn().mockResolvedValue([{ role: "admin" }]),
|
||||
};
|
||||
return { mockSelectChain, mockInsertValues: vi.fn().mockResolvedValue(undefined) };
|
||||
});
|
||||
|
||||
vi.mock("@epicure/db", () => ({
|
||||
db: {
|
||||
select: vi.fn(() => mockSelectChain),
|
||||
insert: vi.fn(() => ({ values: mockInsertValues })),
|
||||
},
|
||||
users: { id: "id", role: "role" },
|
||||
auditLogs: {},
|
||||
eq: vi.fn((a, b) => ({ a, b, op: "eq" })),
|
||||
}));
|
||||
|
||||
const { auth } = await import("@/lib/auth/server");
|
||||
const { setSiteSetting } = await import("@/lib/site-settings");
|
||||
import { PUT } from "../route";
|
||||
|
||||
function makeRequest(body: unknown) {
|
||||
return new NextRequest("http://localhost/api/v1/admin/settings", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(auth.api.getSession).mockResolvedValue(mockAdminSession as never);
|
||||
mockSelectChain.where.mockResolvedValue([{ role: "admin" }]);
|
||||
});
|
||||
|
||||
describe("PUT /api/v1/admin/settings", () => {
|
||||
it("returns 403 when caller is not an admin", async () => {
|
||||
mockSelectChain.where.mockResolvedValue([{ role: "user" }]);
|
||||
const res = await PUT(makeRequest({ OPENAI_API_KEY: "sk-1" }));
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 403 when there is no session", async () => {
|
||||
vi.mocked(auth.api.getSession).mockResolvedValue(null as never);
|
||||
const res = await PUT(makeRequest({ OPENAI_API_KEY: "sk-1" }));
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("updates allowed keys and writes an audit log", async () => {
|
||||
const res = await PUT(makeRequest({ OPENAI_API_KEY: "sk-1" }));
|
||||
expect(res.status).toBe(200);
|
||||
expect(vi.mocked(setSiteSetting)).toHaveBeenCalledWith("OPENAI_API_KEY", "sk-1", "admin-1");
|
||||
expect(mockInsertValues).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("silently ignores keys not in the allow-list", async () => {
|
||||
const res = await PUT(makeRequest({ NOT_A_REAL_KEY: "x" }));
|
||||
expect(res.status).toBe(200);
|
||||
expect(vi.mocked(setSiteSetting)).not.toHaveBeenCalled();
|
||||
expect(mockInsertValues).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user