fix(sync): use town-level routes for prefix validation
Follow-up to 828fc11b addressing code review feedback:
1. Added LoadTownRoutes() - exported function that walks up to find
town-level routes.jsonl (e.g., ~/gt/.beads/routes.jsonl)
2. Updated buildAllowedPrefixSet to use LoadTownRoutes instead of
LoadRoutes, so it finds routes even when importing from a rig's
local beads directory
3. Added unit tests for buildAllowedPrefixSet covering:
- Primary prefix inclusion
- allowed_prefixes config parsing
- Routes from routes.jsonl
- Missing routes.jsonl handling
- Empty beadsDir handling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1058,15 +1058,14 @@ func buildAllowedPrefixSet(primaryPrefix string, allowedPrefixesConfig string, b
|
|||||||
|
|
||||||
// Load prefixes from routes.jsonl for multi-rig setups (Gas Town)
|
// Load prefixes from routes.jsonl for multi-rig setups (Gas Town)
|
||||||
// This allows issues from other rigs to coexist in the same JSONL
|
// This allows issues from other rigs to coexist in the same JSONL
|
||||||
|
// Use LoadTownRoutes to find routes at town level (~/gt/.beads/routes.jsonl)
|
||||||
if beadsDir != "" {
|
if beadsDir != "" {
|
||||||
routes, err := routing.LoadRoutes(beadsDir)
|
routes, _ := routing.LoadTownRoutes(beadsDir)
|
||||||
if err == nil && len(routes) > 0 {
|
for _, route := range routes {
|
||||||
for _, route := range routes {
|
// Normalize: remove trailing - if present
|
||||||
// Normalize: remove trailing - if present
|
prefix := strings.TrimSuffix(route.Prefix, "-")
|
||||||
prefix := strings.TrimSuffix(route.Prefix, "-")
|
if prefix != "" {
|
||||||
if prefix != "" {
|
allowed[prefix] = true
|
||||||
allowed[prefix] = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ package importer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -1708,3 +1710,92 @@ func TestMultiRepoPrefixValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildAllowedPrefixSet(t *testing.T) {
|
||||||
|
t.Run("includes primary prefix", func(t *testing.T) {
|
||||||
|
allowed := buildAllowedPrefixSet("gt", "", "")
|
||||||
|
if allowed == nil {
|
||||||
|
t.Fatal("Expected non-nil allowed set")
|
||||||
|
}
|
||||||
|
if !allowed["gt"] {
|
||||||
|
t.Error("Primary prefix 'gt' should be allowed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("includes allowed_prefixes config", func(t *testing.T) {
|
||||||
|
allowed := buildAllowedPrefixSet("gt", "hq,mol-,other", "")
|
||||||
|
if allowed == nil {
|
||||||
|
t.Fatal("Expected non-nil allowed set")
|
||||||
|
}
|
||||||
|
if !allowed["gt"] {
|
||||||
|
t.Error("Primary prefix 'gt' should be allowed")
|
||||||
|
}
|
||||||
|
if !allowed["hq"] {
|
||||||
|
t.Error("Config prefix 'hq' should be allowed")
|
||||||
|
}
|
||||||
|
if !allowed["mol"] {
|
||||||
|
t.Error("Config prefix 'mol' (normalized from 'mol-') should be allowed")
|
||||||
|
}
|
||||||
|
if !allowed["other"] {
|
||||||
|
t.Error("Config prefix 'other' should be allowed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("includes prefixes from routes.jsonl", func(t *testing.T) {
|
||||||
|
// Create a temp directory with routes.jsonl
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
routesPath := filepath.Join(tmpDir, "routes.jsonl")
|
||||||
|
routesContent := `{"prefix": "hq-", "path": "."}
|
||||||
|
{"prefix": "gt-", "path": "gastown/mayor/rig"}
|
||||||
|
{"prefix": "bd-", "path": "beads/mayor/rig"}
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to write routes.jsonl: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildAllowedPrefixSet uses LoadTownRoutes which tries to find town root
|
||||||
|
// For this unit test, LoadRoutes will work since routes.jsonl is in tmpDir
|
||||||
|
allowed := buildAllowedPrefixSet("gt", "", tmpDir)
|
||||||
|
if allowed == nil {
|
||||||
|
t.Fatal("Expected non-nil allowed set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary prefix should always be included
|
||||||
|
if !allowed["gt"] {
|
||||||
|
t.Error("Primary prefix 'gt' should be allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routed prefixes should be included (normalized without trailing -)
|
||||||
|
if !allowed["hq"] {
|
||||||
|
t.Error("Routed prefix 'hq' (from routes.jsonl) should be allowed")
|
||||||
|
}
|
||||||
|
if !allowed["bd"] {
|
||||||
|
t.Error("Routed prefix 'bd' (from routes.jsonl) should be allowed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles missing routes.jsonl gracefully", func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
// No routes.jsonl file in tmpDir
|
||||||
|
// Note: LoadTownRoutes may still find routes at town level if running in Gas Town
|
||||||
|
|
||||||
|
allowed := buildAllowedPrefixSet("gt", "", tmpDir)
|
||||||
|
if allowed == nil {
|
||||||
|
t.Fatal("Expected non-nil allowed set")
|
||||||
|
}
|
||||||
|
if !allowed["gt"] {
|
||||||
|
t.Error("Primary prefix 'gt' should be allowed even without local routes.jsonl")
|
||||||
|
}
|
||||||
|
// Don't check exact count - town-level routes may be found
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handles empty beadsDir", func(t *testing.T) {
|
||||||
|
allowed := buildAllowedPrefixSet("gt", "", "")
|
||||||
|
if allowed == nil {
|
||||||
|
t.Fatal("Expected non-nil allowed set")
|
||||||
|
}
|
||||||
|
if !allowed["gt"] {
|
||||||
|
t.Error("Primary prefix 'gt' should be allowed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,6 +55,16 @@ func LoadRoutes(beadsDir string) ([]Route, error) {
|
|||||||
return routes, scanner.Err()
|
return routes, scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadTownRoutes loads routes from the town-level routes.jsonl.
|
||||||
|
// It first checks the given beadsDir, then walks up to find the town root
|
||||||
|
// and loads routes from there. This is useful for multi-rig setups (Gas Town)
|
||||||
|
// where routes.jsonl lives at ~/gt/.beads/ rather than in individual rig directories.
|
||||||
|
// Returns routes and nil error on success, or nil routes if not in a town or no routes found.
|
||||||
|
func LoadTownRoutes(beadsDir string) ([]Route, error) {
|
||||||
|
routes, _ := findTownRoutes(beadsDir)
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExtractPrefix extracts the prefix from an issue ID.
|
// ExtractPrefix extracts the prefix from an issue ID.
|
||||||
// For "gt-abc123", returns "gt-".
|
// For "gt-abc123", returns "gt-".
|
||||||
// For "bd-abc123", returns "bd-".
|
// For "bd-abc123", returns "bd-".
|
||||||
|
|||||||
Reference in New Issue
Block a user