Files
beads/cmd/bd/status_test.go
Steve Yegge 1edf3c6c88 Fix bd-9v7l: bd status now uses git history for recent activity
- Changed from database timestamps (7 days) to git log analysis (24 hours)
- Git log is fast (~24ms) and reflects actual JSONL changes
- Shows commits, total changes, created/closed/reopened/updated counts
- Updated tests to verify git-based activity tracking
- Removed misleading database-based getRecentActivity function

Amp-Thread-ID: https://ampcode.com/threads/T-dc29c5ad-ff33-401a-9546-4d5ca1d8421b
Co-authored-by: Amp <amp@ampcode.com>
2025-11-06 18:49:07 -08:00

265 lines
6.8 KiB
Go

package main
import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
"github.com/steveyegge/beads/internal/storage/sqlite"
"github.com/steveyegge/beads/internal/types"
)
// Helper function to create a time pointer
func timePtr(t time.Time) *time.Time {
return &t
}
func TestStatusCommand(t *testing.T) {
// Create a temporary directory for the test database
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, ".beads", "test.db")
// Create .beads directory
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
t.Fatalf("Failed to create .beads directory: %v", err)
}
// Initialize the database
store, err := sqlite.New(dbPath)
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer store.Close()
ctx := context.Background()
// Set issue prefix
if err := store.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set issue prefix: %v", err)
}
// Create some test issues with different statuses
testIssues := []*types.Issue{
{
Title: "Open issue 1",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
Assignee: "alice",
},
{
Title: "Open issue 2",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeBug,
Assignee: "bob",
},
{
Title: "In progress issue",
Status: types.StatusInProgress,
Priority: 1,
IssueType: types.TypeFeature,
Assignee: "alice",
},
{
Title: "Blocked issue",
Status: types.StatusBlocked,
Priority: 0,
IssueType: types.TypeBug,
Assignee: "alice",
},
{
Title: "Closed issue",
Status: types.StatusClosed,
Priority: 3,
IssueType: types.TypeTask,
Assignee: "bob",
ClosedAt: timePtr(time.Now()),
},
}
for _, issue := range testIssues {
if err := store.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create test issue: %v", err)
}
}
// Test GetStatistics
stats, err := store.GetStatistics(ctx)
if err != nil {
t.Fatalf("GetStatistics failed: %v", err)
}
// Verify counts
if stats.TotalIssues != 5 {
t.Errorf("Expected 5 total issues, got %d", stats.TotalIssues)
}
if stats.OpenIssues != 2 {
t.Errorf("Expected 2 open issues, got %d", stats.OpenIssues)
}
if stats.InProgressIssues != 1 {
t.Errorf("Expected 1 in-progress issue, got %d", stats.InProgressIssues)
}
if stats.BlockedIssues != 0 {
// Note: BlockedIssues counts issues that are blocked by dependencies
// Our test issue with status=blocked doesn't have dependencies, so count is 0
t.Logf("BlockedIssues: %d (expected 0, status=blocked without deps)", stats.BlockedIssues)
}
if stats.ClosedIssues != 1 {
t.Errorf("Expected 1 closed issue, got %d", stats.ClosedIssues)
}
// Test status output structures
summary := &StatusSummary{
TotalIssues: stats.TotalIssues,
OpenIssues: stats.OpenIssues,
InProgressIssues: stats.InProgressIssues,
BlockedIssues: stats.BlockedIssues,
ClosedIssues: stats.ClosedIssues,
ReadyIssues: stats.ReadyIssues,
}
// Test JSON marshaling
output := &StatusOutput{
Summary: summary,
}
jsonBytes, err := json.MarshalIndent(output, "", " ")
if err != nil {
t.Fatalf("Failed to marshal JSON: %v", err)
}
t.Logf("Status output:\n%s", string(jsonBytes))
// Verify JSON structure
var decoded StatusOutput
if err := json.Unmarshal(jsonBytes, &decoded); err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}
if decoded.Summary.TotalIssues != 5 {
t.Errorf("Decoded total issues: expected 5, got %d", decoded.Summary.TotalIssues)
}
}
func TestGetGitActivity(t *testing.T) {
// Test getGitActivity - it may return nil if not in a git repo
// or if there's no recent activity
activity := getGitActivity(24)
// If we're in a git repo with activity, verify the structure
if activity != nil {
if activity.HoursTracked != 24 {
t.Errorf("Expected 24 hours tracked, got %d", activity.HoursTracked)
}
// Should have non-negative values
if activity.CommitCount < 0 {
t.Errorf("Negative commit count: %d", activity.CommitCount)
}
if activity.IssuesCreated < 0 {
t.Errorf("Negative issues created: %d", activity.IssuesCreated)
}
if activity.IssuesClosed < 0 {
t.Errorf("Negative issues closed: %d", activity.IssuesClosed)
}
if activity.IssuesUpdated < 0 {
t.Errorf("Negative issues updated: %d", activity.IssuesUpdated)
}
t.Logf("Git activity: commits=%d, created=%d, closed=%d, updated=%d, total=%d",
activity.CommitCount, activity.IssuesCreated, activity.IssuesClosed,
activity.IssuesUpdated, activity.TotalChanges)
} else {
t.Log("No git activity found (not in a git repo or no recent commits)")
}
}
func TestGetAssignedStatus(t *testing.T) {
// Create a temporary directory for the test database
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, ".beads", "test.db")
// Create .beads directory
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
t.Fatalf("Failed to create .beads directory: %v", err)
}
// Initialize the database
testStore, err := sqlite.New(dbPath)
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer testStore.Close()
ctx := context.Background()
// Set issue prefix
if err := testStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set issue prefix: %v", err)
}
// Set global store for getAssignedStatus
store = testStore
// Create test issues with different assignees
testIssues := []*types.Issue{
{
Title: "Alice's issue 1",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
Assignee: "alice",
},
{
Title: "Alice's issue 2",
Status: types.StatusInProgress,
Priority: 1,
IssueType: types.TypeTask,
Assignee: "alice",
},
{
Title: "Bob's issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
Assignee: "bob",
},
}
for _, issue := range testIssues {
if err := testStore.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create test issue: %v", err)
}
}
// Test getAssignedStatus for Alice
summary := getAssignedStatus("alice")
if summary == nil {
t.Fatal("getAssignedStatus returned nil")
}
if summary.TotalIssues != 2 {
t.Errorf("Expected 2 issues for alice, got %d", summary.TotalIssues)
}
if summary.OpenIssues != 1 {
t.Errorf("Expected 1 open issue for alice, got %d", summary.OpenIssues)
}
if summary.InProgressIssues != 1 {
t.Errorf("Expected 1 in-progress issue for alice, got %d", summary.InProgressIssues)
}
// Test for Bob
bobSummary := getAssignedStatus("bob")
if bobSummary == nil {
t.Fatal("getAssignedStatus returned nil for bob")
}
if bobSummary.TotalIssues != 1 {
t.Errorf("Expected 1 issue for bob, got %d", bobSummary.TotalIssues)
}
}