diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 45ac921d..9a3261ea 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,6 +40,9 @@ jobs: uses: ./ci/image env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CODER_URL: ${{ secrets.CODER_URL }} + CODER_EMAIL: ${{ secrets.CODER_EMAIL }} + CODER_PASSWORD: ${{ secrets.CODER_PASSWORD }} with: args: make -j test/coverage gendocs: diff --git a/internal/cmd/cli_test.go b/internal/cmd/cli_test.go new file mode 100644 index 00000000..d1c639dc --- /dev/null +++ b/internal/cmd/cli_test.go @@ -0,0 +1,142 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "strings" + "testing" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/slogtest" + "cdr.dev/slog/sloggers/slogtest/assert" + "golang.org/x/xerrors" + + "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/config" + "cdr.dev/coder-cli/pkg/clog" +) + +func init() { + tmpDir, err := ioutil.TempDir("", "coder-cli-config-dir") + if err != nil { + panic(err) + } + config.SetRoot(tmpDir) + + // TODO: might need to make this a command scoped option to make assertions against its output + clog.SetOutput(ioutil.Discard) + + email := os.Getenv("CODER_EMAIL") + password := os.Getenv("CODER_PASSWORD") + rawURL := os.Getenv("CODER_URL") + if email == "" || password == "" || rawURL == "" { + panic("CODER_EMAIL, CODER_PASSWORD, and CODER_URL are required environment variables") + } + u, err := url.Parse(rawURL) + if err != nil { + panic("invalid CODER_URL: " + err.Error()) + } + client, err := coder.NewClient(coder.ClientOptions{ + BaseURL: u, + Email: email, + Password: password, + }) + if err != nil { + panic("new client: " + err.Error()) + } + if err := config.URL.Write(rawURL); err != nil { + panic("write config url: " + err.Error()) + } + if err := config.Session.Write(client.Token()); err != nil { + panic("write config token: " + err.Error()) + } +} + +type result struct { + outBuffer *bytes.Buffer + errBuffer *bytes.Buffer + exitErr error +} + +func (r result) success(t *testing.T) { + t.Helper() + assert.Success(t, "execute command", r.exitErr) +} + +//nolint +func (r result) stdoutContains(t *testing.T, substring string) { + t.Helper() + if !strings.Contains(r.outBuffer.String(), substring) { + slogtest.Fatal(t, "stdout contains substring", slog.F("substring", substring), slog.F("stdout", r.outBuffer.String())) + } +} + +//nolint +func (r result) stdoutUnmarshals(t *testing.T, target interface{}) { + t.Helper() + err := json.Unmarshal(r.outBuffer.Bytes(), target) + assert.Success(t, "unmarshal json", err) +} + +//nolint +func (r result) stdoutEmpty(t *testing.T) { + t.Helper() + assert.Equal(t, "stdout empty", "", r.outBuffer.String()) +} + +//nolint +func (r result) stderrEmpty(t *testing.T) { + t.Helper() + assert.Equal(t, "stderr empty", "", r.errBuffer.String()) +} + +//nolint +func (r result) stderrContains(t *testing.T, substring string) { + t.Helper() + if !strings.Contains(r.errBuffer.String(), substring) { + slogtest.Fatal(t, "stderr contains substring", slog.F("substring", substring), slog.F("stderr", r.errBuffer.String())) + } +} + +//nolint +func (r result) clogError(t *testing.T) clog.CLIError { + t.Helper() + var cliErr clog.CLIError + if !xerrors.As(r.exitErr, &cliErr) { + slogtest.Fatal(t, "expected clog error, none found", slog.Error(r.exitErr), slog.F("type", fmt.Sprintf("%T", r.exitErr))) + } + slogtest.Debug(t, "clog error", slog.F("message", cliErr.String())) + return cliErr +} + +func execute(t *testing.T, in io.Reader, args ...string) result { + cmd := Make() + + var outStream bytes.Buffer + var errStream bytes.Buffer + + cmd.SetArgs(args) + + cmd.SetIn(in) + cmd.SetOut(&outStream) + cmd.SetErr(&errStream) + + err := cmd.Execute() + + slogtest.Debug(t, "execute command", + slog.F("out_buffer", outStream.String()), + slog.F("err_buffer", errStream.String()), + slog.F("args", args), + slog.F("execute_error", err), + ) + return result{ + outBuffer: &outStream, + errBuffer: &errStream, + exitErr: err, + } +} diff --git a/internal/cmd/envs_test.go b/internal/cmd/envs_test.go index 879a20a0..c7cc6451 100644 --- a/internal/cmd/envs_test.go +++ b/internal/cmd/envs_test.go @@ -1,76 +1,18 @@ package cmd import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "strings" "testing" - "cdr.dev/slog" - "cdr.dev/slog/sloggers/slogtest" - "cdr.dev/slog/sloggers/slogtest/assert" - "golang.org/x/xerrors" - - "cdr.dev/coder-cli/internal/config" - "cdr.dev/coder-cli/pkg/clog" + "cdr.dev/coder-cli/coder-sdk" ) -func init() { - tmpDir, err := ioutil.TempDir("", "coder-cli-config-dir") - if err != nil { - panic(err) - } - config.SetRoot(tmpDir) -} - -func TestEnvsCommand(t *testing.T) { - res := execute(t, []string{"envs", "ls"}, nil) - assert.Error(t, "execute without auth", res.ExitErr) - - err := assertClogErr(t, res.ExitErr) - assert.True(t, "login hint in error", strings.Contains(err.String(), "did you run \"coder login")) -} - -type result struct { - OutBuffer *bytes.Buffer - ErrBuffer *bytes.Buffer - ExitErr error -} +func Test_envs_ls(t *testing.T) { + res := execute(t, nil, "envs", "ls") + res.success(t) -func execute(t *testing.T, args []string, in io.Reader) result { - cmd := Make() - - outStream := bytes.NewBuffer(nil) - errStream := bytes.NewBuffer(nil) - - cmd.SetArgs(args) - - cmd.SetIn(in) - cmd.SetOut(outStream) - cmd.SetErr(errStream) - - err := cmd.Execute() - - slogtest.Debug(t, "execute command", - slog.F("outBuffer", outStream.String()), - slog.F("errBuffer", errStream.String()), - slog.F("args", args), - slog.F("execute_error", err), - ) - return result{ - OutBuffer: outStream, - ErrBuffer: errStream, - ExitErr: err, - } -} + res = execute(t, nil, "envs", "ls", "--output=json") + res.success(t) -func assertClogErr(t *testing.T, err error) clog.CLIError { - var cliErr clog.CLIError - if !xerrors.As(err, &cliErr) { - slogtest.Fatal(t, "expected clog error, none found", slog.Error(err), slog.F("type", fmt.Sprintf("%T", err))) - } - slogtest.Debug(t, "clog error", slog.F("message", cliErr.String())) - return cliErr + var envs []coder.Environment + res.stdoutUnmarshals(t, &envs) } diff --git a/internal/cmd/providers_test.go b/internal/cmd/providers_test.go new file mode 100644 index 00000000..14900759 --- /dev/null +++ b/internal/cmd/providers_test.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "testing" +) + +func Test_providers_ls(t *testing.T) { + res := execute(t, nil, "providers", "ls") + res.success(t) +}