test: add tool-inventory smoke test asserting the 23-tool surface
Every PR in the non-custodial-split series is supposed to preserve `tools/list` returning 23 tools with identical schemas, but nothing enforced it — PRs 2, 6, 7 and 8 will restructure and rename tool families, and a snapshot only means something if it's taken before that starts. Boot the built server, call listTools(), and assert both the count and the exact sorted name list against a checked-in baseline captured from a live run on this branch. Failures report which names were added/removed rather than a bare array-diff. Reuses startServer()/stopServer() from test/setup.ts; does not call getMnemonic(), so it needs no wallet. Wired into CI as a new step after the unit tests. Also raises the server-start timeout in setup.ts from 15s to 60s — the Cardano WASM import makes cold starts slow enough that 15s was flaky on the smoke test's first CI run. test:smoke is intentionally separate from test:unit (which does not build) since startServer() spawns build/index.js.
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -29,3 +29,8 @@ jobs:
|
||||
run: npm run test:unit
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=12288
|
||||
|
||||
- name: Tool inventory smoke test
|
||||
run: npm run test:smoke
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=12288
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"start": "node build/index.js",
|
||||
"test": "npm run test:unit",
|
||||
"test:unit": "node --import tsx --test test/unit/*.test.ts",
|
||||
"test:integration": "npm run build && node --import tsx --test test/integration/*.test.ts"
|
||||
"test:integration": "npm run build && node --import tsx --test test/integration/*.test.ts",
|
||||
"test:smoke": "npm run build && node --import tsx --test test/smoke/*.test.ts"
|
||||
},
|
||||
"files": [
|
||||
"build"
|
||||
|
||||
@@ -31,7 +31,7 @@ export async function startServer(): Promise<ServerContext> {
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Server start timeout (15s)')), 15_000);
|
||||
const timeout = setTimeout(() => reject(new Error('Server start timeout (60s)')), 60_000);
|
||||
child.stderr!.on('data', (chunk: Buffer) => {
|
||||
if (chunk.toString().includes('listening on port')) {
|
||||
clearTimeout(timeout);
|
||||
|
||||
75
test/smoke/tool-inventory.test.ts
Normal file
75
test/smoke/tool-inventory.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { describe, test, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { startServer, stopServer, ServerContext } from '../setup.ts';
|
||||
|
||||
/**
|
||||
* Snapshot of the pre-migration tool surface (23 tools), captured from a real
|
||||
* `listTools()` call against a built server on the `ci/harness-and-gitignore`
|
||||
* branch. This is the baseline for the non-custodial split series
|
||||
* (see docs/architecture/non-custodial-split.md) — PRs 2, 6, 7 and 8
|
||||
* deliberately restructure and rename tool families, and each of those PRs is
|
||||
* expected to update this list deliberately, not accidentally. No wallet,
|
||||
* no network beyond localhost — this never needs a mnemonic.
|
||||
*/
|
||||
const EXPECTED_TOOLS = [
|
||||
'vector_build_transaction',
|
||||
'vector_deploy_contract',
|
||||
'vector_deregister_agent',
|
||||
'vector_discover_agents',
|
||||
'vector_dry_run',
|
||||
'vector_get_address',
|
||||
'vector_get_agent_profile',
|
||||
'vector_get_balance',
|
||||
'vector_get_spend_limits',
|
||||
'vector_get_transaction_history',
|
||||
'vector_get_utxos',
|
||||
'vector_interact_contract',
|
||||
'vector_message_agent',
|
||||
'vector_register_agent',
|
||||
'vector_self_improvement_analyze_metrics',
|
||||
'vector_self_improvement_browse',
|
||||
'vector_self_improvement_critique',
|
||||
'vector_self_improvement_endorse',
|
||||
'vector_self_improvement_submit_proposal',
|
||||
'vector_send_apex',
|
||||
'vector_send_tokens',
|
||||
'vector_transfer_agent',
|
||||
'vector_update_agent',
|
||||
];
|
||||
|
||||
let ctx: ServerContext;
|
||||
let actualNames: string[];
|
||||
|
||||
before(async () => {
|
||||
ctx = await startServer();
|
||||
const result = await ctx.client.listTools();
|
||||
actualNames = result.tools.map((t) => t.name).sort();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await stopServer(ctx);
|
||||
});
|
||||
|
||||
describe('tool inventory smoke test', () => {
|
||||
test('exposes exactly 23 tools', () => {
|
||||
assert.equal(
|
||||
actualNames.length,
|
||||
23,
|
||||
`expected 23 tools, got ${actualNames.length}: ${JSON.stringify(actualNames)}`
|
||||
);
|
||||
});
|
||||
|
||||
test('tool names match the checked-in inventory exactly', () => {
|
||||
const added = actualNames.filter((name) => !EXPECTED_TOOLS.includes(name));
|
||||
const removed = EXPECTED_TOOLS.filter((name) => !actualNames.includes(name));
|
||||
|
||||
assert.deepEqual(
|
||||
actualNames,
|
||||
EXPECTED_TOOLS,
|
||||
'Tool inventory drifted from the checked-in snapshot in test/smoke/tool-inventory.test.ts.\n' +
|
||||
` added: ${added.length ? JSON.stringify(added) : '(none)'}\n` +
|
||||
` removed: ${removed.length ? JSON.stringify(removed) : '(none)'}\n` +
|
||||
'If this drift is intentional, update EXPECTED_TOOLS in this file to match.'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user