Files
beads/cmd/bd/list_test.go
Steve Yegge 0b852f52d9 Remove obsolete renumber and stale commands, fix test
- Remove renumber.go: Hash IDs eliminated need for ID compaction
- Remove stale.go: Executor/heartbeat tables were never implemented
- Fix TestListCommand: duplicate dependency constraint violation
- Update comments removing references to removed commands

Amp-Thread-ID: https://ampcode.com/threads/T-3dcd8681-c7d3-4fe1-9750-b38279b56cdb
Co-authored-by: Amp <amp@ampcode.com>
2025-10-31 21:27:33 -07:00

224 lines
6.2 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"
)
// listTestHelper provides test setup and assertion methods
type listTestHelper struct {
t *testing.T
ctx context.Context
store *sqlite.SQLiteStorage
issues []*types.Issue
}
func newListTestHelper(t *testing.T, store *sqlite.SQLiteStorage) *listTestHelper {
return &listTestHelper{t: t, ctx: context.Background(), store: store}
}
func (h *listTestHelper) createTestIssues() {
now := time.Now()
h.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: testUserAlice,
},
{
Title: "Task Issue",
Description: "Test task",
Priority: 2,
IssueType: types.TypeTask,
Status: types.StatusClosed,
ClosedAt: &now,
},
}
for _, issue := range h.issues {
if err := h.store.CreateIssue(h.ctx, issue, "test-user"); err != nil {
h.t.Fatalf("Failed to create issue: %v", err)
}
}
}
func (h *listTestHelper) addLabel(id, label string) {
if err := h.store.AddLabel(h.ctx, id, label, "test-user"); err != nil {
h.t.Fatalf("Failed to add label: %v", err)
}
}
func (h *listTestHelper) search(filter types.IssueFilter) []*types.Issue {
results, err := h.store.SearchIssues(h.ctx, "", filter)
if err != nil {
h.t.Fatalf("Failed to search issues: %v", err)
}
return results
}
func (h *listTestHelper) assertCount(count, expected int, desc string) {
if count != expected {
h.t.Errorf("Expected %d %s, got %d", expected, desc, count)
}
}
func (h *listTestHelper) assertEqual(expected, actual interface{}, field string) {
if expected != actual {
h.t.Errorf("Expected %s %v, got %v", field, expected, actual)
}
}
func (h *listTestHelper) assertAtMost(count, maxCount int, desc string) {
if count > maxCount {
h.t.Errorf("Expected at most %d %s, got %d", maxCount, desc, count)
}
}
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 := newTestStore(t, testDB)
defer s.Close()
h := newListTestHelper(t, s)
h.createTestIssues()
h.addLabel(h.issues[0].ID, "critical")
t.Run("list all issues", func(t *testing.T) {
results := h.search(types.IssueFilter{})
h.assertCount(len(results), 3, "issues")
})
t.Run("filter by status", func(t *testing.T) {
status := types.StatusOpen
results := h.search(types.IssueFilter{Status: &status})
h.assertCount(len(results), 1, "open issues")
h.assertEqual(types.StatusOpen, results[0].Status, "status")
})
t.Run("filter by priority", func(t *testing.T) {
priority := 0
results := h.search(types.IssueFilter{Priority: &priority})
h.assertCount(len(results), 1, "P0 issues")
h.assertEqual(0, results[0].Priority, "priority")
})
t.Run("filter by assignee", func(t *testing.T) {
assignee := testUserAlice
results := h.search(types.IssueFilter{Assignee: &assignee})
h.assertCount(len(results), 1, "issues for alice")
h.assertEqual(testUserAlice, results[0].Assignee, "assignee")
})
t.Run("filter by issue type", func(t *testing.T) {
issueType := types.TypeBug
results := h.search(types.IssueFilter{IssueType: &issueType})
h.assertCount(len(results), 1, "bug issues")
h.assertEqual(types.TypeBug, results[0].IssueType, "type")
})
t.Run("filter by label", func(t *testing.T) {
results := h.search(types.IssueFilter{Labels: []string{"critical"}})
h.assertCount(len(results), 1, "issues with critical label")
})
t.Run("filter by title search", func(t *testing.T) {
results := h.search(types.IssueFilter{TitleSearch: "Bug"})
h.assertCount(len(results), 1, "issues matching 'Bug'")
})
t.Run("limit results", func(t *testing.T) {
results := h.search(types.IssueFilter{Limit: 2})
h.assertAtMost(len(results), 2, "issues")
})
t.Run("normalize labels", func(t *testing.T) {
labels := []string{" bug ", "critical", "", "bug", " feature "}
normalized := normalizeLabels(labels)
expected := []string{"bug", "critical", "feature"}
h.assertCount(len(normalized), len(expected), "normalized labels")
// 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
}
})
t.Run("output dot format", func(t *testing.T) {
// Add a dependency to make the graph more interesting
dep := &types.Dependency{
IssueID: h.issues[0].ID,
DependsOnID: h.issues[1].ID,
Type: types.DepBlocks,
}
if err := h.store.AddDependency(h.ctx, dep, "test-user"); err != nil {
t.Fatalf("Failed to add dependency: %v", err)
}
err := outputDotFormat(h.ctx, h.store, h.issues)
if err != nil {
t.Errorf("outputDotFormat failed: %v", err)
}
})
t.Run("output formatted list dot", func(t *testing.T) {
err := outputFormattedList(h.ctx, h.store, h.issues, "dot")
if err != nil {
t.Errorf("outputFormattedList with dot format failed: %v", err)
}
})
t.Run("output formatted list digraph preset", func(t *testing.T) {
// Dependency already added in previous test, just use it
err := outputFormattedList(h.ctx, h.store, h.issues, "digraph")
if err != nil {
t.Errorf("outputFormattedList with digraph format failed: %v", err)
}
})
t.Run("output formatted list custom template", func(t *testing.T) {
err := outputFormattedList(h.ctx, h.store, h.issues, "{{.ID}} {{.Title}}")
if err != nil {
t.Errorf("outputFormattedList with custom template failed: %v", err)
}
})
t.Run("output formatted list invalid template", func(t *testing.T) {
err := outputFormattedList(h.ctx, h.store, h.issues, "{{.ID")
if err == nil {
t.Error("Expected error for invalid template")
}
})
}