152 lines
4.6 KiB
JavaScript
152 lines
4.6 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { createServer } = require('../src/server');
|
|
|
|
const baseCfg = {
|
|
port: 0,
|
|
toolsSharedKey: 'test-key',
|
|
toolsPublicBaseUrl: 'http://host.docker.internal:4010',
|
|
devBypassVerification: false,
|
|
twenty: {},
|
|
};
|
|
|
|
function fakeTools() {
|
|
return [
|
|
{
|
|
name: 'search_products',
|
|
description: 'search',
|
|
parameters: { type: 'object', properties: { query: { type: 'string' } } },
|
|
requiresVerification: false,
|
|
handler: async (args, ctx) => ({ echo: args.query || null, verified: ctx.verified }),
|
|
},
|
|
{
|
|
name: 'search_orders',
|
|
description: 'orders',
|
|
parameters: { type: 'object', properties: {} },
|
|
requiresVerification: true,
|
|
handler: async () => ({ orders: [] }),
|
|
},
|
|
];
|
|
}
|
|
|
|
async function startServer(tools = fakeTools(), cfg = baseCfg) {
|
|
const server = createServer({ config: cfg, tools });
|
|
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
const { port } = server.address();
|
|
return {
|
|
base: `http://127.0.0.1:${port}`,
|
|
close: () => new Promise((resolve) => server.close(resolve)),
|
|
};
|
|
}
|
|
|
|
test('GET /healthz is public and reports tool names', async () => {
|
|
const s = await startServer();
|
|
try {
|
|
const res = await fetch(`${s.base}/healthz`);
|
|
assert.equal(res.status, 200);
|
|
const body = await res.json();
|
|
assert.equal(body.ok, true);
|
|
assert.deepEqual(body.tools, ['search_products', 'search_orders']);
|
|
} finally {
|
|
await s.close();
|
|
}
|
|
});
|
|
|
|
test('tools endpoints reject missing/wrong key', async () => {
|
|
const s = await startServer();
|
|
try {
|
|
const missing = await fetch(`${s.base}/tools/manifest`);
|
|
assert.equal(missing.status, 401);
|
|
const wrong = await fetch(`${s.base}/tools/manifest`, { headers: { 'x-tools-key': 'nope' } });
|
|
assert.equal(wrong.status, 401);
|
|
} finally {
|
|
await s.close();
|
|
}
|
|
});
|
|
|
|
test('manifest exposes URL, schema and verification flag', async () => {
|
|
const s = await startServer();
|
|
try {
|
|
const res = await fetch(`${s.base}/tools/manifest`, { headers: { 'x-tools-key': 'test-key' } });
|
|
const body = await res.json();
|
|
assert.equal(res.status, 200);
|
|
const orders = body.tools.find((t) => t.name === 'search_orders');
|
|
assert.equal(orders.url, 'http://host.docker.internal:4010/tools/search_orders');
|
|
assert.equal(orders.requires_verification, true);
|
|
assert.equal(body.tools[0].auth.headers[0].key, 'X-Tools-Key');
|
|
} finally {
|
|
await s.close();
|
|
}
|
|
});
|
|
|
|
test('verification is enforced on the server, not only in LibreDesk', async () => {
|
|
const s = await startServer();
|
|
try {
|
|
const res = await fetch(`${s.base}/tools/search_orders`, {
|
|
method: 'POST',
|
|
headers: { 'x-tools-key': 'test-key', 'content-type': 'application/json' },
|
|
body: '{}',
|
|
});
|
|
assert.equal(res.status, 403);
|
|
const body = await res.json();
|
|
assert.equal(body.error, 'verification_required');
|
|
} finally {
|
|
await s.close();
|
|
}
|
|
});
|
|
|
|
test('verified header is forwarded to handlers and result is returned', async () => {
|
|
const s = await startServer();
|
|
try {
|
|
const res = await fetch(`${s.base}/tools/search_products`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'x-tools-key': 'test-key',
|
|
'content-type': 'application/json',
|
|
'x-libredesk-contact-email': 'A@B.com',
|
|
'x-libredesk-contact-verified': 'true',
|
|
},
|
|
body: JSON.stringify({ query: 'hoops' }),
|
|
});
|
|
const body = await res.json();
|
|
assert.equal(res.status, 200);
|
|
assert.equal(body.ok, true);
|
|
assert.equal(body.tool, 'search_products');
|
|
assert.equal(body.echo, 'hoops');
|
|
assert.equal(body.verified, true);
|
|
} finally {
|
|
await s.close();
|
|
}
|
|
});
|
|
|
|
test('invalid JSON is a 400, unknown tool is a 404', async () => {
|
|
const s = await startServer();
|
|
try {
|
|
const bad = await fetch(`${s.base}/tools/search_products`, {
|
|
method: 'POST',
|
|
headers: { 'x-tools-key': 'test-key', 'content-type': 'application/json' },
|
|
body: '{not-json',
|
|
});
|
|
assert.equal(bad.status, 400);
|
|
const unknown = await fetch(`${s.base}/tools/nope`, {
|
|
method: 'POST',
|
|
headers: { 'x-tools-key': 'test-key' },
|
|
});
|
|
assert.equal(unknown.status, 404);
|
|
} finally {
|
|
await s.close();
|
|
}
|
|
});
|
|
|
|
test('missing TOOLS_SHARED_KEY fails closed with 503', async () => {
|
|
const s = await startServer(fakeTools(), { ...baseCfg, toolsSharedKey: '' });
|
|
try {
|
|
const res = await fetch(`${s.base}/tools/manifest`, { headers: { 'x-tools-key': '' } });
|
|
assert.equal(res.status, 503);
|
|
} finally {
|
|
await s.close();
|
|
}
|
|
});
|