1 package auth
2
3 import (
4 "testing"
5
6 "github.com/cli/go-gh/v2/pkg/config"
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9 )
10
11 func TestTokenForHost(t *testing.T) {
12 tests := []struct {
13 name string
14 host string
15 githubToken string
16 githubEnterpriseToken string
17 ghToken string
18 ghEnterpriseToken string
19 config *config.Config
20 wantToken string
21 wantSource string
22 }{
23 {
24 name: "given there is no env token and no config token, when we get the token for github.com, then it returns the empty string and default source",
25 host: "github.com",
26 config: testNoHostsConfig(),
27 wantToken: "",
28 wantSource: defaultSource,
29 },
30 {
31 name: "given there is no env token and no config token, when we get the token for an enterprise server host, then it returns the empty string and default source",
32 host: "enterprise.com",
33 config: testNoHostsConfig(),
34 wantToken: "",
35 wantSource: defaultSource,
36 },
37 {
38 name: "given GH_TOKEN and GITHUB_TOKEN and a config token are set, when we get the token for github.com, then it returns GH_TOKEN as the priority",
39 host: "github.com",
40 ghToken: "GH_TOKEN",
41 githubToken: "GITHUB_TOKEN",
42 config: testHostsConfig(),
43 wantToken: "GH_TOKEN",
44 wantSource: ghToken,
45 },
46 {
47 name: "given GITHUB_TOKEN and a config token are set, when we get the token for github.com, then it returns GITHUB_TOKEN as the priority",
48 host: "github.com",
49 githubToken: "GITHUB_TOKEN",
50 config: testHostsConfig(),
51 wantToken: "GITHUB_TOKEN",
52 wantSource: githubToken,
53 },
54 {
55 name: "given a config token is set for github.com, when we get the token, then it returns that token and oauth_token source",
56 host: "github.com",
57 config: testHostsConfig(),
58 wantToken: "xxxxxxxxxxxxxxxxxxxx",
59 wantSource: oauthToken,
60 },
61 {
62 name: "given GH_TOKEN and GITHUB_TOKEN and a config token are set, when we get the token for any subdomain of ghe.com, then it returns GH_TOKEN as the priority",
63 host: "tenant.ghe.com",
64 ghToken: "GH_TOKEN",
65 githubToken: "GITHUB_TOKEN",
66 config: testHostsConfig(),
67 wantToken: "GH_TOKEN",
68 wantSource: ghToken,
69 },
70 {
71 name: "given GITHUB_TOKEN and a config token are set, when we get the token for any subdomain of ghe.com, then it returns GITHUB_TOKEN as the priority",
72 host: "tenant.ghe.com",
73 githubToken: "GITHUB_TOKEN",
74 config: testHostsConfig(),
75 wantToken: "GITHUB_TOKEN",
76 wantSource: githubToken,
77 },
78 {
79 name: "given a config token is set for a subdomain of ghe.com, when we get the token for that subdomain, then it returns that token and oauth_token source",
80 host: "tenant.ghe.com",
81 config: testHostsConfig(),
82 wantToken: "zzzzzzzzzzzzzzzzzzzz",
83 wantSource: oauthToken,
84 },
85 {
86 name: "given GH_TOKEN and GITHUB_TOKEN and a config token are set, when we get the token for github.localhost, then it returns GH_TOKEN as the priority",
87 host: "github.localhost",
88 ghToken: "GH_TOKEN",
89 githubToken: "GITHUB_TOKEN",
90 config: testHostsConfig(),
91 wantToken: "GH_TOKEN",
92 wantSource: ghToken,
93 },
94 {
95 name: "given GITHUB_TOKEN and a config token are set, when we get the token for any subdomain of github.localhost, then it returns GITHUB_TOKEN as the priority",
96 host: "github.localhost",
97 githubToken: "GITHUB_TOKEN",
98 config: testHostsConfig(),
99 wantToken: "GITHUB_TOKEN",
100 wantSource: githubToken,
101 },
102 {
103 name: "given GH_ENTERPRISE_TOKEN and GITHUB_ENTERPRISE_TOKEN and a config token are set, when we get the token for an enterprise server host, then it returns GH_ENTERPRISE_TOKEN as the priority",
104 host: "enterprise.com",
105 ghEnterpriseToken: "GH_ENTERPRISE_TOKEN",
106 githubEnterpriseToken: "GITHUB_ENTERPRISE_TOKEN",
107 config: testHostsConfig(),
108 wantToken: "GH_ENTERPRISE_TOKEN",
109 wantSource: ghEnterpriseToken,
110 },
111 {
112 name: "given GITHUB_ENTERPRISE_TOKEN and a config token are set, when we get the token for an enterprise server host, then it returns GITHUB_ENTERPRISE_TOKEN as the priority",
113 host: "enterprise.com",
114 githubEnterpriseToken: "GITHUB_ENTERPRISE_TOKEN",
115 config: testHostsConfig(),
116 wantToken: "GITHUB_ENTERPRISE_TOKEN",
117 wantSource: githubEnterpriseToken,
118 },
119 {
120 name: "given a config token is set for an enterprise server host, when we get the token for that host, then it returns that token and oauth_token source",
121 host: "enterprise.com",
122 config: testHostsConfig(),
123 wantToken: "yyyyyyyyyyyyyyyyyyyy",
124 wantSource: oauthToken,
125 },
126 {
127 name: "given GH_TOKEN or GITHUB_TOKEN are set, when I get the token for any host not owned by GitHub, we do not get those tokens",
128 host: "unknown.com",
129 config: testNoHostsConfig(),
130 ghToken: "GH_TOKEN",
131 githubToken: "GITHUB_TOKEN",
132 wantToken: "",
133 wantSource: defaultSource,
134 },
135 }
136
137 for _, tt := range tests {
138 t.Run(tt.name, func(t *testing.T) {
139 t.Setenv("GITHUB_TOKEN", tt.githubToken)
140 t.Setenv("GITHUB_ENTERPRISE_TOKEN", tt.githubEnterpriseToken)
141 t.Setenv("GH_TOKEN", tt.ghToken)
142 t.Setenv("GH_ENTERPRISE_TOKEN", tt.ghEnterpriseToken)
143 token, source := tokenForHost(tt.config, tt.host)
144 require.Equal(t, tt.wantToken, token, "Expected token for \"%s\" to be \"%s\", got \"%s\"", tt.host, tt.wantToken, token)
145 require.Equal(t, tt.wantSource, source, "Expected source for \"%s\" to be \"%s\", got \"%s\"", tt.host, tt.wantSource, source)
146 })
147 }
148 }
149
150 func TestDefaultHost(t *testing.T) {
151 tests := []struct {
152 name string
153 config *config.Config
154 ghHost string
155 wantHost string
156 wantSource string
157 wantNotFound bool
158 }{
159 {
160 name: "GH_HOST if set",
161 config: testHostsConfig(),
162 ghHost: "test.com",
163 wantHost: "test.com",
164 wantSource: "GH_HOST",
165 },
166 {
167 name: "authenticated host if only one",
168 config: testSingleHostConfig(),
169 wantHost: "enterprise.com",
170 wantSource: "hosts",
171 },
172 {
173 name: "default host if more than one authenticated host",
174 config: testHostsConfig(),
175 wantHost: "github.com",
176 wantSource: "default",
177 wantNotFound: true,
178 },
179 {
180 name: "default host if no authenticated host",
181 config: testNoHostsConfig(),
182 wantHost: "github.com",
183 wantSource: "default",
184 wantNotFound: true,
185 },
186 }
187
188 for _, tt := range tests {
189 t.Run(tt.name, func(t *testing.T) {
190 if tt.ghHost != "" {
191 t.Setenv("GH_HOST", tt.ghHost)
192 }
193 host, source := defaultHost(tt.config)
194 assert.Equal(t, tt.wantHost, host)
195 assert.Equal(t, tt.wantSource, source)
196 })
197 }
198 }
199
200 func TestKnownHosts(t *testing.T) {
201 tests := []struct {
202 name string
203 config *config.Config
204 ghHost string
205 ghToken string
206 wantHosts []string
207 }{
208 {
209 name: "no known hosts",
210 config: testNoHostsConfig(),
211 wantHosts: []string{},
212 },
213 {
214 name: "includes GH_HOST",
215 config: testNoHostsConfig(),
216 ghHost: "test.com",
217 wantHosts: []string{"test.com"},
218 },
219 {
220 name: "includes authenticated hosts",
221 config: testHostsConfig(),
222 wantHosts: []string{"github.com", "enterprise.com", "tenant.ghe.com"},
223 },
224 {
225 name: "includes default host if environment auth token",
226 config: testNoHostsConfig(),
227 ghToken: "TOKEN",
228 wantHosts: []string{"github.com"},
229 },
230 {
231 name: "deduplicates hosts",
232 config: testHostsConfig(),
233 ghHost: "test.com",
234 ghToken: "TOKEN",
235 wantHosts: []string{"test.com", "github.com", "enterprise.com", "tenant.ghe.com"},
236 },
237 }
238
239 for _, tt := range tests {
240 t.Run(tt.name, func(t *testing.T) {
241 if tt.ghHost != "" {
242 t.Setenv("GH_HOST", tt.ghHost)
243 }
244 if tt.ghToken != "" {
245 t.Setenv("GH_TOKEN", tt.ghToken)
246 }
247 hosts := knownHosts(tt.config)
248 assert.Equal(t, tt.wantHosts, hosts)
249 })
250 }
251 }
252
253 func TestIsEnterprise(t *testing.T) {
254 tests := []struct {
255 name string
256 host string
257 wantOut bool
258 }{
259 {
260 name: "github",
261 host: "github.com",
262 wantOut: false,
263 },
264 {
265 name: "github API",
266 host: "api.github.com",
267 wantOut: false,
268 },
269 {
270 name: "localhost",
271 host: "github.localhost",
272 wantOut: false,
273 },
274 {
275 name: "localhost API",
276 host: "api.github.localhost",
277 wantOut: false,
278 },
279 {
280 name: "enterprise",
281 host: "mygithub.com",
282 wantOut: true,
283 },
284 {
285 name: "tenant",
286 host: "tenant.ghe.com",
287 wantOut: false,
288 },
289 {
290 name: "tenant API",
291 host: "api.tenant.ghe.com",
292 wantOut: false,
293 },
294 }
295
296 for _, tt := range tests {
297 t.Run(tt.name, func(t *testing.T) {
298 out := IsEnterprise(tt.host)
299 assert.Equal(t, tt.wantOut, out)
300 })
301 }
302 }
303
304 func TestIsTenancy(t *testing.T) {
305 tests := []struct {
306 name string
307 host string
308 wantOut bool
309 }{
310 {
311 name: "github",
312 host: "github.com",
313 wantOut: false,
314 },
315 {
316 name: "github API",
317 host: "api.github.com",
318 wantOut: false,
319 },
320 {
321 name: "localhost",
322 host: "github.localhost",
323 wantOut: false,
324 },
325 {
326 name: "localhost API",
327 host: "api.github.localhost",
328 wantOut: false,
329 },
330 {
331 name: "enterprise",
332 host: "mygithub.com",
333 wantOut: false,
334 },
335 {
336 name: "tenant",
337 host: "tenant.ghe.com",
338 wantOut: true,
339 },
340 {
341 name: "tenant API",
342 host: "api.tenant.ghe.com",
343 wantOut: true,
344 },
345 }
346
347 for _, tt := range tests {
348 t.Run(tt.name, func(t *testing.T) {
349 out := IsTenancy(tt.host)
350 assert.Equal(t, tt.wantOut, out)
351 })
352 }
353 }
354
355 func TestNormalizeHostname(t *testing.T) {
356 tests := []struct {
357 name string
358 host string
359 wantHost string
360 }{
361 {
362 name: "github domain",
363 host: "test.github.com",
364 wantHost: "github.com",
365 },
366 {
367 name: "capitalized",
368 host: "GitHub.com",
369 wantHost: "github.com",
370 },
371 {
372 name: "localhost domain",
373 host: "test.github.localhost",
374 wantHost: "github.localhost",
375 },
376 {
377 name: "enterprise domain",
378 host: "mygithub.com",
379 wantHost: "mygithub.com",
380 },
381 {
382 name: "bare tenant",
383 host: "tenant.ghe.com",
384 wantHost: "tenant.ghe.com",
385 },
386 {
387 name: "subdomained tenant",
388 host: "api.tenant.ghe.com",
389 wantHost: "tenant.ghe.com",
390 },
391 }
392
393 for _, tt := range tests {
394 t.Run(tt.name, func(t *testing.T) {
395 normalized := NormalizeHostname(tt.host)
396 assert.Equal(t, tt.wantHost, normalized)
397 })
398 }
399 }
400
401 func testNoHostsConfig() *config.Config {
402 var data = ``
403 return config.ReadFromString(data)
404 }
405
406 func testSingleHostConfig() *config.Config {
407 var data = `
408 hosts:
409 enterprise.com:
410 user: user2
411 oauth_token: yyyyyyyyyyyyyyyyyyyy
412 git_protocol: https
413 `
414 return config.ReadFromString(data)
415 }
416
417 func testHostsConfig() *config.Config {
418 var data = `
419 hosts:
420 github.com:
421 user: user1
422 oauth_token: xxxxxxxxxxxxxxxxxxxx
423 git_protocol: ssh
424 enterprise.com:
425 user: user2
426 oauth_token: yyyyyyyyyyyyyyyyyyyy
427 git_protocol: https
428 tenant.ghe.com:
429 user: user3
430 oauth_token: zzzzzzzzzzzzzzzzzzzz
431 git_protocol: https
432 `
433 return config.ReadFromString(data)
434 }
435
View as plain text