import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("~/models/sport", () => ({ createSport: vi.fn(), findAllSports: vi.fn(), isSportIconUrlUsed: 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() }, })); import { action } from "../admin.sports.new"; import { deleteCloudinaryImageByUrl } from "~/lib/cloudinary.server"; import { createSport, isSportIconUrlUsed } from "~/models/sport"; function makeRequest(iconUrl: string, options: { excludeFromHomepage?: boolean } = {}) { const formData = new URLSearchParams(); formData.set("name", "NFL"); formData.set("type", "team"); formData.set("slug", "nfl"); formData.set("description", "Football"); formData.set("iconUrl", iconUrl); if (options.excludeFromHomepage) { formData.set("excludeFromHomepage", "on"); } return new Request("http://test/admin/sports/new", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: formData.toString(), }); } beforeEach(() => { vi.clearAllMocks(); vi.mocked(createSport).mockResolvedValue({} as never); vi.mocked(isSportIconUrlUsed).mockResolvedValue(false); }); describe("admin sport create action", () => { it("stores the public page exclusion flag", async () => { await action({ request: makeRequest("nfl.svg", { excludeFromHomepage: true }), params: {}, context: {}, } as never); expect(createSport).toHaveBeenCalledWith(expect.objectContaining({ excludeFromHomepage: true, })); }); it("rejects arbitrary icon URLs", async () => { const response = await action({ request: makeRequest("https://example.com/icon.svg"), params: {}, context: {}, } as never); expect(response).toEqual({ error: "Sport icon must be an uploaded SVG icon." }); expect(createSport).not.toHaveBeenCalled(); }); it("deletes a newly uploaded icon when create fails", async () => { const iconUrl = "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg"; vi.mocked(createSport).mockRejectedValue(new Error("duplicate")); await action({ request: makeRequest(iconUrl), params: {}, context: {}, } as never); expect(deleteCloudinaryImageByUrl).toHaveBeenCalledWith(iconUrl); }); it("does not delete a reused existing icon when create fails", async () => { const iconUrl = "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg"; vi.mocked(createSport).mockRejectedValue(new Error("duplicate")); vi.mocked(isSportIconUrlUsed).mockResolvedValue(true); await action({ request: makeRequest(iconUrl), params: {}, context: {}, } as never); expect(deleteCloudinaryImageByUrl).not.toHaveBeenCalled(); }); it("returns the create error when icon reuse lookup fails during cleanup", async () => { const iconUrl = "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg"; vi.mocked(createSport).mockRejectedValue(new Error("database unavailable")); vi.mocked(isSportIconUrlUsed).mockRejectedValue(new Error("database unavailable")); const response = await action({ request: makeRequest(iconUrl), params: {}, context: {}, } as never); expect(response).toEqual({ error: "Failed to create sport. The slug might already exist." }); expect(deleteCloudinaryImageByUrl).not.toHaveBeenCalled(); }); });