import { describe, it, expect } from 'vitest'; import { InMemoryCalendarClient } from '../src/dev-client.js'; const WIDE = { timeMin: '2026-01-01T00:00:00Z', timeMax: '2026-12-31T23:59:59Z' }; describe('InMemoryCalendarClient', () => { describe('getEvents', () => { it("returns lauren's seeded events partitioned by userSub", async () => { const client = new InMemoryCalendarClient(); const events = await client.getEvents('lauren@seahavenind.com', WIDE); const ids = events.map((e) => e.id); expect(ids).toContain('evt-lauren-001'); expect(ids).toContain('evt-lauren-002'); expect(events).toHaveLength(2); }); it("returns adam's own events, not lauren's", async () => { const client = new InMemoryCalendarClient(); const events = await client.getEvents('adam@seahavenind.com', WIDE); const ids = events.map((e) => e.id); expect(ids).toEqual(['evt-adam-001']); expect(ids).not.toContain('evt-lauren-001'); }); it('returns an empty list for an unknown sub', async () => { const client = new InMemoryCalendarClient(); const events = await client.getEvents('nobody@example.com', WIDE); expect(events).toEqual([]); }); it('filters by the time window', async () => { const client = new InMemoryCalendarClient(); const events = await client.getEvents('lauren@seahavenind.com', { timeMin: '2026-06-26T00:00:00Z', timeMax: '2026-06-27T00:00:00Z', }); expect(events.map((e) => e.id)).toEqual(['evt-lauren-002']); }); it('respects maxResults', async () => { const client = new InMemoryCalendarClient(); const events = await client.getEvents('lauren@seahavenind.com', { ...WIDE, maxResults: 1, }); expect(events).toHaveLength(1); }); }); describe('checkAvailability', () => { it('reports busy slots and free gaps around them', async () => { const client = new InMemoryCalendarClient(); const result = await client.checkAvailability('lauren@seahavenind.com', WIDE); expect(result.busy).toHaveLength(2); expect(result.busy[0]).toEqual({ start: '2026-06-25T15:00:00Z', end: '2026-06-25T15:30:00Z', }); expect(result.free.length).toBeGreaterThan(0); // window starts before first busy slot -> a leading free gap exists expect(Date.parse(result.free[0].start)).toBe(Date.parse(WIDE.timeMin)); }); it('returns the full window as free when there are no events', async () => { const client = new InMemoryCalendarClient(); const result = await client.checkAvailability('nobody@example.com', WIDE); expect(result.busy).toEqual([]); expect(result.free).toEqual([ { start: new Date(Date.parse(WIDE.timeMin)).toISOString(), end: new Date(Date.parse(WIDE.timeMax)).toISOString(), }, ]); }); }); describe('createEvent', () => { it('appends the new event and returns it', async () => { const client = new InMemoryCalendarClient(); const created = await client.createEvent('lauren@seahavenind.com', { summary: 'New planning session', description: 'Discuss Q3 roadmap', start: '2026-07-01T16:00:00Z', end: '2026-07-01T17:00:00Z', attendees: [ { email: 'adam@seahavenind.com', displayName: 'Adam' }, { email: 'x@example.com' }, ], }); expect(created.id).toMatch(/^evt-dev-\d+$/); expect(created.summary).toBe('New planning session'); expect(created.description).toBe('Discuss Q3 roadmap'); expect(created.status).toBe('confirmed'); expect(created.attendees).toEqual([ { email: 'adam@seahavenind.com', displayName: 'Adam', responseStatus: 'needsAction' }, { email: 'x@example.com', responseStatus: 'needsAction' }, ]); const events = await client.getEvents('lauren@seahavenind.com', WIDE); expect(events.map((e) => e.id)).toContain(created.id); expect(events).toHaveLength(3); }); it('creates an event for a previously-unknown sub', async () => { const client = new InMemoryCalendarClient(); const created = await client.createEvent('fresh@seahavenind.com', { summary: 'First event', start: '2026-07-02T16:00:00Z', end: '2026-07-02T17:00:00Z', }); expect(created.summary).toBe('First event'); expect(created.description).toBeUndefined(); expect(created.attendees).toBeUndefined(); const events = await client.getEvents('fresh@seahavenind.com', WIDE); expect(events).toHaveLength(1); }); }); });