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

1470 lines
60 KiB
Go

package app
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"time"
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/keychain"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pat"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
)
type authCoverageCaller struct {
result *edition.ToolResult
err error
}
func (c *authCoverageCaller) CallTool(context.Context, string, string, map[string]any) (*edition.ToolResult, error) {
return c.result, c.err
}
func (c *authCoverageCaller) CallToolWithToken(ctx context.Context, _ string, productID, toolName string, args map[string]any) (*edition.ToolResult, error) {
return c.CallTool(ctx, productID, toolName, args)
}
func (*authCoverageCaller) Format() string { return "json" }
func (*authCoverageCaller) DryRun() bool { return false }
func (*authCoverageCaller) Fields() string { return "" }
func (*authCoverageCaller) JQ() string { return "" }
func authCoverageRoot(child *cobra.Command, format string, yes bool) (*cobra.Command, *bytes.Buffer, *bytes.Buffer) {
root := &cobra.Command{Use: "dws"}
root.SetContext(context.Background())
child.SetContext(context.Background())
root.PersistentFlags().String("format", format, "")
root.PersistentFlags().Bool("yes", yes, "")
root.PersistentFlags().String("profile", "", "")
root.AddCommand(child)
out := &bytes.Buffer{}
errOut := &bytes.Buffer{}
root.SetOut(out)
root.SetErr(errOut)
return root, out, errOut
}
func authCoverageRunLogin(t *testing.T, caller edition.ToolCaller, format string, yes bool, flags map[string]string) (string, string, error) {
t.Helper()
cmd := newAuthLoginCommand(caller)
root, out, errOut := authCoverageRoot(cmd, format, yes)
for name, value := range flags {
flagSet := cmd.Flags()
if root.PersistentFlags().Lookup(name) != nil {
flagSet = root.PersistentFlags()
}
if err := flagSet.Set(name, value); err != nil {
t.Fatalf("set %s: %v", name, err)
}
}
err := cmd.RunE(cmd, nil)
return out.String(), errOut.String(), err
}
func TestCrossPlatformCoverageAuthCoverageFormsParentAndTargets(t *testing.T) {
oldEdition := edition.Get()
oldClientID := authpkg.ClientID()
oldClientSecret := authpkg.ClientSecret()
oldRunForm := authRunForm
oldPrompt := authLoginManualCredentialsPrompt
oldSaveConfig := authSaveAppConfig
oldResolve := authResolveProfile
t.Cleanup(func() {
edition.Override(oldEdition)
authpkg.SetClientID(oldClientID)
authpkg.SetClientSecret(oldClientSecret)
authRunForm = oldRunForm
authLoginManualCredentialsPrompt = oldPrompt
authSaveAppConfig = oldSaveConfig
authResolveProfile = oldResolve
})
edition.Override(&edition.Hooks{})
parent := buildAuthCommand(nil)
if parent.CommandPath() == "" || parent.RunE(parent, nil) != nil {
t.Fatal("auth parent should render help")
}
edition.Override(&edition.Hooks{HideAuthLogin: true})
if got := buildAuthCommand(nil).Commands(); len(got) != 7 {
t.Fatalf("hidden-login subcommands = %d, want 7", len(got))
}
authRunForm = func(*huh.Form) error { return nil }
if choice, err := selectAuthLoginGuideAction(); err != nil || choice != authLoginGuideDirectCLI {
t.Fatalf("guide choice = %q, %v", choice, err)
}
if id, secret, err := promptAuthLoginManualCredentials(); err != nil || id != "" || secret != "" {
t.Fatalf("manual prompt = %q/%q, %v", id, secret, err)
}
if mode, err := selectLoginRecommendScopeMode(); err != nil || mode != pat.LoginRecommendScopeRecommended {
t.Fatalf("scope mode = %q, %v", mode, err)
}
products := []pat.LoginRecommendProduct{{ProductCode: "doc"}, {ProductCode: ""}}
if selected, err := selectLoginRecommendProducts(products); err != nil || len(selected) != 1 || selected[0] != "doc" {
t.Fatalf("selected products = %#v, %v", selected, err)
}
if selected, err := selectLoginRecommendProducts(nil); err != nil || selected != nil {
t.Fatalf("empty selected products = %#v, %v", selected, err)
}
if selected, err := selectLoginRecommendProducts([]pat.LoginRecommendProduct{{}}); err != nil || selected != nil {
t.Fatalf("blank selected products = %#v, %v", selected, err)
}
many := make([]pat.LoginRecommendProduct, 16)
for i := range many {
many[i].ProductCode = fmt.Sprintf("p%d", i)
}
if selected, err := selectLoginRecommendProducts(many); err != nil || len(selected) != 16 {
t.Fatalf("many selected products = %d, %v", len(selected), err)
}
if authLoginProductsNonEmpty(nil) == nil || authLoginProductsNonEmpty([]string{"doc"}) != nil {
t.Fatal("product validator mismatch")
}
authRunForm = func(*huh.Form) error { return errors.New("cancel") }
if _, err := selectAuthLoginGuideAction(); err == nil {
t.Fatal("guide cancellation should fail")
}
if _, _, err := promptAuthLoginManualCredentials(); err == nil {
t.Fatal("credential cancellation should fail")
}
if _, err := selectLoginRecommendScopeMode(); err == nil {
t.Fatal("scope cancellation should fail")
}
if _, err := selectLoginRecommendProducts(products); err == nil {
t.Fatal("product cancellation should fail")
}
if authLoginNonEmpty("field")(" ") == nil || authLoginNonEmpty("field")("value") != nil {
t.Fatal("non-empty validator mismatch")
}
cmd := &cobra.Command{}
cmd.SetErr(io.Discard)
if err := applyAuthLoginGuideAction(cmd, t.TempDir(), authLoginGuideDirectCLI); err != nil {
t.Fatal(err)
}
if err := applyAuthLoginGuideAction(cmd, t.TempDir(), authLoginGuideConfigureAgentApp); err != nil {
t.Fatal(err)
}
if err := applyAuthLoginGuideAction(cmd, t.TempDir(), "unknown"); err == nil {
t.Fatal("unknown guide action should fail")
}
authLoginManualCredentialsPrompt = func() (string, string, error) { return "", "", errors.New("cancel") }
if err := applyAuthLoginGuideAction(cmd, t.TempDir(), authLoginGuideManualCredentials); err == nil {
t.Fatal("manual prompt error should propagate")
}
authLoginManualCredentialsPrompt = func() (string, string, error) { return "id", "secret", nil }
authSaveAppConfig = func(string, *authpkg.AppConfig) error { return errors.New("save") }
if err := applyAuthLoginGuideAction(cmd, t.TempDir(), authLoginGuideManualCredentials); err == nil {
t.Fatal("app-config save error should propagate")
}
authSaveAppConfig = func(string, *authpkg.AppConfig) error { return nil }
if err := applyAuthLoginGuideAction(cmd, t.TempDir(), authLoginGuideManualCredentials); err != nil {
t.Fatal(err)
}
authResolveProfile = func(string, string) (*authpkg.Profile, error) {
return &authpkg.Profile{CorpID: " ding-profile "}, nil
}
if got, err := resolveAuthLoginTargetCorpID("cfg", "name"); err != nil || got != "ding-profile" {
t.Fatalf("resolved target = %q, %v", got, err)
}
authResolveProfile = func(string, string) (*authpkg.Profile, error) { return nil, errors.New("missing") }
for selector, want := range map[string]struct {
value string
err bool
}{"": {"", false}, "ding-direct": {"ding-direct", false}, "other": {"", true}} {
got, err := resolveAuthLoginTargetCorpID("cfg", selector)
if got != want.value || (err != nil) != want.err {
t.Fatalf("target %q = %q, %v", selector, got, err)
}
}
badToken := &cobra.Command{}
badToken.Flags().Bool("token", false, "")
if _, err := resolveAuthLoginConfig(badToken); err == nil {
t.Fatal("invalid token flag should fail")
}
badDevice := &cobra.Command{}
badDevice.Flags().String("token", "", "")
badDevice.Flags().String("device", "", "")
if _, err := resolveAuthLoginConfig(badDevice); err == nil {
t.Fatal("invalid device flag should fail")
}
badForce := &cobra.Command{}
badForce.Flags().String("token", "", "")
badForce.Flags().Bool("device", false, "")
badForce.Flags().String("force", "", "")
if _, err := resolveAuthLoginConfig(badForce); err == nil {
t.Fatal("invalid force flag should fail")
}
badRecommend := &cobra.Command{}
badRecommend.Flags().String("token", "", "")
badRecommend.Flags().Bool("device", false, "")
badRecommend.Flags().Bool("force", false, "")
badRecommend.Flags().String("recommend", "", "")
if _, err := resolveAuthLoginConfig(badRecommend); err == nil {
t.Fatal("invalid recommend flag should fail")
}
}
func TestCrossPlatformCoverageAuthCoverageLoginFlows(t *testing.T) {
configDir := t.TempDir()
t.Setenv("DWS_CONFIG_DIR", configDir)
oldSave := authSaveTokenData
oldDevice := authDeviceLogin
oldOAuth := authOAuthLogin
oldRunRecommend := authRunLoginRecommend
oldRunWait := authRunDirectPATWait
oldPlan := authPlanLoginRecommend
oldScope := loginRecommendScopeModeSelector
oldProducts := loginRecommendProductSelector
oldInteractive := authLoginInteractiveTerminal
oldResolve := authResolveProfile
t.Cleanup(func() {
authSaveTokenData = oldSave
authDeviceLogin = oldDevice
authOAuthLogin = oldOAuth
authRunLoginRecommend = oldRunRecommend
authRunDirectPATWait = oldRunWait
authPlanLoginRecommend = oldPlan
loginRecommendScopeModeSelector = oldScope
loginRecommendProductSelector = oldProducts
authLoginInteractiveTerminal = oldInteractive
authResolveProfile = oldResolve
})
authInteractiveFalse := func() bool { return false }
authLoginInteractiveTerminal = authInteractiveFalse
authResolveProfile = func(string, string) (*authpkg.Profile, error) { return nil, errors.New("missing") }
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"profile": "bad"}); err == nil {
t.Fatal("invalid login profile should fail")
}
authSaveTokenData = func(string, *authpkg.TokenData) error { return errors.New("save") }
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"token": "token"}); err == nil {
t.Fatal("token save should fail")
}
authSaveTokenData = func(string, *authpkg.TokenData) error { return nil }
if out, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"token": " token "}); err != nil || !strings.Contains(out, "登录成功") {
t.Fatalf("token login = %q, %v", out, err)
}
if out, _, err := authCoverageRunLogin(t, nil, "json", true, map[string]string{"token": "token"}); err != nil || !strings.Contains(out, `"token_valid": true`) {
t.Fatalf("json token login = %q, %v", out, err)
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"token": "token", "pre-url": "https://example.com"}); err == nil {
t.Fatal("invalid pre-release host should fail")
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"token": "token", "mcp-url": "http://remote.example.com"}); err == nil {
t.Fatal("remote plaintext MCP URL should fail")
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"token": "token", "pre-url": "https://pre-login.dingtalk.io"}); err != nil {
t.Fatalf("pre-release token login = %v", err)
}
authDeviceLogin = func(*authpkg.DeviceFlowProvider, context.Context) (*authpkg.TokenData, error) {
return nil, errors.New("device")
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"device": "true"}); err == nil {
t.Fatal("device error should propagate")
}
authDeviceLogin = func(provider *authpkg.DeviceFlowProvider, _ context.Context) (*authpkg.TokenData, error) {
if provider.IdentityEnricher == nil {
t.Error("device login missing shared identity enricher")
}
return &authpkg.TokenData{AccessToken: "a", ExpiresAt: time.Now().Add(time.Hour)}, nil
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"device": "true", "no-browser": "true"}); err != nil {
t.Fatal(err)
}
authDeviceLogin = func(provider *authpkg.DeviceFlowProvider, _ context.Context) (*authpkg.TokenData, error) {
if provider.LoginRegion != authpkg.LoginRegionInternational {
t.Errorf("device login region = %q, want international", provider.LoginRegion)
}
return &authpkg.TokenData{AccessToken: "a", ExpiresAt: time.Now().Add(time.Hour)}, nil
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"device": "true", "intl": "true"}); err != nil {
t.Fatalf("international device login = %v", err)
}
authOAuthLogin = func(*authpkg.OAuthProvider, context.Context, bool) (*authpkg.TokenData, error) {
return nil, errors.New("oauth")
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, nil); err == nil {
t.Fatal("oauth error should propagate")
}
authOAuthLogin = func(provider *authpkg.OAuthProvider, _ context.Context, _ bool) (*authpkg.TokenData, error) {
if provider.IdentityEnricher == nil {
t.Error("OAuth login missing shared identity enricher")
}
return &authpkg.TokenData{
AccessToken: "a", ExpiresAt: time.Now().Add(time.Hour), RefreshToken: "r", RefreshExpAt: time.Now().Add(48 * time.Hour),
CorpName: "Corp", CorpID: "ding1", UserName: "User", UserID: "u",
}, nil
}
caller := &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: `{}`}}}}
if out, _, err := authCoverageRunLogin(t, caller, "table", true, map[string]string{"no-browser": "true"}); err != nil || !strings.Contains(out, "Corp") {
t.Fatalf("oauth success = %q, %v", out, err)
}
authOAuthLogin = func(provider *authpkg.OAuthProvider, _ context.Context, _ bool) (*authpkg.TokenData, error) {
if provider.LoginRegion != authpkg.LoginRegionInternational {
t.Errorf("OAuth login region = %q, want international", provider.LoginRegion)
}
return &authpkg.TokenData{AccessToken: "a", ExpiresAt: time.Now().Add(time.Hour)}, nil
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"intl": "true"}); err != nil {
t.Fatalf("international OAuth login = %v", err)
}
blockedConfigDir := t.TempDir()
if err := os.Mkdir(filepath.Join(blockedConfigDir, "mcp_url"), 0o700); err != nil {
t.Fatal(err)
}
t.Setenv("DWS_CONFIG_DIR", blockedConfigDir)
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"token": "token", "intl": "true"}); err == nil || !strings.Contains(err.Error(), "failed to persist MCP URL") {
t.Fatalf("MCP URL persist failure = %v", err)
}
t.Setenv("DWS_CONFIG_DIR", configDir)
authRunLoginRecommend = func(context.Context, edition.ToolCaller, io.Writer, pat.LoginRecommendOptions) error {
return errors.New("recommend")
}
if _, _, err := authCoverageRunLogin(t, nil, "table", true, map[string]string{"token": "x", "recommend": "true"}); err == nil {
t.Fatal("recommend error should propagate")
}
if _, _, err := authCoverageRunLogin(t, nil, "json", true, map[string]string{"token": "x", "recommend": "true"}); err == nil {
t.Fatal("JSON recommend error should propagate")
}
authRunLoginRecommend = func(context.Context, edition.ToolCaller, io.Writer, pat.LoginRecommendOptions) error {
return &apperrors.PATError{RawJSON: `{"code":"PAT_SCOPE_AUTH_REQUIRED"}`}
}
waited := false
authRunDirectPATWait = func(context.Context, *GlobalFlags, *apperrors.PATError, io.Writer) error {
waited = true
return nil
}
if _, _, err := authCoverageRunLogin(t, nil, "json", true, map[string]string{"token": "x", "recommend": "true"}); err != nil || !waited {
t.Fatalf("PAT wait = %v, waited=%v", err, waited)
}
authLoginInteractiveTerminal = func() bool { return true }
authPlanLoginRecommend = func(context.Context, edition.ToolCaller) (*pat.LoginRecommendPlan, error) {
return nil, errors.New("plan")
}
if _, _, err := authCoverageRunLogin(t, nil, "table", false, map[string]string{"token": "x"}); err == nil {
t.Fatal("plan error should propagate")
}
authPlanLoginRecommend = func(context.Context, edition.ToolCaller) (*pat.LoginRecommendPlan, error) {
return &pat.LoginRecommendPlan{AllGranted: true}, nil
}
if _, stderr, err := authCoverageRunLogin(t, nil, "table", false, map[string]string{"token": "x"}); err != nil || !strings.Contains(stderr, "全部授权") {
t.Fatalf("all-granted plan = %q, %v", stderr, err)
}
authPlanLoginRecommend = func(context.Context, edition.ToolCaller) (*pat.LoginRecommendPlan, error) {
return &pat.LoginRecommendPlan{Scopes: []string{"scope"}, Products: []pat.LoginRecommendProduct{{ProductCode: "doc"}}}, nil
}
loginRecommendScopeModeSelector = func() (pat.LoginRecommendScopeMode, error) { return "", errors.New("scope") }
if _, _, err := authCoverageRunLogin(t, nil, "table", false, map[string]string{"token": "x"}); err == nil {
t.Fatal("scope selector error should propagate")
}
loginRecommendScopeModeSelector = func() (pat.LoginRecommendScopeMode, error) { return pat.LoginRecommendScopeAll, nil }
loginRecommendProductSelector = func([]pat.LoginRecommendProduct) ([]string, error) { return []string{"doc"}, nil }
selected := false
authRunLoginRecommend = func(_ context.Context, _ edition.ToolCaller, _ io.Writer, opts pat.LoginRecommendOptions) error {
if opts.ProductSelector != nil {
_, err := opts.ProductSelector(opts.InitialPlan.Products)
selected = err == nil
}
return nil
}
if _, _, err := authCoverageRunLogin(t, nil, "table", false, map[string]string{"token": "x"}); err != nil || !selected {
t.Fatalf("interactive recommendation = %v, selected=%v", err, selected)
}
}
func TestCrossPlatformCoverageAuthCoverageContactEnrichment(t *testing.T) {
oldSave := authSaveTokenData
t.Cleanup(func() { authSaveTokenData = oldSave })
ctx := context.Background()
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", nil, nil); err != nil {
t.Fatal(err)
}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", &authCoverageCaller{}, &authpkg.TokenData{}); err != nil {
t.Fatal(err)
}
complete := &authpkg.TokenData{CorpID: "ding", CorpName: "Corp", UserID: "u", UserName: "User"}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", &authCoverageCaller{}, complete); err != nil {
t.Fatal(err)
}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", &authCoverageCaller{err: errors.New("call")}, &authpkg.TokenData{CorpID: "ding"}); err != nil {
t.Fatalf("contact failure must remain best effort: %v", err)
}
if err := enrichAuthLoginProfileFromContact(
ctx,
"cfg",
&authCoverageCaller{err: errors.New("call")},
&authpkg.TokenData{CorpID: "ding", UserID: "known", AccessToken: "token"},
); err != nil {
t.Fatalf("optional contact metadata failure with known userId = %v", err)
}
for _, text := range []string{"", "not-json", `{"result":[]}`, `{"result":[{"orgEmployeeModel":{}}]}`} {
caller := &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: text}}}}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", caller, &authpkg.TokenData{CorpID: "ding", AccessToken: "token"}); err != nil {
t.Fatalf("invalid contact %q: %v", text, err)
}
}
mismatch := &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: `{"result":[{"orgEmployeeModel":{"corpId":"other"}}]}`}}}}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", mismatch, &authpkg.TokenData{CorpID: "ding", AccessToken: "token"}); err != nil {
t.Fatalf("contact corp mismatch must remain best effort: %v", err)
}
same := &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: `{"result":[{"orgEmployeeModel":{"corpId":"ding","orgName":"Corp","userid":"u","name":"User"}}]}`}}}}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", same, complete); err != nil {
t.Fatal(err)
}
unchangedPartial := &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: `{"result":[{"orgEmployeeModel":{"corpId":"ding","orgName":"Corp","userid":"u"}}]}`}}}}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", unchangedPartial, &authpkg.TokenData{CorpID: "ding", CorpName: "Corp", UserID: "u"}); err != nil {
t.Fatal(err)
}
authSaveTokenData = func(string, *authpkg.TokenData) error { return errors.New("save") }
data := &authpkg.TokenData{CorpID: "ding", AccessToken: "token"}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", same, data); err != nil || data.CorpName != "Corp" || data.UserID != "u" {
t.Fatalf("enriched = %#v, %v", data, err)
}
known := &authpkg.TokenData{CorpID: "ding", UserID: "exchange-user", AccessToken: "token"}
differentContactUser := &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: `{"result":[{"orgEmployeeModel":{"corpId":"ding","orgName":"Corp","userid":"other-user","name":"Other User"}}]}`}}}}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", differentContactUser, known); err != nil {
t.Fatal(err)
}
if known.UserID != "exchange-user" || known.UserName != "" || known.CorpName != "Corp" {
t.Fatalf("token-exchange identity was overwritten: %#v", known)
}
multiOrg := &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: `{"result":[{"orgEmployeeModel":{"corpId":"other","userid":"other-user"}},{"orgEmployeeModel":{"corpId":"ding","orgName":"Target Corp","userid":"target-user","name":"Target User"}}]}`}}}}
multiOrgData := &authpkg.TokenData{CorpID: "ding", AccessToken: "token"}
if err := enrichAuthLoginProfileFromContact(ctx, "cfg", multiOrg, multiOrgData); err != nil || multiOrgData.UserID != "target-user" || multiOrgData.CorpName != "Target Corp" {
t.Fatalf("multi-org contact selection = %#v, %v", multiOrgData, err)
}
if _, ok := contactProfileIdentityFromJSON(
[]byte(`{"result":[{"orgEmployeeModel":{"corpId":"other-a","userid":"user-a"}},{"orgEmployeeModel":{"corpId":"other-b","userid":"user-b"}}]}`),
"ding",
); ok {
t.Fatal("multiple nonmatching organizations must not select an arbitrary contact identity")
}
if _, ok := contactProfileIdentityFromToolResult(nil); ok {
t.Fatal("nil result should not parse")
}
if got := firstNonEmptyString(" ", " value ", "later"); got != "value" {
t.Fatalf("first non-empty = %q", got)
}
}
func TestCrossPlatformCoverageContactFailureReusesOnlySameCorpHistoricalDisplayMetadata(t *testing.T) {
configDir := t.TempDir()
if err := authpkg.SaveProfiles(configDir, &authpkg.ProfilesConfig{
Version: 1,
Profiles: []authpkg.Profile{{
CorpID: "ding_ecological_worker",
CorpName: "Historical Corp",
UserID: "external-user",
UserName: "Historical Worker",
}},
}); err != nil {
t.Fatalf("SaveProfiles() error = %v", err)
}
for _, tc := range []struct {
name string
caller edition.ToolCaller
wantCorp string
}{
{
name: "contact business error",
caller: &authCoverageCaller{err: apperrors.NewAPI(
"business error: success=false",
apperrors.WithReason("business_error"),
)},
wantCorp: "Fresh Corp",
},
{
name: "contact has no identity",
caller: &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{Text: `{"success":false}`}}}},
wantCorp: "Fresh Corp",
},
{
name: "contact identity is missing user id",
caller: &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{
Text: `{"result":[{"orgEmployeeModel":{"corpId":"ding_ecological_worker","orgName":"Contact Corp"}}]}`,
}}}},
wantCorp: "Contact Corp",
},
{
name: "ordinary contact error",
caller: &authCoverageCaller{err: errors.New("network failure")},
wantCorp: "Fresh Corp",
},
{
name: "other contact business error",
caller: &authCoverageCaller{err: apperrors.NewAPI(
"permission denied",
apperrors.WithReason("business_error"),
)},
wantCorp: "Fresh Corp",
},
{
name: "contact caller unavailable",
caller: nil,
wantCorp: "Fresh Corp",
},
{
name: "contact returns another organization",
caller: &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{
Text: `{"result":[{"orgEmployeeModel":{"corpId":"ding_other","userid":"other-user"}}]}`,
}}}},
wantCorp: "Fresh Corp",
},
} {
t.Run(tc.name, func(t *testing.T) {
data := &authpkg.TokenData{
AccessToken: "new-access",
RefreshToken: "new-refresh",
CorpID: "ding_ecological_worker",
CorpName: "Fresh Corp",
}
if err := enrichAuthLoginProfileFromContact(context.Background(), configDir, tc.caller, data); err != nil {
t.Fatalf("contact failure blocked historical identity recovery: %v", err)
}
if data.UserID != "" || data.UserName != "Historical Worker" {
t.Fatalf("historical metadata supplied UID evidence: %#v", data)
}
if data.CorpName != tc.wantCorp {
t.Fatalf("corp name = %q, want %q", data.CorpName, tc.wantCorp)
}
if data.AccessToken != "new-access" || data.RefreshToken != "new-refresh" {
t.Fatalf("new token material was changed: %#v", data)
}
})
}
}
func TestCrossPlatformCoverageContactFailureDoesNotGuessHistoricalIdentity(t *testing.T) {
businessErr := apperrors.NewAPI(
"business error: success=false",
apperrors.WithReason("business_error"),
)
for _, tc := range []struct {
name string
corpID string
profiles []authpkg.Profile
callErr error
}{
{
name: "same corp has two identities",
corpID: "ding_ecological_worker",
profiles: []authpkg.Profile{
{CorpID: "ding_ecological_worker", UserID: "external-user"},
{CorpID: "ding_ecological_worker", UserID: "external-user-b"},
},
callErr: businessErr,
},
{
name: "same corp has one identity and one blank profile",
corpID: "ding_ecological_worker",
profiles: []authpkg.Profile{
{CorpID: "ding_ecological_worker", UserID: "external-user"},
{CorpID: "ding_ecological_worker"},
},
callErr: businessErr,
},
{
name: "identity belongs to another corp",
corpID: "ding_ecological_worker",
profiles: []authpkg.Profile{
{CorpID: "ding_other", UserID: "external-user"},
},
callErr: businessErr,
},
{
name: "no historical identity",
corpID: "ding_ecological_worker",
callErr: businessErr,
},
} {
t.Run(tc.name, func(t *testing.T) {
configDir := t.TempDir()
if err := authpkg.SaveProfiles(configDir, &authpkg.ProfilesConfig{Version: 2, Profiles: tc.profiles}); err != nil {
t.Fatalf("SaveProfiles() error = %v", err)
}
data := &authpkg.TokenData{AccessToken: "new-access", CorpID: tc.corpID}
err := enrichAuthLoginProfileFromContact(
context.Background(),
configDir,
&authCoverageCaller{err: tc.callErr},
data,
)
if err != nil {
t.Fatalf("contact failure must not block unresolved legacy login: %v", err)
}
if data.UserID != "" {
t.Fatalf("ambiguous/cross-corp identity was reused: %#v", data)
}
})
}
}
func TestCrossPlatformCoverageContactHistoryFallbackEdges(t *testing.T) {
for _, data := range []*authpkg.TokenData{
nil,
{UserID: "known"},
{},
} {
reused, err := enrichAuthLoginProfileFromHistory(t.TempDir(), data)
if reused || err != nil {
t.Fatalf("ineligible history fallback = %v, %v", reused, err)
}
}
configDir := t.TempDir()
if err := authpkg.SaveProfiles(configDir, &authpkg.ProfilesConfig{
Version: 2,
Profiles: []authpkg.Profile{{
CorpID: "ding_external",
CorpName: "Historical Corp",
UserID: "external-user",
UserName: "Historical Worker",
}},
}); err != nil {
t.Fatalf("SaveProfiles() error = %v", err)
}
data := &authpkg.TokenData{CorpID: "ding_external"}
reused, err := enrichAuthLoginProfileFromHistory(configDir, data)
if err != nil || !reused {
t.Fatalf("history fallback = %v, %v", reused, err)
}
if data.CorpName != "Historical Corp" || data.UserName != "Historical Worker" || data.UserID != "" {
t.Fatalf("history metadata = %#v", data)
}
corruptDir := t.TempDir()
if err := os.Mkdir(authpkg.ProfilesPath(corruptDir), 0o700); err != nil {
t.Fatalf("create unreadable profiles path: %v", err)
}
if reused, err := enrichAuthLoginProfileFromHistory(corruptDir, &authpkg.TokenData{CorpID: "ding_external"}); reused || err == nil {
t.Fatalf("corrupt history fallback = %v, %v; want load error", reused, err)
}
businessErr := apperrors.NewAPI(
"business error: success=false",
apperrors.WithReason("business_error"),
)
for _, tc := range []struct {
name string
caller *authCoverageCaller
}{
{
name: "contact business error",
caller: &authCoverageCaller{err: businessErr},
},
{
name: "contact has no identity",
caller: &authCoverageCaller{result: &edition.ToolResult{}},
},
{
name: "contact identity is missing user id",
caller: &authCoverageCaller{result: &edition.ToolResult{Content: []edition.ContentBlock{{
Text: `{"result":[{"orgEmployeeModel":{"corpId":"ding_external"}}]}`,
}}}},
},
} {
t.Run(tc.name, func(t *testing.T) {
err := enrichAuthLoginProfileFromContact(
context.Background(),
corruptDir,
tc.caller,
&authpkg.TokenData{CorpID: "ding_external", AccessToken: "new-access"},
)
if err != nil {
t.Fatalf("best-effort contact/history lookup blocked login: %v", err)
}
})
}
}
func TestCrossPlatformCoverageAuthLoginConfigPreservesHistoryIdentityHint(t *testing.T) {
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
oldResolve := authResolveProfile
oldLoad := authLoadProfiles
t.Cleanup(func() {
authResolveProfile = oldResolve
authLoadProfiles = oldLoad
})
explicit := &authpkg.Profile{CorpID: "ding_same", UserID: "user_2", Name: "second"}
current := &authpkg.Profile{CorpID: "ding_current", UserID: "current_user"}
authResolveProfile = func(_ string, selector string) (*authpkg.Profile, error) {
switch selector {
case "ding_same:user_2":
clone := *explicit
return &clone, nil
case "external-worker":
return &authpkg.Profile{Name: "external-worker", CorpID: "ding_external"}, nil
case "":
clone := *current
return &clone, nil
default:
return nil, errors.New("missing")
}
}
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) {
return &authpkg.ProfilesConfig{}, nil
}
cmd := newAuthLoginCommand(nil)
root, _, _ := authCoverageRoot(cmd, "table", true)
if err := root.PersistentFlags().Set("profile", "ding_same:user_2"); err != nil {
t.Fatal(err)
}
cfg, err := resolveAuthLoginConfig(cmd)
if err != nil {
t.Fatal(err)
}
if cfg.TargetCorpID != "ding_same" || cfg.HistoryProfileSelector != "ding_same:user_2" || !cfg.HistoryProfileSelectorExplicit {
t.Fatalf("explicit login config = %#v", cfg)
}
if target, hint, exact, err := resolveAuthLoginTarget("cfg", "external-worker"); err != nil ||
target != "ding_external" || hint != "ding_external" || !exact {
t.Fatalf("blank-userId profile target = %q/%q/%v, %v", target, hint, exact, err)
}
if err := root.PersistentFlags().Set("profile", ""); err != nil {
t.Fatal(err)
}
cfg, err = resolveAuthLoginConfig(cmd)
if err != nil {
t.Fatal(err)
}
if cfg.TargetCorpID != "" || cfg.HistoryProfileSelector != "ding_current:current_user" || cfg.HistoryProfileSelectorExplicit {
t.Fatalf("implicit login config constrained authorization target: %#v", cfg)
}
if _, _, _, err := resolveAuthLoginTarget("cfg", "ding_same:missing"); err == nil {
t.Fatal("missing exact profile must not be reinterpreted as a corpId")
}
if target, hint, explicitHint, err := resolveAuthLoginTarget("cfg", "ding_new"); err != nil || target != "ding_new" || hint != "" || explicitHint {
t.Fatalf("new organization target = %q/%q/%v, %v", target, hint, explicitHint, err)
}
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) {
return &authpkg.ProfilesConfig{Profiles: []authpkg.Profile{
{CorpID: "ding_ambiguous", UserID: "user_1"},
{CorpID: "ding_ambiguous", UserID: "user_2"},
}}, nil
}
if _, _, _, err := resolveAuthLoginTarget("cfg", "ding_ambiguous"); err == nil {
t.Fatal("ambiguous known organization must require an exact profile")
}
}
func TestCrossPlatformCoverageOAuthAndDeviceKeepFreshUnknownIdentityIsolatedFromExactHistory(t *testing.T) {
oldResolve := authResolveProfile
oldLoad := authLoadProfiles
oldDevice := authDeviceLogin
oldOAuth := authOAuthLogin
oldInteractive := authLoginInteractiveTerminal
t.Cleanup(func() {
authResolveProfile = oldResolve
authLoadProfiles = oldLoad
authDeviceLogin = oldDevice
authOAuthLogin = oldOAuth
authLoginInteractiveTerminal = oldInteractive
})
authResolveProfile = authpkg.ResolveProfile
authLoadProfiles = authpkg.LoadProfiles
authLoginInteractiveTerminal = func() bool { return false }
t.Cleanup(func() { authpkg.SetRuntimeProfile("") })
for _, flow := range []string{"oauth", "device"} {
t.Run(flow, func(t *testing.T) {
configDir := t.TempDir()
keychainDir := t.TempDir()
t.Setenv("DWS_CONFIG_DIR", configDir)
t.Setenv(keychain.DisableKeychainEnv, "1")
t.Setenv(keychain.StorageDirEnv, keychainDir)
// StorageDirEnv isolates file-backed keychains, while Windows uses
// DPAPI-protected HKCU values. Give every flow its own namespace so
// OAuth/device fixtures cannot leak into each other or later tests.
t.Setenv(keychain.TestNamespaceEnv, keychainDir)
t.Cleanup(func() {
if err := keychain.RemoveAuthTokenEntries(keychain.Service); err != nil {
t.Errorf("clean auth keychain fixture: %v", err)
}
})
authpkg.SetRuntimeProfile("")
const (
corpID = "ding_same"
historicalUID = "user_a"
exactSelector = corpID + ":" + historicalUID
)
oldToken := &authpkg.TokenData{
AccessToken: "old-user-a-access",
RefreshToken: "old-user-a-refresh",
ExpiresAt: time.Now().Add(time.Hour),
RefreshExpAt: time.Now().Add(24 * time.Hour),
CorpID: corpID,
CorpName: "Same Corp",
UserID: historicalUID,
UserName: "Historical User A",
}
if err := authpkg.SaveTokenData(configDir, oldToken); err != nil {
t.Fatalf("persist historical exact identity: %v", err)
}
caller := &authCoverageCaller{err: errors.New("contact unavailable")}
var enriched *authpkg.TokenData
freshToken := func() *authpkg.TokenData {
return &authpkg.TokenData{
AccessToken: "fresh-user-b-access-" + flow,
RefreshToken: "fresh-user-b-refresh-" + flow,
ExpiresAt: time.Now().Add(time.Hour),
RefreshExpAt: time.Now().Add(24 * time.Hour),
CorpID: corpID,
}
}
persistUnknown := func(ctx context.Context, identityEnricher func(context.Context, *authpkg.TokenData) error) (*authpkg.TokenData, error) {
if identityEnricher == nil {
return nil, errors.New("missing identity enricher")
}
data := freshToken()
if err := identityEnricher(ctx, data); err != nil {
return nil, err
}
enriched = data
if data.UserID != "" {
return nil, fmt.Errorf("historical profile supplied unproven userId %q", data.UserID)
}
if err := authpkg.SaveTokenData(configDir, data); err != nil {
return nil, err
}
return data, nil
}
flags := map[string]string{"profile": exactSelector}
switch flow {
case "device":
flags["device"] = "true"
authDeviceLogin = func(provider *authpkg.DeviceFlowProvider, ctx context.Context) (*authpkg.TokenData, error) {
return persistUnknown(ctx, provider.IdentityEnricher)
}
case "oauth":
authOAuthLogin = func(provider *authpkg.OAuthProvider, ctx context.Context, _ bool) (*authpkg.TokenData, error) {
if provider.TargetCorpID != corpID {
return nil, fmt.Errorf("OAuth target corp = %q", provider.TargetCorpID)
}
return persistUnknown(ctx, provider.IdentityEnricher)
}
}
if _, _, err := authCoverageRunLogin(t, caller, "table", true, flags); err != nil {
t.Fatalf("%s login with unresolved fresh identity: %v", flow, err)
}
if enriched == nil || enriched.UserID != "" ||
enriched.LegacyOrgScopedProfile != exactSelector ||
enriched.CorpName != "Same Corp" ||
enriched.UserName != "Historical User A" {
t.Fatalf("%s history hint became identity evidence: %#v", flow, enriched)
}
historical, err := authpkg.LoadTokenDataForProfile(configDir, exactSelector)
if err != nil {
t.Fatalf("load historical exact identity: %v", err)
}
if historical.AccessToken != oldToken.AccessToken || historical.UserID != historicalUID {
t.Fatalf("historical exact slot was overwritten: %#v", historical)
}
profiles, err := authpkg.LoadProfiles(configDir)
if err != nil {
t.Fatalf("load profiles: %v", err)
}
var unresolved *authpkg.Profile
for i := range profiles.Profiles {
profile := &profiles.Profiles[i]
if profile.CorpID == corpID && profile.UserID == "" {
unresolved = profile
break
}
}
if unresolved == nil {
t.Fatalf("fresh UID-less token did not create an unresolved profile: %#v", profiles.Profiles)
}
unresolvedSelector := authpkg.ProfileSelectionSelector(*unresolved, profiles)
if unresolvedSelector == "" || unresolvedSelector == exactSelector {
t.Fatalf("unresolved selector = %q", unresolvedSelector)
}
fresh, err := authpkg.LoadTokenDataForProfile(configDir, unresolvedSelector)
if err != nil {
t.Fatalf("load fresh unresolved identity: %v", err)
}
if fresh.AccessToken != "fresh-user-b-access-"+flow || fresh.UserID != "" {
t.Fatalf("fresh token was not isolated in unresolved org slot: %#v", fresh)
}
})
}
}
func TestCrossPlatformCoverageHistoricalIdentityPriorityAndBlankUserID(t *testing.T) {
oldLoad := authLoadProfiles
t.Cleanup(func() { authLoadProfiles = oldLoad })
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return nil, nil }
if reused, err := enrichAuthLoginProfileFromHistory("cfg", &authpkg.TokenData{CorpID: "ding_same"}); reused || err != nil {
t.Fatalf("nil history registry = reused=%v err=%v", reused, err)
}
cfg := &authpkg.ProfilesConfig{
CurrentProfile: "ding_same:user_1",
OrgCurrentProfiles: map[string]string{
"ding_same": "ding_same:user_2",
},
Profiles: []authpkg.Profile{
{CorpID: "ding_same", CorpName: "Same Corp", UserID: "user_1", UserName: "First"},
{CorpID: "ding_same", CorpName: "Same Corp", UserID: "user_2", UserName: "Second"},
},
}
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return cfg, nil }
explicitData := &authpkg.TokenData{CorpID: "ding_same"}
reused, err := enrichAuthLoginProfileFromHistory("cfg", explicitData, authLoginHistoryHint{Selector: "ding_same:user_1", Explicit: true})
if err != nil || !reused || explicitData.UserID != "" ||
explicitData.LegacyOrgScopedProfile != "ding_same:user_1" ||
explicitData.CorpName != "Same Corp" || explicitData.UserName != "First" {
t.Fatalf("explicit history selection = %#v, reused=%v err=%v", explicitData, reused, err)
}
mismatchedHintData := &authpkg.TokenData{CorpID: "ding_same"}
reused, err = enrichAuthLoginProfileFromHistory("cfg", mismatchedHintData, authLoginHistoryHint{Selector: "ding_other:user_9", Explicit: true})
if err != nil || reused || mismatchedHintData.UserID != "" {
t.Fatalf("cross-corp explicit hint reused another identity: %#v, reused=%v err=%v", mismatchedHintData, reused, err)
}
orgCurrentData := &authpkg.TokenData{CorpID: "ding_same"}
reused, err = enrichAuthLoginProfileFromHistory("cfg", orgCurrentData)
if err != nil || reused || orgCurrentData.UserID != "" {
t.Fatalf("implicit multi-account org-current was treated as identity proof: %#v, reused=%v err=%v", orgCurrentData, reused, err)
}
cfg.Profiles = []authpkg.Profile{cfg.Profiles[1]}
soleData := &authpkg.TokenData{CorpID: "ding_same"}
reused, err = enrichAuthLoginProfileFromHistory("cfg", soleData)
if err != nil || !reused || soleData.UserID != "" ||
soleData.CorpName != "Same Corp" || soleData.UserName != "Second" {
t.Fatalf("sole history selection = %#v, reused=%v err=%v", soleData, reused, err)
}
cfg.Profiles = []authpkg.Profile{
{CorpID: "ding_same", CorpName: "Same Corp", UserID: "user_1", UserName: "First"},
{CorpID: "ding_same", CorpName: "Same Corp", UserID: "user_2", UserName: "Second"},
}
cfg.OrgCurrentProfiles = nil
currentData := &authpkg.TokenData{CorpID: "ding_same"}
reused, err = enrichAuthLoginProfileFromHistory("cfg", currentData)
if err != nil || reused || currentData.UserID != "" {
t.Fatalf("implicit multi-account current was treated as identity proof: %#v, reused=%v err=%v", currentData, reused, err)
}
cfg.CurrentProfile = ""
ambiguousData := &authpkg.TokenData{CorpID: "ding_same"}
reused, err = enrichAuthLoginProfileFromHistory("cfg", ambiguousData)
if err != nil || reused || ambiguousData.UserID != "" {
t.Fatalf("ambiguous history selection = %#v, reused=%v err=%v", ambiguousData, reused, err)
}
cfg.Profiles = []authpkg.Profile{{
Name: "external-worker", CorpID: "ding_same", CorpName: "Legacy Corp", UserName: "Legacy Worker",
}}
blankData := &authpkg.TokenData{CorpID: "ding_same"}
reused, err = enrichAuthLoginProfileFromHistory("cfg", blankData, authLoginHistoryHint{Selector: "external-worker", Explicit: true})
if err != nil || !reused || blankData.UserID != "" || blankData.LegacyOrgScopedProfile != "external-worker" || blankData.CorpName != "Legacy Corp" || blankData.UserName != "Legacy Worker" {
t.Fatalf("blank-userId history selection = %#v, reused=%v err=%v", blankData, reused, err)
}
contactBlankData := &authpkg.TokenData{CorpID: "ding_same", AccessToken: "new-token"}
if err := enrichAuthLoginProfileFromContact(
context.Background(),
"cfg",
&authCoverageCaller{err: errors.New("contact unavailable")},
contactBlankData,
authLoginHistoryHint{Selector: "external-worker", Explicit: true},
); err != nil {
t.Fatalf("blank-userId history must keep contact best effort: %v", err)
}
if contactBlankData.LegacyOrgScopedProfile != "external-worker" {
t.Fatalf("blank-userId contact fallback did not authorize the historical organization slot: %#v", contactBlankData)
}
profiles := []*authpkg.Profile{
nil,
{Name: "duplicate", CorpID: "ding_same", UserID: "user_1"},
{Name: "duplicate", CorpID: "ding_same", UserID: "user_2"},
}
for _, tc := range []struct {
name string
selector string
profiles []*authpkg.Profile
want *authpkg.Profile
}{
{name: "empty selector", selector: "", profiles: profiles},
{name: "missing exact identity", selector: "ding_same:missing", profiles: profiles},
{name: "duplicate name", selector: "duplicate", profiles: profiles},
{name: "unmatched name", selector: "not-found", profiles: profiles},
{name: "sole organization selector", selector: "ding_same", profiles: profiles[1:2], want: profiles[1]},
} {
t.Run("selector "+tc.name, func(t *testing.T) {
if got := historicalProfileForSelector("ding_same", tc.selector, tc.profiles); got != tc.want {
t.Fatalf("historicalProfileForSelector(%q) = %#v, want %#v", tc.selector, got, tc.want)
}
})
}
}
func TestCrossPlatformCoverageAuthCoverageDefaultSeamClosures(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
configDir := t.TempDir()
device := authpkg.NewDeviceFlowProvider(configDir, nil)
_, _ = authDeviceLogin(device, ctx)
oauth := authpkg.NewOAuthProvider(configDir, nil)
oauth.NoBrowser = true
_, _ = authOAuthStatus(oauth)
_, _ = authOAuthAccessToken(oauth, ctx)
_, _ = authOAuthLogin(oauth, ctx, true)
_, _ = authOAuthExchange(oauth, ctx, "code", "uid")
_ = fmt.Sprintf("%v", os.ErrNotExist)
}
func TestCrossPlatformCoverageAuthCoverageStatusAndLogout(t *testing.T) {
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
oldEdition := edition.Get()
oldStatus := authOAuthStatus
oldAccess := authOAuthAccessToken
oldDelete := authDeleteTokenData
oldMark := authMarkProfileStatus
oldResolve := authResolveProfile
oldResolveDeletion := authResolveProfileDeletion
oldRevoke := authRevokeToken
oldRevokeForData := authRevokeTokenForData
oldLoadTokenForProfile := authLoadTokenForProfile
oldDeleteProfile := authDeleteProfileToken
oldMigrate := authEnsureProfilesMigration
oldLoadProfiles := authLoadProfiles
oldDeleteAll := authDeleteAllTokenData
t.Cleanup(func() {
edition.Override(oldEdition)
authOAuthStatus = oldStatus
authOAuthAccessToken = oldAccess
authDeleteTokenData = oldDelete
authMarkProfileStatus = oldMark
authResolveProfile = oldResolve
authResolveProfileDeletion = oldResolveDeletion
authRevokeToken = oldRevoke
authRevokeTokenForData = oldRevokeForData
authLoadTokenForProfile = oldLoadTokenForProfile
authDeleteProfileToken = oldDeleteProfile
authEnsureProfilesMigration = oldMigrate
authLoadProfiles = oldLoadProfiles
authDeleteAllTokenData = oldDeleteAll
})
badStatus := newAuthStatusCommand()
bad := &cobra.Command{}
bad.Flags().Bool("profile", false, "")
if err := badStatus.RunE(bad, nil); err == nil {
t.Fatal("invalid profile flag should fail")
}
runStatus := func(format string) (string, error) {
cmd := newAuthStatusCommand()
_, out, _ := authCoverageRoot(cmd, format, false)
err := cmd.RunE(cmd, nil)
return out.String(), err
}
authOAuthStatus = func(*authpkg.OAuthProvider) (*authpkg.TokenData, error) { return nil, nil }
edition.Override(&edition.Hooks{})
if out, err := runStatus("table"); err != nil || !strings.Contains(out, "auth login") {
t.Fatalf("plain unauthenticated status = %q, %v", out, err)
}
authOAuthStatus = func(*authpkg.OAuthProvider) (*authpkg.TokenData, error) {
return nil, keychain.NewUnavailableError("read", errors.New("status"))
}
edition.Override(&edition.Hooks{})
if out, err := runStatus("table"); err != nil || !strings.Contains(out, "未登录") {
t.Fatalf("status error = %q, %v", out, err)
}
if out, err := runStatus("json"); err != nil || !strings.Contains(out, `"authenticated": false`) {
t.Fatalf("json status error = %q, %v", out, err)
}
now := time.Now()
valid := &authpkg.TokenData{AccessToken: "a", ExpiresAt: now.Add(time.Hour), CorpID: "ding", CorpName: "Corp"}
authOAuthStatus = func(*authpkg.OAuthProvider) (*authpkg.TokenData, error) { return valid, nil }
if out, err := runStatus("table"); err != nil || !strings.Contains(out, "已登录") || !strings.Contains(out, "缺失或已过期") {
t.Fatalf("valid access status = %q, %v", out, err)
}
valid.RefreshToken = "r"
valid.RefreshExpAt = now.Add(time.Hour)
if out, err := runStatus("table"); err != nil || !strings.Contains(out, "Refresh Token:") {
t.Fatalf("valid refresh status = %q, %v", out, err)
}
expired := &authpkg.TokenData{AccessToken: "a", ExpiresAt: now.Add(-time.Hour), RefreshToken: "r", RefreshExpAt: now.Add(time.Hour), CorpID: "ding"}
updated := &authpkg.TokenData{AccessToken: "new", ExpiresAt: now.Add(time.Hour), RefreshToken: "r", RefreshExpAt: now.Add(time.Hour), CorpID: "ding"}
calls := 0
authOAuthStatus = func(*authpkg.OAuthProvider) (*authpkg.TokenData, error) {
calls++
if calls == 1 {
return expired, nil
}
return updated, nil
}
authOAuthAccessToken = func(*authpkg.OAuthProvider, context.Context) (string, error) { return "new", nil }
if out, err := runStatus("table"); err != nil || !strings.Contains(out, "自动刷新") {
t.Fatalf("refreshed status = %q, %v", out, err)
}
calls = 0
authOAuthStatus = func(*authpkg.OAuthProvider) (*authpkg.TokenData, error) {
calls++
if calls == 1 {
return expired, nil
}
return nil, errors.New("second status")
}
if _, err := runStatus("table"); err != nil {
t.Fatal(err)
}
authOAuthStatus = func(*authpkg.OAuthProvider) (*authpkg.TokenData, error) { return expired, nil }
authOAuthAccessToken = func(*authpkg.OAuthProvider, context.Context) (string, error) { return "", errors.New("refresh") }
deleted := false
marked := false
authDeleteTokenData = func(string) error { deleted = true; return errors.New("ignored") }
authMarkProfileStatus = func(string, string, string) error { marked = true; return errors.New("ignored") }
edition.Override(&edition.Hooks{AutoPurgeToken: true})
if _, err := runStatus("table"); err != nil || !deleted {
t.Fatalf("auto-purge = %v, deleted=%v", err, deleted)
}
edition.Override(&edition.Hooks{})
if _, err := runStatus("table"); err != nil || !marked {
t.Fatalf("mark-expired = %v, marked=%v", err, marked)
}
authResolveProfileDeletion = func(string, string) (*authpkg.Profile, bool, error) { return nil, false, errors.New("missing") }
if err := logoutOneProfile(nil, context.Background(), "cfg", "x"); err == nil {
t.Fatal("missing profile should fail")
}
authResolveProfileDeletion = func(string, string) (*authpkg.Profile, bool, error) {
return &authpkg.Profile{CorpID: "ding", UserID: "user"}, true, nil
}
authLoadTokenForProfile = func(string, string) (*authpkg.TokenData, error) {
return &authpkg.TokenData{CorpID: "ding", UserID: "user"}, nil
}
authRevokeTokenForData = func(context.Context, *authpkg.TokenData) error { return errors.New("ignored") }
var deletedSelector string
authDeleteProfileToken = func(_ string, selector string) error {
deletedSelector = selector
return errors.New("delete")
}
if err := logoutOneProfile(nil, context.Background(), "cfg", "x"); err == nil {
t.Fatal("profile delete should fail")
}
if deletedSelector != "ding:user" {
t.Fatalf("exact deletion selector = %q, want stable identity selector", deletedSelector)
}
authDeleteProfileToken = func(_ string, selector string) error {
deletedSelector = selector
return nil
}
if err := logoutOneProfile(nil, context.Background(), "cfg", "x"); err != nil {
t.Fatal(err)
}
if deletedSelector != "ding:user" {
t.Fatalf("exact deletion selector = %q, want stable identity selector", deletedSelector)
}
authResolveProfileDeletion = func(string, string) (*authpkg.Profile, bool, error) {
return &authpkg.Profile{CorpID: "ding", UserID: "user"}, false, nil
}
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) {
return &authpkg.ProfilesConfig{Profiles: []authpkg.Profile{{CorpID: "ding", UserID: "user"}}}, nil
}
if err := logoutOneProfile(nil, context.Background(), "cfg", "organization-name"); err != nil {
t.Fatal(err)
}
if deletedSelector != "ding" {
t.Fatalf("organization deletion selector = %q, want stable corpId", deletedSelector)
}
authEnsureProfilesMigration = func(string) error { return errors.New("migrate") }
if err := logoutAllProfiles(nil, context.Background(), "cfg"); err == nil {
t.Fatal("migration should fail")
}
authEnsureProfilesMigration = func(string) error { return nil }
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return nil, errors.New("load") }
if err := logoutAllProfiles(nil, context.Background(), "cfg"); err == nil {
t.Fatal("load profiles should fail")
}
revokes := 0
authRevokeToken = func(context.Context) error { revokes++; return nil }
authRevokeTokenForData = func(context.Context, *authpkg.TokenData) error { revokes++; return nil }
authLoadTokenForProfile = func(string, string) (*authpkg.TokenData, error) {
return &authpkg.TokenData{AccessToken: "token"}, nil
}
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return nil, nil }
authDeleteAllTokenData = func(string) error { return nil }
if err := logoutAllProfiles(nil, context.Background(), "cfg"); err != nil || revokes != 1 {
t.Fatalf("empty profiles = %v, revokes=%d", err, revokes)
}
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) {
return &authpkg.ProfilesConfig{Profiles: []authpkg.Profile{{CorpID: "a"}, {CorpID: "b"}}}, nil
}
if err := logoutAllProfiles(nil, context.Background(), "cfg"); err != nil || revokes != 3 {
t.Fatalf("profile revokes = %v, revokes=%d", err, revokes)
}
authDeleteAllTokenData = func(string) error { return errors.New("delete all") }
if err := logoutAllProfiles(nil, context.Background(), "cfg"); err == nil {
t.Fatal("delete-all should fail")
}
logoutFailure := newAuthLogoutCommand()
_, _, _ = authCoverageRoot(logoutFailure, "table", false)
if err := logoutFailure.RunE(logoutFailure, nil); err == nil {
t.Fatal("logout-all command failure should propagate")
}
logoutFailure = newAuthLogoutCommand()
_, _, _ = authCoverageRoot(logoutFailure, "table", false)
_ = logoutFailure.Flags().Set("profile", "ding")
authDeleteProfileToken = func(string, string) error { return errors.New("delete") }
if err := logoutFailure.RunE(logoutFailure, nil); err == nil {
t.Fatal("logout-one command failure should propagate")
}
logout := newAuthLogoutCommand()
badLogout := &cobra.Command{}
badLogout.Flags().Bool("profile", false, "")
badLogout.SetContext(context.Background())
if err := logout.RunE(badLogout, nil); err == nil {
t.Fatal("invalid logout profile flag should fail")
}
authDeleteAllTokenData = func(string) error { return nil }
authLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return &authpkg.ProfilesConfig{}, nil }
_, out, _ := authCoverageRoot(logout, "table", false)
if err := logout.RunE(logout, nil); err != nil || !strings.Contains(out.String(), "重新登录") {
t.Fatalf("logout = %q, %v", out.String(), err)
}
edition.Override(&edition.Hooks{IsEmbedded: true})
authDeleteProfileToken = func(string, string) error { return nil }
logout = newAuthLogoutCommand()
_, out, _ = authCoverageRoot(logout, "table", false)
if err := logout.Flags().Set("profile", "ding"); err != nil {
t.Fatal(err)
}
if err := logout.RunE(logout, nil); err != nil || strings.Contains(out.String(), "重新登录") {
t.Fatalf("embedded logout = %q, %v", out.String(), err)
}
}
func TestCrossPlatformCoverageAuthCoveragePortableExchangeAndReset(t *testing.T) {
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
oldEdition := edition.Get()
oldSupported := authPortableExportSupported
oldReady := authPortableSourceReady
oldTarget := authPortableTargetPopulated
oldExport := authExportPortableBundle
oldImport := authImportPortableBundle
oldAtomic := authAtomicWrite
oldRead := authReadFile
oldExchange := authOAuthExchange
oldDeleteAll := authDeleteAllTokenData
oldRemove := authRemove
oldDeleteConfig := authDeleteAppConfig
t.Cleanup(func() {
edition.Override(oldEdition)
authPortableExportSupported = oldSupported
authPortableSourceReady = oldReady
authPortableTargetPopulated = oldTarget
authExportPortableBundle = oldExport
authImportPortableBundle = oldImport
authAtomicWrite = oldAtomic
authReadFile = oldRead
authOAuthExchange = oldExchange
authDeleteAllTokenData = oldDeleteAll
authRemove = oldRemove
authDeleteAppConfig = oldDeleteConfig
})
export := newAuthExportCommandWithSupport(func() error { return nil })
badString := &cobra.Command{}
badString.Flags().Bool("output", false, "")
if err := export.RunE(badString, nil); err == nil {
t.Fatal("invalid output flag should fail")
}
badBool := &cobra.Command{}
badBool.Flags().String("output", "x", "")
badBool.Flags().String("base64", "", "")
if err := export.RunE(badBool, nil); err == nil {
t.Fatal("invalid base64 flag should fail")
}
_, _, _ = authCoverageRoot(export, "table", false)
if err := export.RunE(export, nil); err == nil {
t.Fatal("missing export output should fail")
}
authPortableExportSupported = func() bool { return false }
_ = export.Flags().Set("output", "out")
if err := export.RunE(export, nil); err == nil {
t.Fatal("unsupported export should fail")
}
authPortableExportSupported = func() bool { return true }
authPortableSourceReady = func() bool { return false }
if err := export.RunE(export, nil); err == nil {
t.Fatal("unready export should fail")
}
authPortableSourceReady = func() bool { return true }
authExportPortableBundle = func(string, io.Writer) error { return errors.New("export") }
if err := export.RunE(export, nil); err == nil {
t.Fatal("export failure should propagate")
}
authExportPortableBundle = func(_ string, w io.Writer) error { _, _ = io.WriteString(w, "bundle"); return nil }
authAtomicWrite = func(string, []byte, os.FileMode) error { return errors.New("write") }
if err := export.RunE(export, nil); err == nil {
t.Fatal("raw write should fail")
}
authAtomicWrite = func(string, []byte, os.FileMode) error { return nil }
if err := export.RunE(export, nil); err != nil {
t.Fatal(err)
}
export = newAuthExportCommandWithSupport(func() error { return nil })
_, out, _ := authCoverageRoot(export, "table", false)
_ = export.Flags().Set("base64", "true")
export.SetOut(&appFailWriter{err: errors.New("stdout")})
if err := export.RunE(export, nil); err == nil {
t.Fatal("stdout failure should propagate")
}
export.SetOut(out)
_ = export.Flags().Set("output", "encoded")
authAtomicWrite = func(string, []byte, os.FileMode) error { return errors.New("write") }
if err := export.RunE(export, nil); err == nil {
t.Fatal("base64 write should fail")
}
authAtomicWrite = func(string, []byte, os.FileMode) error { return nil }
if err := export.RunE(export, nil); err != nil {
t.Fatal(err)
}
importCmd := newAuthImportCommandWithSupport(func() error { return nil })
badInput := &cobra.Command{}
badInput.Flags().Bool("input", false, "")
if err := importCmd.RunE(badInput, nil); err == nil {
t.Fatal("invalid input flag should fail")
}
badImportBase64 := &cobra.Command{}
badImportBase64.Flags().String("input", "x", "")
badImportBase64.Flags().String("base64", "", "")
if err := importCmd.RunE(badImportBase64, nil); err == nil {
t.Fatal("invalid import base64 flag should fail")
}
badForce := &cobra.Command{}
badForce.Flags().String("input", "x", "")
badForce.Flags().Bool("base64", false, "")
badForce.Flags().String("force", "", "")
if err := importCmd.RunE(badForce, nil); err == nil {
t.Fatal("invalid force flag should fail")
}
_, _, _ = authCoverageRoot(importCmd, "table", false)
if err := importCmd.RunE(importCmd, nil); err == nil {
t.Fatal("missing input should fail")
}
_ = importCmd.Flags().Set("input", "bundle")
authPortableTargetPopulated = func(string) bool { return true }
if err := importCmd.RunE(importCmd, nil); err == nil {
t.Fatal("populated target should require force")
}
authPortableTargetPopulated = func(string) bool { return false }
authReadFile = func(string) ([]byte, error) { return nil, errors.New("read") }
if err := importCmd.RunE(importCmd, nil); err == nil {
t.Fatal("read failure should propagate")
}
authReadFile = func(string) ([]byte, error) { return []byte("%%%"), nil }
_ = importCmd.Flags().Set("base64", "true")
if err := importCmd.RunE(importCmd, nil); err == nil {
t.Fatal("invalid base64 should fail")
}
authReadFile = func(string) ([]byte, error) { return []byte("YnVuZGxl"), nil }
authImportPortableBundle = func(string, io.Reader) (authpkg.PortableImportReport, error) {
return authpkg.PortableImportReport{}, errors.New("import")
}
if err := importCmd.RunE(importCmd, nil); err == nil {
t.Fatal("import failure should propagate")
}
authImportPortableBundle = func(string, io.Reader) (authpkg.PortableImportReport, error) {
return authpkg.PortableImportReport{BundleOS: "other", OSMismatch: true}, nil
}
if err := importCmd.RunE(importCmd, nil); err != nil {
t.Fatal(err)
}
authImportPortableBundle = func(string, io.Reader) (authpkg.PortableImportReport, error) {
return authpkg.PortableImportReport{}, nil
}
if err := importCmd.RunE(importCmd, nil); err != nil {
t.Fatal(err)
}
exchange := newAuthExchangeCommand(nil)
badCode := &cobra.Command{}
badCode.Flags().Bool("code", false, "")
if err := exchange.RunE(badCode, nil); err == nil {
t.Fatal("invalid code flag should fail")
}
badUID := &cobra.Command{}
badUID.Flags().String("code", "code", "")
badUID.Flags().Bool("uid", false, "")
if err := exchange.RunE(badUID, nil); err == nil {
t.Fatal("invalid uid flag should fail")
}
_, out, _ = authCoverageRoot(exchange, "table", false)
if err := exchange.RunE(exchange, nil); err == nil {
t.Fatal("missing code should fail")
}
_ = exchange.Flags().Set("code", "code")
authOAuthExchange = func(*authpkg.OAuthProvider, context.Context, string, string) (*authpkg.TokenData, error) {
return nil, errors.New("exchange")
}
if err := exchange.RunE(exchange, nil); err == nil {
t.Fatal("exchange error should propagate")
}
authOAuthExchange = func(*authpkg.OAuthProvider, context.Context, string, string) (*authpkg.TokenData, error) {
return &authpkg.TokenData{CorpID: "ding", ExpiresAt: time.Now().Add(time.Hour)}, nil
}
_ = exchange.Flags().Set("uid", " user ")
if err := exchange.RunE(exchange, nil); err != nil || !strings.Contains(out.String(), "ding") {
t.Fatalf("exchange = %q, %v", out.String(), err)
}
reset := newAuthResetCommand()
_, out, _ = authCoverageRoot(reset, "table", false)
authDeleteAllTokenData = func(string) error { return errors.New("reset") }
if err := reset.RunE(reset, nil); err == nil {
t.Fatal("reset delete should fail")
}
removed := 0
authDeleteAllTokenData = func(string) error { return nil }
authRemove = func(string) error { removed++; return errors.New("ignored") }
authDeleteAppConfig = func(string) error { removed++; return errors.New("ignored") }
edition.Override(&edition.Hooks{})
if err := reset.RunE(reset, nil); err != nil || removed != 4 || !strings.Contains(out.String(), "重新登录") {
t.Fatalf("reset = %q, %v, removed=%d", out.String(), err, removed)
}
edition.Override(&edition.Hooks{IsEmbedded: true})
reset = newAuthResetCommand()
_, out, _ = authCoverageRoot(reset, "table", false)
if err := reset.RunE(reset, nil); err != nil || strings.Contains(out.String(), "重新登录") {
t.Fatalf("embedded reset = %q, %v", out.String(), err)
}
}