Skip to content

Commit 24cc781

Browse files
committed
Add CLI test for login
1 parent d6a1eb8 commit 24cc781

File tree

9 files changed

+152
-48
lines changed

9 files changed

+152
-48
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@
3535
"goleak",
3636
"hashicorp",
3737
"httpmw",
38+
"isatty",
3839
"Jobf",
40+
"kirsle",
3941
"manifoldco",
42+
"mattn",
4043
"moby",
4144
"nhooyr",
4245
"nolint",

cli/clitest/clitest.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package clitest
2+
3+
import (
4+
"bufio"
5+
"io"
6+
"testing"
7+
8+
"github.com/spf13/cobra"
9+
10+
"github.com/coder/coder/cli"
11+
"github.com/coder/coder/cli/config"
12+
)
13+
14+
func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
15+
cmd := cli.Root()
16+
dir := t.TempDir()
17+
root := config.Root(dir)
18+
cmd.SetArgs(append([]string{"--global-config", dir}, args...))
19+
return cmd, root
20+
}
21+
22+
func StdoutLogs(t *testing.T) io.Writer {
23+
reader, writer := io.Pipe()
24+
scanner := bufio.NewScanner(reader)
25+
t.Cleanup(func() {
26+
_ = reader.Close()
27+
_ = writer.Close()
28+
})
29+
go func() {
30+
for scanner.Scan() {
31+
if scanner.Err() != nil {
32+
return
33+
}
34+
t.Log(scanner.Text())
35+
}
36+
}()
37+
return writer
38+
}

cli/config/file_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestFile(t *testing.T) {
2424
require.NoError(t, err)
2525
data, err := root.Session().Read()
2626
require.NoError(t, err)
27-
require.Equal(t, "test", string(data))
27+
require.Equal(t, "test", data)
2828
})
2929

