2022-02-10 14:33:27 +00:00
|
|
|
package cli_test
|
|
|
|
|
|
|
|
import (
|
2022-03-22 19:17:50 +00:00
|
|
|
"context"
|
2022-09-14 20:15:47 +00:00
|
|
|
"fmt"
|
2022-02-10 14:33:27 +00:00
|
|
|
"testing"
|
|
|
|
|
2022-05-24 07:58:39 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2022-02-17 16:44:49 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2022-02-10 14:33:27 +00:00
|
|
|
"github.com/coder/coder/cli/clitest"
|
2022-07-01 16:49:39 +00:00
|
|
|
"github.com/coder/coder/cli/cliui"
|
2022-02-10 14:33:27 +00:00
|
|
|
"github.com/coder/coder/coderd/coderdtest"
|
2022-02-17 16:44:49 +00:00
|
|
|
"github.com/coder/coder/pty/ptytest"
|
2022-02-10 14:33:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestLogin(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("InitialUserNoTTY", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-02-21 20:36:29 +00:00
|
|
|
client := coderdtest.New(t, nil)
|
2022-02-10 14:33:27 +00:00
|
|
|
root, _ := clitest.New(t, "login", client.URL.String())
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.Run()
|
2022-02-10 14:33:27 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
})
|
|
|
|
|
2022-09-14 20:15:47 +00:00
|
|
|
t.Run("InitialUserBadLoginURL", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
badLoginURL := "https://fcca2077f06e68aaf9"
|
|
|
|
root, _ := clitest.New(t, "login", badLoginURL)
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.Run()
|
2022-09-14 20:15:47 +00:00
|
|
|
errMsg := fmt.Sprintf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser?", badLoginURL)
|
|
|
|
require.ErrorContains(t, err, errMsg)
|
|
|
|
})
|
|
|
|
|
2022-02-10 14:33:27 +00:00
|
|
|
t.Run("InitialUserTTY", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-02-21 20:36:29 +00:00
|
|
|
client := coderdtest.New(t, nil)
|
fix: Run expect tests on Windows with conpty pseudo-terminal (#276)
This brings together a bunch of random, partially implemented packages for support of the new(ish) Windows [`conpty`](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/) API - such that we can leverage the `expect` style of CLI tests, but in a way that works in Linux/OSX `pty`s and Windows `conpty`.
These include:
- Vendoring the `go-expect` library from Netflix w/ some tweaks to work cross-platform
- Vendoring the `pty` cross-platform implementation from [waypoint-plugin-sdk](https://github.com/hashicorp/waypoint-plugin-sdk/tree/b55c787a65ff9b7d2b32cfae80681b78f8f2275e/internal/pkg/pty)
- Vendoring the `conpty` Windows-specific implementation from [waypoint-plugin-sdk](https://github.com/hashicorp/waypoint-plugin-sdk/tree/b55c787a65ff9b7d2b32cfae80681b78f8f2275e/internal/pkg/conpty)
- Adjusting the `pty` interface to work with `go-expect` + the cross-plat version
There were several limitations with the current packages:
- `go-expect` requires the same `os.File` (TTY) for input / output, but `conhost` requires separate file handles
- `conpty` does not handle input, only output
- The cross-platform `pty` didn't expose the full set of primitives needed for `console`
Therefore, the following changes were made:
- Handling of `stdin` was added to the `conpty` interface
- We weren't using the full extent of the `go-expect` interface, so some portions were removed (ie, exec'ing a process) to simplify our implementation and make it easier to extend cross-platform
- Instead of `console` exposing just a `Tty`, it exposes an `InTty` and `OutTty`, to help encapsulate the difference on Windows (on Linux, these point to the same pipe)
Future improvements:
- The `isatty` implementation doesn't support accurate detection of `conhost` pty's without an associated process. In lieu of a more robust check, I've added a `--force-tty` flag intended for test case use - that forces the CLI to run in tty mode.
- It seems the windows implementation doesn't support setting a deadline. This is needed for the expect.Timeout API, but isn't used by us yet.
Fixes #241
2022-02-15 01:05:40 +00:00
|
|
|
// The --force-tty flag is required on Windows, because the `isatty` library does not
|
|
|
|
// accurately detect Windows ptys when they are not attached to a process:
|
|
|
|
// https://github.com/mattn/go-isatty/issues/59
|
2022-03-22 19:17:50 +00:00
|
|
|
doneChan := make(chan struct{})
|
|
|
|
root, _ := clitest.New(t, "login", "--force-tty", client.URL.String())
|
2023-03-23 22:42:20 +00:00
|
|
|
pty := ptytest.New(t).Attach(root)
|
2022-02-10 14:33:27 +00:00
|
|
|
go func() {
|
2022-03-22 19:17:50 +00:00
|
|
|
defer close(doneChan)
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.Run()
|
2022-05-24 07:58:39 +00:00
|
|
|
assert.NoError(t, err)
|
2022-02-10 14:33:27 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
matches := []string{
|
2022-03-22 19:17:50 +00:00
|
|
|
"first user?", "yes",
|
2022-02-10 14:33:27 +00:00
|
|
|
"username", "testuser",
|
|
|
|
"email", "user@coder.com",
|
2023-02-08 20:10:08 +00:00
|
|
|
"password", "SomeSecurePassword!",
|
|
|
|
"password", "SomeSecurePassword!", // Confirm.
|
2022-11-16 23:09:49 +00:00
|
|
|
"trial", "yes",
|
2022-02-10 14:33:27 +00:00
|
|
|
}
|
|
|
|
for i := 0; i < len(matches); i += 2 {
|
|
|
|
match := matches[i]
|
|
|
|
value := matches[i+1]
|
2022-02-17 16:44:49 +00:00
|
|
|
pty.ExpectMatch(match)
|
|
|
|
pty.WriteLine(value)
|
2022-02-10 14:33:27 +00:00
|
|
|
}
|
2022-02-17 16:44:49 +00:00
|
|
|
pty.ExpectMatch("Welcome to Coder")
|
2022-03-22 19:17:50 +00:00
|
|
|
<-doneChan
|
2022-02-10 14:33:27 +00:00
|
|
|
})
|
2022-02-18 04:09:33 +00:00
|
|
|
|
2023-01-07 02:57:25 +00:00
|
|
|
t.Run("InitialUserTTYFlag", func(t *testing.T) {
|
2022-06-15 21:02:18 +00:00
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, nil)
|
|
|
|
// The --force-tty flag is required on Windows, because the `isatty` library does not
|
|
|
|
// accurately detect Windows ptys when they are not attached to a process:
|
|
|
|
// https://github.com/mattn/go-isatty/issues/59
|
2023-03-23 22:42:20 +00:00
|
|
|
inv, _ := clitest.New(t, "--url", client.URL.String(), "login", "--force-tty")
|
|
|
|
pty := ptytest.New(t).Attach(inv)
|
|
|
|
|
|
|
|
clitest.Start(t, inv)
|
2023-01-07 02:57:25 +00:00
|
|
|
|
|
|
|
matches := []string{
|
|
|
|
"first user?", "yes",
|
|
|
|
"username", "testuser",
|
|
|
|
"email", "user@coder.com",
|
2023-02-08 20:10:08 +00:00
|
|
|
"password", "SomeSecurePassword!",
|
|
|
|
"password", "SomeSecurePassword!", // Confirm.
|
2023-01-07 02:57:25 +00:00
|
|
|
"trial", "yes",
|
|
|
|
}
|
|
|
|
for i := 0; i < len(matches); i += 2 {
|
|
|
|
match := matches[i]
|
|
|
|
value := matches[i+1]
|
|
|
|
pty.ExpectMatch(match)
|
|
|
|
pty.WriteLine(value)
|
|
|
|
}
|
|
|
|
pty.ExpectMatch("Welcome to Coder")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("InitialUserFlags", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, nil)
|
|
|
|
doneChan := make(chan struct{})
|
2023-02-08 20:10:08 +00:00
|
|
|
root, _ := clitest.New(t, "login", client.URL.String(), "--first-user-username", "testuser", "--first-user-email", "user@coder.com", "--first-user-password", "SomeSecurePassword!", "--first-user-trial")
|
2023-03-23 22:42:20 +00:00
|
|
|
pty := ptytest.New(t).Attach(root)
|
2022-06-15 21:02:18 +00:00
|
|
|
go func() {
|
|
|
|
defer close(doneChan)
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.Run()
|
2022-06-15 21:02:18 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
}()
|
|
|
|
pty.ExpectMatch("Welcome to Coder")
|
|
|
|
<-doneChan
|
|
|
|
})
|
|
|
|
|
2022-05-06 12:47:38 +00:00
|
|
|
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
client := coderdtest.New(t, nil)
|
|
|
|
// The --force-tty flag is required on Windows, because the `isatty` library does not
|
|
|
|
// accurately detect Windows ptys when they are not attached to a process:
|
|
|
|
// https://github.com/mattn/go-isatty/issues/59
|
|
|
|
doneChan := make(chan struct{})
|
|
|
|
root, _ := clitest.New(t, "login", "--force-tty", client.URL.String())
|
2023-03-23 22:42:20 +00:00
|
|
|
pty := ptytest.New(t).Attach(root)
|
2022-05-06 12:47:38 +00:00
|
|
|
go func() {
|
|
|
|
defer close(doneChan)
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.WithContext(ctx).Run()
|
2022-07-01 16:49:39 +00:00
|
|
|
assert.NoError(t, err)
|
2022-05-06 12:47:38 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
matches := []string{
|
|
|
|
"first user?", "yes",
|
|
|
|
"username", "testuser",
|
|
|
|
"email", "user@coder.com",
|
2023-02-08 20:10:08 +00:00
|
|
|
"password", "MyFirstSecurePassword!",
|
|
|
|
"password", "MyNonMatchingSecurePassword!", // Confirm.
|
2022-05-06 12:47:38 +00:00
|
|
|
}
|
|
|
|
for i := 0; i < len(matches); i += 2 {
|
|
|
|
match := matches[i]
|
|
|
|
value := matches[i+1]
|
|
|
|
pty.ExpectMatch(match)
|
|
|
|
pty.WriteLine(value)
|
|
|
|
}
|
2022-07-01 16:49:39 +00:00
|
|
|
|
|
|
|
// Validate that we reprompt for matching passwords.
|
2022-05-06 12:47:38 +00:00
|
|
|
pty.ExpectMatch("Passwords do not match")
|
2022-07-01 16:49:39 +00:00
|
|
|
pty.ExpectMatch("Enter a " + cliui.Styles.Field.Render("password"))
|
|
|
|
|
2023-02-08 20:10:08 +00:00
|
|
|
pty.WriteLine("SomeSecurePassword!")
|
2022-07-01 16:49:39 +00:00
|
|
|
pty.ExpectMatch("Confirm")
|
2023-02-08 20:10:08 +00:00
|
|
|
pty.WriteLine("SomeSecurePassword!")
|
2022-11-16 23:09:49 +00:00
|
|
|
pty.ExpectMatch("trial")
|
|
|
|
pty.WriteLine("yes")
|
2022-07-01 16:49:39 +00:00
|
|
|
pty.ExpectMatch("Welcome to Coder")
|
2022-05-06 12:47:38 +00:00
|
|
|
<-doneChan
|
|
|
|
})
|
|
|
|
|
2022-02-18 04:09:33 +00:00
|
|
|
t.Run("ExistingUserValidTokenTTY", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-02-21 20:36:29 +00:00
|
|
|
client := coderdtest.New(t, nil)
|
2022-03-07 17:40:54 +00:00
|
|
|
coderdtest.CreateFirstUser(t, client)
|
2022-02-18 04:09:33 +00:00
|
|
|
|
2022-03-22 19:17:50 +00:00
|
|
|
doneChan := make(chan struct{})
|
|
|
|
root, _ := clitest.New(t, "login", "--force-tty", client.URL.String(), "--no-open")
|
2023-03-23 22:42:20 +00:00
|
|
|
pty := ptytest.New(t).Attach(root)
|
2022-02-18 04:09:33 +00:00
|
|
|
go func() {
|
2022-03-22 19:17:50 +00:00
|
|
|
defer close(doneChan)
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.Run()
|
2022-05-24 07:58:39 +00:00
|
|
|
assert.NoError(t, err)
|
2022-02-18 04:09:33 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
pty.ExpectMatch("Paste your token here:")
|
2022-11-09 13:31:24 +00:00
|
|
|
pty.WriteLine(client.SessionToken())
|
2022-02-18 04:09:33 +00:00
|
|
|
pty.ExpectMatch("Welcome to Coder")
|
2022-03-22 19:17:50 +00:00
|
|
|
<-doneChan
|
2022-02-18 04:09:33 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("ExistingUserInvalidTokenTTY", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-02-21 20:36:29 +00:00
|
|
|
client := coderdtest.New(t, nil)
|
2022-03-07 17:40:54 +00:00
|
|
|
coderdtest.CreateFirstUser(t, client)
|
2022-02-18 04:09:33 +00:00
|
|
|
|
2022-03-22 19:17:50 +00:00
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
|
|
defer cancelFunc()
|
|
|
|
doneChan := make(chan struct{})
|
|
|
|
root, _ := clitest.New(t, "login", client.URL.String(), "--no-open")
|
2023-03-23 22:42:20 +00:00
|
|
|
pty := ptytest.New(t).Attach(root)
|
2022-02-18 04:09:33 +00:00
|
|
|
go func() {
|
2022-03-22 19:17:50 +00:00
|
|
|
defer close(doneChan)
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.WithContext(ctx).Run()
|
2022-02-18 04:09:33 +00:00
|
|
|
// An error is expected in this case, since the login wasn't successful:
|
2022-05-24 07:58:39 +00:00
|
|
|
assert.Error(t, err)
|
2022-02-18 04:09:33 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
pty.ExpectMatch("Paste your token here:")
|
|
|
|
pty.WriteLine("an-invalid-token")
|
|
|
|
pty.ExpectMatch("That's not a valid token!")
|
2022-03-22 19:17:50 +00:00
|
|
|
cancelFunc()
|
|
|
|
<-doneChan
|
2022-02-18 04:09:33 +00:00
|
|
|
})
|
2022-05-16 18:07:35 +00:00
|
|
|
|
|
|
|
t.Run("TokenFlag", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, nil)
|
|
|
|
coderdtest.CreateFirstUser(t, client)
|
2022-11-09 13:31:24 +00:00
|
|
|
root, cfg := clitest.New(t, "login", client.URL.String(), "--token", client.SessionToken())
|
2023-03-23 22:42:20 +00:00
|
|
|
err := root.Run()
|
2022-05-16 18:07:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
sessionFile, err := cfg.Session().Read()
|
|
|
|
require.NoError(t, err)
|
2022-11-09 13:31:24 +00:00
|
|
|
require.Equal(t, client.SessionToken(), sessionFile)
|
2022-05-16 18:07:35 +00:00
|
|
|
})
|
2022-02-10 14:33:27 +00:00
|
|
|
}
|