Files
beads/internal/storage/sqlite/lazy_init_test.go
Steve Yegge db8efd534c Fix bd-179: Derive prefix from database filename when config missing
- Add dbPath field to SQLiteStorage to track database file path
- Create derivePrefixFromPath() helper to extract prefix from filename
- Update ID generation in CreateIssue() and generateBatchIDs() to use filename fallback
- Fix tests to explicitly set issue_prefix config for bd- prefixed tests

When config doesn't have issue_prefix set, bd now correctly derives it from
the database filename (e.g., wy-.db -> wy) instead of always defaulting to 'bd'.

Fixes: bd-179
2025-10-20 22:18:08 -07:00

228 lines
5.6 KiB
Go

package sqlite
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/steveyegge/beads/internal/types"
)
// TestLazyCounterInitialization verifies that counters are initialized lazily
// on first use, not by scanning the entire database on every CreateIssue
func TestLazyCounterInitialization(t *testing.T) {
// Create temporary directory
tmpDir, err := os.MkdirTemp("", "beads-lazy-init-test-*")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
dbPath := filepath.Join(tmpDir, "test.db")
// Initialize database
store, err := New(dbPath)
if err != nil {
t.Fatalf("failed to create storage: %v", err)
}
defer store.Close()
ctx := context.Background()
// Set the issue prefix to "bd" for this test
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set issue_prefix: %v", err)
}
// Create some issues with explicit IDs (simulating import)
existingIssues := []string{"bd-5", "bd-10", "bd-15"}
for _, id := range existingIssues {
issue := &types.Issue{
ID: id,
Title: "Existing issue",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err := store.CreateIssue(ctx, issue, "test-user")
if err != nil {
t.Fatalf("CreateIssue with explicit ID failed: %v", err)
}
}
// Verify no counter exists yet (lazy init hasn't happened)
var count int
err = store.db.QueryRow(`SELECT COUNT(*) FROM issue_counters WHERE prefix = 'bd'`).Scan(&count)
if err != nil {
t.Fatalf("Failed to query counters: %v", err)
}
if count != 0 {
t.Errorf("Expected no counter yet, but found %d", count)
}
// Now create an issue with auto-generated ID
// This should trigger lazy initialization
autoIssue := &types.Issue{
Title: "Auto-generated ID",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err = store.CreateIssue(ctx, autoIssue, "test-user")
if err != nil {
t.Fatalf("CreateIssue with auto ID failed: %v", err)
}
// Verify the ID is correct (should be bd-16, after bd-15)
if autoIssue.ID != "bd-16" {
t.Errorf("Expected bd-16, got %s", autoIssue.ID)
}
// Verify counter was initialized
var lastID int
err = store.db.QueryRow(`SELECT last_id FROM issue_counters WHERE prefix = 'bd'`).Scan(&lastID)
if err != nil {
t.Fatalf("Failed to query counter: %v", err)
}
if lastID != 16 {
t.Errorf("Expected counter at 16, got %d", lastID)
}
// Create another issue - should NOT re-scan, just increment
anotherIssue := &types.Issue{
Title: "Another auto-generated ID",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err = store.CreateIssue(ctx, anotherIssue, "test-user")
if err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
if anotherIssue.ID != "bd-17" {
t.Errorf("Expected bd-17, got %s", anotherIssue.ID)
}
}
// TestLazyCounterInitializationMultiplePrefix tests lazy init with multiple prefixes
func TestLazyCounterInitializationMultiplePrefix(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Set a custom prefix
err := store.SetConfig(ctx, "issue_prefix", "custom")
if err != nil {
t.Fatalf("SetConfig failed: %v", err)
}
// Create issue with default prefix first
err = store.SetConfig(ctx, "issue_prefix", "bd")
if err != nil {
t.Fatalf("SetConfig failed: %v", err)
}
bdIssue := &types.Issue{
Title: "BD issue",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err = store.CreateIssue(ctx, bdIssue, "test-user")
if err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
if bdIssue.ID != "bd-1" {
t.Errorf("Expected bd-1, got %s", bdIssue.ID)
}
// Now switch to custom prefix
err = store.SetConfig(ctx, "issue_prefix", "custom")
if err != nil {
t.Fatalf("SetConfig failed: %v", err)
}
customIssue := &types.Issue{
Title: "Custom issue",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err = store.CreateIssue(ctx, customIssue, "test-user")
if err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
if customIssue.ID != "custom-1" {
t.Errorf("Expected custom-1, got %s", customIssue.ID)
}
// Verify both counters exist
var count int
err = store.db.QueryRow(`SELECT COUNT(*) FROM issue_counters`).Scan(&count)
if err != nil {
t.Fatalf("Failed to query counters: %v", err)
}
if count != 2 {
t.Errorf("Expected 2 counters, got %d", count)
}
}
// TestCounterInitializationFromExisting tests that the counter
// correctly initializes from the max ID of existing issues
func TestCounterInitializationFromExisting(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Set the issue prefix to "bd" for this test
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set issue_prefix: %v", err)
}
// Create issues with explicit IDs, out of order
explicitIDs := []string{"bd-5", "bd-100", "bd-42", "bd-7"}
for _, id := range explicitIDs {
issue := &types.Issue{
ID: id,
Title: "Explicit ID",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err := store.CreateIssue(ctx, issue, "test-user")
if err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
}
// Now auto-generate - should start at 101 (max is bd-100)
autoIssue := &types.Issue{
Title: "Auto ID",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err := store.CreateIssue(ctx, autoIssue, "test-user")
if err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
if autoIssue.ID != "bd-101" {
t.Errorf("Expected bd-101 (max was bd-100), got %s", autoIssue.ID)
}
}