Add doctor check to verify that attached molecules exist and are not closed. Detects when a hook's attached_molecule field points to a non-existent or closed issue, which can leave agents with stale work assignments. - Check: hook-attachment-valid - Error if: Hook's attached_molecule field points to non-existent or closed issue - Fix: Automatically detaches invalid molecules when run with --fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
124 lines
2.9 KiB
Go
124 lines
2.9 KiB
Go
package doctor
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewHookAttachmentValidCheck(t *testing.T) {
|
|
check := NewHookAttachmentValidCheck()
|
|
|
|
if check.Name() != "hook-attachment-valid" {
|
|
t.Errorf("expected name 'hook-attachment-valid', got %q", check.Name())
|
|
}
|
|
|
|
if check.Description() != "Verify attached molecules exist and are not closed" {
|
|
t.Errorf("unexpected description: %q", check.Description())
|
|
}
|
|
|
|
if !check.CanFix() {
|
|
t.Error("expected CanFix to return true")
|
|
}
|
|
}
|
|
|
|
func TestHookAttachmentValidCheck_NoBeadsDir(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
check := NewHookAttachmentValidCheck()
|
|
ctx := &CheckContext{TownRoot: tmpDir}
|
|
|
|
result := check.Run(ctx)
|
|
|
|
// No beads dir means nothing to check, should be OK
|
|
if result.Status != StatusOK {
|
|
t.Errorf("expected StatusOK when no beads dir, got %v", result.Status)
|
|
}
|
|
}
|
|
|
|
func TestHookAttachmentValidCheck_EmptyBeadsDir(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
check := NewHookAttachmentValidCheck()
|
|
ctx := &CheckContext{TownRoot: tmpDir}
|
|
|
|
result := check.Run(ctx)
|
|
|
|
// Empty beads dir means no pinned beads, should be OK
|
|
// Note: This may error if bd CLI is not available, but should still handle gracefully
|
|
if result.Status != StatusOK && result.Status != StatusError {
|
|
t.Errorf("expected StatusOK or graceful error, got %v", result.Status)
|
|
}
|
|
}
|
|
|
|
func TestHookAttachmentValidCheck_FormatInvalid(t *testing.T) {
|
|
check := NewHookAttachmentValidCheck()
|
|
|
|
tests := []struct {
|
|
inv invalidAttachment
|
|
expected string
|
|
}{
|
|
{
|
|
inv: invalidAttachment{
|
|
pinnedBeadID: "hq-123",
|
|
moleculeID: "gt-456",
|
|
reason: "not_found",
|
|
},
|
|
expected: "hq-123: attached molecule gt-456 not found",
|
|
},
|
|
{
|
|
inv: invalidAttachment{
|
|
pinnedBeadID: "hq-123",
|
|
moleculeID: "gt-789",
|
|
reason: "closed",
|
|
},
|
|
expected: "hq-123: attached molecule gt-789 is closed",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := check.formatInvalid(tt.inv)
|
|
if result != tt.expected {
|
|
t.Errorf("formatInvalid() = %q, want %q", result, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHookAttachmentValidCheck_FindRigBeadsDirs(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create town-level .beads (should be excluded)
|
|
townBeads := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(townBeads, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create rig-level .beads
|
|
rigBeads := filepath.Join(tmpDir, "myrig", ".beads")
|
|
if err := os.MkdirAll(rigBeads, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
check := NewHookAttachmentValidCheck()
|
|
dirs := check.findRigBeadsDirs(tmpDir)
|
|
|
|
// Should find the rig-level beads but not town-level
|
|
found := false
|
|
for _, dir := range dirs {
|
|
if dir == townBeads {
|
|
t.Error("findRigBeadsDirs should not include town-level .beads")
|
|
}
|
|
if dir == rigBeads {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found && len(dirs) > 0 {
|
|
t.Logf("Found dirs: %v", dirs)
|
|
}
|
|
}
|