test(operations): route the remaining operations tests through the gate - #118
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of four PRs for #112. Migrates the nine remaining files in
test/operations/tools/tocallTool, adds alist_propertiessuite (the oneoperations tool with no test file), and adds the coverage the suite never had:
per-tool coercion, strict unknown-key rejection, and rejection of a
vaultargument 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 typecheckall green.Acceptance
grep -rn '\.handler(' test/operations/tools/returns only the twoedit_noteWRITE_FAILEDtests, each commented. Their subject is theCallToolResultenvelope (
isError+structuredContent), whichcallToolunwraps away, sothey 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'. Insingle-vault mode
vaultParamShapeomitsvaultfrom the schema entirely, sothe gate rejects it as an unrecognized key and the handler never runs —
VAULT_NOT_FOUNDis unreachable through that rig. Fixed the fixture: it nowregisters 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 modetest.set_property—rejects null/undefined value with UNSUPPORTED_VALUE_TYPE.A false pin.
valueisz.union([z.string(), z.number(), z.boolean(), z.array(z.string()), z.array(z.number())]),so
nullnever survives the gate. What a client actually sees isINVALID_PARAMSat pathvalue; the test now asserts that, with a commentnaming the branch it used to reach.
Finding — no change made, out of scope here
set_propertyis the only caller ofinferTypeAndValidate(
src/modules/operations/tool-helpers.ts), and all three of itsUNSUPPORTED_VALUE_TYPEthrows are unreachable behind that union: null orundefined, a list element that is neither string nor number, and a non-scalar
valueare each rejected by the gate first.UNSUPPORTED_VALUE_TYPEis stilldeclared in
OperationsErrorCode(src/modules/operations/types.ts). This PRis 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.createNoteis called withobjectContaining({ frontmatter: { type: 'idea' } }). The tool never forwardsfrontmatter— it serializes it intocontent. The test asserts the serializedcontent instead; the coercion under test (JSON string -> object) is unchanged.
Refs #112
🤖 Generated with Claude Code