399 lines
10 KiB
Go
399 lines
10 KiB
Go
package output
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestWriteJSON_Map(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
err := WriteJSON(&buf, map[string]any{"key": "value"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), `"key"`) {
|
|
t.Fatalf("missing key in output: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteJSON_DoesNotEscapeAmpersands(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
url := "https://open-dev.dingtalk.com/fe/old#/personalAuthorization?flowId=flow&userCode=CODE"
|
|
if err := WriteJSON(&buf, map[string]any{"authorizationUrl": url}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if strings.Contains(buf.String(), `\u0026`) {
|
|
t.Fatalf("WriteJSON escaped ampersand: %s", buf.String())
|
|
}
|
|
if !strings.Contains(buf.String(), "&userCode=CODE") {
|
|
t.Fatalf("WriteJSON output missing literal ampersand: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteJSON_Nil(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := WriteJSON(&buf, nil); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), "null") {
|
|
t.Fatalf("expected null, got: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteJSON_Slice(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := WriteJSON(&buf, []string{"a", "b"}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), `"a"`) {
|
|
t.Fatalf("missing element: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteJSON_Unmarshalable(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
err := WriteJSON(&buf, make(chan int))
|
|
if err == nil {
|
|
t.Fatal("expected error for unmarshalable type")
|
|
}
|
|
}
|
|
|
|
func TestWrite_AllFormats(t *testing.T) {
|
|
t.Parallel()
|
|
payload := map[string]any{"name": "test"}
|
|
|
|
tests := []struct {
|
|
name string
|
|
format Format
|
|
}{
|
|
{"json", FormatJSON},
|
|
{"raw", FormatRaw},
|
|
{"table", FormatTable},
|
|
{"unknown_defaults_to_json", Format("unknown")},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
if err := Write(&buf, tt.format, payload); err != nil {
|
|
t.Fatalf("Write(%s) error: %v", tt.format, err)
|
|
}
|
|
if buf.Len() == 0 {
|
|
t.Fatalf("Write(%s) produced empty output", tt.format)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWriteCommandPayload_NilCmd(t *testing.T) {
|
|
t.Parallel()
|
|
err := WriteCommandPayload(nil, map[string]any{"a": 1}, FormatJSON)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriteCommandPayload_WithCmd(t *testing.T) {
|
|
t.Parallel()
|
|
cmd := &cobra.Command{Use: "test"}
|
|
var buf bytes.Buffer
|
|
cmd.SetOut(&buf)
|
|
err := WriteCommandPayload(cmd, map[string]any{"a": 1}, FormatJSON)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), `"a"`) {
|
|
t.Fatalf("missing key: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestResolveFormat_NilCmd(t *testing.T) {
|
|
t.Parallel()
|
|
f := ResolveFormat(nil, FormatJSON)
|
|
if f != FormatJSON {
|
|
t.Fatalf("expected json, got %s", f)
|
|
}
|
|
}
|
|
|
|
func TestResolveFormat_WithFlag(t *testing.T) {
|
|
t.Parallel()
|
|
cmd := &cobra.Command{Use: "test"}
|
|
cmd.Flags().String("format", "table", "output format")
|
|
f := ResolveFormat(cmd, FormatJSON)
|
|
if f != FormatTable {
|
|
t.Fatalf("expected table, got %s", f)
|
|
}
|
|
}
|
|
|
|
func TestResolveFormat_NoFlag(t *testing.T) {
|
|
t.Parallel()
|
|
cmd := &cobra.Command{Use: "test"}
|
|
f := ResolveFormat(cmd, FormatRaw)
|
|
if f != FormatRaw {
|
|
t.Fatalf("expected raw, got %s", f)
|
|
}
|
|
}
|
|
|
|
func TestWriteRaw_String(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := writeRaw(&buf, "hello world"); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), "hello world") {
|
|
t.Fatalf("missing text: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteRaw_NonString(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := writeRaw(&buf, map[string]any{"x": 1}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), `"x"`) {
|
|
t.Fatalf("missing key: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteRaw_DoesNotEscapeAmpersands(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := writeRaw(&buf, map[string]any{"url": "https://example.test/auth?a=1&b=2"}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if strings.Contains(buf.String(), `\u0026`) {
|
|
t.Fatalf("writeRaw escaped ampersand: %s", buf.String())
|
|
}
|
|
if !strings.Contains(buf.String(), "a=1&b=2") {
|
|
t.Fatalf("writeRaw output missing literal ampersand: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteTableish_Map(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
err := writeTableish(&buf, map[string]any{"name": "alice", "age": 30})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), "name") {
|
|
t.Fatalf("missing key: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteTableish_SliceOfMaps(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
payload := []any{
|
|
map[string]any{"name": "alice", "age": float64(30)},
|
|
map[string]any{"name": "bob", "age": float64(25)},
|
|
}
|
|
if err := writeTableish(&buf, payload); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
if !strings.Contains(out, "name") || !strings.Contains(out, "alice") {
|
|
t.Fatalf("missing table content: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestWriteTableish_SliceOfScalars(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := writeTableish(&buf, []any{"a", "b", "c"}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), "value") {
|
|
t.Fatalf("missing header: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteTableish_EmptySlice(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := writeTableish(&buf, []any{}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriteTableish_MapWithList(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
payload := map[string]any{
|
|
"items": []any{
|
|
map[string]any{"id": float64(1), "name": "x"},
|
|
},
|
|
"total": float64(1),
|
|
}
|
|
if err := writeTableish(&buf, payload); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
if !strings.Contains(out, "id") || !strings.Contains(out, "total") {
|
|
t.Fatalf("missing table/meta content: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestWriteTableish_Nil(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
if err := writeTableish(&buf, nil); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriteTableish_PrimaryObject(t *testing.T) {
|
|
t.Parallel()
|
|
var buf bytes.Buffer
|
|
payload := map[string]any{
|
|
"data": map[string]any{"key": "val"},
|
|
}
|
|
if err := writeTableish(&buf, payload); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), "key") {
|
|
t.Fatalf("missing key: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestUnwrapCompatRuntimePayload_NonResult(t *testing.T) {
|
|
t.Parallel()
|
|
val := "hello"
|
|
if got := unwrapCompatRuntimePayload(val); got != val {
|
|
t.Fatalf("expected passthrough, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestUnwrapCompatRuntimePayload_NotImplemented(t *testing.T) {
|
|
t.Parallel()
|
|
r := executor.Result{Invocation: executor.Invocation{Implemented: false}}
|
|
if got := unwrapCompatRuntimePayload(r); !reflect.DeepEqual(got, r) {
|
|
t.Fatal("expected passthrough for non-implemented")
|
|
}
|
|
}
|
|
|
|
func TestUnwrapCompatRuntimePayload_CompatInvocation(t *testing.T) {
|
|
t.Parallel()
|
|
content := map[string]any{"msg": "hi"}
|
|
r := executor.Result{
|
|
Invocation: executor.Invocation{Implemented: true, Kind: "compat_invocation"},
|
|
Response: map[string]any{"content": content},
|
|
}
|
|
got := unwrapCompatRuntimePayload(r)
|
|
gotMap, ok := got.(map[string]any)
|
|
if !ok || gotMap["msg"] != "hi" {
|
|
t.Fatalf("expected unwrapped content, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestUnwrapCompatRuntimePayload_HelperInvocation(t *testing.T) {
|
|
t.Parallel()
|
|
content := []any{"a"}
|
|
r := executor.Result{
|
|
Invocation: executor.Invocation{Implemented: true, Kind: "helper_invocation"},
|
|
Response: map[string]any{"content": content},
|
|
}
|
|
got := unwrapCompatRuntimePayload(r)
|
|
gotSlice, ok := got.([]any)
|
|
if !ok || len(gotSlice) != 1 {
|
|
t.Fatalf("expected unwrapped content, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestUnwrapCompatRuntimePayload_OtherKind(t *testing.T) {
|
|
t.Parallel()
|
|
r := executor.Result{
|
|
Invocation: executor.Invocation{Implemented: true, Kind: "other"},
|
|
Response: map[string]any{"content": "x"},
|
|
}
|
|
got := unwrapCompatRuntimePayload(r)
|
|
if _, ok := got.(executor.Result); !ok {
|
|
t.Fatal("expected passthrough for unknown kind")
|
|
}
|
|
}
|
|
|
|
func TestNormalizeFormat(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
raw string
|
|
fallback Format
|
|
want Format
|
|
}{
|
|
{"json", FormatTable, FormatJSON},
|
|
{"JSON", FormatTable, FormatJSON},
|
|
{"raw", FormatJSON, FormatRaw},
|
|
{"table", FormatJSON, FormatTable},
|
|
{"", FormatJSON, FormatJSON},
|
|
{"unknown", FormatRaw, FormatRaw},
|
|
}
|
|
for _, tt := range tests {
|
|
got := normalizeFormat(tt.raw, tt.fallback)
|
|
if got != tt.want {
|
|
t.Errorf("normalizeFormat(%q, %s) = %s, want %s", tt.raw, tt.fallback, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatValue(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
input any
|
|
want string
|
|
}{
|
|
{nil, ""},
|
|
{"hello", "hello"},
|
|
{float64(42), "42"},
|
|
{true, "true"},
|
|
{map[string]any{"k": "v"}, `{"k":"v"}`},
|
|
}
|
|
for _, tt := range tests {
|
|
got := formatValue(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("formatValue(%v) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTruncate(t *testing.T) {
|
|
t.Parallel()
|
|
if got := truncate("short", 10); got != "short" {
|
|
t.Errorf("expected short, got %s", got)
|
|
}
|
|
if got := truncate("a very long string indeed", 10); len([]rune(got)) > 10 {
|
|
t.Errorf("expected truncated, got %s (len %d)", got, len([]rune(got)))
|
|
}
|
|
}
|
|
|
|
func TestRuneWidth(t *testing.T) {
|
|
t.Parallel()
|
|
if got := runeWidth("abc"); got != 3 {
|
|
t.Errorf("expected 3, got %d", got)
|
|
}
|
|
// CJK character should count as 2
|
|
if got := runeWidth("中"); got != 2 {
|
|
t.Errorf("expected 2, got %d", got)
|
|
}
|
|
if got := runeWidth("a中b"); got != 4 {
|
|
t.Errorf("expected 4, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestSortedKeys(t *testing.T) {
|
|
t.Parallel()
|
|
keys := map[string]struct{}{"c": {}, "a": {}, "b": {}}
|
|
got := sortedKeys(keys)
|
|
if len(got) != 3 || got[0] != "a" || got[1] != "b" || got[2] != "c" {
|
|
t.Fatalf("expected [a b c], got %v", got)
|
|
}
|
|
}
|