Restore the community photo after reload and reject empty, oversized, SVG, and spoofed uploads.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,7 @@ function multipartRequest(opts: {
|
||||
purpose?: string;
|
||||
fileName?: string;
|
||||
fileContent?: string;
|
||||
contentType?: string;
|
||||
}): NextRequest {
|
||||
const boundary = "----VitestBoundary";
|
||||
const parts: string[] = [];
|
||||
@@ -36,8 +37,9 @@ function multipartRequest(opts: {
|
||||
);
|
||||
}
|
||||
if (opts.fileName && opts.fileContent !== undefined) {
|
||||
const contentType = opts.contentType ?? "image/png";
|
||||
parts.push(
|
||||
`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${opts.fileName}"\r\nContent-Type: image/png\r\n\r\n${opts.fileContent}\r\n`,
|
||||
`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${opts.fileName}"\r\nContent-Type: ${contentType}\r\n\r\n${opts.fileContent}\r\n`,
|
||||
);
|
||||
}
|
||||
parts.push(`--${boundary}--\r\n`);
|
||||
@@ -56,6 +58,7 @@ beforeEach(() => {
|
||||
getUploadRootFromEnvMock.mockReset();
|
||||
isDatabaseConfiguredMock.mockReturnValue(true);
|
||||
getUploadRootFromEnvMock.mockReturnValue("/tmp/uploads");
|
||||
getSessionUserMock.mockResolvedValue({ id: "u1", email: "a@b.c" });
|
||||
});
|
||||
|
||||
describe("POST /api/uploads", () => {
|
||||
@@ -78,7 +81,6 @@ describe("POST /api/uploads", () => {
|
||||
});
|
||||
|
||||
it("returns 500 when UPLOAD_ROOT is unset", async () => {
|
||||
getSessionUserMock.mockResolvedValueOnce({ id: "u1", email: "a@b.c" });
|
||||
getUploadRootFromEnvMock.mockReturnValueOnce(null);
|
||||
const res = await POST(
|
||||
new NextRequest("https://x.test/api/uploads", { method: "POST" }),
|
||||
@@ -90,7 +92,6 @@ describe("POST /api/uploads", () => {
|
||||
});
|
||||
|
||||
it("returns 400 when purpose is missing", async () => {
|
||||
getSessionUserMock.mockResolvedValueOnce({ id: "u1", email: "a@b.c" });
|
||||
const res = await POST(
|
||||
multipartRequest({ fileName: "avatar.png", fileContent: "x" }),
|
||||
undefined,
|
||||
@@ -99,4 +100,56 @@ describe("POST /api/uploads", () => {
|
||||
const body = (await res.json()) as { error: { code: string } };
|
||||
expect(body.error.code).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("returns 400 for an empty file", async () => {
|
||||
const res = await POST(
|
||||
multipartRequest({
|
||||
purpose: "communityAvatar",
|
||||
fileName: "empty.png",
|
||||
fileContent: "",
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
const body = (await res.json()) as {
|
||||
error: { code: string };
|
||||
details?: { reason?: string };
|
||||
};
|
||||
expect(body.error.code).toBe("validation_error");
|
||||
expect(body.details?.reason).toBe("empty");
|
||||
});
|
||||
|
||||
it("returns 400 for SVG even when named .png", async () => {
|
||||
const res = await POST(
|
||||
multipartRequest({
|
||||
purpose: "communityAvatar",
|
||||
fileName: "photo.png",
|
||||
fileContent: '<svg xmlns="http://www.w3.org/2000/svg"></svg>',
|
||||
contentType: "image/png",
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
const body = (await res.json()) as {
|
||||
details?: { reason?: string };
|
||||
};
|
||||
expect(body.details?.reason).toBe("svg");
|
||||
});
|
||||
|
||||
it("returns 400 for a text file spoofed as PNG", async () => {
|
||||
const res = await POST(
|
||||
multipartRequest({
|
||||
purpose: "communityAvatar",
|
||||
fileName: "photo.png",
|
||||
fileContent: "not an image",
|
||||
contentType: "image/png",
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
const body = (await res.json()) as {
|
||||
details?: { reason?: string };
|
||||
};
|
||||
expect(body.details?.reason).toBe("invalidType");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user