cli + daemon: move test seams off package globals onto injected structs

CLI: introduce internal/cli.deps which owns every RPC/SSH/host-command
seam the tree used to reach through mutable package vars. Command
builders, orchestrators, and the completion helpers become methods on
*deps. Tests construct their own deps per case, so fakes no longer leak
across cases and tests are free to run in parallel.

Daemon: move workspaceInspectRepoFunc + workspaceImportFunc onto the
Daemon struct (workspaceInspectRepo / workspaceImport), mirroring the
existing guestWaitForSSH / guestDial pattern. Workspace-prepare tests
drop t.Parallel() guards now that they no longer mutate process-wide
state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-19 19:03:55 -03:00
parent d38f580e00
commit c42fcbe012
No known key found for this signature in database
GPG key ID: 33112E6833C34679
19 changed files with 664 additions and 733 deletions

View file

@ -121,11 +121,8 @@ func TestLegacyRemovedCommandIsRejected(t *testing.T) {
}
func TestDoctorCommandPrintsReportAndFailsOnHardFailures(t *testing.T) {
original := doctorFunc
t.Cleanup(func() {
doctorFunc = original
})
doctorFunc = func(context.Context) (system.Report, error) {
d := defaultDeps()
d.doctor = func(context.Context) (system.Report, error) {
return system.Report{
Checks: []system.CheckResult{
{Name: "runtime bundle", Status: system.CheckStatusPass, Details: []string{"runtime dir /tmp/runtime"}},
@ -134,7 +131,7 @@ func TestDoctorCommandPrintsReportAndFailsOnHardFailures(t *testing.T) {
}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
var stdout bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stdout)
@ -154,15 +151,12 @@ func TestDoctorCommandPrintsReportAndFailsOnHardFailures(t *testing.T) {
}
func TestDoctorCommandReturnsUnderlyingError(t *testing.T) {
original := doctorFunc
t.Cleanup(func() {
doctorFunc = original
})
doctorFunc = func(context.Context) (system.Report, error) {
d := defaultDeps()
d.doctor = func(context.Context) (system.Report, error) {
return system.Report{}, errors.New("load failed")
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
cmd.SetArgs([]string{"doctor"})
err := cmd.Execute()
if err == nil || !strings.Contains(err.Error(), "load failed") {
@ -509,14 +503,7 @@ func TestVMCreateParamsFromFlagsRejectsNonPositiveCPUAndMemory(t *testing.T) {
}
func TestRunVMCreatePollsUntilDone(t *testing.T) {
origBegin := vmCreateBeginFunc
origStatus := vmCreateStatusFunc
origCancel := vmCreateCancelFunc
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
vmCreateStatusFunc = origStatus
vmCreateCancelFunc = origCancel
})
d := defaultDeps()
vm := model.VMRecord{
ID: "vm-id",
@ -528,7 +515,7 @@ func TestRunVMCreatePollsUntilDone(t *testing.T) {
DNSName: "devbox.vm",
},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{
Operation: api.VMCreateOperation{
ID: "op-1",
@ -538,7 +525,7 @@ func TestRunVMCreatePollsUntilDone(t *testing.T) {
}, nil
}
statusCalls := 0
vmCreateStatusFunc = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
d.vmCreateStatus = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
statusCalls++
if statusCalls == 1 {
return api.VMCreateStatusResult{
@ -560,14 +547,14 @@ func TestRunVMCreatePollsUntilDone(t *testing.T) {
},
}, nil
}
vmCreateCancelFunc = func(context.Context, string, string) error {
d.vmCreateCancel = func(context.Context, string, string) error {
t.Fatal("cancel should not be called")
return nil
}
got, err := runVMCreate(context.Background(), "/tmp/bangerd.sock", &bytes.Buffer{}, api.VMCreateParams{Name: "devbox"})
got, err := d.runVMCreate(context.Background(), "/tmp/bangerd.sock", &bytes.Buffer{}, api.VMCreateParams{Name: "devbox"})
if err != nil {
t.Fatalf("runVMCreate: %v", err)
t.Fatalf("d.runVMCreate: %v", err)
}
if got.Name != vm.Name || got.Runtime.GuestIP != vm.Runtime.GuestIP {
t.Fatalf("vm = %+v, want %+v", got, vm)
@ -878,23 +865,18 @@ func TestPrintVMPortsTableSortsAndRendersURLEndpoints(t *testing.T) {
}
func TestRunSSHSessionPrintsReminderWhenHealthCheckPasses(t *testing.T) {
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
d := defaultDeps()
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
d.sshExec = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return nil
}
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
d.vmHealth = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
return api.VMHealthResult{Name: "devbox", Healthy: true}, nil
}
var stderr bytes.Buffer
if err := runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"}, false); err != nil {
t.Fatalf("runSSHSession: %v", err)
if err := d.runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"}, false); err != nil {
t.Fatalf("d.runSSHSession: %v", err)
}
if !strings.Contains(stderr.String(), "devbox is still running") {
t.Fatalf("stderr = %q, want reminder", stderr.String())
@ -902,25 +884,20 @@ func TestRunSSHSessionPrintsReminderWhenHealthCheckPasses(t *testing.T) {
}
func TestRunSSHSessionPreservesSSHExitStatusOnHealthWarning(t *testing.T) {
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
d := defaultDeps()
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
d.sshExec = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return exitErrorWithCode(t, 1)
}
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
d.vmHealth = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
return api.VMHealthResult{}, errors.New("dial failed")
}
var stderr bytes.Buffer
err := runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"}, false)
err := d.runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"}, false)
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) {
t.Fatalf("runSSHSession error = %v, want exit error", err)
t.Fatalf("d.runSSHSession error = %v, want exit error", err)
}
if !strings.Contains(stderr.String(), "failed to check whether devbox is still running") {
t.Fatalf("stderr = %q, want warning", stderr.String())
@ -928,27 +905,22 @@ func TestRunSSHSessionPreservesSSHExitStatusOnHealthWarning(t *testing.T) {
}
func TestRunSSHSessionSkipsReminderOnSSHAuthFailure(t *testing.T) {
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
d := defaultDeps()
healthCalled := false
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
d.sshExec = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return exitErrorWithCode(t, 255)
}
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
d.vmHealth = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
healthCalled = true
return api.VMHealthResult{Name: "devbox", Healthy: true}, nil
}
var stderr bytes.Buffer
err := runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"}, false)
err := d.runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"}, false)
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) || exitErr.ExitCode() != 255 {
t.Fatalf("runSSHSession error = %v, want exit 255", err)
t.Fatalf("d.runSSHSession error = %v, want exit 255", err)
}
if healthCalled {
t.Fatal("vm health should not run after ssh auth failure")
@ -1141,6 +1113,7 @@ func TestValidateSSHPrereqsFailsForMissingKey(t *testing.T) {
// gets a fast error instead of an orphaned VM.
func TestVMRunPreflightRejectsSubmodules(t *testing.T) {
d := defaultDeps()
repoRoot := t.TempDir()
origHostCommandOutput := workspace.HostCommandOutputFunc
@ -1166,36 +1139,16 @@ func TestVMRunPreflightRejectsSubmodules(t *testing.T) {
}
}
_, err := vmRunPreflightRepo(context.Background(), repoRoot)
_, err := d.vmRunPreflightRepo(context.Background(), repoRoot)
if err == nil || !strings.Contains(err.Error(), "submodules") {
t.Fatalf("vmRunPreflightRepo() error = %v, want submodule rejection", err)
t.Fatalf("d.vmRunPreflightRepo() error = %v, want submodule rejection", err)
}
}
func TestRunVMRunWorkspacePreparesAndAttaches(t *testing.T) {
d := defaultDeps()
repoRoot := t.TempDir()
origBegin := vmCreateBeginFunc
origStatus := vmCreateStatusFunc
origCancel := vmCreateCancelFunc
origWaitForSSH := guestWaitForSSHFunc
origGuestDial := guestDialFunc
origBuildVMRunToolingPlan := buildVMRunToolingPlanFunc
origVMWorkspacePrepare := vmWorkspacePrepareFunc
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
vmCreateStatusFunc = origStatus
vmCreateCancelFunc = origCancel
guestWaitForSSHFunc = origWaitForSSH
guestDialFunc = origGuestDial
buildVMRunToolingPlanFunc = origBuildVMRunToolingPlan
vmWorkspacePrepareFunc = origVMWorkspacePrepare
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
vm := model.VMRecord{
ID: "vm-id",
Name: "devbox",
@ -1205,7 +1158,7 @@ func TestRunVMRunWorkspacePreparesAndAttaches(t *testing.T) {
DNSName: "devbox.vm",
},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{
Operation: api.VMCreateOperation{
ID: "op-1", Stage: "ready", Detail: "vm is ready",
@ -1213,45 +1166,45 @@ func TestRunVMRunWorkspacePreparesAndAttaches(t *testing.T) {
},
}, nil
}
vmCreateStatusFunc = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
t.Fatal("vmCreateStatusFunc should not be called")
d.vmCreateStatus = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
t.Fatal("d.vmCreateStatus should not be called")
return api.VMCreateStatusResult{}, nil
}
vmCreateCancelFunc = func(context.Context, string, string) error {
t.Fatal("vmCreateCancelFunc should not be called")
d.vmCreateCancel = func(context.Context, string, string) error {
t.Fatal("d.vmCreateCancel should not be called")
return nil
}
fakeClient := &testVMRunGuestClient{}
guestWaitForSSHFunc = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
d.guestWaitForSSH = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
return nil
}
guestDialFunc = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
d.guestDial = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
return fakeClient, nil
}
var workspaceParams api.VMWorkspacePrepareParams
vmWorkspacePrepareFunc = func(ctx context.Context, socketPath string, params api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
d.vmWorkspacePrepare = func(ctx context.Context, socketPath string, params api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
workspaceParams = params
return api.VMWorkspacePrepareResult{Workspace: model.WorkspacePrepareResult{VMID: vm.ID, GuestPath: "/root/repo", RepoName: "repo", RepoRoot: "/tmp/repo"}}, nil
}
buildVMRunToolingPlanFunc = func(context.Context, string) toolingplan.Plan {
d.buildVMRunToolingPlan = func(context.Context, string) toolingplan.Plan {
return toolingplan.Plan{
RepoManagedTools: []string{"go"},
Steps: []toolingplan.InstallStep{{Tool: "go", Version: "1.25.0", Source: "go.mod"}},
}
}
var sshArgsSeen []string
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
d.sshExec = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
sshArgsSeen = args
return nil
}
vmHealthFunc = func(context.Context, string, string) (api.VMHealthResult, error) {
d.vmHealth = func(context.Context, string, string) (api.VMHealthResult, error) {
return api.VMHealthResult{Name: "devbox", Healthy: false}, nil
}
repo := vmRunRepo{sourcePath: repoRoot}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1263,7 +1216,7 @@ func TestRunVMRunWorkspacePreparesAndAttaches(t *testing.T) {
false,
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
t.Fatalf("d.runVMRun: %v", err)
}
if workspaceParams.IDOrName != "devbox" || workspaceParams.SourcePath != repoRoot {
t.Fatalf("workspaceParams = %+v", workspaceParams)
@ -1283,24 +1236,7 @@ func TestRunVMRunWorkspacePreparesAndAttaches(t *testing.T) {
}
func TestVMRunPrintsPostCreateProgress(t *testing.T) {
origBegin := vmCreateBeginFunc
origStatus := vmCreateStatusFunc
origCancel := vmCreateCancelFunc
origWaitForSSH := guestWaitForSSHFunc
origGuestDial := guestDialFunc
origVMWorkspacePrepare := vmWorkspacePrepareFunc
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
vmCreateStatusFunc = origStatus
vmCreateCancelFunc = origCancel
guestWaitForSSHFunc = origWaitForSSH
guestDialFunc = origGuestDial
vmWorkspacePrepareFunc = origVMWorkspacePrepare
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
d := defaultDeps()
vm := model.VMRecord{
ID: "vm-id",
@ -1310,7 +1246,7 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
GuestIP: "172.16.0.2",
},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{
Operation: api.VMCreateOperation{
ID: "op-1", Stage: "ready", Detail: "vm is ready",
@ -1318,33 +1254,33 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
},
}, nil
}
vmCreateStatusFunc = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
t.Fatal("vmCreateStatusFunc should not be called")
d.vmCreateStatus = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
t.Fatal("d.vmCreateStatus should not be called")
return api.VMCreateStatusResult{}, nil
}
vmCreateCancelFunc = func(context.Context, string, string) error {
t.Fatal("vmCreateCancelFunc should not be called")
d.vmCreateCancel = func(context.Context, string, string) error {
t.Fatal("d.vmCreateCancel should not be called")
return nil
}
guestWaitForSSHFunc = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
d.guestWaitForSSH = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
return nil
}
guestDialFunc = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
d.guestDial = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
return &testVMRunGuestClient{}, nil
}
vmWorkspacePrepareFunc = func(ctx context.Context, socketPath string, params api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
d.vmWorkspacePrepare = func(ctx context.Context, socketPath string, params api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
return api.VMWorkspacePrepareResult{Workspace: model.WorkspacePrepareResult{VMID: vm.ID, GuestPath: "/root/repo", RepoName: "repo", RepoRoot: "/tmp/repo"}}, nil
}
sshExecFunc = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error {
d.sshExec = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error {
return nil
}
vmHealthFunc = func(context.Context, string, string) (api.VMHealthResult, error) {
d.vmHealth = func(context.Context, string, string) (api.VMHealthResult, error) {
return api.VMHealthResult{Name: "devbox", Healthy: false}, nil
}
repo := vmRunRepo{sourcePath: t.TempDir()}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1356,7 +1292,7 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
false,
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
t.Fatalf("d.runVMRun: %v", err)
}
output := stderr.String()
@ -1377,24 +1313,7 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
}
func TestRunVMRunWarnsWhenToolingHarnessStartFails(t *testing.T) {
origBegin := vmCreateBeginFunc
origStatus := vmCreateStatusFunc
origCancel := vmCreateCancelFunc
origWaitForSSH := guestWaitForSSHFunc
origGuestDial := guestDialFunc
origVMWorkspacePrepare := vmWorkspacePrepareFunc
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
vmCreateStatusFunc = origStatus
vmCreateCancelFunc = origCancel
guestWaitForSSHFunc = origWaitForSSH
guestDialFunc = origGuestDial
vmWorkspacePrepareFunc = origVMWorkspacePrepare
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
d := defaultDeps()
vm := model.VMRecord{
ID: "vm-id",
@ -1404,39 +1323,39 @@ func TestRunVMRunWarnsWhenToolingHarnessStartFails(t *testing.T) {
GuestIP: "172.16.0.2",
},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Detail: "vm is ready", Done: true, Success: true, VM: &vm}}, nil
}
vmCreateStatusFunc = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
t.Fatal("vmCreateStatusFunc should not be called")
d.vmCreateStatus = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
t.Fatal("d.vmCreateStatus should not be called")
return api.VMCreateStatusResult{}, nil
}
vmCreateCancelFunc = func(context.Context, string, string) error {
t.Fatal("vmCreateCancelFunc should not be called")
d.vmCreateCancel = func(context.Context, string, string) error {
t.Fatal("d.vmCreateCancel should not be called")
return nil
}
guestWaitForSSHFunc = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
d.guestWaitForSSH = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
return nil
}
fakeClient := &testVMRunGuestClient{launchErr: errors.New("launch failed")}
guestDialFunc = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
d.guestDial = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
return fakeClient, nil
}
vmWorkspacePrepareFunc = func(ctx context.Context, socketPath string, params api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
d.vmWorkspacePrepare = func(ctx context.Context, socketPath string, params api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
return api.VMWorkspacePrepareResult{Workspace: model.WorkspacePrepareResult{VMID: vm.ID, GuestPath: "/root/repo", RepoName: "repo", RepoRoot: "/tmp/repo"}}, nil
}
sshExecCalls := 0
sshExecFunc = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error {
d.sshExec = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error {
sshExecCalls++
return nil
}
vmHealthFunc = func(context.Context, string, string) (api.VMHealthResult, error) {
d.vmHealth = func(context.Context, string, string) (api.VMHealthResult, error) {
return api.VMHealthResult{Healthy: false}, nil
}
repo := vmRunRepo{sourcePath: t.TempDir()}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1448,7 +1367,7 @@ func TestRunVMRunWarnsWhenToolingHarnessStartFails(t *testing.T) {
false,
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
t.Fatalf("d.runVMRun: %v", err)
}
if !strings.Contains(stderr.String(), "[vm run] warning: guest tooling bootstrap start failed: launch guest tooling bootstrap") {
t.Fatalf("stderr = %q, want tooling bootstrap warning", stderr.String())
@ -1459,48 +1378,35 @@ func TestRunVMRunWarnsWhenToolingHarnessStartFails(t *testing.T) {
}
func TestRunVMRunBareModeSkipsWorkspaceAndTooling(t *testing.T) {
origBegin := vmCreateBeginFunc
origWaitForSSH := guestWaitForSSHFunc
origGuestDial := guestDialFunc
origVMWorkspacePrepare := vmWorkspacePrepareFunc
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
guestWaitForSSHFunc = origWaitForSSH
guestDialFunc = origGuestDial
vmWorkspacePrepareFunc = origVMWorkspacePrepare
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
d := defaultDeps()
vm := model.VMRecord{
ID: "vm-id", Name: "bare",
Runtime: model.VMRuntime{State: model.VMStateRunning, GuestIP: "172.16.0.2"},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Done: true, Success: true, VM: &vm}}, nil
}
guestWaitForSSHFunc = func(context.Context, string, string, time.Duration) error { return nil }
guestDialFunc = func(context.Context, string, string) (vmRunGuestClient, error) {
t.Fatal("guestDialFunc should not be called in bare mode")
d.guestWaitForSSH = func(context.Context, string, string, time.Duration) error { return nil }
d.guestDial = func(context.Context, string, string) (vmRunGuestClient, error) {
t.Fatal("d.guestDial should not be called in bare mode")
return nil, nil
}
vmWorkspacePrepareFunc = func(context.Context, string, api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
t.Fatal("vmWorkspacePrepareFunc should not be called in bare mode")
d.vmWorkspacePrepare = func(context.Context, string, api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
t.Fatal("d.vmWorkspacePrepare should not be called in bare mode")
return api.VMWorkspacePrepareResult{}, nil
}
sshExecCalls := 0
sshExecFunc = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error {
d.sshExec = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error {
sshExecCalls++
return nil
}
vmHealthFunc = func(context.Context, string, string) (api.VMHealthResult, error) {
d.vmHealth = func(context.Context, string, string) (api.VMHealthResult, error) {
return api.VMHealthResult{Healthy: false}, nil
}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1512,7 +1418,7 @@ func TestRunVMRunBareModeSkipsWorkspaceAndTooling(t *testing.T) {
false,
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
t.Fatalf("d.runVMRun: %v", err)
}
if sshExecCalls != 1 {
t.Fatalf("sshExec calls = %d, want 1", sshExecCalls)
@ -1523,39 +1429,28 @@ func TestRunVMRunBareModeSkipsWorkspaceAndTooling(t *testing.T) {
}
func TestRunVMRunRMDeletesAfterSessionExits(t *testing.T) {
origBegin := vmCreateBeginFunc
origWaitForSSH := guestWaitForSSHFunc
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
origDelete := vmDeleteFunc
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
guestWaitForSSHFunc = origWaitForSSH
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
vmDeleteFunc = origDelete
})
d := defaultDeps()
vm := model.VMRecord{
ID: "vm-id", Name: "tmpbox",
Runtime: model.VMRuntime{State: model.VMStateRunning, GuestIP: "172.16.0.2"},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Done: true, Success: true, VM: &vm}}, nil
}
guestWaitForSSHFunc = func(context.Context, string, string, time.Duration) error { return nil }
sshExecFunc = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error { return nil }
vmHealthFunc = func(context.Context, string, string) (api.VMHealthResult, error) {
d.guestWaitForSSH = func(context.Context, string, string, time.Duration) error { return nil }
d.sshExec = func(context.Context, io.Reader, io.Writer, io.Writer, []string) error { return nil }
d.vmHealth = func(context.Context, string, string) (api.VMHealthResult, error) {
return api.VMHealthResult{Healthy: false}, nil
}
deletedRef := ""
vmDeleteFunc = func(_ context.Context, _, idOrName string) error {
d.vmDelete = func(_ context.Context, _, idOrName string) error {
deletedRef = idOrName
return nil
}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1567,7 +1462,7 @@ func TestRunVMRunRMDeletesAfterSessionExits(t *testing.T) {
true, // --rm
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
t.Fatalf("d.runVMRun: %v", err)
}
if deletedRef != "tmpbox" {
t.Fatalf("deletedRef = %q, want tmpbox", deletedRef)
@ -1580,15 +1475,10 @@ func TestRunVMRunRMDeletesAfterSessionExits(t *testing.T) {
}
func TestRunVMRunRMSkipsDeleteOnSSHWaitTimeout(t *testing.T) {
origBegin := vmCreateBeginFunc
origWaitForSSH := guestWaitForSSHFunc
origDelete := vmDeleteFunc
d := defaultDeps()
origTimeout := vmRunSSHTimeout
vmRunSSHTimeout = 50 * time.Millisecond
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
guestWaitForSSHFunc = origWaitForSSH
vmDeleteFunc = origDelete
vmRunSSHTimeout = origTimeout
})
@ -1596,21 +1486,21 @@ func TestRunVMRunRMSkipsDeleteOnSSHWaitTimeout(t *testing.T) {
ID: "vm-id", Name: "slowvm",
Runtime: model.VMRuntime{State: model.VMStateRunning, GuestIP: "172.16.0.2"},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Done: true, Success: true, VM: &vm}}, nil
}
guestWaitForSSHFunc = func(ctx context.Context, _, _ string, _ time.Duration) error {
d.guestWaitForSSH = func(ctx context.Context, _, _ string, _ time.Duration) error {
<-ctx.Done()
return ctx.Err()
}
deleteCalled := false
vmDeleteFunc = func(context.Context, string, string) error {
d.vmDelete = func(context.Context, string, string) error {
deleteCalled = true
return nil
}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1630,13 +1520,10 @@ func TestRunVMRunRMSkipsDeleteOnSSHWaitTimeout(t *testing.T) {
}
func TestRunVMRunSSHTimeoutReturnsActionableError(t *testing.T) {
origBegin := vmCreateBeginFunc
origWaitForSSH := guestWaitForSSHFunc
d := defaultDeps()
origTimeout := vmRunSSHTimeout
vmRunSSHTimeout = 50 * time.Millisecond
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
guestWaitForSSHFunc = origWaitForSSH
vmRunSSHTimeout = origTimeout
})
@ -1644,18 +1531,18 @@ func TestRunVMRunSSHTimeoutReturnsActionableError(t *testing.T) {
ID: "vm-id", Name: "slowvm",
Runtime: model.VMRuntime{State: model.VMStateRunning, GuestIP: "172.16.0.2"},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Done: true, Success: true, VM: &vm}}, nil
}
// Simulate the guest never bringing sshd up — the wait-for-ssh
// child context fires its deadline, returning a DeadlineExceeded.
guestWaitForSSHFunc = func(ctx context.Context, _, _ string, _ time.Duration) error {
d.guestWaitForSSH = func(ctx context.Context, _, _ string, _ time.Duration) error {
<-ctx.Done()
return ctx.Err()
}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1683,37 +1570,28 @@ func TestRunVMRunSSHTimeoutReturnsActionableError(t *testing.T) {
}
func TestRunVMRunCommandModePropagatesExitCode(t *testing.T) {
origBegin := vmCreateBeginFunc
origWaitForSSH := guestWaitForSSHFunc
origVMWorkspacePrepare := vmWorkspacePrepareFunc
origSSHExec := sshExecFunc
t.Cleanup(func() {
vmCreateBeginFunc = origBegin
guestWaitForSSHFunc = origWaitForSSH
vmWorkspacePrepareFunc = origVMWorkspacePrepare
sshExecFunc = origSSHExec
})
d := defaultDeps()
vm := model.VMRecord{
ID: "vm-id", Name: "cmdbox",
Runtime: model.VMRuntime{State: model.VMStateRunning, GuestIP: "172.16.0.2"},
}
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
d.vmCreateBegin = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Done: true, Success: true, VM: &vm}}, nil
}
guestWaitForSSHFunc = func(context.Context, string, string, time.Duration) error { return nil }
vmWorkspacePrepareFunc = func(context.Context, string, api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
d.guestWaitForSSH = func(context.Context, string, string, time.Duration) error { return nil }
d.vmWorkspacePrepare = func(context.Context, string, api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
t.Fatal("workspace prepare should not run without spec")
return api.VMWorkspacePrepareResult{}, nil
}
var sshArgsSeen []string
sshExecFunc = func(_ context.Context, _ io.Reader, _, _ io.Writer, args []string) error {
d.sshExec = func(_ context.Context, _ io.Reader, _, _ io.Writer, args []string) error {
sshArgsSeen = args
return exitErrorWithCode(t, 7)
}
var stdout, stderr bytes.Buffer
err := runVMRun(
err := d.runVMRun(
context.Background(),
"/tmp/bangerd.sock",
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
@ -1726,7 +1604,7 @@ func TestRunVMRunCommandModePropagatesExitCode(t *testing.T) {
)
var exitErr ExitCodeError
if !errors.As(err, &exitErr) || exitErr.Code != 7 {
t.Fatalf("runVMRun error = %v, want ExitCodeError{7}", err)
t.Fatalf("d.runVMRun error = %v, want ExitCodeError{7}", err)
}
if len(sshArgsSeen) == 0 || sshArgsSeen[len(sshArgsSeen)-1] != "false" {
t.Fatalf("sshArgsSeen = %v, want trailing command 'false'", sshArgsSeen)
@ -1843,6 +1721,7 @@ func TestNewBangerdCommandRejectsArgs(t *testing.T) {
}
func TestDaemonOutdated(t *testing.T) {
d := defaultDeps()
dir := t.TempDir()
current := filepath.Join(dir, "bangerd-current")
same := filepath.Join(dir, "bangerd-same")
@ -1857,27 +1736,20 @@ func TestDaemonOutdated(t *testing.T) {
t.Fatalf("write stale: %v", err)
}
origBangerdPath := bangerdPathFunc
origDaemonExePath := daemonExePath
t.Cleanup(func() {
bangerdPathFunc = origBangerdPath
daemonExePath = origDaemonExePath
})
bangerdPathFunc = func() (string, error) {
d.bangerdPath = func() (string, error) {
return current, nil
}
daemonExePath = func(pid int) string {
d.daemonExePath = func(pid int) string {
if pid == 1 {
return same
}
return stale
}
if daemonOutdated(1) {
if d.daemonOutdated(1) {
t.Fatal("expected matching daemon executable to be current")
}
if !daemonOutdated(2) {
if !d.daemonOutdated(2) {
t.Fatal("expected replaced daemon executable to be outdated")
}
}
@ -1912,10 +1784,7 @@ func TestDaemonStatusIncludesLogPathWhenStopped(t *testing.T) {
}
func TestDaemonStatusIncludesDaemonBuildInfoWhenRunning(t *testing.T) {
origDaemonPing := daemonPingFunc
t.Cleanup(func() {
daemonPingFunc = origDaemonPing
})
d := defaultDeps()
configHome := filepath.Join(t.TempDir(), "config")
stateHome := filepath.Join(t.TempDir(), "state")
@ -1924,7 +1793,7 @@ func TestDaemonStatusIncludesDaemonBuildInfoWhenRunning(t *testing.T) {
t.Setenv("XDG_STATE_HOME", stateHome)
t.Setenv("XDG_RUNTIME_DIR", runtimeHome)
daemonPingFunc = func(context.Context, string) (api.PingResult, error) {
d.daemonPing = func(context.Context, string) (api.PingResult, error) {
return api.PingResult{
Status: "ok",
PID: 42,
@ -1934,7 +1803,7 @@ func TestDaemonStatusIncludesDaemonBuildInfoWhenRunning(t *testing.T) {
}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
var stdout bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stdout)
@ -2073,26 +1942,26 @@ func TestVMSessionSendRejectsWrongArgCount(t *testing.T) {
}
}
func stubEnsureDaemonForSend(t *testing.T) {
// stubEnsureDaemonForSend isolates XDG dirs and installs a daemon-ping
// fake onto the caller's *deps so `ensureDaemon` short-circuits without
// trying to spawn bangerd. `vm session send` uses this to avoid needing
// a built binary on disk.
func stubEnsureDaemonForSend(t *testing.T, d *deps) {
t.Helper()
t.Setenv("XDG_CONFIG_HOME", filepath.Join(t.TempDir(), "config"))
t.Setenv("XDG_STATE_HOME", filepath.Join(t.TempDir(), "state"))
t.Setenv("XDG_RUNTIME_DIR", filepath.Join(t.TempDir(), "run"))
origPing := daemonPingFunc
t.Cleanup(func() { daemonPingFunc = origPing })
daemonPingFunc = func(context.Context, string) (api.PingResult, error) {
d.daemonPing = func(context.Context, string) (api.PingResult, error) {
return api.PingResult{Status: "ok", PID: os.Getpid()}, nil
}
}
func TestVMSessionSendWithMessageFlag(t *testing.T) {
stubEnsureDaemonForSend(t)
original := guestSessionSendFunc
t.Cleanup(func() { guestSessionSendFunc = original })
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
var capturedParams api.GuestSessionSendParams
guestSessionSendFunc = func(_ context.Context, _ string, params api.GuestSessionSendParams) (api.GuestSessionSendResult, error) {
d.guestSessionSend = func(_ context.Context, _ string, params api.GuestSessionSendParams) (api.GuestSessionSendResult, error) {
capturedParams = params
return api.GuestSessionSendResult{
Session: model.GuestSession{ID: "sess-id", Name: "planner"},
@ -2100,7 +1969,7 @@ func TestVMSessionSendWithMessageFlag(t *testing.T) {
}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetArgs([]string{"vm", "session", "send", "devbox", "planner", "--message", `{"type":"abort"}`})
@ -2124,13 +1993,11 @@ func TestVMSessionSendWithMessageFlag(t *testing.T) {
}
func TestVMSessionSendMessageAlreadyHasNewline(t *testing.T) {
stubEnsureDaemonForSend(t)
original := guestSessionSendFunc
t.Cleanup(func() { guestSessionSendFunc = original })
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
var capturedPayload []byte
guestSessionSendFunc = func(_ context.Context, _ string, params api.GuestSessionSendParams) (api.GuestSessionSendResult, error) {
d.guestSessionSend = func(_ context.Context, _ string, params api.GuestSessionSendParams) (api.GuestSessionSendResult, error) {
capturedPayload = params.Payload
return api.GuestSessionSendResult{
Session: model.GuestSession{Name: "s"},
@ -2138,7 +2005,7 @@ func TestVMSessionSendMessageAlreadyHasNewline(t *testing.T) {
}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"vm", "session", "send", "devbox", "s", "--message", "{\"type\":\"abort\"}\n"})
if err := cmd.Execute(); err != nil {
@ -2155,13 +2022,11 @@ func TestVMSessionSendMessageAlreadyHasNewline(t *testing.T) {
}
func TestVMSessionSendFromStdin(t *testing.T) {
stubEnsureDaemonForSend(t)
original := guestSessionSendFunc
t.Cleanup(func() { guestSessionSendFunc = original })
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
var capturedPayload []byte
guestSessionSendFunc = func(_ context.Context, _ string, params api.GuestSessionSendParams) (api.GuestSessionSendResult, error) {
d.guestSessionSend = func(_ context.Context, _ string, params api.GuestSessionSendParams) (api.GuestSessionSendResult, error) {
capturedPayload = params.Payload
return api.GuestSessionSendResult{
Session: model.GuestSession{Name: "planner"},
@ -2170,7 +2035,7 @@ func TestVMSessionSendFromStdin(t *testing.T) {
}
stdinPayload := `{"type":"steer","message":"Focus on src/"}` + "\n"
cmd := NewBangerCommand()
cmd := d.newRootCommand()
cmd.SetOut(io.Discard)
cmd.SetIn(strings.NewReader(stdinPayload))
cmd.SetArgs([]string{"vm", "session", "send", "devbox", "planner"})
@ -2208,13 +2073,11 @@ func TestVMWorkspaceExportRejectsMissingArg(t *testing.T) {
}
func TestVMWorkspaceExportWritesToStdout(t *testing.T) {
stubEnsureDaemonForSend(t)
origExport := vmWorkspaceExportFunc
t.Cleanup(func() { vmWorkspaceExportFunc = origExport })
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
patch := []byte("diff --git a/main.go b/main.go\nindex 0000000..1111111 100644\n")
vmWorkspaceExportFunc = func(_ context.Context, _ string, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
d.vmWorkspaceExport = func(_ context.Context, _ string, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
return api.WorkspaceExportResult{
GuestPath: params.GuestPath,
Patch: patch,
@ -2223,7 +2086,7 @@ func TestVMWorkspaceExportWritesToStdout(t *testing.T) {
}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(io.Discard)
@ -2237,13 +2100,11 @@ func TestVMWorkspaceExportWritesToStdout(t *testing.T) {
}
func TestVMWorkspaceExportWritesToFile(t *testing.T) {
stubEnsureDaemonForSend(t)
origExport := vmWorkspaceExportFunc
t.Cleanup(func() { vmWorkspaceExportFunc = origExport })
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
patch := []byte("diff --git a/main.go b/main.go\n")
vmWorkspaceExportFunc = func(_ context.Context, _ string, _ api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
d.vmWorkspaceExport = func(_ context.Context, _ string, _ api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
return api.WorkspaceExportResult{
GuestPath: "/root/repo",
Patch: patch,
@ -2253,7 +2114,7 @@ func TestVMWorkspaceExportWritesToFile(t *testing.T) {
}
outFile := filepath.Join(t.TempDir(), "worker.diff")
cmd := NewBangerCommand()
cmd := d.newRootCommand()
cmd.SetOut(io.Discard)
var stderr bytes.Buffer
cmd.SetErr(&stderr)
@ -2275,19 +2136,17 @@ func TestVMWorkspaceExportWritesToFile(t *testing.T) {
}
func TestVMWorkspaceExportNoChanges(t *testing.T) {
stubEnsureDaemonForSend(t)
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
origExport := vmWorkspaceExportFunc
t.Cleanup(func() { vmWorkspaceExportFunc = origExport })
vmWorkspaceExportFunc = func(_ context.Context, _ string, _ api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
d.vmWorkspaceExport = func(_ context.Context, _ string, _ api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
return api.WorkspaceExportResult{
GuestPath: "/root/repo",
HasChanges: false,
}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
var out bytes.Buffer
var stderr bytes.Buffer
cmd.SetOut(&out)
@ -2305,18 +2164,16 @@ func TestVMWorkspaceExportNoChanges(t *testing.T) {
}
func TestVMWorkspaceExportGuestPathFlag(t *testing.T) {
stubEnsureDaemonForSend(t)
origExport := vmWorkspaceExportFunc
t.Cleanup(func() { vmWorkspaceExportFunc = origExport })
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
var capturedParams api.WorkspaceExportParams
vmWorkspaceExportFunc = func(_ context.Context, _ string, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
d.vmWorkspaceExport = func(_ context.Context, _ string, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
capturedParams = params
return api.WorkspaceExportResult{HasChanges: false}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"vm", "workspace", "export", "devbox", "--guest-path", "/root/project"})
@ -2332,13 +2189,11 @@ func TestVMWorkspaceExportGuestPathFlag(t *testing.T) {
}
func TestVMWorkspaceExportBaseCommitFlag(t *testing.T) {
stubEnsureDaemonForSend(t)
origExport := vmWorkspaceExportFunc
t.Cleanup(func() { vmWorkspaceExportFunc = origExport })
d := defaultDeps()
stubEnsureDaemonForSend(t, d)
var capturedParams api.WorkspaceExportParams
vmWorkspaceExportFunc = func(_ context.Context, _ string, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
d.vmWorkspaceExport = func(_ context.Context, _ string, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
capturedParams = params
return api.WorkspaceExportResult{
HasChanges: false,
@ -2346,7 +2201,7 @@ func TestVMWorkspaceExportBaseCommitFlag(t *testing.T) {
}, nil
}
cmd := NewBangerCommand()
cmd := d.newRootCommand()
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"vm", "workspace", "export", "devbox", "--base-commit", "abc1234deadbeef"})