test: Add comprehensive test coverage for storage layer
Added test files for core SQLite storage functionality: - beads_test.go: Database path detection tests - dirty_test.go: Dirty tracking for auto-flush - events_test.go: Event logging tests - labels_test.go: Label management tests - sqlite_test.go: Added metadata tests (SetMetadata, GetMetadata) Merged with upstream TestParallelIssueCreation (bd-89 regression test). All tests passing. Ready to push. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
313
internal/storage/sqlite/dirty_test.go
Normal file
313
internal/storage/sqlite/dirty_test.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
func TestMarkIssueDirty(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create an issue first
|
||||
issue := &types.Issue{
|
||||
Title: "Test issue",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
err := store.CreateIssue(ctx, issue, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
|
||||
// Issue should already be marked dirty by CreateIssue
|
||||
dirtyIssues, err := store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
if len(dirtyIssues) != 1 {
|
||||
t.Fatalf("Expected 1 dirty issue, got %d", len(dirtyIssues))
|
||||
}
|
||||
if dirtyIssues[0] != issue.ID {
|
||||
t.Errorf("Expected dirty issue %s, got %s", issue.ID, dirtyIssues[0])
|
||||
}
|
||||
|
||||
// Clear dirty issues
|
||||
err = store.ClearDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ClearDirtyIssues failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify cleared
|
||||
dirtyIssues, err = store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
if len(dirtyIssues) != 0 {
|
||||
t.Errorf("Expected 0 dirty issues after clear, got %d", len(dirtyIssues))
|
||||
}
|
||||
|
||||
// Mark it dirty again manually
|
||||
err = store.MarkIssueDirty(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkIssueDirty failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's dirty again
|
||||
dirtyIssues, err = store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
if len(dirtyIssues) != 1 {
|
||||
t.Errorf("Expected 1 dirty issue after marking, got %d", len(dirtyIssues))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkIssuesDirty(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create multiple issues
|
||||
var issueIDs []string
|
||||
for i := 0; i < 3; i++ {
|
||||
issue := &types.Issue{
|
||||
Title: "Test issue",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
err := store.CreateIssue(ctx, issue, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
issueIDs = append(issueIDs, issue.ID)
|
||||
}
|
||||
|
||||
// Clear all dirty issues
|
||||
err := store.ClearDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ClearDirtyIssues failed: %v", err)
|
||||
}
|
||||
|
||||
// Mark multiple issues dirty at once
|
||||
err = store.MarkIssuesDirty(ctx, issueIDs)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkIssuesDirty failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify all are dirty
|
||||
dirtyIssues, err := store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
if len(dirtyIssues) != 3 {
|
||||
t.Errorf("Expected 3 dirty issues, got %d", len(dirtyIssues))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkIssuesDirtyEmpty(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Mark empty slice - should not error
|
||||
err := store.MarkIssuesDirty(ctx, []string{})
|
||||
if err != nil {
|
||||
t.Errorf("MarkIssuesDirty with empty slice should not error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDirtyIssueCount(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Count should be 0 initially
|
||||
count, err := store.GetDirtyIssueCount(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssueCount failed: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("Expected 0 dirty issues, got %d", count)
|
||||
}
|
||||
|
||||
// Create an issue
|
||||
issue := &types.Issue{
|
||||
Title: "Test issue",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
err = store.CreateIssue(ctx, issue, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
|
||||
// Count should be 1 now
|
||||
count, err = store.GetDirtyIssueCount(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssueCount failed: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("Expected 1 dirty issue, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearDirtyIssuesByID(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create multiple issues
|
||||
var issueIDs []string
|
||||
for i := 0; i < 5; i++ {
|
||||
issue := &types.Issue{
|
||||
Title: "Test issue",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
err := store.CreateIssue(ctx, issue, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
issueIDs = append(issueIDs, issue.ID)
|
||||
}
|
||||
|
||||
// Verify all are dirty
|
||||
count, err := store.GetDirtyIssueCount(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssueCount failed: %v", err)
|
||||
}
|
||||
if count != 5 {
|
||||
t.Errorf("Expected 5 dirty issues, got %d", count)
|
||||
}
|
||||
|
||||
// Clear only the first 3
|
||||
err = store.ClearDirtyIssuesByID(ctx, issueIDs[:3])
|
||||
if err != nil {
|
||||
t.Fatalf("ClearDirtyIssuesByID failed: %v", err)
|
||||
}
|
||||
|
||||
// Should have 2 remaining
|
||||
count, err = store.GetDirtyIssueCount(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssueCount failed: %v", err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Errorf("Expected 2 dirty issues remaining, got %d", count)
|
||||
}
|
||||
|
||||
// Verify the correct ones remain
|
||||
dirtyIssues, err := store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
for _, id := range dirtyIssues {
|
||||
if id == issueIDs[0] || id == issueIDs[1] || id == issueIDs[2] {
|
||||
t.Errorf("Issue %s should have been cleared", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearDirtyIssuesByIDEmpty(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Clear empty slice - should not error
|
||||
err := store.ClearDirtyIssuesByID(ctx, []string{})
|
||||
if err != nil {
|
||||
t.Errorf("ClearDirtyIssuesByID with empty slice should not error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirtyIssuesOrdering(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create issues with slight time delays to ensure ordering
|
||||
var issueIDs []string
|
||||
for i := 0; i < 3; i++ {
|
||||
issue := &types.Issue{
|
||||
Title: "Test issue",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
err := store.CreateIssue(ctx, issue, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
issueIDs = append(issueIDs, issue.ID)
|
||||
time.Sleep(10 * time.Millisecond) // Ensure different timestamps
|
||||
}
|
||||
|
||||
// Get dirty issues - should be in order by marked_at (oldest first)
|
||||
dirtyIssues, err := store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
|
||||
if len(dirtyIssues) != 3 {
|
||||
t.Fatalf("Expected 3 dirty issues, got %d", len(dirtyIssues))
|
||||
}
|
||||
|
||||
// Verify order matches creation order
|
||||
for i, id := range issueIDs {
|
||||
if dirtyIssues[i] != id {
|
||||
t.Errorf("Expected issue %d to be %s, got %s", i, id, dirtyIssues[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirtyIssuesUpdateTimestamp(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create an issue
|
||||
issue := &types.Issue{
|
||||
Title: "Test issue",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
err := store.CreateIssue(ctx, issue, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
|
||||
// Wait a bit
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Mark dirty again - should update timestamp
|
||||
err = store.MarkIssueDirty(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkIssueDirty failed: %v", err)
|
||||
}
|
||||
|
||||
// Should still have only 1 dirty issue (ON CONFLICT DO UPDATE)
|
||||
count, err := store.GetDirtyIssueCount(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssueCount failed: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("Expected 1 dirty issue after re-marking, got %d", count)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user