fix(chat): add package-level MCP call seam for toolbar remove-custom

- introduce removeChatToolbarCustomShortcutFn in toolbar_remove_custom.go
  as a package-level injection seam; default impl routes through
  callMCPToolOnServer against the im server so production behavior is
  unchanged
- update RunE to dispatch via the seam instead of calling
  callMCPToolOnServer inline
- add two TestCrossPlatformCoverage* tests that swap the seam via
  testseam.Swap and verify: (1) without --yes the seam is never called
  and a typed confirmation_required error is returned, (2) with --yes
  the seam is called exactly once with openCid and shortcutId. The
  stub forwards to deps.Caller.CallTool so the user_required contract
  gate (leaf.go) still sees the CallTool channel.
This commit is contained in:
xlb1130 committed 2026-08-06 11:20:52 +08:00
1 parent 70d58648c8
commit c0b562cc07
2 files changed
+75 -4

No files matched your search

+61
View File
@@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
)
@@ -349,3 +350,63 @@ func TestCrossPlatformCoverageToolbarRemoveCustomCallsMCPWithExactArgsWhenYes(t
t.Fatalf("tool args = %#v, want %#v", call.args, wantArgs)
}
}
type removeCustomSeamCall struct {
openCid string
shortcutId int64
}
type removeCustomSeamStub struct {
calls []removeCustomSeamCall
err error
}
// invoke records the seam call and forwards to deps.Caller.CallTool so the
// user_required contract gate (leaf.go) is satisfied. A pure short-circuit
// would skip the CallTool channel and trigger
// "user_required confirmation was never obtained via CallTool".
func (s *removeCustomSeamStub) invoke(openCid string, shortcutId int64) error {
s.calls = append(s.calls, removeCustomSeamCall{openCid: openCid, shortcutId: shortcutId})
caller := GetCaller()
if caller == nil {
return s.err
}
_, err := caller.CallTool(context.Background(), "im", "remove_chat_toolbar_custom_shortcut", map[string]any{
"openCid": openCid,
"shortcutId": shortcutId,
})
return err
}
func TestCrossPlatformCoverageToolbarRemoveCustomSeamRejectsWithoutYes(t *testing.T) {
stub := &removeCustomSeamStub{}
testseam.Swap(t, &removeChatToolbarCustomShortcutFn, stub.invoke)
err := executeToolbarCommand(t, newToolbarRemoveCustomCommand(), &toolbarTestCaller{},
"--conversation-id", "cidX", "--shortcut-id", "42")
requireTypedConfirmationError(t, err)
if len(stub.calls) != 0 {
t.Fatalf("expected 0 seam calls before --yes, got %d: %+v", len(stub.calls), stub.calls)
}
}
func TestCrossPlatformCoverageToolbarRemoveCustomSeamCallsWithExactArgsWhenYes(t *testing.T) {
stub := &removeCustomSeamStub{}
testseam.Swap(t, &removeChatToolbarCustomShortcutFn, stub.invoke)
err := executeToolbarCommand(t, newToolbarRemoveCustomCommand(), &toolbarTestCaller{},
"--conversation-id", "cidX", "--shortcut-id", "42", "--yes")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(stub.calls) != 1 {
t.Fatalf("expected exactly 1 seam call, got %d: %+v", len(stub.calls), stub.calls)
}
call := stub.calls[0]
if call.openCid != "cidX" {
t.Fatalf("seam openCid = %q, want cidX", call.openCid)
}
if call.shortcutId != int64(42) {
t.Fatalf("seam shortcutId = %d, want 42", call.shortcutId)
}
}
+14 -4
View File
@@ -20,6 +20,19 @@ import (
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
)
// removeChatToolbarCustomShortcutFn is the package-level injection seam for
// the remove-custom command's remote call. The default implementation routes
// through callMCPToolOnServer against the im server, so production behavior is
// unchanged. Tests swap it with testseam.Swap(t, &removeChatToolbarCustomShortcutFn, stub)
// to capture the call without hitting a real remote. Like every package-var
// seam in this repo it is not safe for t.Parallel tests.
var removeChatToolbarCustomShortcutFn = func(openCid string, shortcutId int64) error {
return callMCPToolOnServer("im", "remove_chat_toolbar_custom_shortcut", map[string]any{
"openCid": openCid,
"shortcutId": shortcutId,
})
}
func newToolbarRemoveCustomCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "remove-custom",
@@ -40,10 +53,7 @@ func newToolbarRemoveCustomCommand() *cobra.Command {
apperrors.WithActions("确认目标自定义入口", "获得用户确认后使用 --yes 执行"),
)
}
err = callMCPToolOnServer("im", "remove_chat_toolbar_custom_shortcut", map[string]any{
"openCid": cid,
"shortcutId": shortcutId,
})
err = removeChatToolbarCustomShortcutFn(cid, shortcutId)
if isSystemBusy(err) {
return toolbarNewSystemBusyError()
}