374 lines
15 KiB
Go
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)
|
|
}
|
|
}
|