import { afterEach, describe, expect, it, vi } from "vitest"; import { api, ApiError } from "@/lib/api"; import type { EventPayload } from "@/lib/types"; const payload: EventPayload = { calendarId: 1, name: "Konzert", description: "", startDateTime: "2026-10-03T17:00:00.000Z", endDateTime: "2026-10-03T19:00:00.000Z", location: "", url: "", wholeDay: false, repeatFrequency: "", status: "DRAFT", }; const respond = (status: number, body?: unknown) => vi.fn().mockResolvedValue( new Response(body === undefined ? null : JSON.stringify(body), { status, headers: { "Content-Type": "application/json" }, }), ); afterEach(() => { vi.unstubAllGlobals(); }); describe("api", () => { it("always sends the session cookie", async () => { const fetch = respond(200, { id: "u", email: "e", fullName: "Name", apps: ["calendar"] }); vi.stubGlobal("fetch", fetch); await api.me(); expect(fetch).toHaveBeenCalledWith("https://api.test/admin/me", expect.objectContaining({ credentials: "include" })); }); it("keeps 401 and 403 apart", async () => { vi.stubGlobal("fetch", respond(401, { message: "Anmeldung erforderlich." })); await expect(api.me()).rejects.toMatchObject({ status: 401 }); vi.stubGlobal("fetch", respond(403, { message: "nope" })); await expect(api.me()).rejects.toMatchObject({ status: 403 }); }); it("reports a network failure as status 0", async () => { vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new TypeError("Failed to fetch"))); const error = await api.me().catch((e: unknown) => e); expect(error).toBeInstanceOf(ApiError); expect((error as ApiError).status).toBe(0); }); it("parses events and drops deleted ones", async () => { vi.stubGlobal( "fetch", respond(200, [ { eventId: 1, calendarId: 1, uuid: "a", name: "A", description: "", startDateTime: payload.startDateTime, endDateTime: payload.endDateTime, createdDate: null, location: "", url: "", wholeDay: 0, repeatFrequency: "", status: "PUBLIC" }, { eventId: 2, calendarId: 1, uuid: "b", name: null, description: null, startDateTime: null, endDateTime: null, createdDate: null, location: null, url: null, wholeDay: null, repeatFrequency: null, status: "DELETED" }, ]), ); const events = await api.getEvents("public"); expect(events.map((e) => e.eventId)).toEqual([1]); }); it("returns the new id on create", async () => { const fetch = respond(201, { message: "ok", eventId: 42 }); vi.stubGlobal("fetch", fetch); await expect(api.createEvent(payload)).resolves.toBe(42); expect(JSON.parse(fetch.mock.calls[0][1].body)).toEqual(payload); }); it("treats an update that wrote nothing as a failure", async () => { vi.stubGlobal("fetch", respond(200, { message: "0 rows were updated" })); await expect(api.updateEvent(1, payload)).rejects.toBeInstanceOf(ApiError); }); it("moves and deletes by id", async () => { const fetch = respond(200, { message: "ok" }); vi.stubGlobal("fetch", fetch); await api.moveEvent(9, { ...payload, calendarId: 4 }); await api.deleteEvent(9); expect(fetch.mock.calls.map((c) => [c[0], c[1].method])).toEqual([ ["https://api.test/calendar/events/move/9", "PUT"], ["https://api.test/calendar/events/9", "DELETE"], ]); }); });