From 9d4958500efaba13dc0e6b087d2faca99b28f635 Mon Sep 17 00:00:00 2001 From: FilipBl4gojevic <84703395+FilipBl4gojevic@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:34:43 +0200 Subject: [PATCH] test: add tool-inventory smoke test asserting the 23-tool surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .github/workflows/ci.yml | 5 +++ package.json | 3 +- test/setup.ts | 2 +- test/smoke/tool-inventory.test.ts | 75 +++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/smoke/tool-inventory.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ef81fa..ce7fcd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/package.json b/package.json index 5d47798..cfa4212 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/test/setup.ts b/test/setup.ts index 0651be1..37a7cac 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -31,7 +31,7 @@ export async function startServer(): Promise { }); await new Promise((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); diff --git a/test/smoke/tool-inventory.test.ts b/test/smoke/tool-inventory.test.ts new file mode 100644 index 0000000..fd855f8 --- /dev/null +++ b/test/smoke/tool-inventory.test.ts @@ -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.' + ); + }); +});