3030
t.Run("Delete", func(t *testing.T) {

cli/login.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
package cli
22

33
import (
4-
"errors"
54
"fmt"
65
"net/url"
6+
"os"
77
"strings"
88

9-
"github.com/coder/coder/coderd"
10-
"github.com/coder/coder/codersdk"
119
"github.com/fatih/color"
1210
"github.com/go-playground/validator/v10"
1311
"github.com/manifoldco/promptui"
1412
"github.com/spf13/cobra"
1513
"golang.org/x/xerrors"
14+
15+
"github.com/coder/coder/coderd"
16+
"github.com/coder/coder/codersdk"
1617
)
1718

1819
func login() *cobra.Command {
@@ -46,7 +47,7 @@ func login() *cobra.Command {
4647
if !isTTY(cmd.InOrStdin()) {
4748
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
4849
}
49-
fmt.Fprintf(cmd.OutOrStdout(), "%s Your Coder deployment hasn't been setup!\n", color.HiBlackString(">"))
50+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Your Coder deployment hasn't been setup!\n", color.HiBlackString(">"))
5051

5152
_, err := runPrompt(cmd, &promptui.Prompt{
5253
Label: "Would you like to create the first user?",
@@ -59,7 +60,7 @@ func login() *cobra.Command {
5960

6061
username, err := runPrompt(cmd, &promptui.Prompt{
6162
Label: "What username would you like?",
62-
Default: "kyle",
63+
Default: os.Getenv("USER"),
6364
})
6465
if err != nil {
6566
return err
@@ -78,7 +79,7 @@ func login() *cobra.Command {
7879
Validate: func(s string) error {
7980
err := validator.New().Var(s, "email")
8081
if err != nil {
81-
return errors.New("That's not a valid email address!")
82+
return xerrors.New("That's not a valid email address!")
8283
}
8384
return err
8485
},
@@ -121,7 +122,7 @@ func login() *cobra.Command {
121122
return xerrors.Errorf("write server url: %w", err)
122123
}
123124

124-
fmt.Fprintf(cmd.OutOrStdout(), "%s Welcome to Coder, %s! You're logged in.\n", color.HiBlackString(">"), color.HiCyanString(username))
125+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Welcome to Coder, %s! You're logged in.\n", color.HiBlackString(">"), color.HiCyanString(username))
125126
return nil
126127
}
127128

cli/login_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package cli_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/coder/coder/cli/clitest"
7+
"github.com/coder/coder/coderd/coderdtest"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/Netflix/go-expect"
11+
)
12+
13+
func TestLogin(t *testing.T) {
14+
t.Parallel()
15+
t.Run("InitialUserNoTTY", func(t *testing.T) {
16+
t.Parallel()
17+
client := coderdtest.New(t)
18+
root, _ := clitest.New(t, "login", client.URL.String())
19+
err := root.Execute()
20+
require.Error(t, err)
21+
})
22+
23+
t.Run("InitialUserTTY", func(t *testing.T) {
24+
t.Parallel()
25+
console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t)))
26+
require.NoError(t, err)
27+
client := coderdtest.New(t)
28+
root, _ := clitest.New(t, "login", client.URL.String())
29+
root.SetIn(console.Tty())
30+
root.SetOut(console.Tty())
31+
go func() {
32+
err = root.Execute()
33+
require.NoError(t, err)
34+
}()
35+
36+
matches := []string{
37+
"first user?", "y",
38+
"username", "testuser",
39+
"organization", "testorg",
40+
"email", "user@coder.com",
41+
"password", "password",
42+
}
43+
for i := 0; i < len(matches); i += 2 {
44+
match := matches[i]
45+
value := matches[i+1]
46+
_, err = console.ExpectString(match)
47+
require.NoError(t, err)
48+
_, err = console.SendLine(value)
49+
require.NoError(t, err)
50+
}
51+
_, err = console.ExpectString("Welcome to Coder")
52+
require.NoError(t, err)
53+
})
54+
}

cli/projectcreate.go

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import (
1111
"time"
1212

1313
"github.com/briandowns/spinner"
14-
"github.com/coder/coder/coderd"
15-
"github.com/coder/coder/codersdk"
16-
"github.com/coder/coder/database"
1714
"github.com/fatih/color"
1815
"github.com/manifoldco/promptui"
1916
"github.com/spf13/cobra"
2017
"golang.org/x/xerrors"
18+
19+
"github.com/coder/coder/coderd"
20+
"github.com/coder/coder/codersdk"
21+
"github.com/coder/coder/database"
2122
)
2223

2324
func projectCreate() *cobra.Command {
@@ -48,9 +49,8 @@ func projectCreate() *cobra.Command {
4849
return err
4950
}
5051

51-
name := filepath.Base(workingDir)
52-
name, err = runPrompt(cmd, &promptui.Prompt{
53-
Default: name,
52+
name, err := runPrompt(cmd, &promptui.Prompt{
53+
Default: filepath.Base(workingDir),
5454
Label: "What's your project's name?",
5555
Validate: func(s string) error {
5656
_, err = client.Project(cmd.Context(), organization.Name, s)
@@ -69,7 +69,7 @@ func projectCreate() *cobra.Command {
6969
spin.Start()
7070
defer spin.Stop()
7171

72-
bytes, err := tarDir(workingDir)
72+
bytes, err := tarDirectory(workingDir)
7373
if err != nil {
7474
return err
7575
}
@@ -91,8 +91,6 @@ func projectCreate() *cobra.Command {
9191
}
9292
spin.Stop()
9393

94-
time.Sleep(time.Second)
95-
9694
logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), organization.Name, job.ID, time.Time{})
9795
if err != nil {
9896
return err
@@ -102,54 +100,50 @@ func projectCreate() *cobra.Command {
102100
if !ok {
103101
break
104102
}
105-
fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[parse]"), log.Output)
103+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[parse]"), log.Output)
106104
}
107105

108-
fmt.Printf("Projects %+v %+v\n", projects, organization)
106+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Create project %q!\n", name)
109107
return nil
110108
},
111109
}
112110
}
113111

114-
func tarDir(directory string) ([]byte, error) {
112+
func tarDirectory(directory string) ([]byte, error) {
115113
var buffer bytes.Buffer
116-
tw := tar.NewWriter(&buffer)
117-
// walk through every file in the folder
118-
err := filepath.Walk(directory, func(file string, fi os.FileInfo, err error) error {
119-
// generate tar header
120-
header, err := tar.FileInfoHeader(fi, file)
114+
tarWriter := tar.NewWriter(&buffer)
115+
err := filepath.Walk(directory, func(file string, fileInfo os.FileInfo, err error) error {
116+
if err != nil {
117+
return err
118+
}
119+
header, err := tar.FileInfoHeader(fileInfo, file)
121120
if err != nil {
122121
return err
123122
}
124-
125-
// must provide real name
126-
// (see https://golang.org/src/archive/tar/common.go?#L626)
127123
rel, err := filepath.Rel(directory, file)
128124
if err != nil {
129125
return err
130126
}
131127
header.Name = rel
132-
133-
// write header
134-
if err := tw.WriteHeader(header); err != nil {
128+
if err := tarWriter.WriteHeader(header); err != nil {
135129
return err
136130
}
137-
// if not a dir, write file content
138-
if !fi.IsDir() {
139-
data, err := os.Open(file)
140-
if err != nil {
141-
return err
142-
}
143-
if _, err := io.Copy(tw, data); err != nil {
144-
return err
145-
}
131+
if fileInfo.IsDir() {
132+
return nil
133+
}
134+
data, err := os.Open(file)
135+
if err != nil {
136+
return err
137+
}
138+
if _, err := io.Copy(tarWriter, data); err != nil {
139+
return err
146140
}
147-
return nil
141+
return data.Close()
148142
})
149143
if err != nil {
150144
return nil, err
151145
}
152-
err = tw.Flush()
146+
err = tarWriter.Flush()
153147
if err != nil {
154148
return nil, err
155149
}

cli/root.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import (
77
"os"
88
"strings"
99

10-
"github.com/coder/coder/cli/config"
11-
"github.com/coder/coder/coderd"
12-
"github.com/coder/coder/codersdk"
1310
"github.com/fatih/color"
1411
"github.com/kirsle/configdir"
1512
"github.com/manifoldco/promptui"
1613
"github.com/mattn/go-isatty"
1714
"github.com/spf13/cobra"
15+
"golang.org/x/xerrors"
16+
17+
"github.com/coder/coder/cli/config"
18+
"github.com/coder/coder/coderd"
19+
"github.com/coder/coder/codersdk"
1820
)
1921

2022
const (
@@ -115,8 +117,15 @@ func isTTY(reader io.Reader) bool {
115117
}
116118

117119
func runPrompt(cmd *cobra.Command, prompt *promptui.Prompt) (string, error) {
118-
prompt.Stdin = cmd.InOrStdin().(io.ReadCloser)
119-
prompt.Stdout = cmd.OutOrStdout().(io.WriteCloser)
120+
var ok bool
121+
prompt.Stdin, ok = cmd.InOrStdin().(io.ReadCloser)
122+
if !ok {
123+
return "", xerrors.New("stdin must be a readcloser")
124+
}
125+
prompt.Stdout, ok = cmd.OutOrStdout().(io.WriteCloser)
126+
if !ok {
127+
return "", xerrors.New("stdout must be a readcloser")
128+
}
120129

121130
// The prompt library displays defaults in a jarring way for the user
122131
// by attempting to autocomplete it. This sets no default enabling us

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ replace github.com/hashicorp/terraform-config-inspect => github.com/kylecarbs/te
1010

1111
require (
1212
cdr.dev/slog v1.4.1
13+
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
1314
github.com/briandowns/spinner v1.18.1
1415
github.com/coder/retry v1.3.0
1516
github.com/fatih/color v1.13.0
@@ -58,6 +59,7 @@ require (
5859
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
5960
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
6061
github.com/containerd/continuity v0.2.2 // indirect
62+
github.com/creack/pty v1.1.17 // indirect
6163
github.com/davecgh/go-spew v1.1.1 // indirect
6264
github.com/dhui/dktest v0.3.9 // indirect
6365
github.com/dlclark/regexp2 v1.4.0 // indirect

go.sum

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01
103103
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
104104
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
105105
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
106+
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
107+
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
106108
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
107109
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
108110
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -350,8 +352,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
350352
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
351353
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
352354
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
353-
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
354355
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
356+
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
357+
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
355358
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
356359
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
357360
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=

0 commit comments

Comments
 (0)