386 lines
12 KiB
Go
386 lines
12 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 app
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestRootHelpHidesCompatibilityOnlyCommands(t *testing.T) {
|
|
cmd := NewRootCommand()
|
|
var out bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&out)
|
|
cmd.SetArgs([]string{"--help"})
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("root help: %v\n%s", err, out.String())
|
|
}
|
|
help := out.String()
|
|
if strings.Contains(help, "● conference") {
|
|
t.Fatalf("root help should hide conference compatibility command:\n%s", help)
|
|
}
|
|
for _, want := range []string{
|
|
"● dev",
|
|
"• upgrade",
|
|
} {
|
|
if !strings.Contains(help, want) {
|
|
t.Fatalf("root help missing %q:\n%s", want, help)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCalendarEventCreateHelpKeepsRoomsStringMetavar(t *testing.T) {
|
|
cmd := NewRootCommand()
|
|
var out bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&out)
|
|
cmd.SetArgs([]string{"calendar", "event", "create", "--help"})
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("calendar event create --help: %v\n%s", err, out.String())
|
|
}
|
|
|
|
help := out.String()
|
|
if !strings.Contains(help, "--rooms string") {
|
|
t.Fatalf("calendar event create help missing string metavar for --rooms:\n%s", help)
|
|
}
|
|
if strings.Contains(help, "--rooms room search") {
|
|
t.Fatalf("calendar event create help treated description text as --rooms metavar:\n%s", help)
|
|
}
|
|
}
|
|
|
|
func TestRootKeepsMainBranchChatCompatibilityCommands(t *testing.T) {
|
|
root := NewRootCommand()
|
|
listDirect := mustFindCommand(t, root, "chat", "message", "list-direct")
|
|
for _, flag := range []string{"user", "open-dingtalk-id", "time", "forward", "limit"} {
|
|
if listDirect.Flags().Lookup(flag) == nil {
|
|
t.Fatalf("chat message list-direct missing --%s", flag)
|
|
}
|
|
}
|
|
|
|
mediaUpload := mustFindCommand(t, root, "chat", "media", "upload")
|
|
for _, flag := range []string{"file", "type"} {
|
|
if mediaUpload.Flags().Lookup(flag) == nil {
|
|
t.Fatalf("chat media upload missing --%s", flag)
|
|
}
|
|
}
|
|
|
|
mustFindCommand(t, root, "contact", "get")
|
|
mustFindCommand(t, root, "contact", "search")
|
|
mustFindCommand(t, root, "contact", "user", "list")
|
|
mustFindCommand(t, root, "conference", "meeting", "reserve")
|
|
}
|
|
|
|
func TestRootKeepsContactWukongCompatibilityCommands(t *testing.T) {
|
|
root := NewRootCommand()
|
|
label := mustFindCommand(t, root, "contact", "label")
|
|
if label.Hidden {
|
|
t.Fatal("contact label should be visible as a real command group")
|
|
}
|
|
if !containsString(label.Aliases, "role") {
|
|
t.Fatal("contact label missing role alias")
|
|
}
|
|
mustFindCommand(t, root, "contact", "label", "get")
|
|
mustFindCommand(t, root, "contact", "label", "list")
|
|
mustFindCommand(t, root, "contact", "label", "list-members")
|
|
mustFindCommand(t, root, "contact", "label", "find")
|
|
mustFindCommand(t, root, "contact", "label", "search")
|
|
mustFindCommand(t, root, "contact", "label", "info")
|
|
mustFindCommand(t, root, "contact", "label", "detail")
|
|
mustFindCommand(t, root, "contact", "label", "list-all")
|
|
|
|
getSelf := mustFindCommand(t, root, "contact", "user", "get-self")
|
|
for _, alias := range []string{"self", "me", "whoami", "current"} {
|
|
if !containsString(getSelf.Aliases, alias) {
|
|
t.Fatalf("contact user get-self missing alias %q", alias)
|
|
}
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
args []string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "label list",
|
|
args: []string{"--dry-run", "contact", "label", "list"},
|
|
want: []string{"get_org_labels"},
|
|
},
|
|
{
|
|
name: "label get",
|
|
args: []string{"--dry-run", "contact", "label", "get", "--names", "admin,finance"},
|
|
want: []string{"search_label_by_name", "labelNames", "admin", "finance"},
|
|
},
|
|
{
|
|
name: "label members",
|
|
args: []string{"--dry-run", "contact", "label", "list-members", "--id", "123"},
|
|
want: []string{"get_label_members_by_labelId", "labelId", "123"},
|
|
},
|
|
{
|
|
name: "role shim",
|
|
args: []string{"--dry-run", "contact", "role", "list"},
|
|
want: []string{"get_org_labels"},
|
|
},
|
|
{
|
|
name: "label fuzzy shim",
|
|
args: []string{"--dry-run", "contact", "label", "find", "--names", "admin"},
|
|
want: []string{"search_label_by_name", "labelNames", "admin"},
|
|
},
|
|
{
|
|
name: "label detail shim",
|
|
args: []string{"--dry-run", "contact", "label", "detail", "--id", "123"},
|
|
want: []string{"get_label_members_by_labelId", "labelId", "123"},
|
|
},
|
|
{
|
|
name: "contact search shim",
|
|
args: []string{"--dry-run", "contact", "search", "--query", "admin"},
|
|
want: []string{"search_contact_by_key_word", "keyword", "admin"},
|
|
},
|
|
{
|
|
name: "contact find shim",
|
|
args: []string{"--dry-run", "contact", "find", "--query", "admin"},
|
|
want: []string{"search_contact_by_key_word", "keyword", "admin"},
|
|
},
|
|
{
|
|
name: "contact list defaults to label list",
|
|
args: []string{"--dry-run", "contact", "list"},
|
|
want: []string{"get_org_labels"},
|
|
},
|
|
{
|
|
name: "contact list department members",
|
|
args: []string{"--dry-run", "contact", "list", "--depts", "1"},
|
|
want: []string{"get_dept_members_by_deptId", "deptIds", "1"},
|
|
},
|
|
{
|
|
name: "contact get user details",
|
|
args: []string{"--dry-run", "contact", "get", "--ids", "user1"},
|
|
want: []string{"get_user_info_by_user_ids", "user_id_list", "user1"},
|
|
},
|
|
{
|
|
name: "contact get label by name",
|
|
args: []string{"--dry-run", "contact", "get", "--names", "admin"},
|
|
want: []string{"search_label_by_name", "labelNames", "admin"},
|
|
},
|
|
{
|
|
name: "contact self shim",
|
|
args: []string{"--dry-run", "contact", "self"},
|
|
want: []string{"get_current_user_profile"},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := executeRootCaptureStdout(t, tc.args)
|
|
if err != nil {
|
|
t.Fatalf("Execute(%v) error = %v\n%s", tc.args, err, got)
|
|
}
|
|
for _, want := range tc.want {
|
|
if !strings.Contains(got, want) {
|
|
t.Fatalf("Execute(%v) output missing %q:\n%s", tc.args, want, got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestChatFileUploadDownlinedButMessageFileSendStays(t *testing.T) {
|
|
root := NewRootCommand()
|
|
fileCmd := mustFindCommand(t, root, "chat", "file")
|
|
if !fileCmd.Hidden {
|
|
t.Fatal("chat file should be hidden after upload_conversation_file_by_url downline")
|
|
}
|
|
upload := mustFindCommand(t, root, "chat", "file", "upload")
|
|
if !upload.Hidden {
|
|
t.Fatal("chat file upload should be hidden after downline")
|
|
}
|
|
for _, flag := range []string{"group", "url", "file", "file-name"} {
|
|
if upload.Flags().Lookup(flag) == nil {
|
|
t.Fatalf("chat file upload missing compatibility flag --%s", flag)
|
|
}
|
|
}
|
|
|
|
send := mustFindCommand(t, root, "chat", "message", "send")
|
|
for _, flag := range []string{"msg-type", "file-path"} {
|
|
if send.Flags().Lookup(flag) == nil {
|
|
t.Fatalf("chat message send missing --%s", flag)
|
|
}
|
|
}
|
|
|
|
got, err := executeRootCaptureStdout(t, []string{
|
|
"chat", "file", "upload",
|
|
"--group", "cid",
|
|
"--url", "https://example.com/report.pdf",
|
|
"--file-name", "report.pdf",
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("chat file upload error = nil, want downline error\n%s", got)
|
|
}
|
|
got = got + "\n" + err.Error()
|
|
for _, want := range []string{"已下线", "upload_conversation_file_by_url", "chat message send --msg-type file --file-path"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Fatalf("chat file upload output missing %q:\n%s", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCalendarEventListDryRunPreviewsOnly(t *testing.T) {
|
|
got, err := executeRootCaptureStdout(t, []string{
|
|
"--dry-run", "calendar", "event", "list",
|
|
"--start", "2026-07-07T00:00:00+08:00",
|
|
"--end", "2026-07-07T01:00:00+08:00",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("calendar event list --dry-run error = %v\n%s", err, got)
|
|
}
|
|
for _, want := range []string{"list_calendar_events", "startTime", "endTime"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Fatalf("calendar dry-run output missing %q:\n%s", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRootKeepsSVIPChatCompatibilityFlags(t *testing.T) {
|
|
root := NewRootCommand()
|
|
|
|
listBySender := mustFindCommand(t, root, "chat", "message", "list-by-sender")
|
|
if listBySender.Flags().Lookup("sender") == nil {
|
|
t.Fatal("chat message list-by-sender missing hidden --sender alias")
|
|
}
|
|
|
|
searchAdvanced := mustFindCommand(t, root, "chat", "message", "search-advanced")
|
|
for _, flag := range []string{"sender", "senders", "sender-ids"} {
|
|
if searchAdvanced.Flags().Lookup(flag) == nil {
|
|
t.Fatalf("chat message search-advanced missing --%s", flag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCacheRefreshCompatibilityStub(t *testing.T) {
|
|
cmd := NewRootCommand()
|
|
var out bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&out)
|
|
cmd.SetArgs([]string{"cache", "refresh", "--format", "json"})
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("cache refresh compatibility stub: %v\n%s", err, out.String())
|
|
}
|
|
got := out.String()
|
|
for _, want := range []string{`"status":"deprecated"`, `"command":"dws cache refresh"`, "服务发现已下线"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Fatalf("cache refresh output missing %q:\n%s", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInjectStaticServersMergesStaticAndSupplementServers(t *testing.T) {
|
|
previous := edition.Get()
|
|
defer edition.Override(previous)
|
|
defer SetDynamicServers(nil)
|
|
|
|
edition.Override(&edition.Hooks{
|
|
Name: "test",
|
|
StaticServers: func() []edition.ServerInfo {
|
|
return []edition.ServerInfo{{
|
|
ID: "static-test",
|
|
Name: "Static Test",
|
|
Endpoint: "https://static.example/server/static-test",
|
|
Prefixes: []string{"static-alias"},
|
|
}}
|
|
},
|
|
SupplementServers: func() []edition.ServerInfo {
|
|
return []edition.ServerInfo{{
|
|
ID: "supplement-test",
|
|
Name: "Supplement Test",
|
|
Endpoint: "https://supplement.example/server/supplement-test",
|
|
Prefixes: []string{"supplement-alias"},
|
|
}}
|
|
},
|
|
})
|
|
|
|
injectStaticServers()
|
|
|
|
for _, tc := range []struct {
|
|
productID string
|
|
endpoint string
|
|
}{
|
|
{"static-test", "https://static.example/server/static-test"},
|
|
{"static-alias", "https://static.example/server/static-test"},
|
|
{"supplement-test", "https://supplement.example/server/supplement-test"},
|
|
{"supplement-alias", "https://supplement.example/server/supplement-test"},
|
|
} {
|
|
got, ok := directRuntimeEndpoint(tc.productID, "")
|
|
if !ok || got != tc.endpoint {
|
|
t.Fatalf("directRuntimeEndpoint(%q) = %q, %v; want %q, true", tc.productID, got, ok, tc.endpoint)
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustFindCommand(t *testing.T, root *cobra.Command, path ...string) *cobra.Command {
|
|
t.Helper()
|
|
cmd := root
|
|
for _, name := range path {
|
|
var next *cobra.Command
|
|
for _, child := range cmd.Commands() {
|
|
if child.Name() == name {
|
|
next = child
|
|
break
|
|
}
|
|
}
|
|
if next == nil {
|
|
t.Fatalf("missing command path %q under %q", strings.Join(path, " "), cmd.CommandPath())
|
|
}
|
|
cmd = next
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func containsString(values []string, want string) bool {
|
|
for _, value := range values {
|
|
if value == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func executeRootCaptureStdout(t *testing.T, args []string) (string, error) {
|
|
t.Helper()
|
|
|
|
oldStdout := os.Stdout
|
|
readPipe, writePipe, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("os.Pipe error = %v", err)
|
|
}
|
|
os.Stdout = writePipe
|
|
|
|
cmd := NewRootCommand()
|
|
var out bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&out)
|
|
cmd.SetArgs(args)
|
|
execErr := cmd.Execute()
|
|
|
|
_ = writePipe.Close()
|
|
os.Stdout = oldStdout
|
|
captured, readErr := io.ReadAll(readPipe)
|
|
if readErr != nil {
|
|
t.Fatalf("read stdout pipe error = %v", readErr)
|
|
}
|
|
return out.String() + string(captured), execErr
|
|
}
|