diff --git a/apps/api/tests/routes/events.test.ts b/apps/api/tests/routes/events.test.ts index 9284754..89b7847 100644 --- a/apps/api/tests/routes/events.test.ts +++ b/apps/api/tests/routes/events.test.ts @@ -446,6 +446,85 @@ describe('GET /api/events/sync-status', () => { }) }) +// --------------------------------------------------------------------------- +// CR-01 contract tests — canonical client payload shape (title/start/end) +// +// The PWA sends {title, start, end, allDay, recurrence} — the exact +// CreateEventPayload shape from apps/pwa/src/api/client.ts:119-128. +// These tests assert the server schema accepts that shape (202), NOT 400. +// RED before Task 1 schema rename; GREEN after. +// --------------------------------------------------------------------------- +describe('CR-01: canonical client payload (title/start/end) accepted by server', () => { + beforeEach(() => { + // Seed a calendar row for calendar ownership check in create + mockDbRows = [ + { + id: 1, + url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/', + displayName: 'Default', + color: '#4A90D9', + userId: 1, + isShared: false, + }, + ] + const mockSimpleWhere = vi.fn().mockImplementation(() => Promise.resolve(mockDbRows)) + mockFromFn.mockReturnValue({ where: mockSimpleWhere }) + mockSelectFn.mockReturnValue({ from: mockFromFn }) + }) + + it('POST /create with exact CreateEventPayload shape {title,start,end,allDay,recurrence} returns 202 not 400', async () => { + // This mirrors the exact payload apps/pwa/src/api/client.ts CreateEventPayload sends + const { app } = await import('../../src/index.js') + const res = await app.request('/api/events/create', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: 'Team standup', + allDay: false, + start: '2026-06-15T10:00:00Z', + end: '2026-06-15T10:30:00Z', + recurrence: 'none', + calendarUrl: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/', + }), + }) + expect(res.status).toBe(202) + const body = await res.json() as { uid: string } + expect(body).toHaveProperty('uid') + }) + + it('PATCH /:uid/edit with exact CreateEventPayload shape {title,start,end,allDay,recurrence} returns 202 not 400', async () => { + // Seed an event row for edit lookup + mockDbRows = [ + { + uid: 'uid-cr01@familysync', + etag: '"etag-cr01"', + objectUrl: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/uid-cr01.ics', + calendarId: 1, + calendarUrl: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/', + userId: 1, + }, + ] + const mockSimpleWhere = vi.fn().mockImplementation(() => Promise.resolve(mockDbRows)) + mockFromFn.mockReturnValue({ where: mockSimpleWhere }) + mockSelectFn.mockReturnValue({ from: mockFromFn }) + + const { app } = await import('../../src/index.js') + const res = await app.request('/api/events/uid-cr01%40familysync/edit', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: 'Updated standup', + allDay: false, + start: '2026-06-15T10:00:00Z', + end: '2026-06-15T10:30:00Z', + recurrence: 'weekly', + calendarUrl: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/', + }), + }) + expect(res.status).toBe(202) + }) +}) + // --------------------------------------------------------------------------- // GET /api/events/writable-calendars // ---------------------------------------------------------------------------