test: improve nodb and orphans coverage
This commit is contained in:
committed by
Steve Yegge
parent
9cefa98528
commit
cb280b0fad
@@ -5,6 +5,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/steveyegge/beads/internal/config"
|
||||||
"github.com/steveyegge/beads/internal/storage/memory"
|
"github.com/steveyegge/beads/internal/storage/memory"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
)
|
)
|
||||||
@@ -156,6 +157,61 @@ func TestDetectPrefix(t *testing.T) {
|
|||||||
t.Errorf("Expected prefix 'myproject', got '%s'", prefix)
|
t.Errorf("Expected prefix 'myproject', got '%s'", prefix)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("config override", func(t *testing.T) {
|
||||||
|
memStore := memory.New(filepath.Join(beadsDir, "issues.jsonl"))
|
||||||
|
prev := config.GetString("issue-prefix")
|
||||||
|
config.Set("issue-prefix", "custom-prefix")
|
||||||
|
t.Cleanup(func() { config.Set("issue-prefix", prev) })
|
||||||
|
|
||||||
|
prefix, err := detectPrefix(beadsDir, memStore)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("detectPrefix failed: %v", err)
|
||||||
|
}
|
||||||
|
if prefix != "custom-prefix" {
|
||||||
|
t.Errorf("Expected config override prefix, got %q", prefix)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("sanitizes directory names", func(t *testing.T) {
|
||||||
|
memStore := memory.New(filepath.Join(beadsDir, "issues.jsonl"))
|
||||||
|
weirdDir := filepath.Join(tempDir, "My Project!!!")
|
||||||
|
if err := os.MkdirAll(weirdDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("Failed to create dir: %v", err)
|
||||||
|
}
|
||||||
|
t.Chdir(weirdDir)
|
||||||
|
prev := config.GetString("issue-prefix")
|
||||||
|
config.Set("issue-prefix", "")
|
||||||
|
t.Cleanup(func() { config.Set("issue-prefix", prev) })
|
||||||
|
|
||||||
|
prefix, err := detectPrefix(beadsDir, memStore)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("detectPrefix failed: %v", err)
|
||||||
|
}
|
||||||
|
if prefix != "myproject" {
|
||||||
|
t.Errorf("Expected sanitized prefix 'myproject', got %q", prefix)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid directory falls back to bd", func(t *testing.T) {
|
||||||
|
memStore := memory.New(filepath.Join(beadsDir, "issues.jsonl"))
|
||||||
|
emptyDir := filepath.Join(tempDir, "!!!")
|
||||||
|
if err := os.MkdirAll(emptyDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("Failed to create dir: %v", err)
|
||||||
|
}
|
||||||
|
t.Chdir(emptyDir)
|
||||||
|
prev := config.GetString("issue-prefix")
|
||||||
|
config.Set("issue-prefix", "")
|
||||||
|
t.Cleanup(func() { config.Set("issue-prefix", prev) })
|
||||||
|
|
||||||
|
prefix, err := detectPrefix(beadsDir, memStore)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("detectPrefix failed: %v", err)
|
||||||
|
}
|
||||||
|
if prefix != "bd" {
|
||||||
|
t.Errorf("Expected fallback prefix 'bd', got %q", prefix)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInitializeNoDbMode_SetsStoreActive(t *testing.T) {
|
func TestInitializeNoDbMode_SetsStoreActive(t *testing.T) {
|
||||||
@@ -253,7 +309,8 @@ func TestWriteIssuesToJSONL(t *testing.T) {
|
|||||||
|
|
||||||
issues := []*types.Issue{
|
issues := []*types.Issue{
|
||||||
{ID: "bd-1", Title: "Test Issue 1", Description: "Desc 1"},
|
{ID: "bd-1", Title: "Test Issue 1", Description: "Desc 1"},
|
||||||
{ID: "bd-2", Title: "Test Issue 2", Description: "Desc 2"},
|
{ID: "bd-2", Title: "Test Issue 2", Description: "Desc 2", Ephemeral: true},
|
||||||
|
{ID: "bd-3", Title: "Regular", Description: "Persistent"},
|
||||||
}
|
}
|
||||||
if err := memStore.LoadFromIssues(issues); err != nil {
|
if err := memStore.LoadFromIssues(issues); err != nil {
|
||||||
t.Fatalf("Failed to load issues: %v", err)
|
t.Fatalf("Failed to load issues: %v", err)
|
||||||
@@ -271,6 +328,11 @@ func TestWriteIssuesToJSONL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(loadedIssues) != 2 {
|
if len(loadedIssues) != 2 {
|
||||||
t.Errorf("Expected 2 issues in JSONL, got %d", len(loadedIssues))
|
t.Fatalf("Expected 2 non-ephemeral issues in JSONL, got %d", len(loadedIssues))
|
||||||
|
}
|
||||||
|
for _, issue := range loadedIssues {
|
||||||
|
if issue.Ephemeral {
|
||||||
|
t.Fatalf("Ephemeral issue %s should not be persisted", issue.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ import (
|
|||||||
"github.com/steveyegge/beads/internal/ui"
|
"github.com/steveyegge/beads/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var doctorFindOrphanedIssues = doctor.FindOrphanedIssues
|
||||||
|
|
||||||
|
var closeIssueRunner = func(issueID string) error {
|
||||||
|
cmd := exec.Command("bd", "close", issueID, "--reason", "Implemented")
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
var orphansCmd = &cobra.Command{
|
var orphansCmd = &cobra.Command{
|
||||||
Use: "orphans",
|
Use: "orphans",
|
||||||
Short: "Identify orphaned issues (referenced in commits but still open)",
|
Short: "Identify orphaned issues (referenced in commits but still open)",
|
||||||
@@ -98,7 +105,7 @@ type orphanIssueOutput struct {
|
|||||||
|
|
||||||
// findOrphanedIssues wraps the shared doctor package function and converts to output format
|
// findOrphanedIssues wraps the shared doctor package function and converts to output format
|
||||||
func findOrphanedIssues(path string) ([]orphanIssueOutput, error) {
|
func findOrphanedIssues(path string) ([]orphanIssueOutput, error) {
|
||||||
orphans, err := doctor.FindOrphanedIssues(path)
|
orphans, err := doctorFindOrphanedIssues(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to find orphaned issues: %w", err)
|
return nil, fmt.Errorf("unable to find orphaned issues: %w", err)
|
||||||
}
|
}
|
||||||
@@ -118,8 +125,7 @@ func findOrphanedIssues(path string) ([]orphanIssueOutput, error) {
|
|||||||
|
|
||||||
// closeIssue closes an issue using bd close
|
// closeIssue closes an issue using bd close
|
||||||
func closeIssue(issueID string) error {
|
func closeIssue(issueID string) error {
|
||||||
cmd := exec.Command("bd", "close", issueID, "--reason", "Implemented")
|
return closeIssueRunner(issueID)
|
||||||
return cmd.Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -1,82 +1,89 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"errors"
|
||||||
"os"
|
"strings"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/steveyegge/beads/cmd/bd/doctor"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestOrphansBasic tests basic orphan detection
|
func TestFindOrphanedIssues_ConvertsDoctorOutput(t *testing.T) {
|
||||||
func TestOrphansBasic(t *testing.T) {
|
orig := doctorFindOrphanedIssues
|
||||||
// Create a temporary directory with a git repo and beads database
|
doctorFindOrphanedIssues = func(path string) ([]doctor.OrphanIssue, error) {
|
||||||
tmpDir := t.TempDir()
|
if path != "/tmp/repo" {
|
||||||
|
t.Fatalf("unexpected path %q", path)
|
||||||
// Initialize git repo
|
|
||||||
cmd := exec.Command("git", "init")
|
|
||||||
cmd.Dir = tmpDir
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
t.Fatalf("Failed to init git repo: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure git user (needed for commits)
|
|
||||||
ctx := context.Background()
|
|
||||||
for _, cmd := range []*exec.Cmd{
|
|
||||||
exec.CommandContext(ctx, "git", "-C", tmpDir, "config", "user.email", "test@example.com"),
|
|
||||||
exec.CommandContext(ctx, "git", "-C", tmpDir, "config", "user.name", "Test User"),
|
|
||||||
} {
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
t.Fatalf("Failed to configure git: %v", err)
|
|
||||||
}
|
}
|
||||||
|
return []doctor.OrphanIssue{{
|
||||||
|
IssueID: "bd-123",
|
||||||
|
Title: "Fix login",
|
||||||
|
Status: "open",
|
||||||
|
LatestCommit: "abc123",
|
||||||
|
LatestCommitMessage: "(bd-123) implement fix",
|
||||||
|
}}, nil
|
||||||
}
|
}
|
||||||
|
t.Cleanup(func() { doctorFindOrphanedIssues = orig })
|
||||||
|
|
||||||
// Create .beads directory
|
result, err := findOrphanedIssues("/tmp/repo")
|
||||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
|
||||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
||||||
t.Fatalf("Failed to create .beads dir: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a minimal database with beads.db
|
|
||||||
// For this test, we'll skip creating an actual database
|
|
||||||
// since the test is primarily about integration
|
|
||||||
|
|
||||||
// Test: findOrphanedIssues should handle missing database gracefully
|
|
||||||
orphans, err := findOrphanedIssues(tmpDir)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("findOrphanedIssues failed: %v", err)
|
t.Fatalf("findOrphanedIssues returned error: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(result) != 1 {
|
||||||
// Should be empty list since no database
|
t.Fatalf("expected 1 orphan, got %d", len(result))
|
||||||
if len(orphans) != 0 {
|
}
|
||||||
t.Errorf("Expected empty orphans list, got %d", len(orphans))
|
orphan := result[0]
|
||||||
|
if orphan.IssueID != "bd-123" || orphan.Title != "Fix login" || orphan.Status != "open" {
|
||||||
|
t.Fatalf("unexpected orphan output: %#v", orphan)
|
||||||
|
}
|
||||||
|
if orphan.LatestCommit != "abc123" || !strings.Contains(orphan.LatestCommitMessage, "implement") {
|
||||||
|
t.Fatalf("commit metadata not preserved: %#v", orphan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOrphansNotGitRepo tests behavior in non-git directories
|
func TestFindOrphanedIssues_ErrorWrapped(t *testing.T) {
|
||||||
func TestOrphansNotGitRepo(t *testing.T) {
|
orig := doctorFindOrphanedIssues
|
||||||
tmpDir := t.TempDir()
|
doctorFindOrphanedIssues = func(string) ([]doctor.OrphanIssue, error) {
|
||||||
|
return nil, errors.New("boom")
|
||||||
// Should not error, just return empty list
|
|
||||||
orphans, err := findOrphanedIssues(tmpDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("findOrphanedIssues failed: %v", err)
|
|
||||||
}
|
}
|
||||||
|
t.Cleanup(func() { doctorFindOrphanedIssues = orig })
|
||||||
|
|
||||||
if len(orphans) != 0 {
|
_, err := findOrphanedIssues("/tmp/repo")
|
||||||
t.Errorf("Expected empty orphans list for non-git repo, got %d", len(orphans))
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "unable to find orphaned issues") {
|
||||||
|
t.Fatalf("expected wrapped error message, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCloseIssueCommand tests that close issue command is properly formed
|
func TestCloseIssue_UsesRunner(t *testing.T) {
|
||||||
func TestCloseIssueCommand(t *testing.T) {
|
orig := closeIssueRunner
|
||||||
// This is a basic test to ensure the closeIssue function
|
defer func() { closeIssueRunner = orig }()
|
||||||
// attempts to run the correct command.
|
|
||||||
// In a real environment, this would fail since bd close requires
|
|
||||||
// a valid beads database.
|
|
||||||
|
|
||||||
// Just test that the function doesn't panic
|
called := false
|
||||||
// (actual close will fail, which is expected)
|
closeIssueRunner = func(issueID string) error {
|
||||||
_ = closeIssue("bd-test-invalid")
|
called = true
|
||||||
// Error is expected since the issue doesn't exist
|
if issueID != "bd-999" {
|
||||||
|
t.Fatalf("unexpected issue id %q", issueID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := closeIssue("bd-999"); err != nil {
|
||||||
|
t.Fatalf("closeIssue returned error: %v", err)
|
||||||
|
}
|
||||||
|
if !called {
|
||||||
|
t.Fatal("closeIssueRunner was not invoked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseIssue_PropagatesError(t *testing.T) {
|
||||||
|
orig := closeIssueRunner
|
||||||
|
closeIssueRunner = func(string) error { return errors.New("nope") }
|
||||||
|
t.Cleanup(func() { closeIssueRunner = orig })
|
||||||
|
|
||||||
|
err := closeIssue("bd-1")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "nope") {
|
||||||
|
t.Fatalf("expected delegated error, got %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user