Skip to content

Commit a06d410

Browse files
whitmoclaude
andcommitted
test: Add tests for PRs dlorenc#337, dlorenc#338, dlorenc#339, dlorenc#333 on pr-triage-b2
Tests from multiclaude workers (silly-otter, lively-otter, clever-bear): - PR dlorenc#338: Token-aware status display, hibernate help, rich list_repos response - PR dlorenc#339: Context-aware refresh, worktree path detection, --all flag parsing - PR dlorenc#337: Categorized help (worker report captured, tests via CLI assertions) - PR dlorenc#333: Enhanced repair (worker report captured, daemon handler tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 227919f commit a06d410

2 files changed

Lines changed: 516 additions & 0 deletions

File tree

internal/cli/cli_test.go

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3902,3 +3902,377 @@ func TestLoadStateFunction(t *testing.T) {
39023902
}
39033903
})
39043904
}
3905+
3906+
// =============================================================================
3907+
// Tests for PR #338: Token-aware status display and help improvements
3908+
// =============================================================================
3909+
3910+
func TestHibernateCommandIfExists(t *testing.T) {
3911+
cli, _, cleanup := setupTestEnvironment(t)
3912+
defer cleanup()
3913+
3914+
repoCmd, ok := cli.rootCmd.Subcommands["repo"]
3915+
if !ok {
3916+
t.Fatal("Expected 'repo' command to exist")
3917+
}
3918+
3919+
hibernateCmd, ok := repoCmd.Subcommands["hibernate"]
3920+
if !ok {
3921+
t.Skip("hibernate subcommand not yet present")
3922+
}
3923+
3924+
if hibernateCmd.Description == "" {
3925+
t.Error("hibernate command should have a description")
3926+
}
3927+
if hibernateCmd.Usage == "" {
3928+
t.Error("hibernate command should have usage text")
3929+
}
3930+
}
3931+
3932+
func TestHibernateHelpRendering(t *testing.T) {
3933+
cli, _, cleanup := setupTestEnvironment(t)
3934+
defer cleanup()
3935+
3936+
repoCmd, ok := cli.rootCmd.Subcommands["repo"]
3937+
if !ok {
3938+
t.Fatal("Expected 'repo' command to exist")
3939+
}
3940+
3941+
if _, ok := repoCmd.Subcommands["hibernate"]; !ok {
3942+
t.Skip("hibernate subcommand not yet present")
3943+
}
3944+
3945+
err := cli.Execute([]string{"repo", "hibernate", "--help"})
3946+
if err != nil {
3947+
t.Errorf("repo hibernate --help should not error: %v", err)
3948+
}
3949+
}
3950+
3951+
func TestShowCommandHelpBasic(t *testing.T) {
3952+
cli, _, cleanup := setupTestEnvironment(t)
3953+
defer cleanup()
3954+
3955+
cmd := &Command{
3956+
Name: "test-help",
3957+
Description: "Test help rendering",
3958+
Usage: "test-help [options]",
3959+
}
3960+
3961+
err := cli.showCommandHelp(cmd)
3962+
if err != nil {
3963+
t.Errorf("showCommandHelp() returned error: %v", err)
3964+
}
3965+
}
3966+
3967+
func TestHandleListReposRichResponseShape(t *testing.T) {
3968+
_, d, cleanup := setupTestEnvironment(t)
3969+
defer cleanup()
3970+
3971+
repo := &state.Repository{
3972+
GithubURL: "https://github.com/test/repo",
3973+
TmuxSession: "mc-test-repo",
3974+
Agents: make(map[string]state.Agent),
3975+
}
3976+
if err := d.GetState().AddRepo("test-repo", repo); err != nil {
3977+
t.Fatalf("Failed to add repo: %v", err)
3978+
}
3979+
d.GetState().AddAgent("test-repo", "supervisor", state.Agent{
3980+
Type: state.AgentTypeSupervisor,
3981+
TmuxWindow: "supervisor",
3982+
})
3983+
d.GetState().AddAgent("test-repo", "merge-queue", state.Agent{
3984+
Type: state.AgentTypeMergeQueue,
3985+
TmuxWindow: "merge-queue",
3986+
})
3987+
d.GetState().AddAgent("test-repo", "happy-fox", state.Agent{
3988+
Type: state.AgentTypeWorker,
3989+
TmuxWindow: "happy-fox",
3990+
Task: "implement feature X",
3991+
})
3992+
d.GetState().AddAgent("test-repo", "clever-bear", state.Agent{
3993+
Type: state.AgentTypeWorker,
3994+
TmuxWindow: "clever-bear",
3995+
Task: "fix bug Y",
3996+
})
3997+
3998+
client := socket.NewClient(d.GetPaths().DaemonSock)
3999+
resp, err := client.Send(socket.Request{
4000+
Command: "list_repos",
4001+
Args: map[string]interface{}{"rich": true},
4002+
})
4003+
if err != nil {
4004+
t.Fatalf("Failed to send request: %v", err)
4005+
}
4006+
if !resp.Success {
4007+
t.Fatalf("list_repos failed: %s", resp.Error)
4008+
}
4009+
4010+
repos, ok := resp.Data.([]interface{})
4011+
if !ok {
4012+
t.Fatalf("Expected repos array, got %T", resp.Data)
4013+
}
4014+
if len(repos) != 1 {
4015+
t.Fatalf("Expected 1 repo, got %d", len(repos))
4016+
}
4017+
4018+
repoMap, ok := repos[0].(map[string]interface{})
4019+
if !ok {
4020+
t.Fatalf("Expected repo map, got %T", repos[0])
4021+
}
4022+
4023+
totalAgents, ok := repoMap["total_agents"].(float64)
4024+
if !ok {
4025+
t.Fatal("Expected total_agents field")
4026+
}
4027+
if int(totalAgents) != 4 {
4028+
t.Errorf("total_agents = %v, want 4", totalAgents)
4029+
}
4030+
4031+
workerCount, ok := repoMap["worker_count"].(float64)
4032+
if !ok {
4033+
t.Fatal("Expected worker_count field")
4034+
}
4035+
if int(workerCount) != 2 {
4036+
t.Errorf("worker_count = %v, want 2", workerCount)
4037+
}
4038+
}
4039+
4040+
func TestHandleListReposRichEmptyAgents(t *testing.T) {
4041+
_, d, cleanup := setupTestEnvironment(t)
4042+
defer cleanup()
4043+
4044+
repo := &state.Repository{
4045+
GithubURL: "https://github.com/test/repo",
4046+
TmuxSession: "mc-test-repo",
4047+
Agents: make(map[string]state.Agent),
4048+
}
4049+
if err := d.GetState().AddRepo("test-repo", repo); err != nil {
4050+
t.Fatalf("Failed to add repo: %v", err)
4051+
}
4052+
4053+
client := socket.NewClient(d.GetPaths().DaemonSock)
4054+
resp, err := client.Send(socket.Request{
4055+
Command: "list_repos",
4056+
Args: map[string]interface{}{"rich": true},
4057+
})
4058+
if err != nil {
4059+
t.Fatalf("Failed to send request: %v", err)
4060+
}
4061+
if !resp.Success {
4062+
t.Fatalf("list_repos failed: %s", resp.Error)
4063+
}
4064+
4065+
repos, ok := resp.Data.([]interface{})
4066+
if !ok {
4067+
t.Fatalf("Expected repos array, got %T", resp.Data)
4068+
}
4069+
if len(repos) != 1 {
4070+
t.Fatalf("Expected 1 repo, got %d", len(repos))
4071+
}
4072+
4073+
repoMap := repos[0].(map[string]interface{})
4074+
totalAgents := repoMap["total_agents"].(float64)
4075+
if int(totalAgents) != 0 {
4076+
t.Errorf("total_agents = %v, want 0", totalAgents)
4077+
}
4078+
workerCount := repoMap["worker_count"].(float64)
4079+
if int(workerCount) != 0 {
4080+
t.Errorf("worker_count = %v, want 0", workerCount)
4081+
}
4082+
}
4083+
4084+
// =============================================================================
4085+
// Tests for PR #339: Context-aware refresh with auto-detection
4086+
// =============================================================================
4087+
4088+
func TestRefreshContextDetectionFromWorktreePath(t *testing.T) {
4089+
tests := []struct {
4090+
name string
4091+
cwdSuffix string
4092+
wantRepo string
4093+
wantAgent string
4094+
wantDetect bool
4095+
}{
4096+
{
4097+
name: "agent worktree path",
4098+
cwdSuffix: "my-repo/happy-fox",
4099+
wantRepo: "my-repo",
4100+
wantAgent: "happy-fox",
4101+
wantDetect: true,
4102+
},
4103+
{
4104+
name: "agent worktree with deep subdirectory",
4105+
cwdSuffix: "my-repo/happy-fox/src/main",
4106+
wantRepo: "my-repo",
4107+
wantAgent: "happy-fox",
4108+
wantDetect: true,
4109+
},
4110+
{
4111+
name: "repo-only path",
4112+
cwdSuffix: "my-repo",
4113+
wantRepo: "my-repo",
4114+
wantAgent: "",
4115+
wantDetect: false,
4116+
},
4117+
}
4118+
4119+
for _, tt := range tests {
4120+
t.Run(tt.name, func(t *testing.T) {
4121+
tmpDir, err := os.MkdirTemp("", "refresh-ctx-test-*")
4122+
if err != nil {
4123+
t.Fatalf("Failed to create temp dir: %v", err)
4124+
}
4125+
defer os.RemoveAll(tmpDir)
4126+
4127+
tmpDir, _ = filepath.EvalSymlinks(tmpDir)
4128+
wtsDir := filepath.Join(tmpDir, "wts")
4129+
4130+
testPath := filepath.Join(wtsDir, tt.cwdSuffix)
4131+
if err := os.MkdirAll(testPath, 0755); err != nil {
4132+
t.Fatalf("Failed to create test path: %v", err)
4133+
}
4134+
4135+
if !hasPathPrefix(testPath, wtsDir) {
4136+
t.Fatal("testPath should have wtsDir as prefix")
4137+
}
4138+
4139+
rel, err := filepath.Rel(wtsDir, testPath)
4140+
if err != nil {
4141+
t.Fatalf("filepath.Rel failed: %v", err)
4142+
}
4143+
4144+
parts := strings.SplitN(rel, string(filepath.Separator), 2)
4145+
detected := len(parts) >= 2 && parts[0] != "" && parts[1] != ""
4146+
4147+
if detected != tt.wantDetect {
4148+
t.Errorf("detection = %v, want %v (parts=%v)", detected, tt.wantDetect, parts)
4149+
}
4150+
4151+
if detected {
4152+
repoName := parts[0]
4153+
agentName := strings.SplitN(parts[1], string(filepath.Separator), 2)[0]
4154+
if repoName != tt.wantRepo {
4155+
t.Errorf("repoName = %q, want %q", repoName, tt.wantRepo)
4156+
}
4157+
if agentName != tt.wantAgent {
4158+
t.Errorf("agentName = %q, want %q", agentName, tt.wantAgent)
4159+
}
4160+
}
4161+
})
4162+
}
4163+
}
4164+
4165+
func TestRefreshParseFlagsAllFlag(t *testing.T) {
4166+
tests := []struct {
4167+
name string
4168+
args []string
4169+
wantAll bool
4170+
}{
4171+
{"no flags", []string{}, false},
4172+
{"all flag present", []string{"--all"}, true},
4173+
{"other flags without all", []string{"--repo", "my-repo"}, false},
4174+
{"all flag with other flags", []string{"--all", "--repo", "my-repo"}, true},
4175+
}
4176+
4177+
for _, tt := range tests {
4178+
t.Run(tt.name, func(t *testing.T) {
4179+
flags, _ := ParseFlags(tt.args)
4180+
gotAll := flags["all"] == "true"
4181+
if gotAll != tt.wantAll {
4182+
t.Errorf("--all = %v, want %v (flags=%v)", gotAll, tt.wantAll, flags)
4183+
}
4184+
})
4185+
}
4186+
}
4187+
4188+
func TestRefreshUsageUpdated(t *testing.T) {
4189+
cli, _, cleanup := setupTestEnvironment(t)
4190+
defer cleanup()
4191+
4192+
refreshCmd, ok := cli.rootCmd.Subcommands["refresh"]
4193+
if !ok {
4194+
t.Fatal("Expected 'refresh' command to exist")
4195+
}
4196+
4197+
if refreshCmd.Usage == "" {
4198+
t.Error("refresh command should have a Usage string")
4199+
}
4200+
if refreshCmd.Description == "" {
4201+
t.Error("refresh command should have a description")
4202+
}
4203+
}
4204+
4205+
func TestContextDetectionEdgeCases(t *testing.T) {
4206+
tests := []struct {
4207+
name string
4208+
path string
4209+
wtsDir string
4210+
wantMatch bool
4211+
}{
4212+
{"path outside worktrees dir", "/home/user/projects/my-repo", "/home/user/.multiclaude/wts", false},
4213+
{"path is exactly the wts dir", "/home/user/.multiclaude/wts", "/home/user/.multiclaude/wts", true},
4214+
{"path with similar prefix", "/home/user/.multiclaude/wts-backup/repo/agent", "/home/user/.multiclaude/wts", false},
4215+
{"path with trailing separator", "/home/user/.multiclaude/wts/repo/agent", "/home/user/.multiclaude/wts/", true},
4216+
{"path with dotfiles", "/home/user/.multiclaude/wts/.hidden-repo/agent", "/home/user/.multiclaude/wts", true},
4217+
{"path with spaces", "/home/user/.multiclaude/wts/my repo/agent", "/home/user/.multiclaude/wts", true},
4218+
}
4219+
4220+
for _, tt := range tests {
4221+
t.Run(tt.name, func(t *testing.T) {
4222+
got := hasPathPrefix(tt.path, tt.wtsDir)
4223+
if got != tt.wantMatch {
4224+
t.Errorf("hasPathPrefix(%q, %q) = %v, want %v", tt.path, tt.wtsDir, got, tt.wantMatch)
4225+
}
4226+
})
4227+
}
4228+
}
4229+
4230+
func TestRefreshCommandHelp(t *testing.T) {
4231+
cli, _, cleanup := setupTestEnvironment(t)
4232+
defer cleanup()
4233+
4234+
err := cli.Execute([]string{"refresh", "--help"})
4235+
if err != nil {
4236+
t.Errorf("refresh --help should not error: %v", err)
4237+
}
4238+
}
4239+
4240+
func TestAgentContextPathParsing(t *testing.T) {
4241+
tests := []struct {
4242+
name string
4243+
rel string
4244+
wantRepo string
4245+
wantAgent string
4246+
wantOK bool
4247+
}{
4248+
{"standard two-component", "multiclaude/happy-fox", "multiclaude", "happy-fox", true},
4249+
{"path with subdir", "multiclaude/happy-fox/internal/cli", "multiclaude", "happy-fox", true},
4250+
{"single component", "multiclaude", "", "", false},
4251+
{"empty relative path", ".", "", "", false},
4252+
{"repo with dots", "my.repo.name/agent-1", "my.repo.name", "agent-1", true},
4253+
{"repo with hyphens", "my-cool-repo/lively-otter", "my-cool-repo", "lively-otter", true},
4254+
}
4255+
4256+
for _, tt := range tests {
4257+
t.Run(tt.name, func(t *testing.T) {
4258+
parts := strings.SplitN(tt.rel, string(filepath.Separator), 2)
4259+
ok := len(parts) >= 2 && parts[0] != "" && parts[1] != ""
4260+
4261+
if ok != tt.wantOK {
4262+
t.Errorf("detection = %v, want %v (parts=%v)", ok, tt.wantOK, parts)
4263+
return
4264+
}
4265+
4266+
if ok {
4267+
repoName := parts[0]
4268+
agentName := strings.SplitN(parts[1], string(filepath.Separator), 2)[0]
4269+
if repoName != tt.wantRepo {
4270+
t.Errorf("repo = %q, want %q", repoName, tt.wantRepo)
4271+
}
4272+
if agentName != tt.wantAgent {
4273+
t.Errorf("agent = %q, want %q", agentName, tt.wantAgent)
4274+
}
4275+
}
4276+
})
4277+
}
4278+
}

0 commit comments

Comments
 (0)