Files
dingtalk-workspace-cli/internal/app/runner_full_coverage_test.go

416 lines
18 KiB
Go

package app
import (
"context"
"errors"
"io"
"strings"
"testing"
authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/safety"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/transport"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
)
type runnerCoverageFallback struct {
result executor.Result
err error
}
func (f runnerCoverageFallback) Run(context.Context, executor.Invocation) (executor.Result, error) {
return f.result, f.err
}
func TestCrossPlatformCoverageRunnerRemainingRoutingCoverage(t *testing.T) {
oldResolveMulti := runnerResolveMultiProfileSelections
oldResolveProfile := runnerResolveProfile
oldCachedToken := runnerGetCachedRuntimeToken
t.Cleanup(func() {
runnerResolveMultiProfileSelections = oldResolveMulti
runnerResolveProfile = oldResolveProfile
runnerGetCachedRuntimeToken = oldCachedToken
})
created := newCommandRunnerWithFlags(&GlobalFlags{Timeout: 2})
if created.(*runtimeRunner).transport == nil {
t.Fatal("runner transport was not created")
}
wantErr := errors.New("profiles failed")
runnerResolveMultiProfileSelections = func(string, string) ([]multiProfileSelection, bool, error) {
return nil, false, wantErr
}
if _, err := (&runtimeRunner{}).Run(context.Background(), executor.Invocation{}); err == nil {
t.Fatal("profile resolution error was accepted")
}
inv := executor.Invocation{CanonicalProduct: "product", Tool: "tool"}
prefetched := make(chan struct{}, 1)
runnerGetCachedRuntimeToken = func(context.Context) (string, error) {
prefetched <- struct{}{}
return "", nil
}
r := &runtimeRunner{
transport: transport.NewClient(nil),
fallback: runnerCoverageFallback{},
}
directMiss := inv
directMiss.Kind = "helper_invocation"
if _, err := r.runSingle(context.Background(), directMiss, false); err == nil || !strings.Contains(err.Error(), "no dynamic endpoint registered for product or tool") {
t.Fatalf("direct runtime miss = %v", err)
}
directHit := executor.Invocation{Kind: "helper_invocation", CanonicalProduct: defaultPATProductID, Tool: "pat", DryRun: true}
if got, err := r.runSingle(context.Background(), directHit, false); err != nil || got.Response["dry_run"] != true {
t.Fatalf("direct runtime hit = %#v, %v", got, err)
}
if _, err := r.runSingle(context.Background(), inv, true); err == nil {
t.Fatal("endpoint miss succeeded")
}
<-prefetched
runnerResolveProfile = func(_ string, selector string) (*authpkg.Profile, error) {
if selector == "a,b" {
return nil, errors.New("not a combined profile")
}
if selector == "a" {
return nil, wantErr
}
return nil, nil
}
if _, _, err := resolveMultiProfileSelections("", "a,b"); !errors.Is(err, wantErr) {
t.Fatalf("profile error = %v", err)
}
runnerResolveProfile = func(_ string, selector string) (*authpkg.Profile, error) {
if selector == "a,b" {
return nil, errors.New("not a combined profile")
}
if selector == "a" {
return &authpkg.Profile{CorpID: "corp-a"}, nil
}
return nil, nil
}
if _, _, err := resolveMultiProfileSelections("", "a,b"); err == nil || !strings.Contains(err.Error(), "not found") {
t.Fatalf("nil profile error = %v", err)
}
selections := []multiProfileSelection{
{Selector: "bad", Profile: authpkg.Profile{CorpID: "bad"}},
{Selector: "good", Profile: authpkg.Profile{CorpID: "good"}},
}
r = &runtimeRunner{fallback: runnerCoverageFallback{err: wantErr}}
result, err := r.runMultiProfile(context.Background(), inv, selections[:1])
if err != nil || result.Response == nil {
t.Fatalf("multi failure aggregation = %#v, %v", result, err)
}
r.fallback = runnerCoverageFallback{result: executor.Result{Response: map[string]any{"endpoint": "local", "value": 1}}}
result, err = r.runMultiProfile(context.Background(), inv, selections[1:])
if err != nil || result.Response == nil {
t.Fatalf("multi success aggregation = %#v, %v", result, err)
}
r = &runtimeRunner{
transport: transport.NewClient(nil),
globalFlags: &GlobalFlags{DryRun: true},
fallback: runnerCoverageFallback{},
}
overrideInv := inv
overrideInv.Kind = "helper_invocation"
overrideInv.DryRun = true
t.Setenv("DINGTALK_PRODUCT_MCP_URL", "https://override.test")
got, err := r.runSingle(context.Background(), overrideInv, false)
if err != nil || got.Response["endpoint"] != "https://override.test" {
t.Fatalf("direct runtime override = %#v, %v", got, err)
}
}
func TestCrossPlatformCoverageRunnerRemainingExecutionCoverage(t *testing.T) {
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
oldEdition := edition.Get()
oldPreflight := runnerPreflightDocDownload
oldCall := runnerCallTool
oldHandle := runnerHandlePatAuthCheck
oldRetry := runnerRetryWithPatAuthRetry
oldCapture := runnerCaptureRuntimeFailure
oldResolveSnapshot := runnerResolveAuthSnapshot
t.Cleanup(func() {
edition.Override(oldEdition)
runnerPreflightDocDownload = oldPreflight
runnerCallTool = oldCall
runnerHandlePatAuthCheck = oldHandle
runnerRetryWithPatAuthRetry = oldRetry
runnerCaptureRuntimeFailure = oldCapture
runnerResolveAuthSnapshot = oldResolveSnapshot
})
pluginAuthMu.Lock()
oldPluginRegistry := pluginAuthRegistry
pluginAuthRegistry = make(map[string]*PluginAuth)
pluginAuthMu.Unlock()
t.Cleanup(func() {
pluginAuthMu.Lock()
pluginAuthRegistry = oldPluginRegistry
pluginAuthMu.Unlock()
})
runnerCaptureRuntimeFailure = func(executor.Invocation, error, error) {}
runnerPreflightDocDownload = func(*runtimeRunner, context.Context, *transport.Client, string, executor.Invocation) error {
return nil
}
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{Content: map[string]any{"value": 1}}, nil
}
r := &runtimeRunner{
transport: transport.NewClient(nil),
globalFlags: &GlobalFlags{Token: "token", Timeout: 1},
}
inv := executor.Invocation{CanonicalProduct: "product", Tool: "tool", Params: map[string]any{"x": 1}}
if got, err := r.executeInvocation(context.Background(), "https://example.test", inv); err != nil || !got.Invocation.Implemented {
t.Fatalf("default auth execution = %#v, %v", got, err)
}
runnerResolveAuthSnapshot = func(*runtimeRunner, context.Context) (AccessTokenSnapshot, error) {
return AccessTokenSnapshot{
AccessToken: "international-token",
LoginRegion: authpkg.LoginRegionInternational,
LoginRegionKnown: true,
}, nil
}
successfulCall := runnerCallTool
routedEndpoint := ""
runnerCallTool = func(_ *transport.Client, _ context.Context, endpoint, _ string, _ map[string]any) (transport.ToolCallResult, error) {
routedEndpoint = endpoint
return transport.ToolCallResult{Content: map[string]any{"value": 1}}, nil
}
if _, err := r.executeInvocation(context.Background(), "https://mcp-gw.dingtalk.com/server/contact", inv); err != nil {
t.Fatalf("international auth execution = %v", err)
}
if routedEndpoint != "https://mcp-gw.dingtalk.io/server/contact" {
t.Fatalf("international routed endpoint = %q", routedEndpoint)
}
runnerResolveAuthSnapshot = oldResolveSnapshot
runnerCallTool = successfulCall
wantErr := errors.New("preflight")
runnerPreflightDocDownload = func(*runtimeRunner, context.Context, *transport.Client, string, executor.Invocation) error {
return wantErr
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, wantErr) {
t.Fatalf("preflight error = %v", err)
}
patErr := &apperrors.PATError{RawJSON: `{"code":"PAT_NO_PERMISSION"}`}
runnerPreflightDocDownload = func(*runtimeRunner, context.Context, *transport.Client, string, executor.Invocation) error {
return patErr
}
retrying := context.WithValue(context.Background(), patRetryingKey, true)
if _, err := r.executeInvocation(retrying, "https://example.test", inv); !errors.Is(err, patErr) {
t.Fatalf("retrying preflight PAT = %v", err)
}
handled := errors.New("handled PAT")
runnerHandlePatAuthCheck = func(context.Context, *runtimeRunner, executor.Invocation, *apperrors.PATError, string, io.Writer) (executor.Result, error) {
return executor.Result{}, handled
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, handled) {
t.Fatalf("handled preflight PAT = %v", err)
}
runnerPreflightDocDownload = oldPreflight
runnerPreflightDocDownload = func(*runtimeRunner, context.Context, *transport.Client, string, executor.Invocation) error {
return nil
}
authErr := apperrors.NewAuth("expired", apperrors.WithReason("http_401"))
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{}, authErr
}
overrideErr := errors.New("auth override")
edition.Override(&edition.Hooks{OnAuthError: func(string, error) error { return overrideErr }})
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, overrideErr) {
t.Fatalf("auth override = %v", err)
}
edition.Override(&edition.Hooks{OnAuthError: func(string, error) error { return nil }})
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, authErr) {
t.Fatalf("auth passthrough = %v", err)
}
scopeErr := errors.New("missing_scope calendar:read")
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{}, scopeErr
}
retried := errors.New("scope retried")
runnerRetryWithPatAuthRetry = func(context.Context, executor.Runner, executor.Invocation, *PatScopeError, string, io.Writer) (executor.Result, error) {
return executor.Result{}, retried
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, retried) {
t.Fatalf("scope retry = %v", err)
}
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{}, wantErr
}
edition.Override(&edition.Hooks{})
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, wantErr) {
t.Fatalf("generic call error = %v", err)
}
patContent := map[string]any{"code": "PAT_NO_PERMISSION", "data": map[string]any{"flowId": "f"}}
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{Content: patContent}, nil
}
edition.Override(&edition.Hooks{ClassifyToolResult: func(map[string]any) error { return patErr }})
if _, err := r.executeInvocation(retrying, "https://example.test", inv); !errors.Is(err, patErr) {
t.Fatalf("edition retry PAT = %v", err)
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, handled) {
t.Fatalf("edition handled PAT = %v", err)
}
edition.Override(&edition.Hooks{ClassifyToolResult: func(map[string]any) error { return wantErr }})
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, wantErr) {
t.Fatalf("edition classification = %v", err)
}
edition.Override(&edition.Hooks{})
if _, err := r.executeInvocation(retrying, "https://example.test", inv); err == nil {
t.Fatal("built-in retrying PAT succeeded")
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, handled) {
t.Fatalf("built-in handled PAT = %v", err)
}
callCount := 0
edition.Override(&edition.Hooks{ClassifyToolResult: func(map[string]any) error {
callCount++
if callCount%2 == 0 {
return wantErr
}
return nil
}})
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{IsError: true, Content: map[string]any{"message": "business"}}, nil
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, wantErr) {
t.Fatalf("business hook = %v", err)
}
edition.Override(&edition.Hooks{})
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{IsError: true, Blocks: []transport.ContentBlock{{Text: "missing_scope mail:read"}}, Content: map[string]any{}}, nil
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); !errors.Is(err, retried) {
t.Fatalf("business scope retry = %v", err)
}
r.scanner = coverageScanner{report: safety.Report{Scanned: true, Findings: []safety.Finding{{Pattern: "bad"}}}}
r.enforceContentScan = true
runnerCallTool = func(*transport.Client, context.Context, string, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{Content: map[string]any{"value": 1}}, nil
}
if _, err := r.executeInvocation(context.Background(), "https://example.test", inv); err == nil {
t.Fatal("scan failure succeeded")
}
}
func TestCrossPlatformCoverageRunnerRemainingStdioAuthAndHeadersCoverage(t *testing.T) {
oldStdioInit := runnerStdioEnsureInitialized
oldStdioList := runnerStdioListTools
oldStdioCall := runnerStdioCallTool
oldEdition := edition.Get()
t.Cleanup(func() {
runnerStdioEnsureInitialized = oldStdioInit
runnerStdioListTools = oldStdioList
runnerStdioCallTool = oldStdioCall
edition.Override(oldEdition)
StopAllStdioClients()
})
client := transport.NewStdioClient("unused", nil, nil)
RegisterStdioClient("stdio-product", client)
r := &runtimeRunner{globalFlags: &GlobalFlags{Timeout: 1}}
inv := executor.Invocation{CanonicalProduct: "stdio-product", Tool: "tool"}
wantErr := errors.New("stdio failed")
runnerStdioEnsureInitialized = func(*transport.StdioClient, context.Context) error { return wantErr }
if _, err := r.executeStdioInvocationAtEndpoint(context.Background(), "", inv); err == nil || !strings.Contains(err.Error(), "stdio initialize failed") {
t.Fatalf("stdio initialize error = %v", err)
}
runnerStdioEnsureInitialized = func(*transport.StdioClient, context.Context) error { return nil }
runnerStdioListTools = func(*transport.StdioClient, context.Context) (transport.ToolsListResult, error) {
return transport.ToolsListResult{
Tools: []transport.ToolDescriptor{{
Name: "tool",
InputSchema: map[string]any{"type": "object"},
}},
}, nil
}
runnerStdioCallTool = func(*transport.StdioClient, context.Context, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{}, wantErr
}
if _, err := r.executeStdioInvocationAtEndpoint(context.Background(), "", inv); err == nil || !strings.Contains(err.Error(), "stdio failed") {
t.Fatalf("stdio call error = %v", err)
}
runnerStdioCallTool = func(*transport.StdioClient, context.Context, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{IsError: true, Content: map[string]any{"message": "tool failed"}}, nil
}
if _, err := r.executeStdioInvocationAtEndpoint(context.Background(), "", inv); err == nil || !strings.Contains(err.Error(), "tool failed") {
t.Fatalf("stdio tool error = %v", err)
}
runnerStdioCallTool = func(*transport.StdioClient, context.Context, string, map[string]any) (transport.ToolCallResult, error) {
return transport.ToolCallResult{Content: map[string]any{"ok": true}}, nil
}
if got, err := r.executeStdioInvocationAtEndpoint(context.Background(), "", inv); err != nil || !got.Invocation.Implemented {
t.Fatalf("stdio success = %#v, %v", got, err)
}
RegisterStdioClient("plugin/server-key", client)
overlayIDInvocation := inv
overlayIDInvocation.CanonicalProduct = "overlay-id"
if got, err := r.executeInvocation(
context.Background(),
"stdio://plugin/server-key",
overlayIDInvocation,
); err != nil || !got.Invocation.Implemented {
t.Fatalf("stdio endpoint-key lookup = %#v, %v", got, err)
}
r.globalFlags.Token = " explicit "
if got, err := r.resolveAuthToken(context.Background()); err != nil || got != "explicit" {
t.Fatalf("explicit auth token = %q, %v", got, err)
}
edition.Override(&edition.Hooks{TokenProvider: func(_ context.Context, fallback func() (string, error)) (string, error) {
_, _ = fallback()
return "provided", nil
}})
r.globalFlags.Token = ""
if got, err := r.resolveAuthToken(context.Background()); err != nil || got != "provided" {
t.Fatalf("provided auth token = %q, %v", got, err)
}
if got, err := resolveRuntimeAuthToken(context.Background(), " runtime "); err != nil || got != "runtime" {
t.Fatalf("runtime explicit token = %q, %v", got, err)
}
edition.Override(&edition.Hooks{TokenProvider: func(context.Context, func() (string, error)) (string, error) {
return "", errors.New("snapshot failed")
}})
if _, err := r.resolveAuthToken(context.Background()); err == nil || !strings.Contains(err.Error(), "snapshot failed") {
t.Fatalf("resolveAuthToken error = %v", err)
}
t.Setenv(envDWSChannel, "channel")
edition.Override(&edition.Hooks{
MergeHeaders: func(headers map[string]string) map[string]string { return headers },
EnterpriseCredentialHeaders: func(headers map[string]string) map[string]string {
headers["x-enterprise"] = "yes"
return headers
},
})
headers := resolveIdentityHeaders()
if headers["x-dws-channel"] != "channel" || headers["x-enterprise"] != "yes" {
t.Fatalf("identity headers = %#v", headers)
}
if got := detectBusinessError(map[string]any{"content": map[string]any{"success": false, "errorMsg": "nested"}}); got != "nested" {
t.Fatalf("nested business error = %q", got)
}
if got := detectBusinessError(map[string]any{"success": false, "data": map[string]any{"success": false, "errorMsg": "nested-first"}}); got != "nested-first" {
t.Fatalf("nested-first business error = %q", got)
}
}