import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ReminderRequestError, createReminder, deleteReminder, getReminderEmailStatus, listReminders, snoozeReminder, updateReminder, } from './reminders-api'; const fetchMock = vi.fn(); beforeEach(() => { vi.stubGlobal('fetch', fetchMock); }); afterEach(() => { fetchMock.mockReset(); vi.unstubAllGlobals(); }); const ok = (body: unknown) => ({ ok: true, status: 200, json: async () => body }); describe('reminders-api', () => { it('listReminders ruft GET /reminders mit Cookie-Anmeldung', async () => { fetchMock.mockResolvedValue(ok([])); await expect(listReminders()).resolves.toEqual([]); const [url, init] = fetchMock.mock.calls[0]; expect(url).toMatch(/\/reminders$/); expect(init.credentials).toBe('include'); }); it('createReminder schickt POST mit JSON-Koerper', async () => { fetchMock.mockResolvedValue(ok({ id: 'r1' })); await createReminder({ title: 'a', dueAt: '2099-01-01T10:00:00.000Z' }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toMatch(/\/reminders$/); expect(init.method).toBe('POST'); expect(init.credentials).toBe('include'); expect(JSON.parse(init.body)).toEqual({ title: 'a', dueAt: '2099-01-01T10:00:00.000Z' }); }); it('updateReminder schickt PATCH an /reminders/:id', async () => { fetchMock.mockResolvedValue(ok({ id: 'r 1' })); await updateReminder('r 1', { title: 'neu' }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toMatch(/\/reminders\/r%201$/); expect(init.method).toBe('PATCH'); expect(init.credentials).toBe('include'); expect(JSON.parse(init.body)).toEqual({ title: 'neu' }); }); it('snoozeReminder schickt POST /reminders/:id/snooze mit dueAt', async () => { fetchMock.mockResolvedValue(ok({ id: 'r1' })); await snoozeReminder('r1', '2099-01-01T10:00:00.000Z'); const [url, init] = fetchMock.mock.calls[0]; expect(url).toMatch(/\/reminders\/r1\/snooze$/); expect(init.method).toBe('POST'); expect(JSON.parse(init.body)).toEqual({ dueAt: '2099-01-01T10:00:00.000Z' }); }); it('deleteReminder schickt DELETE an /reminders/:id', async () => { fetchMock.mockResolvedValue(ok({ deleted: true })); await expect(deleteReminder('r1')).resolves.toEqual({ deleted: true }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toMatch(/\/reminders\/r1$/); expect(init.method).toBe('DELETE'); expect(init.credentials).toBe('include'); }); it('getReminderEmailStatus ruft GET /reminders/email-status', async () => { fetchMock.mockResolvedValue(ok({ smtpConfigured: true, hasEmail: false })); await expect(getReminderEmailStatus()).resolves.toEqual({ smtpConfigured: true, hasEmail: false }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toMatch(/\/reminders\/email-status$/); expect(init.credentials).toBe('include'); }); it('bei 404 und 409 tragen die Fehler den Status', async () => { fetchMock.mockResolvedValue({ ok: false, status: 404, json: async () => ({}) }); await expect(deleteReminder('x')).rejects.toMatchObject({ status: 404 }); fetchMock.mockResolvedValue({ ok: false, status: 409, json: async () => ({}) }); await expect(snoozeReminder('x', 'y')).rejects.toMatchObject({ status: 409 }); }); it('ein Status ausserhalb 2xx wirft ReminderRequestError mit diesem Status', async () => { fetchMock.mockResolvedValue({ ok: false, status: 401, json: async () => ({}) }); await expect(listReminders()).rejects.toMatchObject({ status: 401 }); fetchMock.mockResolvedValue({ ok: false, status: 409, json: async () => ({}) }); const err = await createReminder({ title: 'a', dueAt: 'x' }).catch((e) => e); expect(err).toBeInstanceOf(ReminderRequestError); expect(err.status).toBe(409); }); });