Complete bd-175, bd-176, bd-177: Memory tests, corruption docs, prefix validation
- bd-175: Added comprehensive test coverage for internal/storage/memory backend - All CRUD operations, dependencies, labels, comments - Thread safety with race detection - LoadFromIssues and counter sync - Fixed batch duplicate detection - bd-176: Documented corruption vs collision distinction - Added FAQ entry explaining logical vs physical corruption - Updated TROUBLESHOOTING with clear guidance - Clarified when to use collision resolution vs reimport - bd-177: Added prefix validation in SQLite mode - Validates explicit IDs match configured prefix - Works in both CreateIssue and CreateIssues - Comprehensive tests for single and batch operations
This commit is contained in:
165
internal/storage/sqlite/prefix_validation_test.go
Normal file
165
internal/storage/sqlite/prefix_validation_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
func TestPrefixValidation(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "beads-prefix-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
dbPath := filepath.Join(tmpDir, "test.db")
|
||||
store, err := New(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create storage: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Set prefix to "test"
|
||||
if err := store.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
||||
t.Fatalf("failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
issueID string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid prefix - matches",
|
||||
issueID: "test-123",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid prefix - wrong prefix",
|
||||
issueID: "bd-456",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid prefix - no dash",
|
||||
issueID: "test123",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid prefix - empty",
|
||||
issueID: "",
|
||||
wantErr: false, // Empty ID triggers auto-generation
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
issue := &types.Issue{
|
||||
ID: tt.issueID,
|
||||
Title: "Test issue",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
|
||||
err := store.CreateIssue(ctx, issue, "test-user")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CreateIssue() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
// If we expected success and the ID was empty, verify it was generated with correct prefix
|
||||
if err == nil && tt.issueID == "" {
|
||||
if issue.ID == "" {
|
||||
t.Error("ID should be generated")
|
||||
}
|
||||
if issue.ID[:5] != "test-" {
|
||||
t.Errorf("Generated ID should have prefix 'test-', got %s", issue.ID)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixValidationBatch(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "beads-prefix-batch-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
dbPath := filepath.Join(tmpDir, "test.db")
|
||||
store, err := New(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create storage: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Set prefix to "batch"
|
||||
if err := store.SetConfig(ctx, "issue_prefix", "batch"); err != nil {
|
||||
t.Fatalf("failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
issues []*types.Issue
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "all valid prefixes",
|
||||
issues: []*types.Issue{
|
||||
{ID: "batch-1", Title: "Test 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
{ID: "batch-2", Title: "Test 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "one invalid prefix in batch",
|
||||
issues: []*types.Issue{
|
||||
{ID: "batch-10", Title: "Test 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
{ID: "wrong-20", Title: "Test 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "mixed auto-generated and explicit",
|
||||
issues: []*types.Issue{
|
||||
{ID: "batch-100", Title: "Explicit ID", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
{ID: "", Title: "Auto ID", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "mixed with invalid prefix",
|
||||
issues: []*types.Issue{
|
||||
{ID: "", Title: "Auto ID", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
{ID: "invalid-500", Title: "Wrong prefix", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := store.CreateIssues(ctx, tt.issues, "test-user")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CreateIssues() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
// For successful batches, verify all IDs have correct prefix
|
||||
if err == nil {
|
||||
for i, issue := range tt.issues {
|
||||
if issue.ID[:6] != "batch-" {
|
||||
t.Errorf("Issue %d: ID should have prefix 'batch-', got %s", i, issue.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user