mirror of https://github.com/coder/coder.git
364 lines
11 KiB
Go
364 lines
11 KiB
Go
package cliui_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/atomic"
|
|
|
|
"github.com/coder/coder/cli/clibase"
|
|
"github.com/coder/coder/cli/cliui"
|
|
"github.com/coder/coder/codersdk"
|
|
"github.com/coder/coder/pty/ptytest"
|
|
"github.com/coder/coder/testutil"
|
|
)
|
|
|
|
func TestAgent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
var disconnected atomic.Bool
|
|
ptty := ptytest.New(t)
|
|
cmd := &clibase.Cmd{
|
|
Handler: func(inv *clibase.Invocation) error {
|
|
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
|
WorkspaceName: "example",
|
|
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
|
agent := codersdk.WorkspaceAgent{
|
|
Status: codersdk.WorkspaceAgentDisconnected,
|
|
LoginBeforeReady: true,
|
|
}
|
|
if disconnected.Load() {
|
|
agent.Status = codersdk.WorkspaceAgentConnected
|
|
}
|
|
return agent, nil
|
|
},
|
|
FetchInterval: time.Millisecond,
|
|
WarnInterval: 10 * time.Millisecond,
|
|
})
|
|
return err
|
|
},
|
|
}
|
|
|
|
inv := cmd.Invoke()
|
|
ptty.Attach(inv)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
ptty.ExpectMatchContext(ctx, "lost connection")
|
|
disconnected.Store(true)
|
|
<-done
|
|
}
|
|
|
|
func TestAgent_TimeoutWithTroubleshootingURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
wantURL := "https://coder.com/troubleshoot"
|
|
|
|
var connected, timeout atomic.Bool
|
|
cmd := &clibase.Cmd{
|
|
Handler: func(inv *clibase.Invocation) error {
|
|
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
|
WorkspaceName: "example",
|
|
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
|
agent := codersdk.WorkspaceAgent{
|
|
Status: codersdk.WorkspaceAgentConnecting,
|
|
TroubleshootingURL: wantURL,
|
|
LoginBeforeReady: true,
|
|
}
|
|
switch {
|
|
case !connected.Load() && timeout.Load():
|
|
agent.Status = codersdk.WorkspaceAgentTimeout
|
|
case connected.Load():
|
|
agent.Status = codersdk.WorkspaceAgentConnected
|
|
}
|
|
return agent, nil
|
|
},
|
|
FetchInterval: time.Millisecond,
|
|
WarnInterval: 5 * time.Millisecond,
|
|
})
|
|
return err
|
|
},
|
|
}
|
|
ptty := ptytest.New(t)
|
|
|
|
inv := cmd.Invoke()
|
|
ptty.Attach(inv)
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- inv.WithContext(ctx).Run()
|
|
}()
|
|
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
|
timeout.Store(true)
|
|
ptty.ExpectMatchContext(ctx, wantURL)
|
|
connected.Store(true)
|
|
require.NoError(t, <-done)
|
|
}
|
|
|
|
func TestAgent_StartupTimeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
wantURL := "https://coder.com/this-is-a-really-long-troubleshooting-url-that-should-not-wrap"
|
|
|
|
var status, state atomic.String
|
|
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
|
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
|
|
|
cmd := &clibase.Cmd{
|
|
Handler: func(inv *clibase.Invocation) error {
|
|
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
|
WorkspaceName: "example",
|
|
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
|
agent := codersdk.WorkspaceAgent{
|
|
Status: codersdk.WorkspaceAgentConnecting,
|
|
LoginBeforeReady: false,
|
|
LifecycleState: codersdk.WorkspaceAgentLifecycleCreated,
|
|
TroubleshootingURL: wantURL,
|
|
}
|
|
|
|
if s := status.Load(); s != "" {
|
|
agent.Status = codersdk.WorkspaceAgentStatus(s)
|
|
}
|
|
if s := state.Load(); s != "" {
|
|
agent.LifecycleState = codersdk.WorkspaceAgentLifecycle(s)
|
|
}
|
|
return agent, nil
|
|
},
|
|
FetchInterval: time.Millisecond,
|
|
WarnInterval: time.Millisecond,
|
|
NoWait: false,
|
|
})
|
|
return err
|
|
},
|
|
}
|
|
|
|
ptty := ptytest.New(t)
|
|
|
|
inv := cmd.Invoke()
|
|
ptty.Attach(inv)
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- inv.WithContext(ctx).Run()
|
|
}()
|
|
setStatus(codersdk.WorkspaceAgentConnecting)
|
|
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
|
setStatus(codersdk.WorkspaceAgentConnected)
|
|
setState(codersdk.WorkspaceAgentLifecycleStarting)
|
|
ptty.ExpectMatchContext(ctx, "workspace is getting ready")
|
|
setState(codersdk.WorkspaceAgentLifecycleStartTimeout)
|
|
ptty.ExpectMatchContext(ctx, "is taking longer")
|
|
ptty.ExpectMatchContext(ctx, wantURL)
|
|
setState(codersdk.WorkspaceAgentLifecycleReady)
|
|
require.NoError(t, <-done)
|
|
}
|
|
|
|
func TestAgent_StartErrorExit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
wantURL := "https://coder.com/this-is-a-really-long-troubleshooting-url-that-should-not-wrap"
|
|
|
|
var status, state atomic.String
|
|
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
|
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
|
cmd := &clibase.Cmd{
|
|
Handler: func(inv *clibase.Invocation) error {
|
|
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
|
WorkspaceName: "example",
|
|
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
|
agent := codersdk.WorkspaceAgent{
|
|
Status: codersdk.WorkspaceAgentConnecting,
|
|
LoginBeforeReady: false,
|
|
LifecycleState: codersdk.WorkspaceAgentLifecycleCreated,
|
|
TroubleshootingURL: wantURL,
|
|
}
|
|
|
|
if s := status.Load(); s != "" {
|
|
agent.Status = codersdk.WorkspaceAgentStatus(s)
|
|
}
|
|
if s := state.Load(); s != "" {
|
|
agent.LifecycleState = codersdk.WorkspaceAgentLifecycle(s)
|
|
}
|
|
return agent, nil
|
|
},
|
|
FetchInterval: time.Millisecond,
|
|
WarnInterval: 60 * time.Second,
|
|
NoWait: false,
|
|
})
|
|
return err
|
|
},
|
|
}
|
|
|
|
ptty := ptytest.New(t)
|
|
|
|
inv := cmd.Invoke()
|
|
ptty.Attach(inv)
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- inv.WithContext(ctx).Run()
|
|
}()
|
|
setStatus(codersdk.WorkspaceAgentConnected)
|
|
setState(codersdk.WorkspaceAgentLifecycleStarting)
|
|
ptty.ExpectMatchContext(ctx, "to become ready...")
|
|
setState(codersdk.WorkspaceAgentLifecycleStartError)
|
|
ptty.ExpectMatchContext(ctx, "ran into a problem")
|
|
err := <-done
|
|
require.ErrorIs(t, err, cliui.AgentStartError, "lifecycle start_error should exit with error")
|
|
}
|
|
|
|
func TestAgent_NoWait(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
wantURL := "https://coder.com/this-is-a-really-long-troubleshooting-url-that-should-not-wrap"
|
|
|
|
var status, state atomic.String
|
|
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
|
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
|
cmd := &clibase.Cmd{
|
|
Handler: func(inv *clibase.Invocation) error {
|
|
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
|
WorkspaceName: "example",
|
|
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
|
agent := codersdk.WorkspaceAgent{
|
|
Status: codersdk.WorkspaceAgentConnecting,
|
|
LoginBeforeReady: false,
|
|
LifecycleState: codersdk.WorkspaceAgentLifecycleCreated,
|
|
TroubleshootingURL: wantURL,
|
|
}
|
|
|
|
if s := status.Load(); s != "" {
|
|
agent.Status = codersdk.WorkspaceAgentStatus(s)
|
|
}
|
|
if s := state.Load(); s != "" {
|
|
agent.LifecycleState = codersdk.WorkspaceAgentLifecycle(s)
|
|
}
|
|
return agent, nil
|
|
},
|
|
FetchInterval: time.Millisecond,
|
|
WarnInterval: time.Second,
|
|
NoWait: true,
|
|
})
|
|
return err
|
|
},
|
|
}
|
|
|
|
ptty := ptytest.New(t)
|
|
|
|
inv := cmd.Invoke()
|
|
ptty.Attach(inv)
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- inv.WithContext(ctx).Run()
|
|
}()
|
|
setStatus(codersdk.WorkspaceAgentConnecting)
|
|
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
|
|
|
setStatus(codersdk.WorkspaceAgentConnected)
|
|
require.NoError(t, <-done, "created - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleStarting)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "starting - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleStartTimeout)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "start timeout - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleStartError)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "start error - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleReady)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "ready - should exit early")
|
|
}
|
|
|
|
func TestAgent_LoginBeforeReadyEnabled(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
wantURL := "https://coder.com/this-is-a-really-long-troubleshooting-url-that-should-not-wrap"
|
|
|
|
var status, state atomic.String
|
|
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
|
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
|
cmd := &clibase.Cmd{
|
|
Handler: func(inv *clibase.Invocation) error {
|
|
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
|
WorkspaceName: "example",
|
|
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
|
agent := codersdk.WorkspaceAgent{
|
|
Status: codersdk.WorkspaceAgentConnecting,
|
|
LoginBeforeReady: true,
|
|
LifecycleState: codersdk.WorkspaceAgentLifecycleCreated,
|
|
TroubleshootingURL: wantURL,
|
|
}
|
|
|
|
if s := status.Load(); s != "" {
|
|
agent.Status = codersdk.WorkspaceAgentStatus(s)
|
|
}
|
|
if s := state.Load(); s != "" {
|
|
agent.LifecycleState = codersdk.WorkspaceAgentLifecycle(s)
|
|
}
|
|
return agent, nil
|
|
},
|
|
FetchInterval: time.Millisecond,
|
|
WarnInterval: time.Second,
|
|
NoWait: false,
|
|
})
|
|
return err
|
|
},
|
|
}
|
|
|
|
inv := cmd.Invoke()
|
|
|
|
ptty := ptytest.New(t)
|
|
ptty.Attach(inv)
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- inv.WithContext(ctx).Run()
|
|
}()
|
|
setStatus(codersdk.WorkspaceAgentConnecting)
|
|
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
|
|
|
setStatus(codersdk.WorkspaceAgentConnected)
|
|
require.NoError(t, <-done, "created - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleStarting)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "starting - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleStartTimeout)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "start timeout - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleStartError)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "start error - should exit early")
|
|
|
|
setState(codersdk.WorkspaceAgentLifecycleReady)
|
|
go func() { done <- inv.WithContext(ctx).Run() }()
|
|
require.NoError(t, <-done, "ready - should exit early")
|
|
}
|