122 lines
4.2 KiB
JavaScript
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');
|
|
});
|
|
});
|