TalkingBot/backend/tests/public.test.js

122 lines
4.2 KiB
JavaScript

const { PassThrough } = require('stream');
const jwt = require('jsonwebtoken');
const { assertLinkOpen } = require('../src/services/links');
const { createRateLimiter } = require('../src/services/rateLimit');
const { pipeSse } = require('../src/services/engineClient');
const { voiceMetadata, createVoiceAccess } = require('../src/services/livekitVoice');
const { readSessionToken } = require('../src/middleware/auth');
describe('public links', () => {
test('a revoked link is rejected', () => {
expect(() => assertLinkOpen({ revokedAt: '2026-01-01', expiresAt: null })).toThrow(/revoked/);
});
test('an expired link is rejected', () => {
expect(() => assertLinkOpen({ revokedAt: null, expiresAt: '2000-01-01' }, new Date('2026-01-01'))).toThrow(/expired/);
});
test('rate limit blocks the next call', () => {
const limiter = createRateLimiter({ windowMs: 60000, max: 1 });
limiter.check('ip:1');
expect(() => limiter.check('ip:1')).toThrow(/Rate limit/);
});
test('a session token must match the route', () => {
process.env.JWT_SECRET = 'test-secret';
const token = jwt.sign({ sessionId: 's1', linkId: 4, bookId: 3 }, 'test-secret');
const request = { headers: { authorization: `Bearer ${token}` } };
expect(readSessionToken(request).sessionId).toBe('s1');
expect(readSessionToken({ headers: { authorization: 'Bearer nope' } })).toBeNull();
});
});
describe('sse proxy', () => {
test('passes bytes through and abort ends the stream', async () => {
const payload = Buffer.from('data: {"type":"text_delta","text":"Hi"}\n\n');
const body = {
getReader() {
let sent = false;
return {
async read() {
if (sent) return { done: true, value: undefined };
sent = true;
return { done: false, value: payload };
},
async cancel() {},
};
},
};
const stream = new PassThrough();
const chunks = [];
const ended = new Promise((resolve) => stream.on('finish', resolve));
stream.on('data', (chunk) => chunks.push(chunk.toString()));
pipeSse({ body }, stream, new AbortController().signal);
await ended;
expect(chunks.join('')).toContain('text_delta');
let cancelled = false;
const hanging = {
getReader() {
return {
read() {
return new Promise((resolve) => {
if (cancelled) resolve({ done: true, value: undefined });
});
},
async cancel() {
cancelled = true;
},
};
},
};
const out = new PassThrough();
const signal = new AbortController();
const aborted = new Promise((resolve) => out.on('finish', resolve));
pipeSse({ body: hanging }, out, signal.signal);
signal.abort();
await aborted;
expect(out.writableEnded).toBe(true);
expect(cancelled).toBe(true);
});
});
describe('livekit voice', () => {
test('room metadata includes the session and vocabulary', async () => {
process.env.LIVEKIT_URL = 'wss://example.livekit.cloud';
process.env.LIVEKIT_API_KEY = 'key';
process.env.LIVEKIT_API_SECRET = 'secret';
process.env.LIVEKIT_AGENT_NAME = 'book-agent';
const created = [];
const dispatched = [];
const access = await createVoiceAccess({
sessionId: 's1',
bookId: 3,
engineMode: 'HYBRID_AGENTIC',
vocabularyName: 'book-3',
voiceId: 'Joanna',
language: 'en-US',
roomName: 'voice_s1',
identity: 'guest',
}, {
rooms: { async createRoom(options) { created.push(options); } },
dispatch: { async createDispatch(room, name, options) { dispatched.push({ room, name, options }); } },
AccessToken: class {
constructor() { this.grants = null; }
addGrant(grant) { this.grants = grant; }
async toJwt() { return 'signed'; }
},
});
expect(JSON.parse(created[0].metadata)).toEqual(voiceMetadata({
sessionId: 's1',
bookId: 3,
engineMode: 'HYBRID_AGENTIC',
vocabularyName: 'book-3',
voiceId: 'Joanna',
language: 'en-US',
}));
expect(dispatched[0].options.metadata).toBe(created[0].metadata);
expect(access.token).toBe('signed');
expect(access.roomName).toBe('voice_s1');
});
});