- Add testseam.Protect for save-and-restore seams with no up-front stub value (e.g. os.Args mutated by the code under test). - Migrate all 35 remaining manual prev/assign/t.Cleanup-restore trios to testseam.Swap/Protect across app, auth, cli, event, helpers, output, pipeline, and shortcut tests; restores can no longer be forgotten. - check-schema-catalog.sh: fail closed when a manual seam restore reappears in any *_test.go (internal/testseam exempt). Seam injection is now a mechanism, not a convention.
221 lines
6.0 KiB
Go
221 lines
6.0 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 output
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
|
|
"github.com/fatih/color"
|
|
)
|
|
|
|
// forceNoColor disables ANSI codes for assertion simplicity. fatih/color
|
|
// would already do this when writing to a non-TTY, but tests use bytes.Buffer
|
|
// so we flip the flag explicitly.
|
|
func forceNoColor(t *testing.T) {
|
|
t.Helper()
|
|
testseam.Swap(t, &color.NoColor, true)
|
|
}
|
|
|
|
func TestPretty_SchemaListRendersProducts(t *testing.T) {
|
|
forceNoColor(t)
|
|
|
|
payload := map[string]any{
|
|
"kind": "schema",
|
|
"count": 2,
|
|
"products": []any{
|
|
map[string]any{
|
|
"id": "ding",
|
|
"name": "DING消息",
|
|
"description": "DING 消息 / 发送 / 撤回",
|
|
"tools": []any{
|
|
map[string]any{"name": "send_ding_message", "cli_name": "send"},
|
|
map[string]any{"name": "recall_ding_message", "cli_name": "recall"},
|
|
},
|
|
},
|
|
map[string]any{
|
|
"id": "doc",
|
|
"name": "钉钉文档",
|
|
"tools": []any{map[string]any{"name": "create_document", "cli_name": "create"}},
|
|
},
|
|
},
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := writePretty(&buf, payload); err != nil {
|
|
t.Fatalf("writePretty() error = %v", err)
|
|
}
|
|
out := buf.String()
|
|
|
|
wants := []string{
|
|
"Catalog",
|
|
"2 products discovered",
|
|
"ding",
|
|
"send_ding_message",
|
|
"→ send",
|
|
"recall_ding_message",
|
|
"doc",
|
|
"create_document",
|
|
}
|
|
for _, want := range wants {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("pretty list missing %q; got:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPretty_SchemaToolRendersAllSections(t *testing.T) {
|
|
forceNoColor(t)
|
|
|
|
payload := map[string]any{
|
|
"kind": "schema",
|
|
"path": "ding.send_ding_message",
|
|
"product": map[string]any{
|
|
"id": "ding",
|
|
"name": "DING消息",
|
|
},
|
|
"tool": map[string]any{
|
|
"name": "send_ding_message",
|
|
"cli_name": "send",
|
|
"canonical_path": "ding.send_ding_message",
|
|
"group": "message",
|
|
"title": "发送DING消息",
|
|
"description": "使用企业内机器人发送DING消息",
|
|
"sensitive": true,
|
|
"annotations": map[string]any{
|
|
"destructive_hint": true,
|
|
},
|
|
"parameters": map[string]any{
|
|
"robotCode": map[string]any{
|
|
"type": "string",
|
|
"description": "机器人Code",
|
|
},
|
|
"receiverUserIdList": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{"type": "string"},
|
|
"description": "接收者用户ID列表",
|
|
},
|
|
"remindType": map[string]any{
|
|
"type": "number",
|
|
"description": "提醒类型",
|
|
},
|
|
},
|
|
"required": []any{"robotCode", "receiverUserIdList", "remindType"},
|
|
"flag_overlay": map[string]any{
|
|
"receiverUserIdList": map[string]any{
|
|
"alias": "users",
|
|
"transform": "csv_to_array",
|
|
},
|
|
"robotCode": map[string]any{
|
|
"alias": "robot-code",
|
|
"env_default": "DINGTALK_DING_ROBOT_CODE",
|
|
},
|
|
"remindType": map[string]any{
|
|
"alias": "type",
|
|
"transform": "enum_map",
|
|
"transform_args": map[string]any{
|
|
"app": 1,
|
|
"sms": 2,
|
|
"call": 3,
|
|
"_default": 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := writePretty(&buf, payload); err != nil {
|
|
t.Fatalf("writePretty() error = %v", err)
|
|
}
|
|
out := buf.String()
|
|
|
|
wants := []string{
|
|
"Tool send_ding_message", // header
|
|
"canonical:", // meta section
|
|
"ding.send_ding_message",
|
|
"cli path:",
|
|
"ding message send",
|
|
"sensitive:",
|
|
"yes (needs --yes)",
|
|
"annotations:",
|
|
"destructive_hint=true",
|
|
"Parameters",
|
|
"robotCode",
|
|
"--robot-code", // overlay alias rendered
|
|
"env default:",
|
|
"$DINGTALK_DING_ROBOT_CODE",
|
|
"receiverUserIdList",
|
|
"--users",
|
|
"transform:",
|
|
"csv_to_array",
|
|
"string[]", // array<string> shorthand
|
|
"remindType",
|
|
"--type",
|
|
"enum_map(", // with args inlined
|
|
"app=1",
|
|
}
|
|
for _, want := range wants {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("pretty tool missing %q; full output:\n%s", want, out)
|
|
}
|
|
}
|
|
|
|
// required params must show the red asterisk marker (even with color disabled
|
|
// the literal '*' still appears).
|
|
for _, req := range []string{"robotCode", "receiverUserIdList", "remindType"} {
|
|
// format: " * <name>"
|
|
if !strings.Contains(out, "* "+req) {
|
|
t.Errorf("required marker missing for %q", req)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPretty_NonSchemaFallsBackToTableish(t *testing.T) {
|
|
forceNoColor(t)
|
|
|
|
// Payload that doesn't have kind="schema" should fall through to
|
|
// the tableish renderer, not error or render a schema header.
|
|
payload := map[string]any{"items": []any{
|
|
map[string]any{"id": "a", "name": "Alice"},
|
|
map[string]any{"id": "b", "name": "Bob"},
|
|
}}
|
|
|
|
var buf bytes.Buffer
|
|
if err := writePretty(&buf, payload); err != nil {
|
|
t.Fatalf("writePretty() error = %v", err)
|
|
}
|
|
out := buf.String()
|
|
if strings.Contains(out, "Catalog") || strings.Contains(out, "Tool ") {
|
|
t.Errorf("pretty wrongly ran schema renderer on non-schema payload:\n%s", out)
|
|
}
|
|
// tableish should produce header + rows
|
|
for _, want := range []string{"id", "name", "Alice", "Bob"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("tableish fallback missing %q:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNormalizeFormat_RecognisesPretty(t *testing.T) {
|
|
if got := normalizeFormat("pretty", FormatJSON); got != FormatPretty {
|
|
t.Errorf("normalizeFormat(pretty) = %q, want %q", got, FormatPretty)
|
|
}
|
|
if got := normalizeFormat("PRETTY", FormatJSON); got != FormatPretty {
|
|
t.Errorf("normalizeFormat(PRETTY) = %q, want %q", got, FormatPretty)
|
|
}
|
|
}
|