test(03-10): add RED tests for ICS builder wiring + WR-04 + CR-02

- vevent.test.ts: D-13 form-parsed contract block — timed and all-day cases
  (all-day DTEND+1 fails: emits 20260610 not 20260611)
- outboxWorker.test.ts: worker integration — create/update must pass BEGIN:VCALENDAR
  to CalDAV write functions (fails: raw JSON passes through today)
- worker: unparseable payload must mark row failed (fails: marks done today)
- Update makeRow default payload to form JSON shape the worker should parse
This commit is contained in:
Lucas Berger
2026-06-05 20:46:50 -04:00
parent 1fc56f42d0
commit 813a7ba697
2 changed files with 108 additions and 1 deletions
+65 -1
View File
@@ -73,6 +73,15 @@ vi.mock('../../src/broker/client.js', () => ({
}),
}))
// Default payload is form JSON (the worker must build ICS from this — not pass raw JSON to CalDAV)
const DEFAULT_FORM_PAYLOAD = JSON.stringify({
title: 'Lunch',
allDay: false,
start: '2026-06-10T12:00:00',
end: '2026-06-10T13:00:00',
recurrence: 'none',
})
const makeRow = (overrides: Record<string, unknown> = {}) => ({
id: 1,
userId: 42,
@@ -82,7 +91,7 @@ const makeRow = (overrides: Record<string, unknown> = {}) => ({
calendarUrl: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
calendarObjectUrl: null,
etag: null,
payload: 'BEGIN:VCALENDAR\r\nEND:VCALENDAR',
payload: DEFAULT_FORM_PAYLOAD,
attemptCount: 0,
nextAttemptAt: new Date(Date.now() - 5000), // already due
lastError: null,
@@ -175,6 +184,61 @@ describe('runOutboxDrain — state transitions', () => {
})
})
describe('runOutboxDrain — ICS building from form JSON (CR-02)', () => {
beforeEach(() => {
vi.clearAllMocks()
mockPendingRows = []
mockUpdateSet.mockReturnValue({ where: vi.fn().mockResolvedValue(undefined) })
mockUpdate.mockReturnValue({ set: mockUpdateSet })
mockWherePending.mockImplementation(() => Promise.resolve(mockPendingRows))
mockFromFn.mockReturnValue({ where: mockWherePending })
mockSelectFn.mockReturnValue({ from: mockFromFn })
})
it('create row: icsString passed to createCalendarEvent starts with BEGIN:VCALENDAR and contains SUMMARY', async () => {
const { createCalendarEvent } = await import('../../src/broker/write.js')
let capturedIcsString: unknown = null
vi.mocked(createCalendarEvent).mockImplementation(async (_client, _cal, _uid, icsString) => {
capturedIcsString = icsString
return makeResponse(201)
})
mockPendingRows = [makeRow()]
await runOutboxDrain()
expect(typeof capturedIcsString).toBe('string')
expect((capturedIcsString as string).startsWith('BEGIN:VCALENDAR')).toBe(true)
expect(capturedIcsString).toContain('SUMMARY:Lunch')
})
it('update row: icsString passed to updateCalendarEvent starts with BEGIN:VCALENDAR', async () => {
const { updateCalendarEvent } = await import('../../src/broker/write.js')
let capturedIcsString: unknown = null
vi.mocked(updateCalendarEvent).mockImplementation(async (_client, _url, icsString, _etag) => {
capturedIcsString = icsString
return makeResponse(204)
})
mockPendingRows = [makeRow({
operation: 'update',
calendarObjectUrl: 'https://caldav.fastmail.com/dav/calendars/user/test/uid.ics',
})]
await runOutboxDrain()
expect(typeof capturedIcsString).toBe('string')
expect((capturedIcsString as string).startsWith('BEGIN:VCALENDAR')).toBe(true)
})
it('create row with unparseable payload marks the row failed (hard fail, no retry)', async () => {
mockPendingRows = [makeRow({ payload: 'NOT_VALID_JSON{{{' })]
await runOutboxDrain()
const setArg = mockUpdateSet.mock.calls[0]?.[0] as { status?: string }
expect(setArg?.status).toBe('failed')
})
})
describe('runOutboxDrain — edit-as-move ordering (D-04)', () => {
beforeEach(() => {
vi.clearAllMocks()