Sync with DingTalk-Real-AI/main (1.0.35–1.0.40): - 1.0.35–1.0.39: chat @-mentions, sheet export, doc strip unsafe chars, PAT batch grants, devdoc RAG, agent_code detection, AI-tag opt-in - 1.0.40 (#486): China mirror via Gitee + npmmirror - (#488) embed skills/ into binary - (#464) strip duplicate H1 on JSONML doc-create # Conflicts: # .github/workflows/release.yml # CHANGELOG.md # README.md # README_zh.md # docs/command-index.md # internal/app/runner.go # internal/app/runner_test.go # internal/auth/channel.go # internal/helpers/devdoc.go # internal/helpers/devdoc_test.go # internal/pat/chmod.go # internal/pat/chmod_test.go # internal/pat/pat.go # internal/transport/client.go # scripts/install-skills.sh # scripts/install.ps1 # scripts/install.sh # skills/mono/references/products/devdoc.md # skills/mono/references/products/simple.md # skills/multi/dingtalk-devdoc/references/devdoc.md # skills/multi/dingtalk-skill/references/skill.md # test/cli_compat/devdoc_test.go # test/cli_compat/helpers_test.go # test/skill_tests.md # test/skill_tests_results.md # test/unit/pat_host_owned_signal_test.go
544 lines
17 KiB
Go
544 lines
17 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"
|
|
"strings"
|
|
"testing"
|
|
|
|
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
|
|
)
|
|
|
|
type devdocCommandRunner struct {
|
|
last executor.Invocation
|
|
calls []executor.Invocation
|
|
errors map[string]error
|
|
responses map[string]map[string]any
|
|
}
|
|
|
|
func (r *devdocCommandRunner) Run(_ context.Context, invocation executor.Invocation) (executor.Result, error) {
|
|
r.last = invocation
|
|
r.calls = append(r.calls, invocation)
|
|
if r.errors != nil {
|
|
if err := r.errors[invocation.Tool]; err != nil {
|
|
return executor.Result{}, err
|
|
}
|
|
}
|
|
if r.responses != nil {
|
|
if content, ok := r.responses[invocation.Tool]; ok {
|
|
invocation.Implemented = true
|
|
return executor.Result{
|
|
Invocation: invocation,
|
|
Response: map[string]any{"content": content},
|
|
}, nil
|
|
}
|
|
}
|
|
return executor.Result{Invocation: invocation}, nil
|
|
}
|
|
|
|
func TestDevdocArticleSearchAcceptsWukongKeywordAlias(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "--keyword", "openConversationId", "--cursor", "tok-d", "--page-size", "5"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if runner.last.Tool != devdocArticleSearchTool {
|
|
t.Fatalf("tool = %q, want %s", runner.last.Tool, devdocArticleSearchTool)
|
|
}
|
|
if got := runner.last.Params["keyword"]; got != "openConversationId" {
|
|
t.Fatalf("keyword = %#v, want openConversationId", got)
|
|
}
|
|
if _, ok := runner.last.Params["query"]; ok {
|
|
t.Fatalf("query must not be sent to article RAG search: %#v", runner.last.Params)
|
|
}
|
|
if got := runner.last.Params["cursor"]; got != "tok-d" {
|
|
t.Fatalf("cursor = %#v, want tok-d", got)
|
|
}
|
|
if got := runner.last.Params["size"]; got != 5 {
|
|
t.Fatalf("size = %#v, want 5", got)
|
|
}
|
|
if _, ok := runner.last.Params["pageSize"]; ok {
|
|
t.Fatalf("pageSize must not be sent to RAG search: %#v", runner.last.Params)
|
|
}
|
|
if _, ok := runner.last.Params["CliRagSearchReqVO"]; ok {
|
|
t.Fatalf("CliRagSearchReqVO wrapper must not be sent to RAG search: %#v", runner.last.Params)
|
|
}
|
|
}
|
|
|
|
func TestDevdocArticleSearchPassesCursor(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "--query", "Webhook", "--cursor", "3", "--size", "5"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if runner.last.Tool != devdocArticleSearchTool {
|
|
t.Fatalf("tool = %q, want %s", runner.last.Tool, devdocArticleSearchTool)
|
|
}
|
|
if got := runner.last.Params["cursor"]; got != "3" {
|
|
t.Fatalf("cursor = %#v, want 3", got)
|
|
}
|
|
if _, ok := runner.last.Params["page"]; ok {
|
|
t.Fatalf("page must be omitted when cursor is set: %#v", runner.last.Params)
|
|
}
|
|
}
|
|
|
|
func TestDevdocErrorDiagnoseBuildsErrorSearchQuery(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{
|
|
"error", "diagnose",
|
|
"--error-code", "40014",
|
|
"--error-message", "不合法的access_token",
|
|
"--api", "获取用户信息",
|
|
"--cursor", "2",
|
|
"--page", "2",
|
|
"--size", "3",
|
|
})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if runner.last.Tool != devdocErrorDiagnoseTool {
|
|
t.Fatalf("tool = %q, want %s", runner.last.Tool, devdocErrorDiagnoseTool)
|
|
}
|
|
if got := runner.last.Params["errorCode"]; got != "40014" {
|
|
t.Fatalf("errorCode = %#v, want 40014", got)
|
|
}
|
|
if got := runner.last.Params["query"]; got != "不合法的access_token 获取用户信息" {
|
|
t.Fatalf("query = %#v, want merged message and api", got)
|
|
}
|
|
if _, ok := runner.last.Params["page"]; ok {
|
|
t.Fatalf("page must be omitted when cursor is set: %#v", runner.last.Params)
|
|
}
|
|
if got := runner.last.Params["cursor"]; got != "2" {
|
|
t.Fatalf("cursor = %#v, want 2", got)
|
|
}
|
|
if got := runner.last.Params["size"]; got != 3 {
|
|
t.Fatalf("size = %#v, want 3", got)
|
|
}
|
|
}
|
|
|
|
func TestDevdocErrorDiagnoseFallsBackToArticleSearchTools(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{
|
|
errors: map[string]error{
|
|
devdocErrorDiagnoseTool: errors.New("PARAM_ERROR - 未找到指定工具"),
|
|
devdocArticleSearchTool: errors.New("unknown tool"),
|
|
},
|
|
}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{
|
|
"error", "diagnose",
|
|
"--error-code", "40014",
|
|
"--query", "获取用户信息",
|
|
"--request-id", "req-1",
|
|
"--cursor", "2",
|
|
})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if len(runner.calls) != 3 {
|
|
t.Fatalf("calls = %d, want 3", len(runner.calls))
|
|
}
|
|
if got := runner.calls[0].Tool; got != devdocErrorDiagnoseTool {
|
|
t.Fatalf("first tool = %q, want %s", got, devdocErrorDiagnoseTool)
|
|
}
|
|
if got := runner.calls[1].Tool; got != devdocArticleSearchTool {
|
|
t.Fatalf("second tool = %q, want %s", got, devdocArticleSearchTool)
|
|
}
|
|
if got := runner.calls[1].Params["cursor"]; got != "2" {
|
|
t.Fatalf("RAG fallback cursor = %#v, want 2", got)
|
|
}
|
|
if got := runner.calls[1].Params["keyword"]; got != "40014 获取用户信息 req-1" {
|
|
t.Fatalf("RAG fallback keyword = %#v, want merged diagnostic query", got)
|
|
}
|
|
if _, ok := runner.calls[1].Params["query"]; ok {
|
|
t.Fatalf("RAG fallback query must not be sent: %#v", runner.calls[1].Params)
|
|
}
|
|
if _, ok := runner.calls[1].Params["page"]; ok {
|
|
t.Fatalf("RAG fallback page must be omitted when cursor is set: %#v", runner.calls[1].Params)
|
|
}
|
|
if got := runner.calls[2].Tool; got != devdocArticleSearchLegacyTool {
|
|
t.Fatalf("third tool = %q, want %s", got, devdocArticleSearchLegacyTool)
|
|
}
|
|
if got := runner.last.Params["keyword"]; got != "40014 获取用户信息 req-1" {
|
|
t.Fatalf("fallback keyword = %#v, want merged diagnostic keyword", got)
|
|
}
|
|
if _, ok := runner.last.Params["cursor"]; ok {
|
|
t.Fatalf("legacy fallback params must not include cursor: %#v", runner.last.Params)
|
|
}
|
|
if got := runner.last.Params["page"]; got != 2 {
|
|
t.Fatalf("legacy fallback page = %#v, want cursor-derived page 2", got)
|
|
}
|
|
}
|
|
|
|
func TestDevdocArticleSearchFallsBackOnStructuredToolMetadataError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{
|
|
errors: map[string]error{
|
|
devdocArticleSearchTool: apperrors.NewAPI("business error: success=false",
|
|
apperrors.WithReason("business_error"),
|
|
apperrors.WithServerDiag(apperrors.ServerDiagnostics{
|
|
ServerErrorCode: "PARAM_ERROR",
|
|
TechnicalDetail: "Tool metadata API error: PARAM_ERROR - 未找到指定工具",
|
|
}),
|
|
),
|
|
},
|
|
}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "--query", "Webhook", "--cursor", "3"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if len(runner.calls) != 2 {
|
|
t.Fatalf("calls = %d, want 2", len(runner.calls))
|
|
}
|
|
if got := runner.calls[0].Tool; got != devdocArticleSearchTool {
|
|
t.Fatalf("first tool = %q, want %s", got, devdocArticleSearchTool)
|
|
}
|
|
if got := runner.calls[1].Tool; got != devdocArticleSearchLegacyTool {
|
|
t.Fatalf("second tool = %q, want %s", got, devdocArticleSearchLegacyTool)
|
|
}
|
|
if got := runner.calls[1].Params["page"]; got != 3 {
|
|
t.Fatalf("legacy fallback page = %#v, want cursor-derived page 3", got)
|
|
}
|
|
}
|
|
|
|
func TestDevdocArticleSearchDoesNotFallbackOnGenericToolMetadataError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{
|
|
errors: map[string]error{
|
|
devdocArticleSearchTool: apperrors.NewAPI("business error: success=false",
|
|
apperrors.WithReason("business_error"),
|
|
apperrors.WithServerDiag(apperrors.ServerDiagnostics{
|
|
ServerErrorCode: "PARAM_ERROR",
|
|
TechnicalDetail: "Tool metadata API error: PARAM_ERROR - 参数不能为空",
|
|
}),
|
|
),
|
|
},
|
|
}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "--query", "Webhook"})
|
|
|
|
if err := cmd.Execute(); err == nil {
|
|
t.Fatalf("Execute() error = nil, want metadata PARAM_ERROR")
|
|
}
|
|
if len(runner.calls) != 1 {
|
|
t.Fatalf("calls = %d, want 1", len(runner.calls))
|
|
}
|
|
if got := runner.calls[0].Tool; got != devdocArticleSearchTool {
|
|
t.Fatalf("first tool = %q, want %s", got, devdocArticleSearchTool)
|
|
}
|
|
}
|
|
|
|
func TestDevdocArticleSearchFallsBackOnEmptyRAGContent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{
|
|
responses: map[string]map[string]any{
|
|
devdocArticleSearchTool: {
|
|
"materials": []any{},
|
|
"references": []any{},
|
|
"ragContext": nil,
|
|
},
|
|
devdocArticleSearchLegacyTool: {
|
|
"success": true,
|
|
"result": map[string]any{
|
|
"items": []any{map[string]any{"title": "OAuth2.0鉴权 - 开放平台"}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "--keyword", "OAuth2", "--size", "2"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if len(runner.calls) != 2 {
|
|
t.Fatalf("calls = %d, want 2", len(runner.calls))
|
|
}
|
|
if got := runner.calls[0].Tool; got != devdocArticleSearchTool {
|
|
t.Fatalf("first tool = %q, want %s", got, devdocArticleSearchTool)
|
|
}
|
|
if got := runner.calls[1].Tool; got != devdocArticleSearchLegacyTool {
|
|
t.Fatalf("second tool = %q, want %s", got, devdocArticleSearchLegacyTool)
|
|
}
|
|
}
|
|
|
|
func TestDevdocArticleSearchFallsBackOnPaginationOnlyRAGContent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{
|
|
responses: map[string]map[string]any{
|
|
devdocArticleSearchTool: {
|
|
"success": true,
|
|
"result": map[string]any{"nextCursor": "2"},
|
|
},
|
|
devdocArticleSearchLegacyTool: {
|
|
"success": true,
|
|
"result": map[string]any{
|
|
"items": []any{map[string]any{"title": "获取用户token - 开放平台"}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "--query", "OAuth2", "--cursor", "3"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if len(runner.calls) != 2 {
|
|
t.Fatalf("calls = %d, want 2", len(runner.calls))
|
|
}
|
|
if got := runner.calls[1].Tool; got != devdocArticleSearchLegacyTool {
|
|
t.Fatalf("second tool = %q, want %s", got, devdocArticleSearchLegacyTool)
|
|
}
|
|
if got := runner.calls[1].Params["page"]; got != 3 {
|
|
t.Fatalf("legacy fallback page = %#v, want cursor-derived page 3", got)
|
|
}
|
|
}
|
|
|
|
func TestDevdocArticleSearchKeepsNonEmptyRAGContent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{
|
|
responses: map[string]map[string]any{
|
|
devdocArticleSearchTool: {
|
|
"materials": []any{map[string]any{"title": "OAuth2.0鉴权 - 开放平台"}},
|
|
},
|
|
},
|
|
}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "--keyword", "OAuth2"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if len(runner.calls) != 1 {
|
|
t.Fatalf("calls = %d, want 1", len(runner.calls))
|
|
}
|
|
if got := runner.calls[0].Tool; got != devdocArticleSearchTool {
|
|
t.Fatalf("first tool = %q, want %s", got, devdocArticleSearchTool)
|
|
}
|
|
}
|
|
|
|
func TestDevdocArticleSearchAcceptsPositionalKeyword(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"article", "search", "MCP"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if got := runner.last.Params["keyword"]; got != "MCP" {
|
|
t.Fatalf("keyword = %#v, want MCP", got)
|
|
}
|
|
if _, ok := runner.last.Params["query"]; ok {
|
|
t.Fatalf("query must not be sent to article RAG search: %#v", runner.last.Params)
|
|
}
|
|
}
|
|
|
|
func TestDevdocErrorDiagnosePassesRequestID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"error", "diagnose", "--request-id", "req-123", "--page", "2", "--size", "5"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if runner.last.Tool != "search_open_error_code_rag" {
|
|
t.Fatalf("tool = %q, want search_open_error_code_rag", runner.last.Tool)
|
|
}
|
|
if got := runner.last.Params["requestId"]; got != "req-123" {
|
|
t.Fatalf("requestId = %#v, want req-123", got)
|
|
}
|
|
if got := runner.last.Params["page"]; got != 2 {
|
|
t.Fatalf("page = %#v, want 2", got)
|
|
}
|
|
if got := runner.last.Params["size"]; got != 5 {
|
|
t.Fatalf("size = %#v, want 5", got)
|
|
}
|
|
}
|
|
|
|
func TestDevdocErrorDiagnoseMapsTraceIDAlias(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"error", "diagnose", "--trace-id", "trace-abc", "--api", "创建日程"})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if got := runner.last.Params["requestId"]; got != "trace-abc" {
|
|
t.Fatalf("requestId = %#v, want trace-abc", got)
|
|
}
|
|
if _, ok := runner.last.Params["traceId"]; ok {
|
|
t.Fatalf("traceId should not be sent, params = %#v", runner.last.Params)
|
|
}
|
|
if _, ok := runner.last.Params["apiName"]; ok {
|
|
t.Fatalf("apiName should not be sent, params = %#v", runner.last.Params)
|
|
}
|
|
if got := runner.last.Params["query"]; got != "创建日程" {
|
|
t.Fatalf("query = %#v, want 创建日程", got)
|
|
}
|
|
}
|
|
|
|
func TestDevdocErrorDiagnosePassesErrorContext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{
|
|
"error", "troubleshoot",
|
|
"--error-code", "33012",
|
|
"--error-message", "missing scope",
|
|
"--context", "create calendar failed",
|
|
})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if got := runner.last.Params["errorCode"]; got != "33012" {
|
|
t.Fatalf("errorCode = %#v, want 33012", got)
|
|
}
|
|
if _, ok := runner.last.Params["errorMessage"]; ok {
|
|
t.Fatalf("errorMessage should not be sent, params = %#v", runner.last.Params)
|
|
}
|
|
if _, ok := runner.last.Params["context"]; ok {
|
|
t.Fatalf("context should not be sent, params = %#v", runner.last.Params)
|
|
}
|
|
if got := runner.last.Params["query"]; got != "missing scope create calendar failed" {
|
|
t.Fatalf("query = %#v, want merged error context", got)
|
|
}
|
|
}
|
|
|
|
func TestDevdocErrorDiagnoseMergesAllContextIntoQuery(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{
|
|
"error", "diagnose",
|
|
"--query", "机器人回调失败",
|
|
"--error-message", "missing scope",
|
|
"--api", "创建日程",
|
|
"--context", "应用无权限",
|
|
})
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
|
|
}
|
|
if got := runner.last.Tool; got != "search_open_error_code_rag" {
|
|
t.Fatalf("tool = %q, want search_open_error_code_rag", got)
|
|
}
|
|
if got := runner.last.Params["query"]; got != "机器人回调失败 missing scope 创建日程 应用无权限" {
|
|
t.Fatalf("query = %#v, want merged context", got)
|
|
}
|
|
for _, key := range []string{"apiName", "errorMessage", "context"} {
|
|
if _, ok := runner.last.Params[key]; ok {
|
|
t.Fatalf("%s should not be sent, params = %#v", key, runner.last.Params)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDevdocErrorDiagnoseRequiresTroubleshootInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runner := &devdocCommandRunner{}
|
|
cmd := devdocHandler{}.Command(runner)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetArgs([]string{"error", "diagnose", "--api", "创建日程"})
|
|
|
|
err := cmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("Execute() error = nil, want validation error")
|
|
}
|
|
if !strings.Contains(err.Error(), "one of --query") {
|
|
t.Fatalf("error = %q, want required input hint", err.Error())
|
|
}
|
|
if runner.last.Tool != "" {
|
|
t.Fatalf("tool = %q, want no call", runner.last.Tool)
|
|
}
|
|
}
|