Cloudflare Workers + Hono + D1 + KV + R2 stack with 4 specialized AI agents (onboarding, troubleshoot, asset, billing), OpenAI function calling with 7 tool definitions, human escalation, pending action approval workflow, feedback collection, audit logging, i18n (ko/en), and Workers AI fallback. 43 source files, 45 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
138 lines
4.5 KiB
TypeScript
138 lines
4.5 KiB
TypeScript
import { describe, it, expect, beforeAll, afterEach } from 'vitest';
|
|
import { SessionManager, BaseSession } from '../../src/utils/session-manager';
|
|
import { createTestUser, getTestDB } from '../setup';
|
|
|
|
// Use onboarding_sessions table for testing
|
|
const manager = new SessionManager<BaseSession>({
|
|
tableName: 'onboarding_sessions',
|
|
ttlMs: 30 * 60 * 1000, // 30 minutes
|
|
maxMessages: 5,
|
|
});
|
|
|
|
describe('SessionManager', () => {
|
|
describe('create()', () => {
|
|
it('returns session with correct fields', () => {
|
|
const session = manager.create('user123', 'greeting');
|
|
|
|
expect(session.user_id).toBe('user123');
|
|
expect(session.status).toBe('greeting');
|
|
expect(session.collected_info).toEqual({});
|
|
expect(session.messages).toEqual([]);
|
|
expect(session.created_at).toBeTypeOf('number');
|
|
expect(session.updated_at).toBeTypeOf('number');
|
|
expect(session.expires_at).toBeGreaterThan(session.created_at);
|
|
});
|
|
});
|
|
|
|
describe('save() and get() round-trip', () => {
|
|
it('saves and retrieves session from DB', async () => {
|
|
const db = getTestDB();
|
|
const session = manager.create('user456', 'gathering');
|
|
session.collected_info = { purpose: 'hosting' };
|
|
session.messages = [{ role: 'user', content: 'hello' }];
|
|
|
|
await manager.save(db, session);
|
|
|
|
const retrieved = await manager.get(db, 'user456');
|
|
expect(retrieved).not.toBeNull();
|
|
expect(retrieved!.user_id).toBe('user456');
|
|
expect(retrieved!.status).toBe('gathering');
|
|
expect(retrieved!.collected_info).toEqual({ purpose: 'hosting' });
|
|
expect(retrieved!.messages).toEqual([{ role: 'user', content: 'hello' }]);
|
|
});
|
|
});
|
|
|
|
describe('delete()', () => {
|
|
it('removes session from DB', async () => {
|
|
const db = getTestDB();
|
|
const session = manager.create('user789', 'greeting');
|
|
await manager.save(db, session);
|
|
|
|
// Verify it exists
|
|
const exists = await manager.has(db, 'user789');
|
|
expect(exists).toBe(true);
|
|
|
|
// Delete
|
|
await manager.delete(db, 'user789');
|
|
|
|
// Verify it's gone
|
|
const afterDelete = await manager.get(db, 'user789');
|
|
expect(afterDelete).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('has()', () => {
|
|
it('returns true for existing session', async () => {
|
|
const db = getTestDB();
|
|
const session = manager.create('user_has_test', 'greeting');
|
|
await manager.save(db, session);
|
|
|
|
expect(await manager.has(db, 'user_has_test')).toBe(true);
|
|
});
|
|
|
|
it('returns false for missing session', async () => {
|
|
const db = getTestDB();
|
|
expect(await manager.has(db, 'nonexistent_user')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('addMessage()', () => {
|
|
it('adds message to session', () => {
|
|
const session = manager.create('user_msg', 'greeting');
|
|
manager.addMessage(session, 'user', 'hello');
|
|
|
|
expect(session.messages).toHaveLength(1);
|
|
expect(session.messages[0]).toEqual({ role: 'user', content: 'hello' });
|
|
});
|
|
|
|
it('trims old messages when over limit', () => {
|
|
const session = manager.create('user_trim', 'greeting');
|
|
|
|
// maxMessages is 5, add 7 messages
|
|
for (let i = 0; i < 7; i++) {
|
|
manager.addMessage(session, 'user', `message ${i}`);
|
|
}
|
|
|
|
expect(session.messages).toHaveLength(5);
|
|
// Should keep the last 5 messages (2..6)
|
|
expect(session.messages[0].content).toBe('message 2');
|
|
expect(session.messages[4].content).toBe('message 6');
|
|
});
|
|
});
|
|
|
|
describe('expired sessions', () => {
|
|
it('get() returns null for expired session', async () => {
|
|
const db = getTestDB();
|
|
|
|
// Create a session that's already expired
|
|
const now = Date.now();
|
|
const expiredSession: BaseSession = {
|
|
user_id: 'expired_user',
|
|
status: 'greeting',
|
|
collected_info: {},
|
|
messages: [],
|
|
created_at: now - 60000,
|
|
updated_at: now - 60000,
|
|
expires_at: now - 1000, // expired 1 second ago
|
|
};
|
|
|
|
// Insert directly with past expires_at
|
|
await db.prepare(
|
|
`INSERT INTO onboarding_sessions (user_id, status, collected_info, messages, created_at, updated_at, expires_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
).bind(
|
|
expiredSession.user_id,
|
|
expiredSession.status,
|
|
JSON.stringify(expiredSession.collected_info),
|
|
JSON.stringify(expiredSession.messages),
|
|
expiredSession.created_at,
|
|
expiredSession.updated_at,
|
|
expiredSession.expires_at
|
|
).run();
|
|
|
|
const result = await manager.get(db, 'expired_user');
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
});
|