* fix(beads): align agent bead prefixes and force multi-hyphen IDs * fix(checkpoint): treat threshold as stale at boundary
253 lines
6.6 KiB
Go
253 lines
6.6 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|