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

@ -15,7 +15,7 @@ import (
"github.com/spf13/cobra"
)
func newVMSessionCommand() *cobra.Command {
func (d *deps) newVMSessionCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "session",
Short: "Manage long-lived guest commands inside a VM",
@ -23,19 +23,19 @@ func newVMSessionCommand() *cobra.Command {
RunE: helpNoArgs,
}
cmd.AddCommand(
newVMSessionStartCommand(),
newVMSessionListCommand(),
newVMSessionShowCommand(),
newVMSessionLogsCommand(),
newVMSessionStopCommand(),
newVMSessionKillCommand(),
newVMSessionAttachCommand(),
newVMSessionSendCommand(),
d.newVMSessionStartCommand(),
d.newVMSessionListCommand(),
d.newVMSessionShowCommand(),
d.newVMSessionLogsCommand(),
d.newVMSessionStopCommand(),
d.newVMSessionKillCommand(),
d.newVMSessionAttachCommand(),
d.newVMSessionSendCommand(),
)
return cmd
}
func newVMSessionStartCommand() *cobra.Command {
func (d *deps) newVMSessionStartCommand() *cobra.Command {
var name string
var cwd string
var stdinMode string
@ -47,13 +47,13 @@ func newVMSessionStartCommand() *cobra.Command {
Short: "Start a managed guest command",
Long: "Start a daemon-managed guest command. The daemon verifies that the guest working directory exists and that the requested command is present in guest PATH before launch. Use --stdin-mode pipe when you need live attach.",
Args: minArgsUsage(2, "usage: banger vm session start <id-or-name> [flags] -- <command> [args...]"),
ValidArgsFunction: completeVMNameOnlyAtPos0,
ValidArgsFunction: d.completeVMNameOnlyAtPos0,
Example: strings.TrimSpace(`
banger vm session start devbox --name planner --cwd /root/repo --stdin-mode pipe --require-command git -- pi --mode rpc --no-session
banger vm session start devbox --name shell --stdin-mode pipe -- bash -lc 'exec bash'
`),
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
@ -65,7 +65,7 @@ func newVMSessionStartCommand() *cobra.Command {
if err != nil {
return err
}
result, err := guestSessionStartFunc(cmd.Context(), layout.SocketPath, api.GuestSessionStartParams{
result, err := d.guestSessionStart(cmd.Context(), layout.SocketPath, api.GuestSessionStartParams{
VMIDOrName: args[0],
Name: name,
Command: args[1],
@ -97,19 +97,19 @@ func newVMSessionStartCommand() *cobra.Command {
return cmd
}
func newVMSessionListCommand() *cobra.Command {
func (d *deps) newVMSessionListCommand() *cobra.Command {
return &cobra.Command{
Use: "list <id-or-name>",
Aliases: []string{"ls"},
Short: "List managed guest commands for a VM",
Args: exactArgsUsage(1, "usage: banger vm session list <id-or-name>"),
ValidArgsFunction: completeVMNameOnlyAtPos0,
ValidArgsFunction: d.completeVMNameOnlyAtPos0,
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := guestSessionListFunc(cmd.Context(), layout.SocketPath, args[0])
result, err := d.guestSessionList(cmd.Context(), layout.SocketPath, args[0])
if err != nil {
return err
}
@ -118,18 +118,18 @@ func newVMSessionListCommand() *cobra.Command {
}
}
func newVMSessionShowCommand() *cobra.Command {
func (d *deps) newVMSessionShowCommand() *cobra.Command {
return &cobra.Command{
Use: "show <id-or-name> <session>",
Short: "Show managed guest command details",
Args: exactArgsUsage(2, "usage: banger vm session show <id-or-name> <session>"),
ValidArgsFunction: completeSessionNames,
ValidArgsFunction: d.completeSessionNames,
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := guestSessionGetFunc(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
result, err := d.guestSessionGet(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
if err != nil {
return err
}
@ -138,20 +138,20 @@ func newVMSessionShowCommand() *cobra.Command {
}
}
func newVMSessionLogsCommand() *cobra.Command {
func (d *deps) newVMSessionLogsCommand() *cobra.Command {
var stream string
var tailLines int
cmd := &cobra.Command{
Use: "logs <id-or-name> <session>",
Short: "Show stdout or stderr for a guest session",
Args: exactArgsUsage(2, "usage: banger vm session logs [--stream stdout|stderr] [-n LINES] <id-or-name> <session>"),
ValidArgsFunction: completeSessionNames,
ValidArgsFunction: d.completeSessionNames,
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := guestSessionLogsFunc(cmd.Context(), layout.SocketPath, api.GuestSessionLogsParams{VMIDOrName: args[0], SessionIDOrName: args[1], Stream: stream, TailLines: tailLines})
result, err := d.guestSessionLogs(cmd.Context(), layout.SocketPath, api.GuestSessionLogsParams{VMIDOrName: args[0], SessionIDOrName: args[1], Stream: stream, TailLines: tailLines})
if err != nil {
return err
}
@ -164,18 +164,18 @@ func newVMSessionLogsCommand() *cobra.Command {
return cmd
}
func newVMSessionStopCommand() *cobra.Command {
func (d *deps) newVMSessionStopCommand() *cobra.Command {
return &cobra.Command{
Use: "stop <id-or-name> <session>",
Short: "Send SIGTERM to a guest session",
Args: exactArgsUsage(2, "usage: banger vm session stop <id-or-name> <session>"),
ValidArgsFunction: completeSessionNames,
ValidArgsFunction: d.completeSessionNames,
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := guestSessionStopFunc(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
result, err := d.guestSessionStop(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
if err != nil {
return err
}
@ -184,18 +184,18 @@ func newVMSessionStopCommand() *cobra.Command {
}
}
func newVMSessionKillCommand() *cobra.Command {
func (d *deps) newVMSessionKillCommand() *cobra.Command {
return &cobra.Command{
Use: "kill <id-or-name> <session>",
Short: "Send SIGKILL to a guest session",
Args: exactArgsUsage(2, "usage: banger vm session kill <id-or-name> <session>"),
ValidArgsFunction: completeSessionNames,
ValidArgsFunction: d.completeSessionNames,
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := guestSessionKillFunc(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
result, err := d.guestSessionKill(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
if err != nil {
return err
}
@ -204,19 +204,19 @@ func newVMSessionKillCommand() *cobra.Command {
}
}
func newVMSessionAttachCommand() *cobra.Command {
func (d *deps) newVMSessionAttachCommand() *cobra.Command {
return &cobra.Command{
Use: "attach <id-or-name> <session>",
Short: "Attach local stdio to an attachable guest session",
Long: "Attach local stdio to a pipe-mode session through a daemon-created local Unix socket bridge. Only one active attach is allowed at a time, and the client must run on the same host as the daemon.",
Args: exactArgsUsage(2, "usage: banger vm session attach <id-or-name> <session>"),
ValidArgsFunction: completeSessionNames,
ValidArgsFunction: d.completeSessionNames,
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := guestSessionAttachBeginFunc(cmd.Context(), layout.SocketPath, api.GuestSessionAttachBeginParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
result, err := d.guestSessionAttachBegin(cmd.Context(), layout.SocketPath, api.GuestSessionAttachBeginParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
if err != nil {
return err
}
@ -229,21 +229,21 @@ func newVMSessionAttachCommand() *cobra.Command {
}
}
func newVMSessionSendCommand() *cobra.Command {
func (d *deps) newVMSessionSendCommand() *cobra.Command {
var message string
cmd := &cobra.Command{
Use: "send <id-or-name> <session>",
Short: "Write bytes to a running guest session's stdin pipe",
Long: "Write a payload to the stdin pipe of a running pipe-mode guest session without holding the exclusive attach. Use --message for an inline JSONL string, or pipe bytes via stdin when --message is omitted. A trailing newline is appended to --message values that lack one.",
Args: exactArgsUsage(2, "usage: banger vm session send <id-or-name> <session> [--message '<json>']"),
ValidArgsFunction: completeSessionNames,
ValidArgsFunction: d.completeSessionNames,
Example: strings.TrimSpace(`
banger vm session send devbox planner --message '{"type":"abort"}'
banger vm session send devbox planner --message '{"type":"steer","message":"Focus on src/"}'
echo '{"type":"prompt","prompt":"Summarize."}' | banger vm session send devbox planner
`),
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
layout, _, err := d.ensureDaemon(cmd.Context())
if err != nil {
return err
}
@ -259,7 +259,7 @@ func newVMSessionSendCommand() *cobra.Command {
return fmt.Errorf("read stdin: %w", err)
}
}
result, err := guestSessionSendFunc(cmd.Context(), layout.SocketPath, api.GuestSessionSendParams{
result, err := d.guestSessionSend(cmd.Context(), layout.SocketPath, api.GuestSessionSendParams{
VMIDOrName: args[0],
SessionIDOrName: args[1],
Payload: payload,