feat: add BoxBrain persona runtime APIs
This commit is contained in:
143
tests/availability.test.ts
Normal file
143
tests/availability.test.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
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<ReturnType<typeof createDb>>) {
|
||||
const structured: StructuredModelAdapter = {
|
||||
provider: 'fake-structured',
|
||||
model: 'schedule-model',
|
||||
async generateObject<TOutput>(): Promise<TOutput> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user