← back to Exo Helper
test/exo-client.test.mjs
126 lines
import test from 'node:test';
import assert from 'node:assert/strict';
import { ExoClient, DEFAULT_MODEL, DEEPSEEK_MODEL, summarizeState } from '../exo-client.mjs';
const state = (ready = true) => ({
topology: { nodes: ['node-a'] },
nodeIdentities: { 'node-a': { chipId: 'Test chip' } },
instances: { instance: { MlxRingInstance: { shardAssignments: {
modelId: DEFAULT_MODEL, nodeToRunner: { 'node-a': 'runner-a' }, runnerToShard: { 'runner-a': {} },
} } } },
runners: { 'runner-a': ready ? { RunnerReady: {} } : { RunnerLoading: {} } },
});
const json = data => ({ ok: true, json: async () => data });
test('an online cluster does not imply model readiness', () => {
assert.equal(summarizeState(state(false), DEFAULT_MODEL).model_ready, false);
const missing = state(); missing.topology.nodes = [];
assert.equal(summarizeState(missing, DEFAULT_MODEL).model_ready, false);
assert.equal(summarizeState(state(), 'another-model').model_ready, false);
});
test('not-ready model never sends inference or fallback requests', async () => {
const calls = [];
const client = new ExoClient({ fetchImpl: async url => { calls.push(url); return json(state(false)); } });
await assert.rejects(client.ask({ prompt: 'hello' }), { code: 'MODEL_NOT_READY' });
assert.deepEqual(calls, ['http://127.0.0.1:52415/state']);
});
test('input limits reject before contacting Exo', async () => {
const client = new ExoClient({ fetchImpl: () => assert.fail('must not fetch') });
await assert.rejects(client.ask({ prompt: 'x'.repeat(16000), context: 'x' }), { code: 'INPUT_TOO_LARGE' });
await assert.rejects(client.ask({ prompt: 'hello', max_tokens: 99999 }), { code: 'INVALID_INPUT' });
await assert.rejects(client.ask({ prompt: ' ' }), { code: 'INVALID_INPUT' });
});
test('only explicit loopback origins are accepted', () => {
for (const baseUrl of ['https://api.openai.com', 'http://127.0.0.1.evil.test', 'http://user:pass@127.0.0.1', 'http://127.0.0.1/v1']) {
assert.throws(() => new ExoClient({ baseUrl }), /loopback/);
}
});
test('successful answer retains model, correlation, usage, and truncation evidence', async () => {
const client = new ExoClient({ fetchImpl: async (url, init) => {
if (url.endsWith('/state')) return json(state());
const body = JSON.parse(init.body);
assert.equal(body.model, DEFAULT_MODEL);
assert.equal(body.max_tokens, 12);
assert.equal(init.redirect, 'error');
assert.ok(init.headers['X-Request-ID']);
assert.equal(body.messages.at(-1).content, 'Summarize\n\n<context>\nhello\n</context>');
return json({ id: 'response-a', model: DEFAULT_MODEL, choices: [{ message: { content: 'Summary' }, finish_reason: 'length' }], usage: { total_tokens: 17 } });
} });
const result = await client.ask({ prompt: 'Summarize', context: 'hello', max_tokens: 12 });
assert.equal(result.answer, 'Summary'); assert.equal(result.truncated, true);
assert.equal(result.response_id, 'response-a'); assert.equal(result.review_required, true);
});
test('timeout produces one failure, no retry, and releases process capacity', async () => {
let count = 0;
const client = new ExoClient({ fetchImpl: async url => {
if (url.endsWith('/state')) return json(state());
count++; throw Object.assign(new Error('timeout'), { name: 'TimeoutError' });
} });
await assert.rejects(client.ask({ prompt: 'hello' }), { code: 'TIMEOUT' });
assert.equal(count, 1); assert.equal(client.busy, false);
});
test('HTTP failures do not expose arbitrary upstream response bodies', async () => {
const client = new ExoClient({ fetchImpl: async () => ({ ok: false, status: 401, text: () => assert.fail('must not echo response body') }) });
await assert.rejects(client.ask({ prompt: 'hello' }), { code: 'EXO_HTTP_ERROR' });
});
test('simultaneous requests in one client are rejected before extra work', async () => {
let release;
const client = new ExoClient({ fetchImpl: () => new Promise(resolve => { release = resolve; }) });
const first = client.ask({ prompt: 'hello' });
await assert.rejects(client.ask({ prompt: 'second' }), { code: 'BUSY' });
release(json(state(false)));
await assert.rejects(first, { code: 'MODEL_NOT_READY' });
});
test('a different model response cannot count as success', async () => {
const client = new ExoClient({ fetchImpl: async url => json(url.endsWith('/state') ? state() : {
model: 'wrong-model', choices: [{ message: { content: 'hello' }, finish_reason: 'stop' }],
}) });
await assert.rejects(client.ask({ prompt: 'hello' }), { code: 'MODEL_MISMATCH' });
});
test('DeepSeek selection routes explicitly and does not change the default', async () => {
const models = [];
const both = state();
both.instances.deepseek = { MlxRingInstance: { shardAssignments: {
modelId: DEEPSEEK_MODEL, nodeToRunner: { 'node-a': 'runner-b' }, runnerToShard: { 'runner-b': {} },
} } };
both.runners['runner-b'] = { RunnerReady: {} };
const client = new ExoClient({ fetchImpl: async (url, init) => {
if (url.endsWith('/state')) return json(both);
const body = JSON.parse(init.body); models.push(body.model);
if (body.model === DEEPSEEK_MODEL) {
assert.equal(body.max_tokens, 1024);
assert.deepEqual(body.messages.map(m => m.role), ['user']);
}
return json({ id: 'selected-response', model: body.model, choices: [{ message: { content: '42' }, finish_reason: 'stop' }] });
} });
assert.equal((await client.ask({ model: 'deepseek', prompt: 'Add' })).model, DEEPSEEK_MODEL);
assert.equal((await client.ask({ prompt: 'Add' })).model, DEFAULT_MODEL);
assert.deepEqual(models, [DEEPSEEK_MODEL, DEFAULT_MODEL]);
const status = await client.status(undefined, 'deepseek');
assert.equal(status.model_ready, true);
assert.deepEqual(status.selectable_models.map(m => m.ready), [true, true]);
});
test('unready DeepSeek cannot fall back to a ready Qwen instance', async () => {
const calls = [];
const client = new ExoClient({ fetchImpl: async url => { calls.push(url); return json(state()); } });
await assert.rejects(client.ask({ model: 'deepseek', prompt: 'hello' }), { code: 'MODEL_NOT_READY' });
assert.equal(calls.length, 1);
});
test('unknown or malformed model selection fails before network access', async () => {
const client = new ExoClient({ fetchImpl: () => assert.fail('must not fetch') });
for (const model of ['untrusted/model', 'toString', '__proto__', null, 7]) {
await assert.rejects(client.ask({ model, prompt: 'hello' }), { code: 'INVALID_MODEL' });
}
});