test(worker): assert the full external MCP tool-name inventory
Guardrail for the upcoming ExternalMcpService.cs topic split: proves the move is name-preserving by pinning the complete set of registered tool names (computed via the SDK's own McpServerTool.Create, not a reimplementation of the naming rule) before any file is moved.
This commit is contained in:
@@ -84,4 +84,57 @@ public sealed class ExternalMcpToolSchemaTests
|
|||||||
|
|
||||||
Assert.True(hasHint, "No external tool description carries McpToolDocs.TaskNumberHint.");
|
Assert.True(hasHint, "No external tool description carries McpToolDocs.TaskNumberHint.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Full inventory of registered external tool names, computed via the SDK's own
|
||||||
|
// McpServerTool.Create (the same call Program.cs's WithTools<T>() makes internally) rather
|
||||||
|
// than reimplementing the PascalCase-to-snake_case conversion — so this only breaks on an
|
||||||
|
// actual rename/add/remove, not on a divergent reimplementation of the naming rule. Used to
|
||||||
|
// prove that splitting ExternalMcpService.cs into topic files (MergeMcpTools, WorktreeMcpTools,
|
||||||
|
// ...) is a byte-identical rename-preserving move: the set below must not change across that split.
|
||||||
|
private static readonly string[] ExpectedToolNames =
|
||||||
|
[
|
||||||
|
"list_task_lists", "list_tasks", "get_task", "add_task", "update_task", "add_subtask",
|
||||||
|
"update_task_status", "review_task", "run_task_now", "cancel_task", "delete_task",
|
||||||
|
"get_task_worktree", "get_task_diff", "merge_task", "continue_merge", "verify_merges",
|
||||||
|
"abort_merge", "get_merge_conflicts", "resolve_conflict_hunk", "preview_merge",
|
||||||
|
"preview_merge_set", "revert_merge", "list_worktrees", "cleanup_task_worktree",
|
||||||
|
"continue_task", "get_daily_prep_candidates", "set_my_day",
|
||||||
|
"batch_get_tasks", "batch_add_tasks", "batch_update_task_status", "batch_cancel_tasks",
|
||||||
|
"batch_delete_tasks", "batch_set_my_day", "batch_cleanup_task_worktrees",
|
||||||
|
"create_list", "update_list", "delete_list",
|
||||||
|
"get_list_config", "set_list_config", "set_task_config", "get_task_config", "get_effective_run_config",
|
||||||
|
"list_runs", "get_run", "get_task_log",
|
||||||
|
"list_agents",
|
||||||
|
"reset_failed_task", "submit_task_for_review",
|
||||||
|
"handoff_list_handler",
|
||||||
|
"get_app_settings",
|
||||||
|
"wait_for_task_change",
|
||||||
|
"get_queue_state",
|
||||||
|
"add_task_attachment", "list_task_attachments", "remove_task_attachment",
|
||||||
|
"save_finding",
|
||||||
|
"add_note",
|
||||||
|
];
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExternalToolNames_MatchExpectedInventory()
|
||||||
|
{
|
||||||
|
// A throwaway, never-constructed instance per declaring type — McpServerTool.Create needs a
|
||||||
|
// non-null target reference to bind the method to, but naming/schema derivation never
|
||||||
|
// touches instance state, so skipping the real constructor (and its DI dependencies) is safe.
|
||||||
|
var targets = new Dictionary<Type, object>();
|
||||||
|
var actual = ExternalToolMethods()
|
||||||
|
.Select(m =>
|
||||||
|
{
|
||||||
|
var target = targets.TryGetValue(m.DeclaringType!, out var t)
|
||||||
|
? t
|
||||||
|
: targets[m.DeclaringType!] = System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(m.DeclaringType!);
|
||||||
|
return McpServerTool.Create(m, target).ProtocolTool.Name;
|
||||||
|
})
|
||||||
|
.OrderBy(n => n, StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var expected = ExpectedToolNames.OrderBy(n => n, StringComparer.Ordinal).ToList();
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user