test(01-03): refine sync RED tests with richer db mock (select + insert)
- Use separate mock functions for insert/select chain to enable per-test assertions
- Reset mock implementations in beforeEach after vi.clearAllMocks
- Mock returns {id:42} from select (calendar ID for event upsert)
This commit is contained in:
@@ -16,23 +16,37 @@ import {
|
|||||||
SAMPLE_VEVENT_ALLDAY,
|
SAMPLE_VEVENT_ALLDAY,
|
||||||
} from '../helpers/db.js'
|
} from '../helpers/db.js'
|
||||||
|
|
||||||
|
// Track calls for assertions
|
||||||
|
const mockOnDuplicateKeyUpdate = vi.fn().mockResolvedValue([{ insertId: 1 }])
|
||||||
|
const mockValues = vi.fn().mockReturnValue({ onDuplicateKeyUpdate: mockOnDuplicateKeyUpdate })
|
||||||
|
const mockInsert = vi.fn().mockReturnValue({ values: mockValues })
|
||||||
|
const mockLimit = vi.fn().mockResolvedValue([{ id: 42 }])
|
||||||
|
const mockWhere = vi.fn().mockReturnValue({ limit: mockLimit })
|
||||||
|
const mockFrom = vi.fn().mockReturnValue({ where: mockWhere })
|
||||||
|
const mockSelect = vi.fn().mockReturnValue({ from: mockFrom })
|
||||||
|
|
||||||
// Mock the db singleton at module level (Vitest hoisting)
|
// Mock the db singleton at module level (Vitest hoisting)
|
||||||
vi.mock('../../src/db/client.js', () => {
|
vi.mock('../../src/db/client.js', () => ({
|
||||||
const onDuplicateKeyUpdate = vi.fn().mockResolvedValue([{ id: 1 }])
|
db: {
|
||||||
const values = vi.fn().mockReturnValue({ onDuplicateKeyUpdate })
|
insert: mockInsert,
|
||||||
const insert = vi.fn().mockReturnValue({ values })
|
select: mockSelect,
|
||||||
return {
|
},
|
||||||
db: { insert },
|
}))
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('syncCalendar', () => {
|
describe('syncCalendar', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
// Reset mock implementations after clearAllMocks
|
||||||
|
mockOnDuplicateKeyUpdate.mockResolvedValue([{ insertId: 1 }])
|
||||||
|
mockValues.mockReturnValue({ onDuplicateKeyUpdate: mockOnDuplicateKeyUpdate })
|
||||||
|
mockInsert.mockReturnValue({ values: mockValues })
|
||||||
|
mockLimit.mockResolvedValue([{ id: 42 }])
|
||||||
|
mockWhere.mockReturnValue({ limit: mockLimit })
|
||||||
|
mockFrom.mockReturnValue({ where: mockWhere })
|
||||||
|
mockSelect.mockReturnValue({ from: mockFrom })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores all-day events with dtstart_date (DATE) and dtstart_utc=NULL', async () => {
|
it('stores all-day events with dtstart_date (DATE) and dtstart_utc=NULL', async () => {
|
||||||
const { db } = await import('../../src/db/client.js')
|
|
||||||
const { syncCalendar } = await import('../../src/broker/sync.js')
|
const { syncCalendar } = await import('../../src/broker/sync.js')
|
||||||
|
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
@@ -49,19 +63,16 @@ describe('syncCalendar', () => {
|
|||||||
|
|
||||||
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
||||||
|
|
||||||
const insertMock = vi.mocked(db.insert)
|
// insert called twice: calendars + calendarEvents
|
||||||
// Should have been called at least twice: once for calendars, once for calendarEvents
|
expect(mockInsert).toHaveBeenCalledTimes(2)
|
||||||
expect(insertMock).toHaveBeenCalledTimes(2)
|
// Event insert: second call's values arg
|
||||||
|
const eventValuesArg = mockValues.mock.calls[1][0]
|
||||||
// The second insert call is for calendarEvents
|
|
||||||
const eventValuesArg = vi.mocked(insertMock).mock.results[1].value.values.mock.calls[0][0]
|
|
||||||
expect(eventValuesArg.allDay).toBe(true)
|
expect(eventValuesArg.allDay).toBe(true)
|
||||||
expect(eventValuesArg.dtstartDate).toBeTruthy() // YYYY-MM-DD string
|
expect(eventValuesArg.dtstartDate).toBeTruthy()
|
||||||
expect(eventValuesArg.dtstartUtc).toBeNull()
|
expect(eventValuesArg.dtstartUtc).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores timed events with dtstart_utc (TIMESTAMP) and dtstart_date=NULL', async () => {
|
it('stores timed events with dtstart_utc (TIMESTAMP) and dtstart_date=NULL', async () => {
|
||||||
const { db } = await import('../../src/db/client.js')
|
|
||||||
const { syncCalendar } = await import('../../src/broker/sync.js')
|
const { syncCalendar } = await import('../../src/broker/sync.js')
|
||||||
|
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
@@ -78,17 +89,14 @@ describe('syncCalendar', () => {
|
|||||||
|
|
||||||
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
||||||
|
|
||||||
const insertMock = vi.mocked(db.insert)
|
expect(mockInsert).toHaveBeenCalledTimes(2)
|
||||||
expect(insertMock).toHaveBeenCalledTimes(2)
|
const eventValuesArg = mockValues.mock.calls[1][0]
|
||||||
|
|
||||||
const eventValuesArg = vi.mocked(insertMock).mock.results[1].value.values.mock.calls[0][0]
|
|
||||||
expect(eventValuesArg.allDay).toBe(false)
|
expect(eventValuesArg.allDay).toBe(false)
|
||||||
expect(eventValuesArg.dtstartUtc).toBeInstanceOf(Date)
|
expect(eventValuesArg.dtstartUtc).toBeInstanceOf(Date)
|
||||||
expect(eventValuesArg.dtstartDate).toBeNull()
|
expect(eventValuesArg.dtstartDate).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets allDay=true for all-day events, allDay=false for timed', async () => {
|
it('sets allDay=true for all-day events, allDay=false for timed', async () => {
|
||||||
const { db } = await import('../../src/db/client.js')
|
|
||||||
const { syncCalendar } = await import('../../src/broker/sync.js')
|
const { syncCalendar } = await import('../../src/broker/sync.js')
|
||||||
|
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
@@ -104,13 +112,11 @@ describe('syncCalendar', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
||||||
const insertMock = vi.mocked(db.insert)
|
const eventArg = mockValues.mock.calls[1][0]
|
||||||
const eventArg = vi.mocked(insertMock).mock.results[1].value.values.mock.calls[0][0]
|
|
||||||
expect(eventArg.allDay).toBe(true)
|
expect(eventArg.allDay).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('upserts on duplicate UID within the same calendar (onDuplicateKeyUpdate called)', async () => {
|
it('upserts on duplicate UID within the same calendar (onDuplicateKeyUpdate called for events)', async () => {
|
||||||
const { db } = await import('../../src/db/client.js')
|
|
||||||
const { syncCalendar } = await import('../../src/broker/sync.js')
|
const { syncCalendar } = await import('../../src/broker/sync.js')
|
||||||
|
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
@@ -127,16 +133,11 @@ describe('syncCalendar', () => {
|
|||||||
|
|
||||||
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
||||||
|
|
||||||
// Both calendar insert and event insert must use onDuplicateKeyUpdate
|
// onDuplicateKeyUpdate must be called for both the calendar upsert and the event upsert
|
||||||
const insertMock = vi.mocked(db.insert)
|
expect(mockOnDuplicateKeyUpdate).toHaveBeenCalledTimes(2)
|
||||||
const calOnDup = vi.mocked(insertMock).mock.results[0].value.values.mock.results[0].value.onDuplicateKeyUpdate
|
|
||||||
const evtOnDup = vi.mocked(insertMock).mock.results[1].value.values.mock.results[0].value.onDuplicateKeyUpdate
|
|
||||||
expect(calOnDup).toHaveBeenCalledTimes(1)
|
|
||||||
expect(evtOnDup).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the raw VEVENT blob in rawVevent column', async () => {
|
it('stores the raw VEVENT blob in rawVevent column', async () => {
|
||||||
const { db } = await import('../../src/db/client.js')
|
|
||||||
const { syncCalendar } = await import('../../src/broker/sync.js')
|
const { syncCalendar } = await import('../../src/broker/sync.js')
|
||||||
|
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
@@ -152,13 +153,11 @@ describe('syncCalendar', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
||||||
const insertMock = vi.mocked(db.insert)
|
const eventArg = mockValues.mock.calls[1][0]
|
||||||
const eventArg = vi.mocked(insertMock).mock.results[1].value.values.mock.calls[0][0]
|
|
||||||
expect(eventArg.rawVevent).toBe(SAMPLE_VEVENT_TIMED)
|
expect(eventArg.rawVevent).toBe(SAMPLE_VEVENT_TIMED)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updates the calendar ctag/syncToken after a successful sync', async () => {
|
it('updates the calendar ctag/syncToken after a successful sync', async () => {
|
||||||
const { db } = await import('../../src/db/client.js')
|
|
||||||
const { syncCalendar } = await import('../../src/broker/sync.js')
|
const { syncCalendar } = await import('../../src/broker/sync.js')
|
||||||
|
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
@@ -173,8 +172,8 @@ describe('syncCalendar', () => {
|
|||||||
|
|
||||||
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
await syncCalendar(mockClient as never, mockDavCal as never, 1)
|
||||||
|
|
||||||
const insertMock = vi.mocked(db.insert)
|
// Calendar insert values must include the new ctag and syncToken
|
||||||
const calValuesArg = vi.mocked(insertMock).mock.results[0].value.values.mock.calls[0][0]
|
const calValuesArg = mockValues.mock.calls[0][0]
|
||||||
expect(calValuesArg.ctag).toBe('new-ctag-123')
|
expect(calValuesArg.ctag).toBe('new-ctag-123')
|
||||||
expect(calValuesArg.syncToken).toBe('sync-token-abc')
|
expect(calValuesArg.syncToken).toBe('sync-token-abc')
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user