mirror of
https://github.com/Sea-Haven-Industries/sh-mcp.git
synced 2026-10-01 18:43:18 +00:00
119 lines
4.6 KiB
TypeScript
119 lines
4.6 KiB
TypeScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|