Accept corpId:userId and friendly organization/account selectors across global --profile, profile switch/use, event child processes, and multi-profile command execution. List every local account in storage order with live identity-token status, preserve exact current and previous identities, and require explicit selection when an organization has no deterministic current account. Extend auth logout to remove one exact account, every account in one organization, or all local accounts while revoking each token with its persisted credentials. Use in-memory login tokens for identity enrichment before persistence, refresh generated Schema artifacts, and cover the complete CLI flow with isolated beta.3 end-to-end tests.
166 lines
6.4 KiB
Go
166 lines
6.4 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"testing"
|
|
|
|
authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestCrossPlatformCoverageProfileRemainingCoverage(t *testing.T) {
|
|
oldMigrate := profileEnsureProfilesMigration
|
|
oldLoad := profileLoadProfiles
|
|
oldPrevious := profileUsePrevious
|
|
oldCurrent := profileSetCurrent
|
|
oldInteractive := profileSwitchInteractiveTerminal
|
|
oldTUI := profileSwitchTUIRunner
|
|
oldProgram := profileRunTeaProgram
|
|
t.Cleanup(func() {
|
|
profileEnsureProfilesMigration = oldMigrate
|
|
profileLoadProfiles = oldLoad
|
|
profileUsePrevious = oldPrevious
|
|
profileSetCurrent = oldCurrent
|
|
profileSwitchInteractiveTerminal = oldInteractive
|
|
profileSwitchTUIRunner = oldTUI
|
|
profileRunTeaProgram = oldProgram
|
|
})
|
|
fail := errors.New("failure")
|
|
|
|
list := newProfileListCommand()
|
|
_, _, _ = authCoverageRoot(list, "table", false)
|
|
profileEnsureProfilesMigration = func(string) error { return fail }
|
|
if err := list.RunE(list, nil); err == nil {
|
|
t.Fatal("profile-list migration should fail")
|
|
}
|
|
profileEnsureProfilesMigration = func(string) error { return nil }
|
|
profileLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return nil, fail }
|
|
if err := list.RunE(list, nil); err == nil {
|
|
t.Fatal("profile-list load should fail")
|
|
}
|
|
|
|
selectorCmd := &cobra.Command{}
|
|
addProfileSwitchSelectorFlags(selectorCmd)
|
|
_ = selectorCmd.Flags().Set("name", " ")
|
|
if _, err := profileSwitchSelectorFromCommand(selectorCmd, nil); err == nil {
|
|
t.Fatal("blank profile selector should fail")
|
|
}
|
|
|
|
profileSetCurrent = func(string, string) (*authpkg.Profile, error) {
|
|
return &authpkg.Profile{CorpID: "ding", CorpName: "Corp"}, nil
|
|
}
|
|
profileUsePrevious = func(string) (*authpkg.Profile, error) { return nil, fail }
|
|
cmd := &cobra.Command{}
|
|
_, _, _ = authCoverageRoot(cmd, "table", false)
|
|
if err := switchProfileAndWrite(cmd, "cfg", "-", false); err == nil {
|
|
t.Fatal("previous-profile error should fail")
|
|
}
|
|
profileUsePrevious = func(string) (*authpkg.Profile, error) { return &authpkg.Profile{CorpID: "previous"}, nil }
|
|
if err := switchProfileAndWrite(cmd, "cfg", "-", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
jsonCmd := &cobra.Command{}
|
|
_, _, _ = authCoverageRoot(jsonCmd, "json", false)
|
|
profileLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return nil, fail }
|
|
if err := switchProfileAndWrite(jsonCmd, "cfg", "ding", false); err == nil {
|
|
t.Fatal("JSON profile load should fail")
|
|
}
|
|
profileLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) {
|
|
return &authpkg.ProfilesConfig{CurrentProfile: "ding"}, nil
|
|
}
|
|
jsonCmd.SetOut(&appFailWriter{err: fail})
|
|
if err := switchProfileAndWrite(jsonCmd, "cfg", "ding", false); err == nil {
|
|
t.Fatal("JSON profile write should fail")
|
|
}
|
|
|
|
profileSwitchInteractiveTerminal = func() bool { return true }
|
|
profileEnsureProfilesMigration = func(string) error { return fail }
|
|
if _, err := selectProfileSwitchProfile(cmd, "cfg"); err == nil {
|
|
t.Fatal("selector migration should fail")
|
|
}
|
|
profileEnsureProfilesMigration = func(string) error { return nil }
|
|
profileLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return nil, fail }
|
|
if _, err := selectProfileSwitchProfile(cmd, "cfg"); err == nil {
|
|
t.Fatal("selector load should fail")
|
|
}
|
|
profileLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return nil, nil }
|
|
if _, err := selectProfileSwitchProfile(cmd, "cfg"); err == nil {
|
|
t.Fatal("empty selector profiles should fail")
|
|
}
|
|
choices := []string{}
|
|
profileSwitchTUIRunner = func(_ *cobra.Command, _ *authpkg.ProfilesConfig, choice string) (string, error) {
|
|
choices = append(choices, choice)
|
|
return choice, nil
|
|
}
|
|
for _, cfg := range []*authpkg.ProfilesConfig{
|
|
{CurrentProfile: "current", PrimaryProfile: "primary", Profiles: []authpkg.Profile{{CorpID: "first"}}},
|
|
{PrimaryProfile: "primary", Profiles: []authpkg.Profile{{CorpID: "first"}}},
|
|
{Profiles: []authpkg.Profile{{CorpID: "first"}}},
|
|
} {
|
|
profileLoadProfiles = func(string) (*authpkg.ProfilesConfig, error) { return cfg, nil }
|
|
if _, err := selectProfileSwitchProfile(cmd, "cfg"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if len(choices) != 3 || choices[0] != "current" || choices[1] != "first" || choices[2] != "first" {
|
|
t.Fatalf("profile choices = %#v", choices)
|
|
}
|
|
|
|
tuiCmd := &cobra.Command{}
|
|
tuiCmd.SetContext(context.Background())
|
|
tuiCmd.SetIn(io.NopCloser(&emptyReader{}))
|
|
tuiCmd.SetErr(io.Discard)
|
|
cfg := &authpkg.ProfilesConfig{Profiles: []authpkg.Profile{{CorpID: "ding"}}}
|
|
profileRunTeaProgram = func(*tea.Program) (tea.Model, error) { return nil, tea.ErrInterrupted }
|
|
if _, err := runProfileSwitchTUI(tuiCmd, cfg, "ding"); err == nil {
|
|
t.Fatal("interrupted TUI should fail")
|
|
}
|
|
profileRunTeaProgram = func(*tea.Program) (tea.Model, error) { return nil, fail }
|
|
if _, err := runProfileSwitchTUI(tuiCmd, cfg, "ding"); err == nil {
|
|
t.Fatal("failed TUI should fail")
|
|
}
|
|
profileRunTeaProgram = func(*tea.Program) (tea.Model, error) { return structModel{}, nil }
|
|
if _, err := runProfileSwitchTUI(tuiCmd, cfg, "ding"); err == nil {
|
|
t.Fatal("wrong TUI model should fail")
|
|
}
|
|
for _, model := range []profileSwitchTUIModel{{aborted: true}, {submitted: false}} {
|
|
model := model
|
|
profileRunTeaProgram = func(*tea.Program) (tea.Model, error) { return model, nil }
|
|
if _, err := runProfileSwitchTUI(tuiCmd, cfg, "ding"); err == nil {
|
|
t.Fatal("aborted TUI should fail")
|
|
}
|
|
}
|
|
final := newProfileSwitchTUIModel(cfg, "ding")
|
|
final.submitted = true
|
|
profileRunTeaProgram = func(*tea.Program) (tea.Model, error) { return final, nil }
|
|
if got, err := runProfileSwitchTUI(tuiCmd, cfg, "ding"); err != nil || got != "ding" {
|
|
t.Fatalf("submitted TUI = %q, %v", got, err)
|
|
}
|
|
|
|
profiles := make([]authpkg.Profile, 8)
|
|
model := profileSwitchTUIModel{profiles: profiles, selected: 7, offset: 99}
|
|
model.ensureSelectedVisible()
|
|
if model.offset != 3 {
|
|
t.Fatalf("clamped offset = %d", model.offset)
|
|
}
|
|
model.offset = -2
|
|
model.selected = 0
|
|
model.ensureSelectedVisible()
|
|
if model.offset != 0 {
|
|
t.Fatalf("negative offset = %d", model.offset)
|
|
}
|
|
}
|
|
|
|
type emptyReader struct{}
|
|
|
|
func (*emptyReader) Read([]byte) (int, error) { return 0, io.EOF }
|
|
|
|
type structModel struct{}
|
|
|
|
func (structModel) Init() tea.Cmd { return nil }
|
|
func (structModel) Update(tea.Msg) (tea.Model, tea.Cmd) { return structModel{}, nil }
|
|
func (structModel) View() string { return "" }
|