tests/fixtures/fake-server.js (2528 bytes)
1 // A stand-in for the dashboard server in the tests: an OpenAI-compatible POST /v1/embeddings, and 2 // PUT for the files that `ideamine publish` uploads. The vectors are a hashed bag of words, so 3 // texts that share words are similar, and the same text always gets the same vector. 4 5 import crypto from 'node:crypto'; 6 import http from 'node:http'; 7 8 export const DIM = 64; 9 10 export function fakeVector(text) { 11 const vec = new Array(DIM).fill(0); 12 const words = String(text).toLowerCase().replace(/^search_(query|document): /, '').match(/[\p{L}\p{N}]{3,}/gu) || ['empty']; 13 for (const w of words) vec[crypto.createHash('sha1').update(w).digest().readUInt16LE(0) % DIM] += 1; 14 return vec; 15 } 16 17 /** 18 * Start the server on a free port. Options: `maxChars` makes it refuse longer inputs the way 19 * llama.cpp does; `down` makes it answer 503. Returns { url, inputs, files, close, options }. 20 */ 21 export async function startFakeServer(options = {}) { 22 const inputs = []; // every text that /v1/embeddings got, in order 23 const files = new Map(); // path -> body of each PUT 24 const server = http.createServer((req, res) => { 25 let body = ''; 26 req.setEncoding('utf8'); 27 req.on('data', (chunk) => (body += chunk)); 28 req.on('end', () => { 29 if (req.method === 'PUT') { 30 files.set(req.url, body); 31 res.writeHead(201).end(); 32 return; 33 } 34 if (req.method !== 'POST' || req.url !== '/v1/embeddings') return res.writeHead(404).end(); 35 if (options.down) return res.writeHead(503).end('{"error":"model is loading"}'); 36 const { input } = JSON.parse(body); 37 const texts = Array.isArray(input) ? input : [input]; 38 inputs.push(...texts); 39 const long = texts.find((t) => options.maxChars && t.length > options.maxChars); 40 if (long) { 41 const error = { code: 500, message: `input (${long.length} tokens) is too large to process. increase the physical batch size`, type: 'server_error' }; 42 return res.writeHead(500, { 'content-type': 'application/json' }).end(JSON.stringify({ error })); 43 } 44 const data = texts.map((t, index) => ({ object: 'embedding', index, embedding: fakeVector(t) })); 45 res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ object: 'list', data })); 46 }); 47 }); 48 await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); 49 const url = `http://127.0.0.1:${server.address().port}`; 50 return { url, inputs, files, options, close: () => new Promise((resolve) => server.close(resolve)) }; 51 }