feat: add BoxBrain persona runtime APIs
This commit is contained in:
178
tests/schedule.test.ts
Normal file
178
tests/schedule.test.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
generateSchedule,
|
||||
getAvailabilitySnapshot,
|
||||
listScheduleEvents,
|
||||
pruneExpiredSchedule,
|
||||
pruneScheduleBefore,
|
||||
type ScheduleGenerationResult,
|
||||
type SpecialDateProvider,
|
||||
type StructuredModelAdapter,
|
||||
} from '../src';
|
||||
import { closeOpenDbs, createDb } from './helpers';
|
||||
|
||||
afterEach(closeOpenDbs);
|
||||
|
||||
describe('generateSchedule', () => {
|
||||
it('generates schedule events, stores them, and includes special date context in the prompt', async () => {
|
||||
const db = await createDb();
|
||||
const prompts: string[] = [];
|
||||
const structured: StructuredModelAdapter = {
|
||||
provider: 'fake-structured',
|
||||
model: 'schedule-model',
|
||||
async generateObject<TOutput>(request: { prompt: string }): Promise<TOutput> {
|
||||
prompts.push(request.prompt);
|
||||
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',
|
||||
topics: [{ name: 'lecture', category: 'concept' }],
|
||||
},
|
||||
{
|
||||
title: 'Birthday dinner',
|
||||
description: 'Family dinner for a parent birthday.',
|
||||
startAt: '2026-05-12T18:00:00.000Z',
|
||||
endAt: '2026-05-12T20:00:00.000Z',
|
||||
availabilityMode: 'offline',
|
||||
availabilityReason: 'Family dinner',
|
||||
kind: 'special',
|
||||
topics: [{ name: 'birthday dinner', category: 'concept' }],
|
||||
},
|
||||
],
|
||||
} satisfies ScheduleGenerationResult as TOutput;
|
||||
},
|
||||
};
|
||||
const specialDateProvider: SpecialDateProvider = {
|
||||
async listSpecialDates() {
|
||||
return [
|
||||
{
|
||||
date: '2026-05-12',
|
||||
title: 'Parents Day',
|
||||
description: 'A family-focused observance in Korea.',
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
const result = await generateSchedule(db, {
|
||||
spaceName: 'persona:minji',
|
||||
displayName: 'Minji',
|
||||
currentDate: '2026-05-12',
|
||||
scope: 'day',
|
||||
timezone: 'Asia/Seoul',
|
||||
structuredModel: structured,
|
||||
specialDateProvider,
|
||||
});
|
||||
|
||||
expect(prompts[0]).toContain('Parents Day');
|
||||
expect(prompts[0]).toContain('Most of the time should remain routine');
|
||||
expect(result.events.map((event) => event.title)).toEqual(['Morning lecture', 'Birthday dinner']);
|
||||
expect(result.availabilityEntries.map((entry) => entry.mode)).toEqual(['do_not_disturb', 'offline']);
|
||||
|
||||
const stored = await listScheduleEvents(db, { spaceName: 'persona:minji' });
|
||||
expect(stored.map((event) => event.title)).toEqual(['Morning lecture', 'Birthday dinner']);
|
||||
expect(stored[0]?.topics.map((topic) => topic.name)).toContain('2026-05-12');
|
||||
});
|
||||
|
||||
it('can prune events that ended before a reference time', async () => {
|
||||
const db = await createDb();
|
||||
await seedSchedule(db);
|
||||
|
||||
const pruned = await pruneExpiredSchedule(db, {
|
||||
spaceName: 'persona:minji',
|
||||
referenceTime: '2026-05-12T12:00:00.000Z',
|
||||
graceSeconds: 0,
|
||||
});
|
||||
|
||||
expect(pruned.deletedEventIds).toHaveLength(1);
|
||||
expect((await listScheduleEvents(db, { spaceName: 'persona:minji' })).map((event) => event.title)).toEqual(['Evening shift']);
|
||||
const snapshot = await getAvailabilitySnapshot(db, {
|
||||
spaceName: 'persona:minji',
|
||||
at: '2026-05-12T09:30:00.000Z',
|
||||
});
|
||||
expect(snapshot.current.mode).toBe('online');
|
||||
expect(snapshot.current.sourceType).toBe('default');
|
||||
});
|
||||
|
||||
it('can prune events scheduled before a cutoff date', async () => {
|
||||
const db = await createDb();
|
||||
await seedSchedule(db);
|
||||
|
||||
const pruned = await pruneScheduleBefore(db, {
|
||||
spaceName: 'persona:minji',
|
||||
before: '2026-05-12T18:00:00.000Z',
|
||||
});
|
||||
|
||||
expect(pruned.deletedEventIds).toHaveLength(1);
|
||||
expect((await listScheduleEvents(db, { spaceName: 'persona:minji' })).map((event) => event.title)).toEqual(['Evening shift']);
|
||||
});
|
||||
|
||||
it('rejects invalid schedule timestamps from the structured model', async () => {
|
||||
const db = await createDb();
|
||||
const structured: StructuredModelAdapter = {
|
||||
provider: 'fake-structured',
|
||||
model: 'schedule-model',
|
||||
async generateObject<TOutput>(): Promise<TOutput> {
|
||||
return {
|
||||
events: [
|
||||
{
|
||||
title: 'Broken event',
|
||||
startAt: 'not-a-date',
|
||||
endAt: '2026-05-12T10:00:00.000Z',
|
||||
availabilityMode: 'online',
|
||||
},
|
||||
],
|
||||
} satisfies ScheduleGenerationResult as TOutput;
|
||||
},
|
||||
};
|
||||
|
||||
await expect(generateSchedule(db, {
|
||||
spaceName: 'persona:minji',
|
||||
displayName: 'Minji',
|
||||
currentDate: '2026-05-12',
|
||||
scope: 'day',
|
||||
structuredModel: structured,
|
||||
})).rejects.toThrow('Schedule event at index 0 startAt must be a valid ISO timestamp.');
|
||||
});
|
||||
});
|
||||
|
||||
async function seedSchedule(db: Awaited<ReturnType<typeof createDb>>) {
|
||||
const structured: StructuredModelAdapter = {
|
||||
provider: 'fake-structured',
|
||||
model: 'schedule-model',
|
||||
async generateObject<TOutput>(): Promise<TOutput> {
|
||||
return {
|
||||
events: [
|
||||
{
|
||||
title: 'Morning lecture',
|
||||
startAt: '2026-05-12T09:00:00.000Z',
|
||||
endAt: '2026-05-12T10:30:00.000Z',
|
||||
availabilityMode: 'do_not_disturb',
|
||||
kind: 'routine',
|
||||
},
|
||||
{
|
||||
title: 'Evening shift',
|
||||
startAt: '2026-05-12T18:00:00.000Z',
|
||||
endAt: '2026-05-12T22:00:00.000Z',
|
||||
availabilityMode: 'offline',
|
||||
kind: 'routine',
|
||||
},
|
||||
],
|
||||
} satisfies ScheduleGenerationResult as TOutput;
|
||||
},
|
||||
};
|
||||
|
||||
await generateSchedule(db, {
|
||||
spaceName: 'persona:minji',
|
||||
displayName: 'Minji',
|
||||
currentDate: '2026-05-12',
|
||||
scope: 'day',
|
||||
structuredModel: structured,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user