package main import ( "context" "encoding/json" "os" "path/filepath" "testing" "time" "github.com/steveyegge/beads/internal/storage/sqlite" "github.com/steveyegge/beads/internal/types" ) // TestEnsureDatabaseFresh_AutoImportsOnStale verifies that when the database // is stale (JSONL is newer), ensureDatabaseFresh triggers auto-import instead // of returning an error. This is the fix for bd-9dao. func TestEnsureDatabaseFresh_AutoImportsOnStale(t *testing.T) { ctx := context.Background() tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatalf("Failed to create beads dir: %v", err) } testDBPath := filepath.Join(beadsDir, "bd.db") jsonlPath := filepath.Join(beadsDir, "issues.jsonl") // Create database testStore, err := sqlite.New(ctx, testDBPath) if err != nil { t.Fatalf("Failed to create store: %v", err) } defer testStore.Close() // Set prefix if err := testStore.SetConfig(ctx, "issue_prefix", "test"); err != nil { t.Fatalf("Failed to set prefix: %v", err) } // Set an old last_import_time to make DB appear stale oldTime := time.Now().Add(-1 * time.Hour) if err := testStore.SetMetadata(ctx, "last_import_time", oldTime.Format(time.RFC3339Nano)); err != nil { t.Fatalf("Failed to set metadata: %v", err) } // Create JSONL with a new issue that should be auto-imported jsonlIssue := &types.Issue{ ID: "test-stale-auto-bd9dao", Title: "Should Auto Import on Stale DB", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, } f, err := os.Create(jsonlPath) if err != nil { t.Fatalf("Failed to create JSONL: %v", err) } encoder := json.NewEncoder(f) if err := encoder.Encode(jsonlIssue); err != nil { t.Fatalf("Failed to encode issue: %v", err) } f.Close() // Save and set global state oldNoAutoImport := noAutoImport oldAutoImportEnabled := autoImportEnabled oldStore := store oldDbPath := dbPath oldRootCtx := rootCtx oldStoreActive := storeActive oldAllowStale := allowStale noAutoImport = false // Allow auto-import autoImportEnabled = true // Enable auto-import allowStale = false // Don't skip staleness check store = testStore dbPath = testDBPath rootCtx = ctx storeActive = true defer func() { noAutoImport = oldNoAutoImport autoImportEnabled = oldAutoImportEnabled allowStale = oldAllowStale store = oldStore dbPath = oldDbPath rootCtx = oldRootCtx storeActive = oldStoreActive }() // Call ensureDatabaseFresh - should auto-import and return nil err = ensureDatabaseFresh(ctx) if err != nil { t.Errorf("ensureDatabaseFresh() returned error when it should have auto-imported: %v", err) } // Verify issue was auto-imported imported, err := testStore.GetIssue(ctx, "test-stale-auto-bd9dao") if err != nil { t.Fatalf("Failed to check for issue: %v", err) } if imported == nil { t.Error("ensureDatabaseFresh() did not auto-import when DB was stale - bd-9dao fix failed") } } // TestEnsureDatabaseFresh_NoAutoImportFlag verifies that when noAutoImport is // true, ensureDatabaseFresh returns an error instead of auto-importing. func TestEnsureDatabaseFresh_NoAutoImportFlag(t *testing.T) { ctx := context.Background() tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatalf("Failed to create beads dir: %v", err) } testDBPath := filepath.Join(beadsDir, "bd.db") jsonlPath := filepath.Join(beadsDir, "issues.jsonl") // Create database testStore, err := sqlite.New(ctx, testDBPath) if err != nil { t.Fatalf("Failed to create store: %v", err) } defer testStore.Close() // Set prefix if err := testStore.SetConfig(ctx, "issue_prefix", "test"); err != nil { t.Fatalf("Failed to set prefix: %v", err) } // Set an old last_import_time to make DB appear stale oldTime := time.Now().Add(-1 * time.Hour) if err := testStore.SetMetadata(ctx, "last_import_time", oldTime.Format(time.RFC3339Nano)); err != nil { t.Fatalf("Failed to set metadata: %v", err) } // Create JSONL with a new issue jsonlIssue := &types.Issue{ ID: "test-noauto-bd9dao", Title: "Should NOT Auto Import", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, } f, err := os.Create(jsonlPath) if err != nil { t.Fatalf("Failed to create JSONL: %v", err) } encoder := json.NewEncoder(f) if err := encoder.Encode(jsonlIssue); err != nil { t.Fatalf("Failed to encode issue: %v", err) } f.Close() // Save and set global state oldNoAutoImport := noAutoImport oldAutoImportEnabled := autoImportEnabled oldStore := store oldDbPath := dbPath oldRootCtx := rootCtx oldStoreActive := storeActive oldAllowStale := allowStale noAutoImport = true // Disable auto-import autoImportEnabled = false // Disable auto-import allowStale = false // Don't skip staleness check store = testStore dbPath = testDBPath rootCtx = ctx storeActive = true defer func() { noAutoImport = oldNoAutoImport autoImportEnabled = oldAutoImportEnabled allowStale = oldAllowStale store = oldStore dbPath = oldDbPath rootCtx = oldRootCtx storeActive = oldStoreActive }() // Call ensureDatabaseFresh - should return error since noAutoImport is set err = ensureDatabaseFresh(ctx) if err == nil { t.Error("ensureDatabaseFresh() should have returned error when noAutoImport is true") } // Verify issue was NOT imported imported, err := testStore.GetIssue(ctx, "test-noauto-bd9dao") if err != nil { t.Fatalf("Failed to check for issue: %v", err) } if imported != nil { t.Error("ensureDatabaseFresh() imported despite noAutoImport=true") } } // TestEnsureDatabaseFresh_AllowStaleFlag verifies that when allowStale is // true, ensureDatabaseFresh skips the check entirely. func TestEnsureDatabaseFresh_AllowStaleFlag(t *testing.T) { ctx := context.Background() tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatalf("Failed to create beads dir: %v", err) } testDBPath := filepath.Join(beadsDir, "bd.db") jsonlPath := filepath.Join(beadsDir, "issues.jsonl") // Create database testStore, err := sqlite.New(ctx, testDBPath) if err != nil { t.Fatalf("Failed to create store: %v", err) } defer testStore.Close() // Set prefix if err := testStore.SetConfig(ctx, "issue_prefix", "test"); err != nil { t.Fatalf("Failed to set prefix: %v", err) } // Set an old last_import_time to make DB appear stale oldTime := time.Now().Add(-1 * time.Hour) if err := testStore.SetMetadata(ctx, "last_import_time", oldTime.Format(time.RFC3339Nano)); err != nil { t.Fatalf("Failed to set metadata: %v", err) } // Create JSONL with a new issue jsonlIssue := &types.Issue{ ID: "test-allowstale-bd9dao", Title: "Should Skip Check", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, } f, err := os.Create(jsonlPath) if err != nil { t.Fatalf("Failed to create JSONL: %v", err) } encoder := json.NewEncoder(f) if err := encoder.Encode(jsonlIssue); err != nil { t.Fatalf("Failed to encode issue: %v", err) } f.Close() // Save and set global state oldNoAutoImport := noAutoImport oldAutoImportEnabled := autoImportEnabled oldStore := store oldDbPath := dbPath oldRootCtx := rootCtx oldStoreActive := storeActive oldAllowStale := allowStale noAutoImport = true // Disable auto-import (shouldn't matter with allowStale) autoImportEnabled = false allowStale = true // Skip staleness check entirely store = testStore dbPath = testDBPath rootCtx = ctx storeActive = true defer func() { noAutoImport = oldNoAutoImport autoImportEnabled = oldAutoImportEnabled allowStale = oldAllowStale store = oldStore dbPath = oldDbPath rootCtx = oldRootCtx storeActive = oldStoreActive }() // Call ensureDatabaseFresh - should return nil (skip check) err = ensureDatabaseFresh(ctx) if err != nil { t.Errorf("ensureDatabaseFresh() should have returned nil with allowStale=true: %v", err) } // Verify issue was NOT imported (check was skipped entirely) imported, err := testStore.GetIssue(ctx, "test-allowstale-bd9dao") if err != nil { t.Fatalf("Failed to check for issue: %v", err) } if imported != nil { t.Error("ensureDatabaseFresh() imported even though allowStale=true (should skip check entirely)") } } // TestEnsureDatabaseFresh_FreshDB verifies that when the database is fresh, // ensureDatabaseFresh returns nil without doing anything. func TestEnsureDatabaseFresh_FreshDB(t *testing.T) { ctx := context.Background() tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatalf("Failed to create beads dir: %v", err) } testDBPath := filepath.Join(beadsDir, "bd.db") jsonlPath := filepath.Join(beadsDir, "issues.jsonl") // Create JSONL first jsonlIssue := &types.Issue{ ID: "test-fresh-bd9dao", Title: "Fresh DB Test", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, } f, err := os.Create(jsonlPath) if err != nil { t.Fatalf("Failed to create JSONL: %v", err) } encoder := json.NewEncoder(f) if err := encoder.Encode(jsonlIssue); err != nil { t.Fatalf("Failed to encode issue: %v", err) } f.Close() // Create database testStore, err := sqlite.New(ctx, testDBPath) if err != nil { t.Fatalf("Failed to create store: %v", err) } defer testStore.Close() // Set prefix if err := testStore.SetConfig(ctx, "issue_prefix", "test"); err != nil { t.Fatalf("Failed to set prefix: %v", err) } // Set a future last_import_time to make DB appear fresh futureTime := time.Now().Add(1 * time.Hour) if err := testStore.SetMetadata(ctx, "last_import_time", futureTime.Format(time.RFC3339Nano)); err != nil { t.Fatalf("Failed to set metadata: %v", err) } // Save and set global state oldNoAutoImport := noAutoImport oldAutoImportEnabled := autoImportEnabled oldStore := store oldDbPath := dbPath oldRootCtx := rootCtx oldStoreActive := storeActive oldAllowStale := allowStale noAutoImport = false autoImportEnabled = true allowStale = false store = testStore dbPath = testDBPath rootCtx = ctx storeActive = true defer func() { noAutoImport = oldNoAutoImport autoImportEnabled = oldAutoImportEnabled allowStale = oldAllowStale store = oldStore dbPath = oldDbPath rootCtx = oldRootCtx storeActive = oldStoreActive }() // Call ensureDatabaseFresh - should return nil (DB is fresh) err = ensureDatabaseFresh(ctx) if err != nil { t.Errorf("ensureDatabaseFresh() should have returned nil for fresh DB: %v", err) } }