import { afterEach, describe, expect, it } from 'vitest'; import { generateSchedule, getAvailabilitySnapshot, setAvailabilityStatus, type ScheduleGenerationResult, type StructuredModelAdapter, } from '../src'; import { closeOpenDbs, createDb } from './helpers'; afterEach(closeOpenDbs); describe('availability status APIs', () => { it('defaults to online when no explicit status exists', async () => { const db = await createDb(); const snapshot = await getAvailabilitySnapshot(db, { spaceName: 'persona:minji', at: '2026-05-12T08:00:00.000Z', }); expect(snapshot.current.mode).toBe('online'); expect(snapshot.current.sourceType).toBe('default'); }); it('derives current and next availability from the stored schedule', async () => { const db = await createDb(); await seedSchedule(db); const classTime = await getAvailabilitySnapshot(db, { spaceName: 'persona:minji', at: '2026-05-12T09:15:00.000Z', }); expect(classTime.current.mode).toBe('do_not_disturb'); expect(classTime.current.reason).toBe('In class'); expect(classTime.next?.mode).toBe('online'); expect(classTime.next?.effectiveFrom).toBe('2026-05-12T10:30:00.000Z'); const afterDinner = await getAvailabilitySnapshot(db, { spaceName: 'persona:minji', at: '2026-05-12T21:30:00.000Z', }); expect(afterDinner.current.mode).toBe('offline'); expect(afterDinner.current.until).toBe('2026-05-12T22:00:00.000Z'); }); it('allows explicit status updates that override schedule-derived status', async () => { const db = await createDb(); await seedSchedule(db); await setAvailabilityStatus(db, { spaceName: 'persona:minji', mode: 'online', reason: 'Taking a quick break before class starts again.', effectiveFrom: '2026-05-12T09:10:00.000Z', until: '2026-05-12T09:40:00.000Z', sourceType: 'tool', }); const snapshot = await getAvailabilitySnapshot(db, { spaceName: 'persona:minji', at: '2026-05-12T09:20:00.000Z', }); expect(snapshot.current.mode).toBe('online'); expect(snapshot.current.sourceType).toBe('tool'); expect(snapshot.current.reason).toContain('quick break'); expect(snapshot.next?.mode).toBe('do_not_disturb'); expect(snapshot.next?.effectiveFrom).toBe('2026-05-12T09:40:00.000Z'); }); it('keeps caller metadata separate from reserved availability fields', async () => { const db = await createDb(); const entry = await setAvailabilityStatus(db, { spaceName: 'persona:minji', mode: 'online', effectiveFrom: '2026-05-12T08:00:00.000Z', metadata: { id: 'caller-id', mode: 'offline', note: 'still just metadata', }, }); expect(entry.id).not.toBe('caller-id'); expect(entry.mode).toBe('online'); expect(entry.metadata).toEqual({ id: 'caller-id', mode: 'offline', note: 'still just metadata', }); }); it('rejects invalid availability timestamps', async () => { const db = await createDb(); await expect(setAvailabilityStatus(db, { spaceName: 'persona:minji', mode: 'online', effectiveFrom: 'not-a-date', })).rejects.toThrow('Availability effectiveFrom must be a valid ISO timestamp.'); }); }); async function seedSchedule(db: Awaited>) { const structured: StructuredModelAdapter = { provider: 'fake-structured', model: 'schedule-model', async generateObject(): Promise { return { events: [ { title: 'Morning lecture', description: 'Regular major lecture on campus.', startAt: '2026-05-12T09:00:00.000Z', endAt: '2026-05-12T10:30:00.000Z', availabilityMode: 'do_not_disturb', availabilityReason: 'In class', kind: 'routine', }, { title: 'Family dinner', description: 'Dinner with family.', startAt: '2026-05-12T18:00:00.000Z', endAt: '2026-05-12T22:00:00.000Z', availabilityMode: 'offline', availabilityReason: 'Family dinner', kind: 'special', }, ], } satisfies ScheduleGenerationResult as TOutput; }, }; await generateSchedule(db, { spaceName: 'persona:minji', displayName: 'Minji', currentDate: '2026-05-12', scope: 'day', structuredModel: structured, }); }