mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
One-shot prettier pass so the new format:check gate starts from a clean baseline. Formatting only — no behaviour change. Generator-owned and vendored artifacts stay untouched via .prettierignore.
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
/**
|
|
* v1.4.43 W6 — multi-issue 422 envelope on POST /api/bugreport.
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { NextRequest } from "next/server";
|
|
|
|
vi.mock("@/lib/db", () => ({
|
|
prisma: {
|
|
appSettings: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/lib/auth/session", () => ({ getSession: vi.fn() }));
|
|
vi.mock("@/lib/auth/audit", () => ({
|
|
auditLog: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
vi.mock("@/lib/rate-limit", () => ({
|
|
checkRateLimit: vi.fn(),
|
|
}));
|
|
vi.mock("@/lib/crypto", () => ({
|
|
decrypt: (s: string) => s.replace(/^enc-/, ""),
|
|
}));
|
|
vi.mock("@/lib/logging/transports", () => ({ emitIfSampled: vi.fn() }));
|
|
vi.mock("@/lib/db-compat", () => ({
|
|
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
vi.mock("next/headers", () => ({
|
|
headers: vi.fn(async () => ({ get: () => null })),
|
|
cookies: vi.fn(async () => ({
|
|
get: () => undefined,
|
|
set: () => {},
|
|
delete: () => {},
|
|
})),
|
|
}));
|
|
|
|
import { POST } from "../route";
|
|
import { prisma } from "@/lib/db";
|
|
import { getSession } from "@/lib/auth/session";
|
|
import { checkRateLimit } from "@/lib/rate-limit";
|
|
|
|
const SESSION_OK = {
|
|
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
|
|
user: { id: "user-1", username: "tester", role: "USER" as const },
|
|
};
|
|
|
|
function postReq(body: unknown): NextRequest {
|
|
return new NextRequest("http://localhost/api/bugreport", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.resetAllMocks();
|
|
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
|
vi.mocked(checkRateLimit).mockResolvedValue({ allowed: true } as never);
|
|
vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({
|
|
githubIssueTokenEncrypted: "enc-token",
|
|
githubIssueRepo: "owner/repo",
|
|
bugReportEnabled: true,
|
|
} as never);
|
|
});
|
|
|
|
describe("POST /api/bugreport — 422 multi-issue (v1.4.43 W6)", () => {
|
|
it("surfaces TWO simultaneous validation errors", async () => {
|
|
// description too short + screenshot doesn't match data-URL regex.
|
|
const res = await POST(
|
|
postReq({ description: "tiny", screenshot: "not-a-data-url" }),
|
|
);
|
|
expect(res.status).toBe(422);
|
|
const body = (await res.json()) as {
|
|
data: null;
|
|
error: string;
|
|
details: {
|
|
issues: Array<{ path: string; code: string; message: string }>;
|
|
};
|
|
};
|
|
expect(body.data).toBeNull();
|
|
expect(body.error).toBe("Validation failed");
|
|
expect(body.details.issues.length).toBeGreaterThanOrEqual(2);
|
|
for (const issue of body.details.issues) {
|
|
expect(Object.keys(issue).sort()).toEqual(["code", "message", "path"]);
|
|
}
|
|
});
|
|
|
|
it("surfaces multiple simultaneous validation errors (≥ 2)", async () => {
|
|
// Schema has only two knobs (description, screenshot). Pin contract
|
|
// on ≥ 2.
|
|
const res = await POST(postReq({ description: 123, screenshot: 999 }));
|
|
expect(res.status).toBe(422);
|
|
const body = (await res.json()) as {
|
|
details: { issues: Array<unknown> };
|
|
};
|
|
expect(body.details.issues.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
});
|