Skip to content

Commit a77c791

Browse files
committed
feat: Add tests
1 parent a7cb149 commit a77c791

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed

cli/configssh_test.go

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net"
77
"os"
88
"os/exec"
9+
"path/filepath"
910
"strconv"
1011
"strings"
1112
"testing"
@@ -138,3 +139,292 @@ func TestConfigSSH(t *testing.T) {
138139
require.NoError(t, err)
139140
require.Equal(t, "test", strings.TrimSpace(string(data)))
140141
}
142+
143+
func sshConfigFileNames(t *testing.T) (sshConfig string, coderConfig string) {
144+
t.Helper()
145+
tmpdir := t.TempDir()
146+
n1 := filepath.Join(tmpdir, "config")
147+
n2 := filepath.Join(tmpdir, "coder")
148+
return n1, n2
149+
}
150+
151+
func sshConfigFileCreate(t *testing.T, name string, data io.Reader) {
152+
t.Helper()
153+
t.Logf("Writing %s", name)
154+
f, err := os.Create(name)
155+
require.NoError(t, err)
156+
n, err := io.Copy(f, data)
157+
t.Logf("Wrote %d", n)
158+
require.NoError(t, err)
159+
err = f.Close()
160+
require.NoError(t, err)
161+
}
162+
163+
func sshConfigFileRead(t *testing.T, name string) string {
164+
b, err := os.ReadFile(name)
165+
require.NoError(t, err)
166+
return string(b)
167+
}
168+
169+
func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
170+
t.Parallel()
171+
172+
type writeConfig struct {
173+
ssh string
174+
coder string
175+
}
176+
type wantConfig struct {
177+
ssh string
178+
coder string
179+
coderPartial bool
180+
}
181+
type match struct {
182+
match, write string
183+
}
184+
tests := []struct {
185+
name string
186+
args []string
187+
matches []match
188+
writeConfig writeConfig
189+
wantConfig wantConfig
190+
wantErr bool
191+
}{
192+
{
193+
name: "Config files are created",
194+
matches: []match{
195+
{match: "Continue?", write: "yes"},
196+
},
197+
wantConfig: wantConfig{
198+
ssh: strings.Join([]string{
199+
"Include coder",
200+
"",
201+
}, "\n"),
202+
coder: "# This file is managed by coder. DO NOT EDIT.",
203+
coderPartial: true,
204+
},
205+
},
206+
{
207+
name: "Include is written to top of ssh config",
208+
writeConfig: writeConfig{
209+
ssh: strings.Join([]string{
210+
"# This is a host",
211+
"Host test",
212+
" HostName test",
213+
}, "\n"),
214+
},
215+
wantConfig: wantConfig{
216+
ssh: strings.Join([]string{
217+
"Include coder",
218+
"",
219+
"# This is a host",
220+
"Host test",
221+
" HostName test",
222+
}, "\n"),
223+
},
224+
matches: []match{
225+
{match: "Continue?", write: "yes"},
226+
},
227+
},
228+
{
229+
name: "Include below Host is invalid, move it to the top",
230+
writeConfig: writeConfig{
231+
ssh: strings.Join([]string{
232+
"Host test",
233+
" HostName test",
234+
"",
235+
"Include coder",
236+
"",
237+
"",
238+
}, "\n"),
239+
},
240+
wantConfig: wantConfig{
241+
ssh: strings.Join([]string{
242+
"Include coder",
243+
"",
244+
"Host test",
245+
" HostName test",
246+
"",
247+
// Only "Include coder" with accompanying
248+
// newline is removed.
249+
"",
250+
"",
251+
}, "\n"),
252+
},
253+
matches: []match{
254+
{match: "Continue?", write: "yes"},
255+
},
256+
},
257+
{
258+
name: "SSH Config does not need modification",
259+
writeConfig: writeConfig{
260+
ssh: strings.Join([]string{
261+
"Include something/other",
262+
"Include coder",
263+
"",
264+
"# This is a host",
265+
"Host test",
266+
" HostName test",
267+
}, "\n"),
268+
},
269+
wantConfig: wantConfig{
270+
ssh: strings.Join([]string{
271+
"Include something/other",
272+
"Include coder",
273+
"",
274+
"# This is a host",
275+
"Host test",
276+
" HostName test",
277+
}, "\n"),
278+
},
279+
matches: []match{
280+
{match: "Continue?", write: "yes"},
281+
},
282+
},
283+
{
284+
name: "When options differ, selecting yes overwrites previous options",
285+
writeConfig: writeConfig{
286+
coder: strings.Join([]string{
287+
"# This file is managed by coder. DO NOT EDIT.",
288+
"#",
289+
"# You should not hand-edit this file, all changes will be lost upon workspace",
290+
"# creation, deletion or when running \"coder config-ssh\".",
291+
"#",
292+
"# Last config-ssh options:",
293+
"# :ssh-option=ForwardAgent=yes",
294+
"#",
295+
}, "\n"),
296+
},
297+
wantConfig: wantConfig{
298+
coder: strings.Join([]string{
299+
"# This file is managed by coder. DO NOT EDIT.",
300+
"#",
301+
"# You should not hand-edit this file, all changes will be lost upon workspace",
302+
"# creation, deletion or when running \"coder config-ssh\".",
303+
"#",
304+
"# Last config-ssh options:",
305+
"#",
306+
}, "\n"),
307+
coderPartial: true,
308+
},
309+
matches: []match{
310+
{match: "Use new options?", write: "yes"},
311+
{match: "Continue?", write: "yes"},
312+
},
313+
},
314+
{
315+
name: "When options differ, selecting no preserves previous options",
316+
writeConfig: writeConfig{
317+
coder: strings.Join([]string{
318+
"# This file is managed by coder. DO NOT EDIT.",
319+
"#",
320+
"# You should not hand-edit this file, all changes will be lost upon workspace",
321+
"# creation, deletion or when running \"coder config-ssh\".",
322+
"#",
323+
"# Last config-ssh options:",
324+
"# :ssh-option=ForwardAgent=yes",
325+
"#",
326+
}, "\n"),
327+
},
328+
wantConfig: wantConfig{
329+
coder: strings.Join([]string{
330+
"# This file is managed by coder. DO NOT EDIT.",
331+
"#",
332+
"# You should not hand-edit this file, all changes will be lost upon workspace",
333+
"# creation, deletion or when running \"coder config-ssh\".",
334+
"#",
335+
"# Last config-ssh options:",
336+
"# :ssh-option=ForwardAgent=yes",
337+
"#",
338+
}, "\n"),
339+
coderPartial: true,
340+
},
341+
matches: []match{
342+
{match: "Use new options?", write: "no"},
343+
{match: "Continue?", write: "yes"},
344+
},
345+
},
346+
{
347+
name: "Do not overwrite unknown coder config",
348+
writeConfig: writeConfig{
349+
coder: strings.Join([]string{
350+
"We're no strangers to love",
351+
"You know the rules and so do I (do I)",
352+
}, "\n"),
353+
},
354+
wantConfig: wantConfig{
355+
coder: strings.Join([]string{
356+
"We're no strangers to love",
357+
"You know the rules and so do I (do I)",
358+
}, "\n"),
359+
},
360+
wantErr: true,
361+
},
362+
}
363+
for _, tt := range tests {
364+
tt := tt
365+
t.Run(tt.name, func(t *testing.T) {
366+
t.Parallel()
367+
368+
var (
369+
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
370+
user = coderdtest.CreateFirstUser(t, client)
371+
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
372+
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
373+
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
374+
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
375+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
376+
)
377+
378+
// Prepare ssh config files.
379+
sshConfigName, coderConfigName := sshConfigFileNames(t)
380+
if tt.writeConfig.ssh != "" {
381+
sshConfigFileCreate(t, sshConfigName, strings.NewReader(tt.writeConfig.ssh))
382+
}
383+
if tt.writeConfig.coder != "" {
384+
sshConfigFileCreate(t, coderConfigName, strings.NewReader(tt.writeConfig.coder))
385+
}
386+
387+
args := []string{
388+
"config-ssh",
389+
"--ssh-config-file", sshConfigName,
390+
"--test.default-ssh-config-file", sshConfigName,
391+
"--test.ssh-coder-config-file", coderConfigName,
392+
}
393+
args = append(args, tt.args...)
394+
cmd, root := clitest.New(t, args...)
395+
clitest.SetupConfig(t, client, root)
396+
397+
pty := ptytest.New(t)
398+
cmd.SetIn(pty.Input())
399+
cmd.SetOut(pty.Output())
400+
done := tGo(t, func() {
401+
err := cmd.Execute()
402+
if !tt.wantErr {
403+
assert.NoError(t, err)
404+
} else {
405+
assert.Error(t, err)
406+
}
407+
})
408+
409+
for _, m := range tt.matches {
410+
pty.ExpectMatch(m.match)
411+
pty.WriteLine(m.write)
412+
}
413+
414+
<-done
415+
416+
if tt.wantConfig.ssh != "" {
417+
got := sshConfigFileRead(t, sshConfigName)
418+
assert.Equal(t, tt.wantConfig.ssh, got)
419+
}
420+
if tt.wantConfig.coder != "" {
421+
got := sshConfigFileRead(t, coderConfigName)
422+
if tt.wantConfig.coderPartial {
423+
assert.Contains(t, got, tt.wantConfig.coder)
424+
} else {
425+
assert.Equal(t, tt.wantConfig.coder, got)
426+
}
427+
}
428+
})
429+
}
430+
}

0 commit comments

Comments
 (0)