test: Add unit tests for formatTrackBeadID helper

Extract the cross-rig bead formatting logic into a testable helper
function and add comprehensive unit tests:

- TestFormatTrackBeadID: 8 test cases covering HQ beads, cross-rig
  beads, and edge cases (single segment, empty string, many segments)
- TestFormatTrackBeadIDConsumerCompatibility: 3 test cases verifying
  the external ref format can be correctly parsed by consumers in
  convoy.go, model.go, feed/convoy.go, and web/fetcher.go

The helper function includes godoc with examples showing expected
behavior for different bead ID formats.
This commit is contained in:
alice
2026-01-04 20:51:08 -05:00
committed by akatz-ai
parent e11bcb931e
commit 8f03b44771
2 changed files with 155 additions and 9 deletions

View File

@@ -1240,15 +1240,7 @@ func createAutoConvoy(beadID, beadTitle string) (string, error) {
}
// Add tracking relation: convoy tracks the issue
// Format cross-prefix beads as external refs so bd can resolve them
trackBeadID := beadID
if !strings.HasPrefix(beadID, "hq-") {
parts := strings.SplitN(beadID, "-", 3)
if len(parts) >= 2 {
rigPrefix := parts[0] + "-" + parts[1]
trackBeadID = fmt.Sprintf("external:%s:%s", rigPrefix, beadID)
}
}
trackBeadID := formatTrackBeadID(beadID)
depArgs := []string{"dep", "add", convoyID, trackBeadID, "--type=tracks"}
depCmd := exec.Command("bd", depArgs...)
depCmd.Dir = townBeads
@@ -1408,3 +1400,24 @@ func runBatchSling(beadIDs []string, rigName string, townBeadsDir string) error
return nil
}
// formatTrackBeadID formats a bead ID for use in convoy tracking dependencies.
// Cross-rig beads (non-hq- prefixed) are formatted as external references
// so the bd tool can resolve them when running from HQ context.
//
// Examples:
// - "hq-abc123" -> "hq-abc123" (HQ beads unchanged)
// - "gt-mol-xyz" -> "external:gt-mol:gt-mol-xyz"
// - "beads-task-123" -> "external:beads-task:beads-task-123"
func formatTrackBeadID(beadID string) string {
if strings.HasPrefix(beadID, "hq-") {
return beadID
}
parts := strings.SplitN(beadID, "-", 3)
if len(parts) >= 2 {
rigPrefix := parts[0] + "-" + parts[1]
return fmt.Sprintf("external:%s:%s", rigPrefix, beadID)
}
// Fallback for malformed IDs (single segment)
return beadID
}

133
internal/cmd/sling_test.go Normal file
View File

@@ -0,0 +1,133 @@
package cmd
import "testing"
func TestFormatTrackBeadID(t *testing.T) {
tests := []struct {
name string
beadID string
expected string
}{
// HQ beads should remain unchanged
{
name: "hq bead unchanged",
beadID: "hq-abc123",
expected: "hq-abc123",
},
{
name: "hq convoy unchanged",
beadID: "hq-cv-xyz789",
expected: "hq-cv-xyz789",
},
// Cross-rig beads get external: prefix
{
name: "gastown rig bead",
beadID: "gt-mol-abc123",
expected: "external:gt-mol:gt-mol-abc123",
},
{
name: "beads rig task",
beadID: "beads-task-xyz",
expected: "external:beads-task:beads-task-xyz",
},
{
name: "two segment ID",
beadID: "foo-bar",
expected: "external:foo-bar:foo-bar",
},
// Edge cases
{
name: "single segment fallback",
beadID: "orphan",
expected: "orphan",
},
{
name: "empty string fallback",
beadID: "",
expected: "",
},
{
name: "many segments",
beadID: "a-b-c-d-e-f",
expected: "external:a-b:a-b-c-d-e-f",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := formatTrackBeadID(tt.beadID)
if result != tt.expected {
t.Errorf("formatTrackBeadID(%q) = %q, want %q", tt.beadID, result, tt.expected)
}
})
}
}
// TestFormatTrackBeadIDConsumerCompatibility verifies that the external ref format
// produced by formatTrackBeadID can be correctly parsed by the consumer pattern
// used in convoy.go, model.go, feed/convoy.go, and web/fetcher.go.
func TestFormatTrackBeadIDConsumerCompatibility(t *testing.T) {
// Consumer pattern from convoy.go:1062-1068:
// if strings.HasPrefix(issueID, "external:") {
// parts := strings.SplitN(issueID, ":", 3)
// if len(parts) == 3 {
// issueID = parts[2] // Extract the actual issue ID
// }
// }
tests := []struct {
name string
beadID string
wantOriginalID string
}{
{
name: "cross-rig bead round-trips",
beadID: "gt-mol-abc123",
wantOriginalID: "gt-mol-abc123",
},
{
name: "beads rig bead round-trips",
beadID: "beads-task-xyz",
wantOriginalID: "beads-task-xyz",
},
{
name: "hq bead unchanged",
beadID: "hq-abc123",
wantOriginalID: "hq-abc123",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
formatted := formatTrackBeadID(tt.beadID)
// Simulate consumer parsing logic
parsed := formatted
if len(formatted) > 9 && formatted[:9] == "external:" {
parts := make([]string, 0, 3)
start := 0
count := 0
for i := 0; i < len(formatted) && count < 2; i++ {
if formatted[i] == ':' {
parts = append(parts, formatted[start:i])
start = i + 1
count++
}
}
if count == 2 {
parts = append(parts, formatted[start:])
}
if len(parts) == 3 {
parsed = parts[2]
}
}
if parsed != tt.wantOriginalID {
t.Errorf("round-trip failed: formatTrackBeadID(%q) = %q, parsed back to %q, want %q",
tt.beadID, formatted, parsed, tt.wantOriginalID)
}
})
}
}