Skip to content

Commit e53a145

Browse files
test: add unit tests for auth, config, report, and utils packages
- auth/login: test command creation, interactive login flows, re-auth prompts - auth/logout: test config errors, not-logged-in state, user cancellation - auth/refresh: test config errors, client creation, token refresh - auth/status: test config errors, not-logged-in, valid/expired tokens - config/generate: test config generation with analyzers, transformers, patterns - report: test sanitize, validateKey, git commit detection, makeQuery - utils/prompt: test ConfirmFromUser, SelectFromOptions, GetSingleLineInput - utils/cmd_validator: test ExactArgs, MaxNArgs, NoArgs validators
1 parent 31284e3 commit e53a145

10 files changed

Lines changed: 2040 additions & 0 deletions

File tree

command/auth/login/login_test.go

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
package login
2+
3+
import (
4+
"errors"
5+
"sync"
6+
"testing"
7+
"time"
8+
9+
"github.com/deepsourcelabs/cli/config"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
var mockMu sync.Mutex
14+
15+
func TestNewCmdLogin(t *testing.T) {
16+
t.Run("creates command with correct properties", func(t *testing.T) {
17+
cmd := NewCmdLogin()
18+
assert.Equal(t, "login", cmd.Use)
19+
assert.Equal(t, "Log in to DeepSource using Command Line Interface", cmd.Short)
20+
assert.NotEmpty(t, cmd.Long)
21+
})
22+
23+
t.Run("has hostname flag", func(t *testing.T) {
24+
cmd := NewCmdLogin()
25+
flag := cmd.Flags().Lookup("hostname")
26+
assert.NotNil(t, flag)
27+
assert.Equal(t, "", flag.DefValue)
28+
})
29+
30+
t.Run("has interactive flag", func(t *testing.T) {
31+
cmd := NewCmdLogin()
32+
flag := cmd.Flags().Lookup("interactive")
33+
assert.NotNil(t, flag)
34+
assert.Equal(t, "false", flag.DefValue)
35+
})
36+
37+
t.Run("has with-token flag", func(t *testing.T) {
38+
cmd := NewCmdLogin()
39+
flag := cmd.Flags().Lookup("with-token")
40+
assert.NotNil(t, flag)
41+
assert.Equal(t, "", flag.DefValue)
42+
})
43+
}
44+
45+
func TestLoginOptions_handleInteractiveLogin(t *testing.T) {
46+
t.Run("DeepSource Cloud selected - no hostname prompt", func(t *testing.T) {
47+
mockMu.Lock()
48+
originalSelect := selectFromOptionsFn
49+
selectFromOptionsFn = func(msg, helpText string, opts []string) (string, error) {
50+
return "DeepSource (deepsource.io)", nil
51+
}
52+
mockMu.Unlock()
53+
54+
defer func() {
55+
mockMu.Lock()
56+
selectFromOptionsFn = originalSelect
57+
mockMu.Unlock()
58+
}()
59+
60+
opts := LoginOptions{Interactive: true}
61+
err := opts.handleInteractiveLogin()
62+
assert.NoError(t, err)
63+
assert.Empty(t, opts.HostName)
64+
})
65+
66+
t.Run("DeepSource Enterprise selected - prompts for hostname", func(t *testing.T) {
67+
mockMu.Lock()
68+
originalSelect := selectFromOptionsFn
69+
originalInput := getSingleLineInputFn
70+
71+
selectFromOptionsFn = func(msg, helpText string, opts []string) (string, error) {
72+
return "DeepSource Enterprise", nil
73+
}
74+
getSingleLineInputFn = func(msg, helpText string) (string, error) {
75+
return "enterprise.example.com", nil
76+
}
77+
mockMu.Unlock()
78+
79+
defer func() {
80+
mockMu.Lock()
81+
selectFromOptionsFn = originalSelect
82+
getSingleLineInputFn = originalInput
83+
mockMu.Unlock()
84+
}()
85+
86+
opts := LoginOptions{Interactive: true}
87+
err := opts.handleInteractiveLogin()
88+
assert.NoError(t, err)
89+
assert.Equal(t, "enterprise.example.com", opts.HostName)
90+
})
91+
92+
t.Run("error from account type selection", func(t *testing.T) {
93+
mockMu.Lock()
94+
originalSelect := selectFromOptionsFn
95+
selectFromOptionsFn = func(msg, helpText string, opts []string) (string, error) {
96+
return "", errors.New("selection error")
97+
}
98+
mockMu.Unlock()
99+
100+
defer func() {
101+
mockMu.Lock()
102+
selectFromOptionsFn = originalSelect
103+
mockMu.Unlock()
104+
}()
105+
106+
opts := LoginOptions{Interactive: true}
107+
err := opts.handleInteractiveLogin()
108+
assert.Error(t, err)
109+
assert.Contains(t, err.Error(), "selection error")
110+
})
111+
112+
t.Run("error from hostname input", func(t *testing.T) {
113+
mockMu.Lock()
114+
originalSelect := selectFromOptionsFn
115+
originalInput := getSingleLineInputFn
116+
117+
selectFromOptionsFn = func(msg, helpText string, opts []string) (string, error) {
118+
return "DeepSource Enterprise", nil
119+
}
120+
getSingleLineInputFn = func(msg, helpText string) (string, error) {
121+
return "", errors.New("input error")
122+
}
123+
mockMu.Unlock()
124+
125+
defer func() {
126+
mockMu.Lock()
127+
selectFromOptionsFn = originalSelect
128+
getSingleLineInputFn = originalInput
129+
mockMu.Unlock()
130+
}()
131+
132+
opts := LoginOptions{Interactive: true}
133+
err := opts.handleInteractiveLogin()
134+
assert.Error(t, err)
135+
assert.Contains(t, err.Error(), "input error")
136+
})
137+
}
138+
139+
func TestLoginOptions_Run(t *testing.T) {
140+
t.Run("already authenticated user declines re-auth", func(t *testing.T) {
141+
mockMu.Lock()
142+
originalGetConfig := getConfigFn
143+
originalConfirm := confirmFromUserFn
144+
145+
getConfigFn = func() (*config.CLIConfig, error) {
146+
return &config.CLIConfig{
147+
User: "test@example.com",
148+
Token: "valid_token",
149+
TokenExpiresIn: time.Now().Add(24 * time.Hour),
150+
}, nil
151+
}
152+
confirmFromUserFn = func(msg, helpText string) (bool, error) {
153+
return false, nil // User declines
154+
}
155+
mockMu.Unlock()
156+
157+
defer func() {
158+
mockMu.Lock()
159+
getConfigFn = originalGetConfig
160+
confirmFromUserFn = originalConfirm
161+
mockMu.Unlock()
162+
}()
163+
164+
opts := LoginOptions{TokenExpired: false}
165+
err := opts.Run()
166+
assert.NoError(t, err)
167+
})
168+
169+
t.Run("confirmation prompt returns error", func(t *testing.T) {
170+
mockMu.Lock()
171+
originalGetConfig := getConfigFn
172+
originalConfirm := confirmFromUserFn
173+
174+
getConfigFn = func() (*config.CLIConfig, error) {
175+
return &config.CLIConfig{
176+
User: "test@example.com",
177+
Token: "valid_token",
178+
TokenExpiresIn: time.Now().Add(24 * time.Hour),
179+
}, nil
180+
}
181+
confirmFromUserFn = func(msg, helpText string) (bool, error) {
182+
return false, errors.New("prompt error")
183+
}
184+
mockMu.Unlock()
185+
186+
defer func() {
187+
mockMu.Lock()
188+
getConfigFn = originalGetConfig
189+
confirmFromUserFn = originalConfirm
190+
mockMu.Unlock()
191+
}()
192+
193+
opts := LoginOptions{TokenExpired: false}
194+
err := opts.Run()
195+
assert.Error(t, err)
196+
assert.Contains(t, err.Error(), "Error in fetching response")
197+
})
198+
199+
t.Run("interactive login error is propagated", func(t *testing.T) {
200+
mockMu.Lock()
201+
originalGetConfig := getConfigFn
202+
originalSelect := selectFromOptionsFn
203+
204+
getConfigFn = func() (*config.CLIConfig, error) {
205+
return &config.CLIConfig{
206+
TokenExpiresIn: time.Now().Add(-24 * time.Hour), // Expired token
207+
}, nil
208+
}
209+
selectFromOptionsFn = func(msg, helpText string, opts []string) (string, error) {
210+
return "", errors.New("interactive error")
211+
}
212+
mockMu.Unlock()
213+
214+
defer func() {
215+
mockMu.Lock()
216+
getConfigFn = originalGetConfig
217+
selectFromOptionsFn = originalSelect
218+
mockMu.Unlock()
219+
}()
220+
221+
opts := LoginOptions{Interactive: true}
222+
err := opts.Run()
223+
assert.Error(t, err)
224+
assert.Contains(t, err.Error(), "interactive error")
225+
})
226+
227+
t.Run("custom hostname is stored", func(t *testing.T) {
228+
mockMu.Lock()
229+
originalGetConfig := getConfigFn
230+
originalConfirm := confirmFromUserFn
231+
232+
var capturedCfg *config.CLIConfig
233+
getConfigFn = func() (*config.CLIConfig, error) {
234+
capturedCfg = &config.CLIConfig{
235+
TokenExpiresIn: time.Now().Add(24 * time.Hour),
236+
User: "test@example.com",
237+
Token: "valid_token",
238+
}
239+
return capturedCfg, nil
240+
}
241+
confirmFromUserFn = func(msg, helpText string) (bool, error) {
242+
return false, nil // User declines re-auth
243+
}
244+
mockMu.Unlock()
245+
246+
defer func() {
247+
mockMu.Lock()
248+
getConfigFn = originalGetConfig
249+
confirmFromUserFn = originalConfirm
250+
mockMu.Unlock()
251+
}()
252+
253+
opts := LoginOptions{HostName: "custom.deepsource.io"}
254+
err := opts.Run()
255+
assert.NoError(t, err)
256+
assert.Equal(t, "custom.deepsource.io", capturedCfg.Host)
257+
})
258+
}

command/auth/logout/logout_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package logout
2+
3+
import (
4+
"errors"
5+
"sync"
6+
"testing"
7+
8+
"github.com/deepsourcelabs/cli/config"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
var mockMu sync.Mutex
13+
14+
func TestNewCmdLogout(t *testing.T) {
15+
t.Run("creates command with correct properties", func(t *testing.T) {
16+
cmd := NewCmdLogout()
17+
assert.Equal(t, "logout", cmd.Use)
18+
assert.Equal(t, "Logout of your active DeepSource account", cmd.Short)
19+
})
20+
}
21+
22+
func TestLogoutOptions_Run(t *testing.T) {
23+
t.Run("error reading config", func(t *testing.T) {
24+
mockMu.Lock()
25+
originalGetConfig := getConfigFn
26+
getConfigFn = func() (*config.CLIConfig, error) {
27+
return nil, errors.New("config read error")
28+
}
29+
mockMu.Unlock()
30+
31+
defer func() {
32+
mockMu.Lock()
33+
getConfigFn = originalGetConfig
34+
mockMu.Unlock()
35+
}()
36+
37+
opts := LogoutOptions{}
38+
err := opts.Run()
39+
assert.Error(t, err)
40+
assert.Contains(t, err.Error(), "Error while reading DeepSource CLI config")
41+
})
42+
43+
t.Run("not logged in - empty token", func(t *testing.T) {
44+
mockMu.Lock()
45+
originalGetConfig := getConfigFn
46+
getConfigFn = func() (*config.CLIConfig, error) {
47+
return &config.CLIConfig{Token: ""}, nil
48+
}
49+
mockMu.Unlock()
50+
51+
defer func() {
52+
mockMu.Lock()
53+
getConfigFn = originalGetConfig
54+
mockMu.Unlock()
55+
}()
56+
57+
opts := LogoutOptions{}
58+
err := opts.Run()
59+
assert.Error(t, err)
60+
assert.Contains(t, err.Error(), "not logged into DeepSource")
61+
})
62+
63+
t.Run("user cancels logout", func(t *testing.T) {
64+
mockMu.Lock()
65+
originalGetConfig := getConfigFn
66+
originalConfirm := confirmFromUserFn
67+
68+
getConfigFn = func() (*config.CLIConfig, error) {
69+
return &config.CLIConfig{Token: "valid_token"}, nil
70+
}
71+
confirmFromUserFn = func(msg, helpText string) (bool, error) {
72+
return false, nil // User cancels
73+
}
74+
mockMu.Unlock()
75+
76+
defer func() {
77+
mockMu.Lock()
78+
getConfigFn = originalGetConfig
79+
confirmFromUserFn = originalConfirm
80+
mockMu.Unlock()
81+
}()
82+
83+
opts := LogoutOptions{}
84+
err := opts.Run()
85+
assert.NoError(t, err)
86+
})
87+
88+
t.Run("confirmation prompt returns error", func(t *testing.T) {
89+
mockMu.Lock()
90+
originalGetConfig := getConfigFn
91+
originalConfirm := confirmFromUserFn
92+
93+
getConfigFn = func() (*config.CLIConfig, error) {
94+
return &config.CLIConfig{Token: "valid_token"}, nil
95+
}
96+
confirmFromUserFn = func(msg, helpText string) (bool, error) {
97+
return false, errors.New("prompt error")
98+
}
99+
mockMu.Unlock()
100+
101+
defer func() {
102+
mockMu.Lock()
103+
getConfigFn = originalGetConfig
104+
confirmFromUserFn = originalConfirm
105+
mockMu.Unlock()
106+
}()
107+
108+
opts := LogoutOptions{}
109+
err := opts.Run()
110+
assert.Error(t, err)
111+
assert.Contains(t, err.Error(), "prompt error")
112+
})
113+
}

0 commit comments

Comments
 (0)