Files
dingtalk-workspace-cli/internal/helpers/devapp_connect_coverage_more_test.go
T

374 lines
15 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/spf13/cobra"
)
type connectCoverageRunner struct {
response map[string]any
err error
}
func (r connectCoverageRunner) Run(_ context.Context, inv executor.Invocation) (executor.Result, error) {
return executor.Result{Invocation: inv, Response: r.response}, r.err
}
func connectCoverageCommand(t *testing.T) (*cobra.Command, *bytes.Buffer) {
t.Helper()
cmd := newDevAppRobotConnectCommand(&captureRunner{})
buf := &bytes.Buffer{}
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetContext(context.Background())
return cmd, buf
}
func writeKnowledgeFile(t *testing.T, dir, name string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte("# handbook\ncovered knowledge"), 0o600); err != nil {
t.Fatal(err)
}
}
func TestCrossPlatformCoverageBuildConnectPlanQoderAndOpencodeCoverage(t *testing.T) {
for _, ch := range []string{"qoder", "qoderwork", "opencode", "gemini"} {
if got := buildConnectPlan(ch, "id", "robot")["method"]; got == "unknown" {
t.Fatalf("buildConnectPlan(%q) = unknown", ch)
}
}
}
func TestCrossPlatformCoverageLaunchConnectorExternalAndGuidanceCoverage(t *testing.T) {
cmd, out := connectCoverageCommand(t)
binDir := t.TempDir()
writeShellExecutable(t, binDir, "connector-ok", "test \"$CID:$SEC:$DWS_AGENT_CHANNEL\" = \"id:secret:hermes\"\n")
writeShellExecutable(t, binDir, "connector-fail", "exit 7\n")
t.Setenv("DWS_CONNECT_CMD", filepath.Join(binDir, "connector-ok"))
if err := launchConnector(cmd, &captureRunner{}, "hermes", "id", "secret", connectAgentOptions{}); err != nil {
t.Fatalf("external connector: %v\n%s", err, out.String())
}
t.Setenv("DWS_CONNECT_CMD", filepath.Join(binDir, "connector-fail"))
if err := launchConnector(cmd, &captureRunner{}, "hermes", "id", "secret", connectAgentOptions{}); err == nil {
t.Fatal("failing external connector returned nil")
}
t.Setenv("DWS_CONNECT_CMD", "")
if err := launchConnector(cmd, &captureRunner{}, "hermes", "id", "secret", connectAgentOptions{}); err != nil {
t.Fatalf("official guidance: %v", err)
}
if err := launchConnector(cmd, &captureRunner{}, "unknown", "id", "secret", connectAgentOptions{}); err == nil {
t.Fatal("unknown connector returned nil")
}
}
func TestCrossPlatformCoverageLaunchConnectorValidationAndKnowledgeCoverage(t *testing.T) {
t.Setenv("DWS_CONNECT_CMD", "")
t.Setenv("DWS_AGENT_CMD", "sh -c printf\\ ok")
cmd, _ := connectCoverageCommand(t)
badRole := filepath.Join(t.TempDir(), "missing.yaml")
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{RoleConfigPath: badRole}); err == nil {
t.Fatal("missing role returned nil")
}
roleDir := t.TempDir()
rolePath := filepath.Join(roleDir, "role.yaml")
if err := os.WriteFile(rolePath, []byte("name: role\nclient_id: other\nowner_user_id: owner\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{RoleConfigPath: rolePath}); err == nil {
t.Fatal("mismatched role returned nil")
}
t.Setenv("DWS_AGENT_CMD", "")
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{}); err == nil {
t.Fatal("missing custom agent command returned nil")
}
t.Setenv("DWS_AGENT_CMD", "sh -c printf\\ ok")
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{KnowledgeDir: filepath.Join(t.TempDir(), "missing")}); err == nil {
t.Fatal("missing knowledge directory returned nil")
}
knowledge := t.TempDir()
writeKnowledgeFile(t, knowledge, "guide.md")
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{KnowledgeSource: filepath.Join(knowledge, "missing")}); err == nil {
t.Fatal("missing knowledge source returned nil")
}
}
func TestCrossPlatformCoverageLaunchConnectorFullStreamAssemblyCoverage(t *testing.T) {
t.Setenv("DWS_CONNECT_CMD", "")
t.Setenv("DWS_AGENT_CMD", "sh -c printf\\ ok")
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
knowledgeA := t.TempDir()
knowledgeB := t.TempDir()
writeKnowledgeFile(t, knowledgeA, "a.md")
writeKnowledgeFile(t, knowledgeB, "b.txt")
rolePath := filepath.Join(t.TempDir(), "role.yaml")
role := fmt.Sprintf("name: helper\nclient_id: id\nowner_user_id: owner\nconfirm_policy: auto\npersona: precise\nknowledge_sources:\n - %s\n - %s\nallowed_scopes:\n - todo\n", knowledgeA, knowledgeB)
if err := os.WriteFile(rolePath, []byte(role), 0o600); err != nil {
t.Fatal(err)
}
origRun := devAppRunStreamConnector
origLoad := devAppLoadConnectKnowledge
t.Cleanup(func() {
devAppRunStreamConnector = origRun
devAppLoadConnectKnowledge = origLoad
})
var calls int
devAppLoadConnectKnowledge = func(cmd *cobra.Command, runner executor.Runner, clientID, raw string) (*knowledgeBase, error) {
calls++
switch raw {
case "nil-source":
return nil, nil
case "error-source":
return nil, errors.New("source failed")
default:
return loadConnectKnowledgeSource(cmd, runner, clientID, raw)
}
}
var gotExtras *connectExtras
var gotCard *aiCardClient
devAppRunStreamConnector = func(_ context.Context, channel, clientID, clientSecret string, fwd forwarder, card *aiCardClient, extras *connectExtras) error {
if channel != "custom" || clientID != "id" || clientSecret != "secret" || fwd == nil {
return fmt.Errorf("unexpected stream arguments")
}
gotExtras, gotCard = extras, card
return nil
}
cmd, out := connectCoverageCommand(t)
opts := connectAgentOptions{
RoleConfigPath: rolePath, ReplyCard: true, CardTemplate: "card-template",
KnowledgeDir: knowledgeA, KnowledgeSource: knowledgeB,
KnowledgeSources: []string{"nil-source", knowledgeA, knowledgeB},
AllowedUsers: []string{"u1"}, AllowedGroups: []string{"g1"}, UserRateLimit: 3,
OwnerUserID: "owner", ApprovalCardTemplate: "approval-template",
AuditSheetNode: "node", AuditSheetTab: "tab", RoleScopes: []string{"todo"}, ConfirmPolicy: "auto",
}
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", opts); err != nil {
t.Fatalf("launchConnector: %v\n%s", err, out.String())
}
if calls < 5 || gotExtras == nil || gotExtras.kb == nil || gotExtras.gate == nil || gotExtras.approval == nil || gotCard == nil {
t.Fatalf("assembled connector incomplete: calls=%d extras=%+v card=%v", calls, gotExtras, gotCard)
}
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{
KnowledgeSources: []string{knowledgeA},
}); err != nil {
t.Fatalf("first role knowledge source: %v", err)
}
// No card template selects the text-approval path and also covers the
// card-enabled/plain-template reply style. A cancelled context stops retry.
ctx, cancel := context.WithCancel(context.Background())
cmd.SetContext(ctx)
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{
ReplyCard: true, OwnerUserID: "owner", WorkDir: t.TempDir(),
}); err != nil {
t.Fatal(err)
}
cancel()
if err := launchConnector(cmd, &captureRunner{}, "custom", "id", "secret", connectAgentOptions{
KnowledgeSources: []string{"error-source"},
}); err == nil {
t.Fatal("role knowledge error returned nil")
}
}
func TestCrossPlatformCoverageConnectAgentOptionRemainingCoverage(t *testing.T) {
clearChannelEnv(t)
t.Setenv("DWS_REPLY_CARD", "false")
t.Setenv("DWS_AGENT_MODEL", "model")
t.Setenv("DWS_AGENT_WORKDIR", "/work")
t.Setenv("DWS_CARD_TEMPLATE", "public")
t.Setenv("DWS_KNOWLEDGE_DIR", "/knowledge")
t.Setenv("DWS_KNOWLEDGE_SOURCE", "wiki:space")
t.Setenv("DWS_ALLOWED_USERS", "u1,u2")
t.Setenv("DWS_ALLOWED_GROUPS", "g1")
t.Setenv("DWS_USER_RATE_LIMIT", "7")
t.Setenv("DWS_OWNER_USER_ID", "owner")
t.Setenv("DWS_APPROVAL_CARD_TEMPLATE", "approval")
t.Setenv("DWS_ROLE_CONFIG", "role.yaml")
t.Setenv("DWS_AUDIT_SHEET", "node")
t.Setenv("DWS_AUDIT_SHEET_TAB", "tab")
cmd := newDevAppRobotConnectCommand(nil)
if err := cmd.Flags().Set("audit-sheet-tab", ""); err != nil {
t.Fatal(err)
}
opts, err := connectAgentOptionsFromCommand(cmd)
if err != nil {
t.Fatal(err)
}
if opts.Model != "model" || opts.WorkDir != "/work" || opts.ReplyCard || opts.CardTemplate != defaultAICardTemplateID || opts.UserRateLimit != 7 || opts.AuditSheetTab != "tab" {
t.Fatalf("env options = %+v", opts)
}
t.Setenv("DWS_AUDIT_SHEET_TAB", "")
opts, err = connectAgentOptionsFromCommand(cmd)
if err != nil || opts.AuditSheetTab != "Sheet1" {
t.Fatalf("default audit tab = %q, err=%v", opts.AuditSheetTab, err)
}
t.Setenv("DWS_USER_RATE_LIMIT", "invalid")
if _, err := connectAgentOptionsFromCommand(cmd); err != nil {
t.Fatalf("invalid rate should retain default: %v", err)
}
cases := []struct {
flag, value, env, envValue string
want bool
wantErr bool
}{
{env: "DWS_AGENT_PERMISSION_MODE", envValue: "ask", want: false},
{env: "DWS_AGENT_PERMISSION_MODE", envValue: "bad", wantErr: true},
{flag: "agent-approval-mode", value: "ask", want: false},
{env: "DWS_AGENT_APPROVAL_MODE", envValue: "ask", want: false},
{env: "DWS_AGENT_APPROVAL_MODE", envValue: "bad", wantErr: true},
}
for i, tc := range cases {
t.Run(fmt.Sprint(i), func(t *testing.T) {
clearChannelEnv(t)
c := newDevAppRobotConnectCommand(nil)
if tc.env != "" {
t.Setenv(tc.env, tc.envValue)
}
if tc.flag != "" {
if err := c.Flags().Set(tc.flag, tc.value); err != nil {
t.Fatal(err)
}
}
got, err := resolveAgentYoloMode(c)
if (err != nil) != tc.wantErr || (!tc.wantErr && got != tc.want) {
t.Fatalf("got (%v,%v), want (%v, err=%v)", got, err, tc.want, tc.wantErr)
}
})
}
}
func TestCrossPlatformCoverageConnectAgentOptionsPayloadRemainingCoverage(t *testing.T) {
cases := []struct {
channel string
memory bool
}{
{"codex", false}, {"opencode", true}, {"opencode", false},
{"claudecode", true}, {"claudecode", false}, {"gemini", true},
}
for _, tc := range cases {
payload := connectAgentOptionsPayload(tc.channel, connectAgentOptions{
Memory: tc.memory, ReplyCard: true, CardTemplate: "template", Yolo: true,
})
if payload["replyStyle"] != "ai-card" || payload["yolo"] != "enabled" {
t.Fatalf("payload(%s,%v) = %#v", tc.channel, tc.memory, payload)
}
}
}
func TestCrossPlatformCoverageDevAppConnectCommandRemainingCoverage(t *testing.T) {
origInteractive := devAppConnectStdinInteractive
origOnboarding := devAppRunConnectOnboarding
origDaemon := devAppStartConnectDaemon
origStream := devAppRunStreamConnector
t.Cleanup(func() {
devAppConnectStdinInteractive = origInteractive
devAppRunConnectOnboarding = origOnboarding
devAppStartConnectDaemon = origDaemon
devAppRunStreamConnector = origStream
})
devAppRunStreamConnector = func(context.Context, string, string, string, forwarder, *aiCardClient, *connectExtras) error {
return nil
}
run := func(runner executor.Runner, args ...string) (string, error) {
root := newDevAppTestRoot(runner)
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetIn(strings.NewReader(""))
root.SetArgs(append([]string{"dev", "connect"}, args...))
return out.String(), root.Execute()
}
clearChannelEnv(t)
if _, err := run(&captureRunner{}, "--dry-run"); err == nil {
t.Fatal("undetected channel returned nil")
}
if _, err := run(&captureRunner{}, "--channel", "custom", "--agent-cmd", "sh -c printf\\ ok", "--robot-client-id", "id", "--robot-client-secret", "secret"); err != nil {
t.Fatalf("agent-cmd launch: %v", err)
}
if _, err := run(&captureRunner{}, "--channel", "custom", "--robot-client-id", "id", "--robot-client-secret", "secret", "--agent-permission-mode", "bad"); err == nil {
t.Fatal("invalid agent options returned nil")
}
boom := errors.New("credentials boom")
if _, err := run(connectCoverageRunner{err: boom}, "--channel", "hermes", "--unified-app-id", "app"); !errors.Is(err, boom) {
t.Fatalf("credential error = %v", err)
}
if _, err := run(connectCoverageRunner{response: map[string]any{}}, "--channel", "hermes", "--unified-app-id", "app"); err == nil {
t.Fatal("empty credentials returned nil")
}
response := map[string]any{"result": map[string]any{"clientId": "id", "clientSecret": "secret"}}
if _, err := run(connectCoverageRunner{response: response}, "--channel", "hermes", "--unified-app-id", "app"); err != nil {
t.Fatalf("resolved credentials launch: %v", err)
}
devAppConnectStdinInteractive = func() bool { return true }
devAppRunConnectOnboarding = func(executor.Runner, *cobra.Command, io.Reader, io.Writer) (connectCreds, error) {
return connectCreds{}, boom
}
if _, err := run(&captureRunner{}, "--channel", "hermes"); !errors.Is(err, boom) {
t.Fatalf("onboarding error = %v", err)
}
devAppRunConnectOnboarding = func(executor.Runner, *cobra.Command, io.Reader, io.Writer) (connectCreds, error) {
return connectCreds{clientID: "id", clientSecret: "secret", source: "onboarding"}, nil
}
if _, err := run(&captureRunner{}, "--channel", "hermes"); err != nil {
t.Fatalf("onboarding success: %v", err)
}
var daemonCalled bool
devAppStartConnectDaemon = func(_ *cobra.Command, dirKey, clientID, unifiedAppID, channel, notifyStaffID, profile string, alwaysOn bool) error {
daemonCalled = dirKey != "" && clientID == "id" && channel == "hermes" && notifyStaffID == "staff" && alwaysOn
return boom
}
if _, err := run(&captureRunner{}, "--channel", "hermes", "--robot-client-id", "id", "--robot-client-secret", "secret", "--daemon", "--alwayson", "--notify-staff-id", "staff"); !errors.Is(err, boom) || !daemonCalled {
t.Fatalf("daemon result err=%v called=%v", err, daemonCalled)
}
clearChannelEnv(t)
if _, err := run(&captureRunner{}, "--daemon-supervise"); err == nil || !strings.Contains(err.Error(), "DIRKEY") {
t.Fatalf("supervisor error = %v", err)
}
}
func TestCrossPlatformCoverageDevAppFetchCredentialsErrorCoverage(t *testing.T) {
cmd, _ := connectCoverageCommand(t)
want := errors.New("runner failure")
if _, _, err := devAppFetchCredentials(connectCoverageRunner{err: want}, cmd, "app"); !errors.Is(err, want) {
t.Fatalf("devAppFetchCredentials error = %v", err)
}
}