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:
1 parent
70d58648c8
commit
c0b562cc07
2 files changed
+75
-4
No files matched your search
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user