import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("~/models/sport", () => ({ findSportById: vi.fn(), updateSport: vi.fn(), })); vi.mock("~/lib/cloudinary.server", () => ({ deleteCloudinaryImageByUrl: vi.fn().mockResolvedValue(undefined), })); vi.mock("~/lib/logger", () => ({ logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, })); vi.mock("~/services/simulations/registry", () => ({ SIMULATOR_TYPES: ["playoff_bracket"], getSimulatorInfo: vi.fn((type: string) => ({ name: type })), })); import { action } from "../admin.sports.$id"; import { deleteCloudinaryImageByUrl } from "~/lib/cloudinary.server"; import { findSportById, updateSport } from "~/models/sport"; function makeRequest(iconUrl: string) { const formData = new FormData(); formData.set("name", "NFL"); formData.set("type", "team"); formData.set("slug", "nfl"); formData.set("description", "Football"); formData.set("simulatorType", "playoff_bracket"); formData.set("iconUrl", iconUrl); return new Request("http://test/admin/sports/sport-1", { method: "POST", body: formData, }); } beforeEach(() => { vi.clearAllMocks(); vi.mocked(findSportById).mockResolvedValue({ id: "sport-1", name: "Old NFL", type: "team", slug: "nfl", description: null, iconUrl: "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/old.svg", simulatorType: null, createdAt: new Date(), updatedAt: new Date(), } as never); }); describe("admin sport edit action", () => { it("stores the submitted icon URL and deletes a replaced Cloudinary icon", async () => { const nextIconUrl = "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/new.svg"; vi.mocked(updateSport).mockResolvedValue({ iconUrl: nextIconUrl, } as never); await action({ request: makeRequest(nextIconUrl), params: { id: "sport-1" }, context: {}, } as never); expect(updateSport).toHaveBeenCalledWith("sport-1", expect.objectContaining({ iconUrl: nextIconUrl })); expect(deleteCloudinaryImageByUrl).toHaveBeenCalledWith( "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/old.svg" ); }); it("stores null when the icon is removed", async () => { vi.mocked(updateSport).mockResolvedValue({ iconUrl: null, } as never); await action({ request: makeRequest(""), params: { id: "sport-1" }, context: {}, } as never); expect(updateSport).toHaveBeenCalledWith("sport-1", expect.objectContaining({ iconUrl: null })); }); it("rejects arbitrary icon URLs", async () => { const response = await action({ request: makeRequest("https://example.com/icon.svg"), params: { id: "sport-1" }, context: {}, } as never); expect(response).toEqual({ error: "Sport icon must be an uploaded SVG icon." }); expect(updateSport).not.toHaveBeenCalled(); }); it("deletes a newly uploaded icon when update fails", async () => { const nextIconUrl = "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/new.svg"; vi.mocked(updateSport).mockRejectedValue(new Error("duplicate")); await action({ request: makeRequest(nextIconUrl), params: { id: "sport-1" }, context: {}, } as never); expect(deleteCloudinaryImageByUrl).toHaveBeenCalledWith(nextIconUrl); }); });