package beads import ( "os" "path/filepath" "testing" "github.com/steveyegge/gastown/internal/config" ) func TestGetPrefixForRig(t *testing.T) { // Create a temporary directory with routes.jsonl tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatal(err) } routesContent := `{"prefix": "gt-", "path": "gastown/mayor/rig"} {"prefix": "bd-", "path": "beads/mayor/rig"} {"prefix": "hq-", "path": "."} ` if err := os.WriteFile(filepath.Join(beadsDir, "routes.jsonl"), []byte(routesContent), 0644); err != nil { t.Fatal(err) } tests := []struct { rig string expected string }{ {"gastown", "gt"}, {"beads", "bd"}, {"unknown", "gt"}, // default {"", "gt"}, // empty rig -> default } for _, tc := range tests { t.Run(tc.rig, func(t *testing.T) { result := GetPrefixForRig(tmpDir, tc.rig) if result != tc.expected { t.Errorf("GetPrefixForRig(%q, %q) = %q, want %q", tmpDir, tc.rig, result, tc.expected) } }) } } func TestGetPrefixForRig_NoRoutesFile(t *testing.T) { tmpDir := t.TempDir() // No routes.jsonl file result := GetPrefixForRig(tmpDir, "anything") if result != "gt" { t.Errorf("Expected default 'gt' when no routes file, got %q", result) } } func TestGetPrefixForRig_RigsConfigFallback(t *testing.T) { tmpDir := t.TempDir() // Write rigs.json with a non-gt prefix rigsPath := filepath.Join(tmpDir, "mayor", "rigs.json") if err := os.MkdirAll(filepath.Dir(rigsPath), 0755); err != nil { t.Fatal(err) } cfg := &config.RigsConfig{ Version: config.CurrentRigsVersion, Rigs: map[string]config.RigEntry{ "project_ideas": { BeadsConfig: &config.BeadsConfig{Prefix: "pi"}, }, }, } if err := config.SaveRigsConfig(rigsPath, cfg); err != nil { t.Fatalf("SaveRigsConfig: %v", err) } result := GetPrefixForRig(tmpDir, "project_ideas") if result != "pi" { t.Errorf("Expected prefix from rigs config, got %q", result) } } func TestExtractPrefix(t *testing.T) { tests := []struct { beadID string expected string }{ {"ap-qtsup.16", "ap-"}, {"hq-cv-abc", "hq-"}, {"gt-mol-xyz", "gt-"}, {"bd-123", "bd-"}, {"", ""}, {"nohyphen", ""}, {"-startswithhyphen", ""}, // Leading hyphen = invalid prefix {"-", ""}, // Just hyphen = invalid {"a-", "a-"}, // Trailing hyphen is valid } for _, tc := range tests { t.Run(tc.beadID, func(t *testing.T) { result := ExtractPrefix(tc.beadID) if result != tc.expected { t.Errorf("ExtractPrefix(%q) = %q, want %q", tc.beadID, result, tc.expected) } }) } } func TestGetRigPathForPrefix(t *testing.T) { // Create a temporary directory with routes.jsonl tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatal(err) } routesContent := `{"prefix": "ap-", "path": "ai_platform/mayor/rig"} {"prefix": "gt-", "path": "gastown/mayor/rig"} {"prefix": "hq-", "path": "."} ` if err := os.WriteFile(filepath.Join(beadsDir, "routes.jsonl"), []byte(routesContent), 0644); err != nil { t.Fatal(err) } tests := []struct { prefix string expected string }{ {"ap-", filepath.Join(tmpDir, "ai_platform/mayor/rig")}, {"gt-", filepath.Join(tmpDir, "gastown/mayor/rig")}, {"hq-", tmpDir}, // Town-level beads return townRoot {"unknown-", ""}, // Unknown prefix returns empty {"", ""}, // Empty prefix returns empty } for _, tc := range tests { t.Run(tc.prefix, func(t *testing.T) { result := GetRigPathForPrefix(tmpDir, tc.prefix) if result != tc.expected { t.Errorf("GetRigPathForPrefix(%q, %q) = %q, want %q", tmpDir, tc.prefix, result, tc.expected) } }) } } func TestGetRigPathForPrefix_NoRoutesFile(t *testing.T) { tmpDir := t.TempDir() // No routes.jsonl file result := GetRigPathForPrefix(tmpDir, "ap-") if result != "" { t.Errorf("Expected empty string when no routes file, got %q", result) } } func TestResolveHookDir(t *testing.T) { // Create a temporary directory with routes.jsonl tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatal(err) } routesContent := `{"prefix": "ap-", "path": "ai_platform/mayor/rig"} {"prefix": "hq-", "path": "."} ` if err := os.WriteFile(filepath.Join(beadsDir, "routes.jsonl"), []byte(routesContent), 0644); err != nil { t.Fatal(err) } tests := []struct { name string beadID string hookWorkDir string expected string }{ { name: "prefix resolution takes precedence over hookWorkDir", beadID: "ap-test", hookWorkDir: "/custom/path", expected: filepath.Join(tmpDir, "ai_platform/mayor/rig"), }, { name: "resolves rig path from prefix", beadID: "ap-test", hookWorkDir: "", expected: filepath.Join(tmpDir, "ai_platform/mayor/rig"), }, { name: "town-level bead returns townRoot", beadID: "hq-test", hookWorkDir: "", expected: tmpDir, }, { name: "unknown prefix uses hookWorkDir as fallback", beadID: "xx-unknown", hookWorkDir: "/fallback/path", expected: "/fallback/path", }, { name: "unknown prefix without hookWorkDir falls back to townRoot", beadID: "xx-unknown", hookWorkDir: "", expected: tmpDir, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { result := ResolveHookDir(tmpDir, tc.beadID, tc.hookWorkDir) if result != tc.expected { t.Errorf("ResolveHookDir(%q, %q, %q) = %q, want %q", tmpDir, tc.beadID, tc.hookWorkDir, result, tc.expected) } }) } } func TestAgentBeadIDsWithPrefix(t *testing.T) { tests := []struct { name string fn func() string expected string }{ {"PolecatBeadIDWithPrefix bd beads obsidian", func() string { return PolecatBeadIDWithPrefix("bd", "beads", "obsidian") }, "bd-beads-polecat-obsidian"}, {"PolecatBeadIDWithPrefix gt gastown Toast", func() string { return PolecatBeadIDWithPrefix("gt", "gastown", "Toast") }, "gt-gastown-polecat-Toast"}, {"WitnessBeadIDWithPrefix bd beads", func() string { return WitnessBeadIDWithPrefix("bd", "beads") }, "bd-beads-witness"}, {"RefineryBeadIDWithPrefix bd beads", func() string { return RefineryBeadIDWithPrefix("bd", "beads") }, "bd-beads-refinery"}, {"CrewBeadIDWithPrefix bd beads max", func() string { return CrewBeadIDWithPrefix("bd", "beads", "max") }, "bd-beads-crew-max"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { result := tc.fn() if result != tc.expected { t.Errorf("got %q, want %q", result, tc.expected) } }) } }