package app import ( "bytes" "context" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/plugin" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/transport" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" "github.com/spf13/cobra" ) type appFailWriter struct{ err error } func (w appFailWriter) Write([]byte) (int, error) { return 0, w.err } type fakeAccessTokenGetter struct { token string err error } func (g fakeAccessTokenGetter) GetAccessToken(context.Context) (string, error) { return g.token, g.err } func (g fakeAccessTokenGetter) ForceRefreshRejectedToken(context.Context, string) (string, error) { return g.token, g.err } type fakeLegacyTokenGetter struct { token string err error } func (g fakeLegacyTokenGetter) GetToken() (string, string, error) { return g.token, "test", g.err } type fakeAppTokenGetter struct { token string err error } func (g fakeAppTokenGetter) GetToken(context.Context) (string, error) { return g.token, g.err } type fakeSkillDirEntry struct{ dir bool } func (e fakeSkillDirEntry) Name() string { return "entry" } func (e fakeSkillDirEntry) IsDir() bool { return e.dir } func (e fakeSkillDirEntry) Type() os.FileMode { return 0 } func (e fakeSkillDirEntry) Info() (os.FileInfo, error) { return nil, nil } func docPreflightServer(t *testing.T, result map[string]any) *httptest.Server { t.Helper() return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var request struct { ID int `json:"id"` } _ = json.NewDecoder(r.Body).Decode(&request) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "jsonrpc": "2.0", "id": request.ID, "result": result, }) })) } func TestCrossPlatformCoverageDocDownloadPreflightCoverage(t *testing.T) { runner := &runtimeRunner{} base := executor.Invocation{CanonicalProduct: "doc", Tool: "download_file", Params: map[string]any{"nodeId": " node "}} if err := runner.preflightDocDownload(context.Background(), transport.NewClient(nil), "", executor.Invocation{}); err != nil { t.Fatal(err) } if err := runner.preflightDocDownload(context.Background(), transport.NewClient(nil), "", executor.Invocation{CanonicalProduct: "DOC", Tool: "download_file"}); err != nil { t.Fatal(err) } if !isDocDownloadInvocation(base) || docDownloadNodeID(map[string]any{"node": " n "}) != "n" || docDownloadNodeID(map[string]any{"dentryUuid": " d "}) != "d" || docDownloadNodeID(map[string]any{"nodeId": 1}) != "" { t.Fatal("doc download invocation helpers returned unexpected values") } if documentInfoExtension(map[string]any{"data": map[string]any{"extension": " doc "}}) != "doc" || documentInfoExtension(map[string]any{"extension": " pdf "}) != "pdf" || stringAtPath(map[string]any{"x": "value"}, "x", "nested") != "" || stringAtPath(map[string]any{"x": 1}, "x") != "" { t.Fatal("document extension helpers returned unexpected values") } if unsupportedAXLSDownloadError() == nil { t.Fatal("missing AXLS validation error") } oldEdition := edition.Get() t.Cleanup(func() { edition.Override(oldEdition) }) hookErr := errors.New("classified") for _, tc := range []struct { name string result map[string]any hooks *edition.Hooks want string }{ {name: "ok", result: map[string]any{"content": map[string]any{"result": map[string]any{"extension": "docx"}}}, hooks: &edition.Hooks{}}, {name: "edition classifier", result: map[string]any{"content": map[string]any{}}, hooks: &edition.Hooks{ClassifyToolResult: func(map[string]any) error { return hookErr }}, want: "classified"}, {name: "pat", result: map[string]any{"content": map[string]any{"errorCode": "PAT_NO_PERMISSION"}}, hooks: &edition.Hooks{}, want: "PAT_NO_PERMISSION"}, {name: "mcp error", result: map[string]any{"isError": true, "content": []map[string]any{{"type": "text", "text": "mcp failed"}}}, hooks: &edition.Hooks{}, want: "mcp failed"}, {name: "business error", result: map[string]any{"content": map[string]any{"success": false, "errorMsg": "business failed"}}, hooks: &edition.Hooks{}, want: "business failed"}, {name: "axls", result: map[string]any{"content": map[string]any{"data": map[string]any{"extension": "AXLS"}}}, hooks: &edition.Hooks{}, want: "extension=axls"}, } { t.Run(tc.name, func(t *testing.T) { edition.Override(tc.hooks) server := docPreflightServer(t, tc.result) defer server.Close() client := transport.NewClient(nil) client.TrustedDomains = []string{"127.0.0.1"} err := runner.preflightDocDownload(context.Background(), client, server.URL, base) if tc.want == "" && err != nil { t.Fatalf("preflight error = %v", err) } if tc.want != "" && (err == nil || !strings.Contains(err.Error(), tc.want)) { t.Fatalf("preflight error = %v, want %q", err, tc.want) } }) } server := docPreflightServer(t, map[string]any{}) endpoint := server.URL server.Close() client := transport.NewClient(nil) client.TrustedDomains = []string{"127.0.0.1"} client.MaxRetries = 0 if err := runner.preflightDocDownload(context.Background(), client, endpoint, base); err == nil { t.Fatal("network preflight failure succeeded") } } func TestCrossPlatformCoverageRootHelpRemainingCoverage(t *testing.T) { configureRootHelp(nil) renderRootGlobalFlags(nil) if visiblePersistentFlags(nil) != nil || formatRootFlag(nil) != "" || commandShort(nil) != "" || visibleMCPRootCommands(nil) != nil || visibleUtilityRootCommands(nil) != nil { t.Fatal("nil root helper contract changed") } oldEdition := edition.Get() edition.Override(&edition.Hooks{VisibleProducts: func() []string { return []string{"service"} }}) t.Cleanup(func() { edition.Override(oldEdition); SetDynamicServers(nil) }) root := &cobra.Command{Use: "root", Long: "long help"} root.SetOut(io.Discard) root.PersistentFlags().StringP("value", "x", "", "value") root.PersistentFlags().Bool("hidden", false, "hidden") _ = root.PersistentFlags().MarkHidden("hidden") root.AddCommand(&cobra.Command{Use: "service", Short: "service"}, &cobra.Command{Use: "utility", Short: "utility"}) configureRootHelp(root) if err := root.Commands()[0].Help(); err != nil { t.Fatal(err) } root.SetArgs([]string{"help", "missing"}) if err := root.Execute(); err != nil { t.Fatal(err) } root.SetArgs([]string{"help", "utility"}) if err := root.Execute(); err != nil { t.Fatal(err) } if got := formatRootFlag(root.PersistentFlags().Lookup("value")); !strings.Contains(got, "-x") { t.Fatalf("formatted flag = %q", got) } if got := formatRootFlag(root.PersistentFlags().Lookup("hidden")); !strings.Contains(got, "--hidden") { t.Fatalf("formatted long flag = %q", got) } _ = commandShort(&cobra.Command{Use: "help", Short: "Help about any command"}) renderRootGlobalFlags(&cobra.Command{Use: "no-flags"}) edition.Override(&edition.Hooks{}) SetDynamicServers(nil) _ = visibleMCPRootCommands(root) } func TestCrossPlatformCoverageConfigAndTokenSeamsCoverage(t *testing.T) { oldHome, oldExe, oldEval := userHomeDir, executablePath, evaluateSymlink oldEdition := edition.Get() t.Cleanup(func() { userHomeDir, executablePath, evaluateSymlink = oldHome, oldExe, oldEval edition.Override(oldEdition) }) t.Setenv("DWS_CONFIG_DIR", "") edition.Override(&edition.Hooks{}) userHomeDir = func() (string, error) { return "", errors.New("home") } executablePath = func() (string, error) { return "", errors.New("exe") } if got := defaultConfigDir(); got != ".dws" { t.Fatalf("fallback config dir = %q", got) } executablePath = func() (string, error) { return filepath.Join("", "tmp", "dws"), nil } evaluateSymlink = func(string) (string, error) { return "", errors.New("link") } if got := exeRelativeConfigDir(); !strings.HasSuffix(got, filepath.Join("tmp", ".dws")) { t.Fatalf("executable config dir = %q", got) } userHomeDir = func() (string, error) { return "/home/test", nil } if got := defaultConfigDir(); got != filepath.Join("/home/test", ".dws") { t.Fatalf("home config dir = %q", got) } edition.Override(&edition.Hooks{ConfigDir: func() string { return "/edition" }}) if got := defaultConfigDir(); got != "/edition" { t.Fatalf("edition config dir = %q", got) } oldProvider, oldManager := newAccessTokenProvider, newLegacyTokenManager t.Cleanup(func() { newAccessTokenProvider, newLegacyTokenManager = oldProvider, oldManager }) newAccessTokenProvider = func(string) accessTokenGetter { return fakeAccessTokenGetter{err: authpkg.ErrTokenDecryption} } if _, err := resolveAccessTokenFromDir(context.Background(), "unused"); !errors.Is(err, authpkg.ErrTokenDecryption) { t.Fatalf("decryption error = %v", err) } newAccessTokenProvider = func(string) accessTokenGetter { return fakeAccessTokenGetter{err: authpkg.ErrTokenDataNotFound} } newLegacyTokenManager = func(string) legacyTokenGetter { return fakeLegacyTokenGetter{token: " legacy "} } if got, err := resolveAccessTokenFromDir(context.Background(), "unused"); err != nil || got != "legacy" { t.Fatalf("legacy token = %q, %v", got, err) } authpkg.SetRuntimeProfile("corp:user") t.Cleanup(func() { authpkg.SetRuntimeProfile("") }) if got, err := resolveAccessTokenFromDir(context.Background(), "unused"); got != "" || !errors.Is(err, authpkg.ErrTokenDataNotFound) { t.Fatalf("explicit profile fallback = token %q error %v, want profile error", got, err) } authpkg.SetRuntimeProfile("") newAccessTokenProvider = func(string) accessTokenGetter { return fakeAccessTokenGetter{err: errors.New("load")} } newLegacyTokenManager = func(string) legacyTokenGetter { return fakeLegacyTokenGetter{err: errors.New("missing")} } edition.Override(&edition.Hooks{}) other := filepath.Join(t.TempDir(), "other") if _, err := ResolveAuxiliaryAccessToken(context.Background(), other, ""); err == nil { t.Fatal("auxiliary provider failure succeeded") } newAccessTokenProvider = func(string) accessTokenGetter { return fakeAccessTokenGetter{err: authpkg.ErrTokenDecryption} } if _, err := ResolveAuxiliaryAccessToken(context.Background(), other, ""); !errors.Is(err, authpkg.ErrTokenDecryption) { t.Fatalf("auxiliary decryption error = %v", err) } t.Setenv("DWS_CONFIG_DIR", other) ResetRuntimeTokenCache() if _, err := ResolveAuxiliaryAccessToken(context.Background(), other, ""); err == nil { t.Fatal("current config without credentials succeeded") } edition.Override(&edition.Hooks{IsEmbedded: true}) if !strings.Contains(noCredentialsError().Error(), "认证") { t.Fatal("embedded credentials error changed") } } func TestCrossPlatformCoverageForceRefreshAndStdioFailureCoverage(t *testing.T) { oldLoad, oldFactory := loadRefreshTokenData, newRefreshProvider oldStop := stopStdio t.Cleanup(func() { loadRefreshTokenData, newRefreshProvider = oldLoad, oldFactory stopStdio = oldStop stdioMu.Lock() stdioClients = make(map[string]*transport.StdioClient) stdioMu.Unlock() }) fail := errors.New("failure") _ = oldFactory(t.TempDir()) loadRefreshTokenData = func(string) (*authpkg.TokenData, error) { return nil, fail } if _, err := ForceRefreshAccessToken(context.Background(), "config"); !errors.Is(err, fail) { t.Fatalf("load rejected token error = %v", err) } loadRefreshTokenData = func(string) (*authpkg.TokenData, error) { return &authpkg.TokenData{AccessToken: "rejected"}, nil } for _, tc := range []struct { getter fakeAccessTokenGetter want string }{ {getter: fakeAccessTokenGetter{err: fail}, want: "failure"}, {getter: fakeAccessTokenGetter{token: " "}, want: "empty"}, {getter: fakeAccessTokenGetter{token: " refreshed "}}, } { newRefreshProvider = func(string) rejectedAccessTokenRefresher { return tc.getter } got, err := ForceRefreshAccessToken(context.Background(), "config") if tc.want != "" && (err == nil || !strings.Contains(err.Error(), tc.want)) { t.Fatalf("refresh error = %v, want %q", err, tc.want) } if tc.want == "" && (err != nil || got != "refreshed") { t.Fatalf("refreshed token = %q, %v", got, err) } } stopStdio = func(*transport.StdioClient) error { return fail } RegisterStdioClient("all", transport.NewStdioClient("unused", nil, nil)) StopAllStdioClients() RegisterStdioClient("one", transport.NewStdioClient("unused", nil, nil)) if !StopStdioClient("one") { t.Fatal("registered stdio client not stopped") } RegisterStdioClient("plugin/server", transport.NewStdioClient("unused", nil, nil)) if got := StopStdioClientsByPlugin("plugin"); got != 1 { t.Fatalf("stopped plugin clients = %d", got) } } func TestCrossPlatformCoverageOverlayRecoveryHostAndHelperRemainingCoverage(t *testing.T) { root := t.TempDir() writeOverlay := filepath.Join(root, "overlay.json") if err := os.WriteFile(writeOverlay, []byte(`{"toolOverrides":{"tool":{}}}`), 0o600); err != nil { t.Fatal(err) } for _, raw := range []json.RawMessage{ json.RawMessage(`"missing.json"`), json.RawMessage(`"unterminated`), json.RawMessage(`{`), json.RawMessage(`"overlay.json"`), json.RawMessage(`{"id":"","command":"","toolOverrides":{"tool":{}}}`), } { p := &plugin.Plugin{Root: root, Manifest: plugin.Manifest{Name: "plugin", Description: "description", MCPServers: map[string]*plugin.MCPServer{"server": {CLI: raw}}}} overlay := resolveStdioOverlay(p, plugin.StdioServerClient{Key: "server", Client: transport.NewStdioClient("unused", nil, nil)}) if overlay.ID == "" || overlay.Command == "" { t.Fatalf("overlay defaults missing: %#v", overlay) } } p := &plugin.Plugin{Root: root, Manifest: plugin.Manifest{Name: "plugin", Description: "description", MCPServers: map[string]*plugin.MCPServer{"server": {CLI: json.RawMessage(`{}`)}}}} if descriptor := registerStdioServerFromManifest(p, plugin.StdioServerClient{Key: "server"}); descriptor.Endpoint == "" { t.Fatalf("empty overlay descriptor = %#v", descriptor) } p.Manifest.MCPServers["server"].CLI = json.RawMessage(`{"toolOverrides":{"tool":{}}}`) if descriptor := registerStdioServerFromManifest(p, plugin.StdioServerClient{Key: "server", Client: transport.NewStdioClient("unused", nil, nil)}); descriptor.Endpoint == "" { t.Fatalf("stdio overlay registration = %#v", descriptor) } oldEdition := edition.Get() t.Cleanup(func() { edition.Override(oldEdition); SetDynamicServers(nil) }) edition.Override(&edition.Hooks{ConfigDir: func() string { return "" }}) captureRuntimeFailure(executor.Invocation{}, nil, nil) captureRuntimeFailure(executor.Invocation{}, errors.New("raw"), nil) t.Setenv(authpkg.AgentCodeEnv, "") if hostControlProviderFromEnv() != "" { t.Fatal("host control enabled without agent code") } t.Setenv(authpkg.AgentCodeEnv, "agent") t.Setenv(agentproduct.EnvName, "") edition.Override(&edition.Hooks{MergeHeaders: func(headers map[string]string) map[string]string { return headers }}) if got := hostControlProviderFromEnv(); got != edition.DefaultOSSClawType { t.Fatalf("default claw type = %q", got) } edition.Override(&edition.Hooks{MergeHeaders: func(map[string]string) map[string]string { return map[string]string{"claw-type": "custom"} }}) if got := effectiveClawType(); got != "custom" { t.Fatalf("custom claw type = %q", got) } } func TestCrossPlatformCoverageConfigAndCacheCommandRemainingCoverage(t *testing.T) { for _, command := range []*cobra.Command{newConfigCommand(), newCacheCommand()} { command.SetOut(io.Discard) rootWrap := &cobra.Command{Use: "dws"} rootWrap.PersistentFlags().String("format", "json", "") rootWrap.AddCommand(command) command.SetOut(io.Discard) if err := command.RunE(command, nil); err != nil { t.Fatal(err) } } t.Setenv("DWS_CONFIG_DIR", "configured") configCmd := &cobra.Command{Use: "config"} var configOut bytes.Buffer configCmd.SetOut(&configOut) if err := writeConfigJSON(configCmd, filterVisible(nil), true); err != nil { t.Fatal(err) } list := newConfigListCommand() list.SetOut(io.Discard) _ = list.Flags().Set("category", "core") _ = list.Flags().Set("show-values", "true") _ = list.Flags().Set("show-hidden", "true") _ = list.Flags().Set("json", "true") if err := runConfigList(list, nil); err != nil { t.Fatal(err) } cacheRoot := &cobra.Command{Use: "root"} cacheRoot.PersistentFlags().String("format", "", "") cacheCmd := &cobra.Command{Use: "cache"} cacheRoot.AddCommand(cacheCmd) for _, format := range []string{"json", "pretty", "table"} { _ = cacheRoot.PersistentFlags().Set("format", format) cacheCmd.SetOut(io.Discard) if err := printCacheCompatNotice(cacheCmd, "dws cache status"); err != nil { t.Fatal(err) } } fail := errors.New("write") cacheCmd.SetOut(appFailWriter{err: fail}) _ = cacheRoot.PersistentFlags().Set("format", "pretty") if err := printCacheCompatNotice(cacheCmd, "dws cache status"); !errors.Is(err, fail) { t.Fatalf("pretty write error = %v", err) } _ = cacheRoot.PersistentFlags().Set("format", "table") if err := printCacheCompatNotice(cacheCmd, "dws cache status"); !errors.Is(err, fail) { t.Fatalf("table write error = %v", err) } }