Skip to content

test(operations): route the remaining operations tests through the gate - #118

Merged
AlexMost merged 5 commits into
mainfrom
test/operations-gate-migration
Aug 30, 2026
Merged

AlexMost merged 5 commits into
mainfrom
test/operations-gate-migration

Conversation

@AlexMost

Copy link
Copy Markdown
Owner

Second of four PRs for #112. Migrates the nine remaining files in
test/operations/tools/ to callTool, adds a list_properties suite (the one
operations tool with no test file), and adds the coverage the suite never had:
per-tool coercion, strict unknown-key rejection, and rejection of a vault
argument in single-vault mode.

Operations tool tests: 11 files, 140 tests (was 10 files, ~100).
Full suite 1331 passing; npm test, npm run lint, npm run typecheck all green.

Acceptance

grep -rn '\.handler(' test/operations/tools/ returns only the two edit_note
WRITE_FAILED tests, each commented. Their subject is the CallToolResult
envelope (isError + structuredContent), which callTool unwraps away, so
they stay handler-direct on the registration by design.

Triage — tests the migration changed

Two previously-green tests were passing against behaviour the gate makes
unreachable. Neither was weakened, and .strict() was not relaxed anywhere.

create_note — throws VAULT_NOT_FOUND when vault is provided but unknown.
The fixture used a single-vault registry and passed vault: 'ghost'. In
single-vault mode vaultParamShape omits vault from the schema entirely, so
the gate rejects it as an unrecognized key and the handler never runs —
VAULT_NOT_FOUND is unreachable through that rig. Fixed the fixture: it now
registers two vaults, so the parameter is declared and the error stays covered.
A comment records why. The single-vault rejection is covered separately by the
new rejects a vault argument in single-vault mode test.

set_property — rejects null/undefined value with UNSUPPORTED_VALUE_TYPE.
A false pin. value is
z.union([z.string(), z.number(), z.boolean(), z.array(z.string()), z.array(z.number())]),
so null never survives the gate. What a client actually sees is
INVALID_PARAMS at path value; the test now asserts that, with a comment
naming the branch it used to reach.

Finding — no change made, out of scope here

set_property is the only caller of inferTypeAndValidate
(src/modules/operations/tool-helpers.ts), and all three of its
UNSUPPORTED_VALUE_TYPE throws are unreachable behind that union: null or
undefined, a list element that is neither string nor number, and a non-scalar
value are each rejected by the gate first. UNSUPPORTED_VALUE_TYPE is still
declared in OperationsErrorCode (src/modules/operations/types.ts). This PR
is tests-only by design, so the dead branch is left in place — it deserves its
own change rather than a drive-by deletion here.

Plan deviation

Plan Task 4 Step 2 asserted provider.createNote is called with
objectContaining({ frontmatter: { type: 'idea' } }). The tool never forwards
frontmatter — it serializes it into content. The test asserts the serialized
content instead; the coercion under test (JSON string -> object) is unchanged.

Refs #112

🤖 Generated with Claude Code

AlexMost and others added 5 commits August 30, 2026 16:30
Every call now goes through registerTool + callTool, so the coercing,
.strict() input gate runs before the handler the way it does for a real
MCP client. Adds the coverage the file never had: overwrite coerced from
"true", frontmatter parsed from a JSON string, a rejected `vault` key in
single-vault mode, and a rejected unknown key.

The VAULT_NOT_FOUND fixture moves to a two-vault registry: single-vault
mode omits `vault` from the schema, so the gate rejected it as an
unrecognized key and the error under test had become unreachable.

Refs #112

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every call goes through registerTool + callTool, dropping the casts the
raw-handler form needed. Adds coverage the file never had: filter, limit
and include_content parsed from their string forms, path_prefix as a
JSON-string array, a rejected unknown key, and a rejected `vault` key in
single-vault mode.

Refs #112

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds coverage for the ambiguous `value` union — a JSON-string array takes
the list branch while a plain string stays text — plus an out-of-enum
`type`, a rejected `vault` key in single-vault mode, and a rejected
unknown key.

The null-value test was a false pin: `value` is a union of
string/number/boolean/string[]/number[], so the gate rejects null as
INVALID_PARAMS at `value` and the handler's UNSUPPORTED_VALUE_TYPE branch
never runs. It now asserts what a client actually sees.

Refs #112

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a rejected unknown key and a rejected `vault` key in single-vault
mode. The WRITE_FAILED block keeps calling reg.handler directly and now
carries a comment saying why: its subject is the CallToolResult envelope
itself, which callTool unwraps away.

Refs #112

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
read_daily, get_vault_overview, list_tags, get_note_links and
remove_property now call through registerTool + callTool, and
list_properties gains the dedicated suite it never had. Each file also
picks up a strict unknown-key rejection and a rejected `vault` key in
single-vault mode.

The two edit_note WRITE_FAILED tests stay handler-direct — their subject
is the CallToolResult envelope — and both now say so.

Refs #112

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AlexMost
AlexMost merged commit 8478ee7 into main Aug 30, 2026
2 checks passed
@AlexMost
AlexMost deleted the test/operations-gate-migration branch August 30, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant