Files
gastown/internal/cmd/prime_test.go
dementus dd9cd61075 feat(prime): add --state, --dry-run, --explain flags with mutual exclusivity validation
Add three new flags to gt prime command:
- --state: Output role state as JSON and exit early (for scripting)
- --dry-run: Skip side effects (persistence, locks, events)
- --explain: Show verbose role detection reasoning

The --state flag is mutually exclusive with all other flags and errors
if combined. The other flags (--dry-run, --explain, --hook) can be
combined freely.

Also fixes missing filepath import in beads.go.

Closes: bd-t8ven

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 01:14:27 -08:00

170 lines
3.9 KiB
Go

package cmd
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/steveyegge/gastown/internal/beads"
)
func writeTestRoutes(t *testing.T, townRoot string, routes []beads.Route) {
t.Helper()
beadsDir := filepath.Join(townRoot, ".beads")
if err := os.MkdirAll(beadsDir, 0755); err != nil {
t.Fatalf("create beads dir: %v", err)
}
if err := beads.WriteRoutes(beadsDir, routes); err != nil {
t.Fatalf("write routes: %v", err)
}
}
func TestGetAgentBeadID_UsesRigPrefix(t *testing.T) {
townRoot := t.TempDir()
writeTestRoutes(t, townRoot, []beads.Route{
{Prefix: "bd-", Path: "beads/mayor/rig"},
})
cases := []struct {
name string
ctx RoleContext
want string
}{
{
name: "mayor",
ctx: RoleContext{
Role: RoleMayor,
TownRoot: townRoot,
},
want: "hq-mayor",
},
{
name: "deacon",
ctx: RoleContext{
Role: RoleDeacon,
TownRoot: townRoot,
},
want: "hq-deacon",
},
{
name: "witness",
ctx: RoleContext{
Role: RoleWitness,
Rig: "beads",
TownRoot: townRoot,
},
want: "bd-beads-witness",
},
{
name: "refinery",
ctx: RoleContext{
Role: RoleRefinery,
Rig: "beads",
TownRoot: townRoot,
},
want: "bd-beads-refinery",
},
{
name: "polecat",
ctx: RoleContext{
Role: RolePolecat,
Rig: "beads",
Polecat: "lex",
TownRoot: townRoot,
},
want: "bd-beads-polecat-lex",
},
{
name: "crew",
ctx: RoleContext{
Role: RoleCrew,
Rig: "beads",
Polecat: "lex",
TownRoot: townRoot,
},
want: "bd-beads-crew-lex",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := getAgentBeadID(tc.ctx)
if got != tc.want {
t.Fatalf("getAgentBeadID() = %q, want %q", got, tc.want)
}
})
}
}
func TestPrimeFlagCombinations(t *testing.T) {
// Find the gt binary - we need to test CLI flag validation
gtBin, err := exec.LookPath("gt")
if err != nil {
t.Skip("gt binary not found in PATH")
}
cases := []struct {
name string
args []string
wantError bool
errorMsg string
}{
{
name: "state_alone_is_valid",
args: []string{"prime", "--state"},
wantError: false, // May fail for other reasons (not in workspace), but not flag validation
},
{
name: "state_with_hook_errors",
args: []string{"prime", "--state", "--hook"},
wantError: true,
errorMsg: "--state cannot be combined with other flags",
},
{
name: "state_with_dry_run_errors",
args: []string{"prime", "--state", "--dry-run"},
wantError: true,
errorMsg: "--state cannot be combined with other flags",
},
{
name: "state_with_explain_errors",
args: []string{"prime", "--state", "--explain"},
wantError: true,
errorMsg: "--state cannot be combined with other flags",
},
{
name: "dry_run_and_explain_valid",
args: []string{"prime", "--dry-run", "--explain"},
wantError: false, // May fail for other reasons, but not flag validation
},
{
name: "hook_and_dry_run_valid",
args: []string{"prime", "--hook", "--dry-run"},
wantError: false, // May fail for other reasons, but not flag validation
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cmd := exec.Command(gtBin, tc.args...)
output, err := cmd.CombinedOutput()
if tc.wantError {
if err == nil {
t.Fatalf("expected error, got success with output: %s", output)
}
if tc.errorMsg != "" && !strings.Contains(string(output), tc.errorMsg) {
t.Fatalf("expected error containing %q, got: %s", tc.errorMsg, output)
}
}
// For non-error cases, we don't fail on other errors (like "not in workspace")
// because we're only testing flag validation
if !tc.wantError && tc.errorMsg != "" && strings.Contains(string(output), tc.errorMsg) {
t.Fatalf("unexpected error message %q in output: %s", tc.errorMsg, output)
}
})
}
}