b75b82e676
LaunchOpts.Agent (string) and WorkspaceEntryOptions.Agent (string) are replaced by *agent.Resolved, carrying Command, Args, and Env. The five entry commands (new, resume, last, open, attach) each construct an AgentSpec from the workspace metadata, apply --agent as a one-shot agent.command override (Open Q 1 — keeps muscle memory for users passing executable paths), call agent.Resolve, and pass the result through. resolveEntryAgent centralises the resume/last/open/attach path. shell.ExecAgent and shell.ExecTmuxAgent gain an args parameter; agent.env is merged into the env map at the session.Run launch switch, AFTER ctask's exported CTASK_* vars (per spec §5: agent.env wins on collision). mergeAgentEnv is the centralised merge. Lease, manifest, write lock, heartbeat, summary, and provisional cleanup are unchanged. The Agent string fields on Lease, SessionSummary, and SessionInfo continue to record the launched command for diagnostics.
90 lines
2.7 KiB
Go
90 lines
2.7 KiB
Go
package cmd
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/warrenronsiek/ctask/internal/agent"
|
|
"github.com/warrenronsiek/ctask/internal/session"
|
|
"github.com/warrenronsiek/ctask/internal/workspace"
|
|
)
|
|
|
|
// dispatchPersistent is a pure decision function — table tests are the
|
|
// right shape.
|
|
func TestDispatchPersistentOwnerWhenNoTmuxSession(t *testing.T) {
|
|
got := dispatchPersistent(false, session.LeaseStateNone)
|
|
if got != dispatchOwnerCreate {
|
|
t.Errorf("got %v, want %v", got, dispatchOwnerCreate)
|
|
}
|
|
}
|
|
|
|
func TestDispatchPersistentPassiveWhenFreshLocal(t *testing.T) {
|
|
got := dispatchPersistent(true, session.LeaseStateFreshLocal)
|
|
if got != dispatchPassive {
|
|
t.Errorf("got %v, want %v", got, dispatchPassive)
|
|
}
|
|
}
|
|
|
|
func TestDispatchPersistentAdoptedWhenStaleNoneOrRemote(t *testing.T) {
|
|
for _, st := range []session.LeaseState{
|
|
session.LeaseStateStale,
|
|
session.LeaseStateNone,
|
|
session.LeaseStateFreshRemote,
|
|
} {
|
|
got := dispatchPersistent(true, st)
|
|
if got != dispatchAdopted {
|
|
t.Errorf("state %v: got %v, want %v", st, got, dispatchAdopted)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SessionName is computed by callers — sanity check determinism.
|
|
func TestEntrySessionNameStable(t *testing.T) {
|
|
abs, _ := filepath.Abs("/tmp/x")
|
|
a := session.SessionName("projects", "demo", abs)
|
|
b := session.SessionName("projects", "demo", abs)
|
|
if a != b {
|
|
t.Errorf("not stable: %q vs %q", a, b)
|
|
}
|
|
}
|
|
|
|
// runWorkspaceEntry must be injectable so per-command tests can capture
|
|
// the WorkspaceEntryOptions each command produces. This test installs a
|
|
// stub and verifies the wiring works end-to-end.
|
|
//
|
|
// Tests in this file mutate the package-level runWorkspaceEntry. Do not
|
|
// run with t.Parallel().
|
|
func TestRunWorkspaceEntryIsInjectable(t *testing.T) {
|
|
var captured WorkspaceEntryOptions
|
|
orig := runWorkspaceEntry
|
|
runWorkspaceEntry = func(opts WorkspaceEntryOptions) error {
|
|
captured = opts
|
|
return nil
|
|
}
|
|
t.Cleanup(func() { runWorkspaceEntry = orig })
|
|
|
|
want := WorkspaceEntryOptions{
|
|
WsPath: "/tmp/ws",
|
|
WsRoot: "/tmp",
|
|
WsMeta: &workspace.TaskMeta{Slug: "demo", Category: "projects", Mode: "local", Agent: workspace.AgentSpec{Type: "claude"}},
|
|
ResolvedAgent: &agent.Resolved{Command: "claude"},
|
|
Shell: true,
|
|
CommandName: "test",
|
|
}
|
|
if err := runWorkspaceEntry(want); err != nil {
|
|
t.Fatalf("runWorkspaceEntry: %v", err)
|
|
}
|
|
if captured.CommandName != "test" {
|
|
t.Errorf("CommandName: got %q", captured.CommandName)
|
|
}
|
|
if !captured.Shell {
|
|
t.Error("Shell should be true")
|
|
}
|
|
if captured.WsMeta == nil || captured.WsMeta.Slug != "demo" {
|
|
t.Errorf("WsMeta not propagated: %+v", captured.WsMeta)
|
|
}
|
|
if captured.WsPath != "/tmp/ws" || captured.WsRoot != "/tmp" {
|
|
t.Errorf("path/root not propagated: path=%q root=%q", captured.WsPath, captured.WsRoot)
|
|
}
|
|
}
|