Files
beads/cmd/bd/list_test.go
Steve Yegge 8023a6cd6c Improve test coverage to 57.7% (+13.5%)
Added comprehensive test coverage for previously untested commands:
- version_test.go: Plain text and JSON version output
- list_test.go: All filter operations and label normalization
- export_test.go: JSONL export with labels & dependencies
- stale_test.go: Duration formatting and stale issue detection
- comments_test.go: Comment management and error handling
- delete_test.go: Batch deletion helpers
- metrics_test.go: RPC metrics recording and snapshots

Coverage improvement:
- Overall: 44.2% → 57.7% (+13.5%)
- cmd/bd: 17.9% → 19.8% (+1.9%)
- internal/rpc: 45.2% → 45.8% (+0.6%)

All tests passing with no new linter warnings.

Amp-Thread-ID: https://ampcode.com/threads/T-1ee1734e-0164-4c6f-834e-cb8051d14302
Co-authored-by: Amp <amp@ampcode.com>
2025-10-24 00:56:18 -07:00

207 lines
5.1 KiB
Go

package main
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/steveyegge/beads/internal/storage/sqlite"
"github.com/steveyegge/beads/internal/types"
)
func TestListCommand(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "bd-test-list-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
testDB := filepath.Join(tmpDir, "test.db")
s, err := sqlite.New(testDB)
if err != nil {
t.Fatalf("Failed to create store: %v", err)
}
defer s.Close()
ctx := context.Background()
// Create test issues
now := time.Now()
issues := []*types.Issue{
{
Title: "Bug Issue",
Description: "Test bug",
Priority: 0,
IssueType: types.TypeBug,
Status: types.StatusOpen,
},
{
Title: "Feature Issue",
Description: "Test feature",
Priority: 1,
IssueType: types.TypeFeature,
Status: types.StatusInProgress,
Assignee: "alice",
},
{
Title: "Task Issue",
Description: "Test task",
Priority: 2,
IssueType: types.TypeTask,
Status: types.StatusClosed,
ClosedAt: &now,
},
}
for _, issue := range issues {
if err := s.CreateIssue(ctx, issue, "test-user"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
}
// Add labels to first issue
if err := s.AddLabel(ctx, issues[0].ID, "critical", "test-user"); err != nil {
t.Fatalf("Failed to add label: %v", err)
}
t.Run("list all issues", func(t *testing.T) {
filter := types.IssueFilter{}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) != 3 {
t.Errorf("Expected 3 issues, got %d", len(results))
}
})
t.Run("filter by status", func(t *testing.T) {
status := types.StatusOpen
filter := types.IssueFilter{Status: &status}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) != 1 {
t.Errorf("Expected 1 open issue, got %d", len(results))
}
if results[0].Status != types.StatusOpen {
t.Errorf("Expected status %s, got %s", types.StatusOpen, results[0].Status)
}
})
t.Run("filter by priority", func(t *testing.T) {
priority := 0
filter := types.IssueFilter{Priority: &priority}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) != 1 {
t.Errorf("Expected 1 P0 issue, got %d", len(results))
}
if results[0].Priority != 0 {
t.Errorf("Expected priority 0, got %d", results[0].Priority)
}
})
t.Run("filter by assignee", func(t *testing.T) {
assignee := "alice"
filter := types.IssueFilter{Assignee: &assignee}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) != 1 {
t.Errorf("Expected 1 issue for alice, got %d", len(results))
}
if results[0].Assignee != "alice" {
t.Errorf("Expected assignee alice, got %s", results[0].Assignee)
}
})
t.Run("filter by issue type", func(t *testing.T) {
issueType := types.TypeBug
filter := types.IssueFilter{IssueType: &issueType}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) != 1 {
t.Errorf("Expected 1 bug issue, got %d", len(results))
}
if results[0].IssueType != types.TypeBug {
t.Errorf("Expected type %s, got %s", types.TypeBug, results[0].IssueType)
}
})
t.Run("filter by label", func(t *testing.T) {
filter := types.IssueFilter{Labels: []string{"critical"}}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) != 1 {
t.Errorf("Expected 1 issue with critical label, got %d", len(results))
}
})
t.Run("filter by title search", func(t *testing.T) {
filter := types.IssueFilter{TitleSearch: "Bug"}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) != 1 {
t.Errorf("Expected 1 issue matching 'Bug', got %d", len(results))
}
})
t.Run("limit results", func(t *testing.T) {
filter := types.IssueFilter{Limit: 2}
results, err := s.SearchIssues(ctx, "", filter)
if err != nil {
t.Fatalf("Failed to search issues: %v", err)
}
if len(results) > 2 {
t.Errorf("Expected at most 2 issues, got %d", len(results))
}
})
t.Run("normalize labels", func(t *testing.T) {
labels := []string{" bug ", "critical", "", "bug", " feature "}
normalized := normalizeLabels(labels)
expected := []string{"bug", "critical", "feature"}
if len(normalized) != len(expected) {
t.Errorf("Expected %d normalized labels, got %d", len(expected), len(normalized))
}
// Check deduplication and trimming
seen := make(map[string]bool)
for _, label := range normalized {
if label == "" {
t.Error("Found empty label after normalization")
}
if label != strings.TrimSpace(label) {
t.Errorf("Label not trimmed: '%s'", label)
}
if seen[label] {
t.Errorf("Duplicate label found: %s", label)
}
seen[label] = true
}
})
}