feat(260929-if2): faellige Erinnerungen erledigen, spaeter erinnern, bearbeiten und loeschen

- API: Aendern (409 wenn faellig), Spaeter erinnern (409 wenn nicht faellig, setzt E-Mail-Spur zurueck), Loeschen; fremde Kennungen 404
- Kachel: faellige Zeilen hervorgehoben mit Erledigt und drei Spaeter-Optionen, kuenftige mit Bearbeiten und Loeschen
- Zeit-Hilfen (morgen zur gleichen Uhrzeit), Zugriffsklassifikation nachgemessen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 13:59:43 +02:00
parent 325c5ddbf2
commit 709b41a007
16 changed files with 946 additions and 57 deletions
+43 -1
View File
@@ -1,5 +1,12 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ReminderRequestError, createReminder, listReminders } from './reminders-api';
import {
ReminderRequestError,
createReminder,
deleteReminder,
listReminders,
snoozeReminder,
updateReminder,
} from './reminders-api';
const fetchMock = vi.fn();
@@ -33,6 +40,41 @@ describe('reminders-api', () => {
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('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 });