104 lines
3 KiB
TypeScript
104 lines
3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("~/lib/auth.server", () => ({
|
|
auth: {
|
|
api: {
|
|
getSession: vi.fn(),
|
|
},
|
|
},
|
|
}));
|
|
vi.mock("~/models/user", () => ({
|
|
isUserAdmin: vi.fn(),
|
|
}));
|
|
vi.mock("~/lib/cloudinary.server", () => ({
|
|
uploadSportIcon: vi.fn(),
|
|
}));
|
|
vi.mock("~/lib/logger", () => ({
|
|
logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
|
|
}));
|
|
|
|
import { action } from "../api.upload-sport-icon";
|
|
import { auth } from "~/lib/auth.server";
|
|
import { uploadSportIcon } from "~/lib/cloudinary.server";
|
|
import { isUserAdmin } from "~/models/user";
|
|
|
|
function makeRequest(file?: File) {
|
|
const formData = new FormData();
|
|
if (file) formData.append("file", file);
|
|
|
|
return {
|
|
headers: new Headers(),
|
|
formData: async () => formData,
|
|
} as Request;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: "admin-1" } } as never);
|
|
vi.mocked(isUserAdmin).mockResolvedValue(true as never);
|
|
vi.mocked(uploadSportIcon).mockResolvedValue({
|
|
secure_url: "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg",
|
|
} as never);
|
|
});
|
|
|
|
describe("upload sport icon action", () => {
|
|
it("requires a signed-in user", async () => {
|
|
vi.mocked(auth.api.getSession).mockResolvedValue(null as never);
|
|
|
|
const response = await action({ request: makeRequest(), params: {}, context: {} } as never);
|
|
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it("requires an admin user", async () => {
|
|
vi.mocked(isUserAdmin).mockResolvedValue(false as never);
|
|
|
|
const response = await action({ request: makeRequest(), params: {}, context: {} } as never);
|
|
|
|
expect(response.status).toBe(403);
|
|
});
|
|
|
|
it("rejects non-SVG files", async () => {
|
|
const response = await action({
|
|
request: makeRequest(new File(["hello"], "icon.png", { type: "image/png" })),
|
|
params: {},
|
|
context: {},
|
|
} as never);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(uploadSportIcon).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects SVG files over 1 MB", async () => {
|
|
const response = await action({
|
|
request: makeRequest(new File([new Uint8Array(1024 * 1024 + 1)], "icon.svg", { type: "image/svg+xml" })),
|
|
params: {},
|
|
context: {},
|
|
} as never);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(uploadSportIcon).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("uploads valid SVG files to Cloudinary", async () => {
|
|
const file = new File(["<svg viewBox=\"0 0 1 1\" />"], "icon.svg", { type: "image/svg+xml" });
|
|
Object.defineProperty(file, "arrayBuffer", {
|
|
value: async () => Buffer.from("<svg viewBox=\"0 0 1 1\" />"),
|
|
});
|
|
|
|
const response = await action({
|
|
request: makeRequest(file),
|
|
params: {},
|
|
context: {},
|
|
} as never);
|
|
|
|
expect(response.status).toBe(200);
|
|
await expect(response.json()).resolves.toEqual({
|
|
iconUrl: "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg",
|
|
});
|
|
expect(uploadSportIcon).toHaveBeenCalledWith({
|
|
buffer: expect.any(Buffer),
|
|
slug: null,
|
|
});
|
|
});
|
|
});
|