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:
File diff suppressed because one or more lines are too long
258
beads_test.go
Normal file
258
beads_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package beads
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFindDatabasePathEnvVar(t *testing.T) {
|
||||
// Save original env var
|
||||
originalEnv := os.Getenv("BEADS_DB")
|
||||
defer func() {
|
||||
if originalEnv != "" {
|
||||
os.Setenv("BEADS_DB", originalEnv)
|
||||
} else {
|
||||
os.Unsetenv("BEADS_DB")
|
||||
}
|
||||
}()
|
||||
|
||||
// Set env var to a test path
|
||||
testPath := "/test/path/test.db"
|
||||
os.Setenv("BEADS_DB", testPath)
|
||||
|
||||
result := FindDatabasePath()
|
||||
if result != testPath {
|
||||
t.Errorf("Expected '%s', got '%s'", testPath, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindDatabasePathInTree(t *testing.T) {
|
||||
// Save original env var and working directory
|
||||
originalEnv := os.Getenv("BEADS_DB")
|
||||
originalWd, _ := os.Getwd()
|
||||
defer func() {
|
||||
if originalEnv != "" {
|
||||
os.Setenv("BEADS_DB", originalEnv)
|
||||
} else {
|
||||
os.Unsetenv("BEADS_DB")
|
||||
}
|
||||
os.Chdir(originalWd)
|
||||
}()
|
||||
|
||||
// Clear env var
|
||||
os.Unsetenv("BEADS_DB")
|
||||
|
||||
// Create temporary directory structure
|
||||
tmpDir, err := os.MkdirTemp("", "beads-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create .beads directory with a database file
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
err = os.MkdirAll(beadsDir, 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create .beads dir: %v", err)
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(beadsDir, "test.db")
|
||||
f, err := os.Create(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create db file: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
// Create a subdirectory and change to it
|
||||
subDir := filepath.Join(tmpDir, "sub", "nested")
|
||||
err = os.MkdirAll(subDir, 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create subdirectory: %v", err)
|
||||
}
|
||||
|
||||
err = os.Chdir(subDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to change directory: %v", err)
|
||||
}
|
||||
|
||||
// Should find the database in the parent directory tree
|
||||
result := FindDatabasePath()
|
||||
|
||||
// Resolve symlinks for both paths (macOS uses /private/var symlinked to /var)
|
||||
expectedPath, err := filepath.EvalSymlinks(dbPath)
|
||||
if err != nil {
|
||||
expectedPath = dbPath
|
||||
}
|
||||
resultPath, err := filepath.EvalSymlinks(result)
|
||||
if err != nil {
|
||||
resultPath = result
|
||||
}
|
||||
|
||||
if resultPath != expectedPath {
|
||||
t.Errorf("Expected '%s', got '%s'", expectedPath, resultPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindDatabasePathNotFound(t *testing.T) {
|
||||
// Save original env var and working directory
|
||||
originalEnv := os.Getenv("BEADS_DB")
|
||||
originalWd, _ := os.Getwd()
|
||||
defer func() {
|
||||
if originalEnv != "" {
|
||||
os.Setenv("BEADS_DB", originalEnv)
|
||||
} else {
|
||||
os.Unsetenv("BEADS_DB")
|
||||
}
|
||||
os.Chdir(originalWd)
|
||||
}()
|
||||
|
||||
// Clear env var
|
||||
os.Unsetenv("BEADS_DB")
|
||||
|
||||
// Create temporary directory without .beads
|
||||
tmpDir, err := os.MkdirTemp("", "beads-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = os.Chdir(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to change directory: %v", err)
|
||||
}
|
||||
|
||||
// Should return empty string (no database found)
|
||||
result := FindDatabasePath()
|
||||
// Result might be the home directory default if it exists, or empty string
|
||||
// Just verify it doesn't error
|
||||
_ = result
|
||||
}
|
||||
|
||||
func TestFindJSONLPathWithExistingFile(t *testing.T) {
|
||||
// Create temporary directory
|
||||
tmpDir, err := os.MkdirTemp("", "beads-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create a .jsonl file
|
||||
jsonlPath := filepath.Join(tmpDir, "custom.jsonl")
|
||||
f, err := os.Create(jsonlPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create jsonl file: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
// Create a fake database path in the same directory
|
||||
dbPath := filepath.Join(tmpDir, "test.db")
|
||||
|
||||
// Should find the existing .jsonl file
|
||||
result := FindJSONLPath(dbPath)
|
||||
if result != jsonlPath {
|
||||
t.Errorf("Expected '%s', got '%s'", jsonlPath, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindJSONLPathDefault(t *testing.T) {
|
||||
// Create temporary directory
|
||||
tmpDir, err := os.MkdirTemp("", "beads-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create a fake database path (no .jsonl files exist)
|
||||
dbPath := filepath.Join(tmpDir, "test.db")
|
||||
|
||||
// Should return default issues.jsonl
|
||||
result := FindJSONLPath(dbPath)
|
||||
expected := filepath.Join(tmpDir, "issues.jsonl")
|
||||
if result != expected {
|
||||
t.Errorf("Expected '%s', got '%s'", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindJSONLPathEmpty(t *testing.T) {
|
||||
// Empty database path should return empty string
|
||||
result := FindJSONLPath("")
|
||||
if result != "" {
|
||||
t.Errorf("Expected empty string for empty db path, got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindJSONLPathMultipleFiles(t *testing.T) {
|
||||
// Create temporary directory
|
||||
tmpDir, err := os.MkdirTemp("", "beads-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create multiple .jsonl files
|
||||
jsonlFiles := []string{"issues.jsonl", "backup.jsonl", "archive.jsonl"}
|
||||
for _, filename := range jsonlFiles {
|
||||
f, err := os.Create(filepath.Join(tmpDir, filename))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create jsonl file: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// Create a fake database path
|
||||
dbPath := filepath.Join(tmpDir, "test.db")
|
||||
|
||||
// Should return the first .jsonl file found (lexicographically sorted by Glob)
|
||||
result := FindJSONLPath(dbPath)
|
||||
// Verify it's one of the .jsonl files we created
|
||||
found := false
|
||||
for _, filename := range jsonlFiles {
|
||||
if result == filepath.Join(tmpDir, filename) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected one of the created .jsonl files, got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindDatabasePathHomeDefault(t *testing.T) {
|
||||
// This test verifies that if no database is found, it falls back to home directory
|
||||
// We can't reliably test this without modifying the home directory, so we'll skip
|
||||
// creating the file and just verify the function doesn't crash
|
||||
|
||||
originalEnv := os.Getenv("BEADS_DB")
|
||||
originalWd, _ := os.Getwd()
|
||||
defer func() {
|
||||
if originalEnv != "" {
|
||||
os.Setenv("BEADS_DB", originalEnv)
|
||||
} else {
|
||||
os.Unsetenv("BEADS_DB")
|
||||
}
|
||||
os.Chdir(originalWd)
|
||||
}()
|
||||
|
||||
os.Unsetenv("BEADS_DB")
|
||||
|
||||
// Create an empty temp directory and cd to it
|
||||
tmpDir, err := os.MkdirTemp("", "beads-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = os.Chdir(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to change directory: %v", err)
|
||||
}
|
||||
|
||||
// Call FindDatabasePath - it might return home dir default or empty string
|
||||
result := FindDatabasePath()
|
||||
|
||||
// If result is not empty, verify it contains .beads
|
||||
if result != "" && !filepath.IsAbs(result) {
|
||||
t.Errorf("Expected absolute path or empty string, got '%s'", result)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
339
internal/storage/sqlite/events_test.go
Normal file
339
internal/storage/sqlite/events_test.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
func TestAddComment(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)
|
||||
}
|
||||
|
||||
// Add a comment
|
||||
err = store.AddComment(ctx, issue.ID, "alice", "This is a test comment")
|
||||
if err != nil {
|
||||
t.Fatalf("AddComment failed: %v", err)
|
||||
}
|
||||
|
||||
// Get events to verify comment was added
|
||||
events, err := store.GetEvents(ctx, issue.ID, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEvents failed: %v", err)
|
||||
}
|
||||
|
||||
// Should have 2 events: created and commented
|
||||
if len(events) < 2 {
|
||||
t.Fatalf("Expected at least 2 events, got %d", len(events))
|
||||
}
|
||||
|
||||
// Find the comment event (most recent should be first due to DESC order)
|
||||
var commentEvent *types.Event
|
||||
for _, event := range events {
|
||||
if event.EventType == types.EventCommented {
|
||||
commentEvent = event
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if commentEvent == nil {
|
||||
t.Fatal("Comment event not found")
|
||||
}
|
||||
|
||||
if commentEvent.Actor != "alice" {
|
||||
t.Errorf("Expected actor 'alice', got '%s'", commentEvent.Actor)
|
||||
}
|
||||
|
||||
if commentEvent.Comment == nil || *commentEvent.Comment != "This is a test comment" {
|
||||
t.Errorf("Expected comment 'This is a test comment', got %v", commentEvent.Comment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMultipleComments(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)
|
||||
}
|
||||
|
||||
// Add multiple comments
|
||||
comments := []struct {
|
||||
actor string
|
||||
comment string
|
||||
}{
|
||||
{"alice", "First comment"},
|
||||
{"bob", "Second comment"},
|
||||
{"charlie", "Third comment"},
|
||||
}
|
||||
|
||||
for _, c := range comments {
|
||||
err = store.AddComment(ctx, issue.ID, c.actor, c.comment)
|
||||
if err != nil {
|
||||
t.Fatalf("AddComment failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get events
|
||||
events, err := store.GetEvents(ctx, issue.ID, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEvents failed: %v", err)
|
||||
}
|
||||
|
||||
// Count comment events
|
||||
commentCount := 0
|
||||
var commentEvents []*types.Event
|
||||
for _, event := range events {
|
||||
if event.EventType == types.EventCommented {
|
||||
commentCount++
|
||||
commentEvents = append(commentEvents, event)
|
||||
}
|
||||
}
|
||||
|
||||
if commentCount != 3 {
|
||||
t.Fatalf("Expected 3 comment events, got %d", commentCount)
|
||||
}
|
||||
|
||||
// Verify we can find all three comments
|
||||
foundComments := make(map[string]bool)
|
||||
for _, event := range commentEvents {
|
||||
if event.Comment != nil {
|
||||
foundComments[*event.Comment] = true
|
||||
}
|
||||
}
|
||||
|
||||
expectedComments := []string{"First comment", "Second comment", "Third comment"}
|
||||
for _, expected := range expectedComments {
|
||||
if !foundComments[expected] {
|
||||
t.Errorf("Expected to find comment '%s'", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEventsWithLimit(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)
|
||||
}
|
||||
|
||||
// Add 5 comments
|
||||
for i := 0; i < 5; i++ {
|
||||
err = store.AddComment(ctx, issue.ID, "alice", "Comment")
|
||||
if err != nil {
|
||||
t.Fatalf("AddComment failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get events with limit
|
||||
events, err := store.GetEvents(ctx, issue.ID, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEvents failed: %v", err)
|
||||
}
|
||||
|
||||
if len(events) != 3 {
|
||||
t.Errorf("Expected 3 events with limit, got %d", len(events))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEventsEmpty(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Get events for non-existent issue
|
||||
events, err := store.GetEvents(ctx, "bd-999", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEvents failed: %v", err)
|
||||
}
|
||||
|
||||
if len(events) != 0 {
|
||||
t.Errorf("Expected 0 events for non-existent issue, got %d", len(events))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddCommentMarksDirty(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)
|
||||
}
|
||||
|
||||
// Clear dirty issues
|
||||
err = store.ClearDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ClearDirtyIssues failed: %v", err)
|
||||
}
|
||||
|
||||
// Add comment - should mark issue dirty
|
||||
err = store.AddComment(ctx, issue.ID, "alice", "Test comment")
|
||||
if err != nil {
|
||||
t.Fatalf("AddComment failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify issue is dirty
|
||||
dirtyIssues, err := store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
|
||||
if len(dirtyIssues) != 1 || dirtyIssues[0] != issue.ID {
|
||||
t.Error("Expected issue to be marked dirty after adding comment")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddCommentUpdatesTimestamp(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)
|
||||
}
|
||||
|
||||
originalUpdatedAt := issue.UpdatedAt
|
||||
|
||||
// Add comment
|
||||
err = store.AddComment(ctx, issue.ID, "alice", "Test comment")
|
||||
if err != nil {
|
||||
t.Fatalf("AddComment failed: %v", err)
|
||||
}
|
||||
|
||||
// Get issue again and verify updated_at changed
|
||||
updatedIssue, err := store.GetIssue(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetIssue failed: %v", err)
|
||||
}
|
||||
|
||||
if !updatedIssue.UpdatedAt.After(originalUpdatedAt) {
|
||||
t.Error("Expected updated_at to be updated after adding comment")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventTypesInHistory(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)
|
||||
}
|
||||
|
||||
// Perform various operations that create events
|
||||
err = store.UpdateIssue(ctx, issue.ID, map[string]interface{}{
|
||||
"priority": 2,
|
||||
}, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateIssue failed: %v", err)
|
||||
}
|
||||
|
||||
err = store.AddComment(ctx, issue.ID, "alice", "A comment")
|
||||
if err != nil {
|
||||
t.Fatalf("AddComment failed: %v", err)
|
||||
}
|
||||
|
||||
err = store.AddLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
err = store.CloseIssue(ctx, issue.ID, "Done", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CloseIssue failed: %v", err)
|
||||
}
|
||||
|
||||
// Get events
|
||||
events, err := store.GetEvents(ctx, issue.ID, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEvents failed: %v", err)
|
||||
}
|
||||
|
||||
// Should have multiple event types
|
||||
eventTypes := make(map[types.EventType]bool)
|
||||
for _, event := range events {
|
||||
eventTypes[event.EventType] = true
|
||||
}
|
||||
|
||||
// Verify we have different event types
|
||||
if !eventTypes[types.EventCreated] {
|
||||
t.Error("Expected EventCreated in history")
|
||||
}
|
||||
if !eventTypes[types.EventUpdated] {
|
||||
t.Error("Expected EventUpdated in history")
|
||||
}
|
||||
if !eventTypes[types.EventCommented] {
|
||||
t.Error("Expected EventCommented in history")
|
||||
}
|
||||
if !eventTypes[types.EventLabelAdded] {
|
||||
t.Error("Expected EventLabelAdded in history")
|
||||
}
|
||||
if !eventTypes[types.EventClosed] {
|
||||
t.Error("Expected EventClosed in history")
|
||||
}
|
||||
}
|
||||
394
internal/storage/sqlite/labels_test.go
Normal file
394
internal/storage/sqlite/labels_test.go
Normal file
@@ -0,0 +1,394 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
func TestAddLabel(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)
|
||||
}
|
||||
|
||||
// Add a label
|
||||
err = store.AddLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
// Get labels
|
||||
labels, err := store.GetLabels(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLabels failed: %v", err)
|
||||
}
|
||||
|
||||
if len(labels) != 1 {
|
||||
t.Fatalf("Expected 1 label, got %d", len(labels))
|
||||
}
|
||||
|
||||
if labels[0] != "bug" {
|
||||
t.Errorf("Expected label 'bug', got '%s'", labels[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMultipleLabels(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)
|
||||
}
|
||||
|
||||
// Add multiple labels
|
||||
labelsToAdd := []string{"bug", "critical", "ui"}
|
||||
for _, label := range labelsToAdd {
|
||||
err = store.AddLabel(ctx, issue.ID, label, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed for %s: %v", label, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get labels
|
||||
labels, err := store.GetLabels(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLabels failed: %v", err)
|
||||
}
|
||||
|
||||
if len(labels) != 3 {
|
||||
t.Fatalf("Expected 3 labels, got %d", len(labels))
|
||||
}
|
||||
|
||||
// Verify labels are sorted alphabetically
|
||||
expectedOrder := []string{"bug", "critical", "ui"}
|
||||
for i, expected := range expectedOrder {
|
||||
if labels[i] != expected {
|
||||
t.Errorf("Expected label %d to be '%s', got '%s'", i, expected, labels[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddDuplicateLabel(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)
|
||||
}
|
||||
|
||||
// Add a label
|
||||
err = store.AddLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
// Add the same label again - should not error (INSERT OR IGNORE)
|
||||
err = store.AddLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel duplicate should not error: %v", err)
|
||||
}
|
||||
|
||||
// Should still have only 1 label
|
||||
labels, err := store.GetLabels(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLabels failed: %v", err)
|
||||
}
|
||||
|
||||
if len(labels) != 1 {
|
||||
t.Errorf("Expected 1 label after duplicate add, got %d", len(labels))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveLabel(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)
|
||||
}
|
||||
|
||||
// Add labels
|
||||
err = store.AddLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
err = store.AddLabel(ctx, issue.ID, "critical", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
// Remove one label
|
||||
err = store.RemoveLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("RemoveLabel failed: %v", err)
|
||||
}
|
||||
|
||||
// Get labels
|
||||
labels, err := store.GetLabels(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLabels failed: %v", err)
|
||||
}
|
||||
|
||||
if len(labels) != 1 {
|
||||
t.Fatalf("Expected 1 label after removal, got %d", len(labels))
|
||||
}
|
||||
|
||||
if labels[0] != "critical" {
|
||||
t.Errorf("Expected remaining label 'critical', got '%s'", labels[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveNonexistentLabel(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)
|
||||
}
|
||||
|
||||
// Remove a label that doesn't exist - should not error
|
||||
err = store.RemoveLabel(ctx, issue.ID, "nonexistent", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("RemoveLabel for nonexistent label should not error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLabelsEmpty(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create an issue without labels
|
||||
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)
|
||||
}
|
||||
|
||||
// Get labels - should return nil or empty slice (both valid in Go)
|
||||
labels, err := store.GetLabels(ctx, issue.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLabels failed: %v", err)
|
||||
}
|
||||
|
||||
if len(labels) != 0 {
|
||||
t.Errorf("Expected 0 labels, got %d", len(labels))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIssuesByLabel(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create issues with different labels
|
||||
issue1 := &types.Issue{
|
||||
Title: "Bug 1",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 0,
|
||||
IssueType: types.TypeBug,
|
||||
}
|
||||
err := store.CreateIssue(ctx, issue1, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
err = store.AddLabel(ctx, issue1.ID, "critical", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
issue2 := &types.Issue{
|
||||
Title: "Bug 2",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeBug,
|
||||
}
|
||||
err = store.CreateIssue(ctx, issue2, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
err = store.AddLabel(ctx, issue2.ID, "critical", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
issue3 := &types.Issue{
|
||||
Title: "Feature",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 2,
|
||||
IssueType: types.TypeFeature,
|
||||
}
|
||||
err = store.CreateIssue(ctx, issue3, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssue failed: %v", err)
|
||||
}
|
||||
err = store.AddLabel(ctx, issue3.ID, "enhancement", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
// Get issues by label "critical"
|
||||
issues, err := store.GetIssuesByLabel(ctx, "critical")
|
||||
if err != nil {
|
||||
t.Fatalf("GetIssuesByLabel failed: %v", err)
|
||||
}
|
||||
|
||||
if len(issues) != 2 {
|
||||
t.Fatalf("Expected 2 issues with 'critical' label, got %d", len(issues))
|
||||
}
|
||||
|
||||
// Verify both critical issues are returned
|
||||
foundIssue1 := false
|
||||
foundIssue2 := false
|
||||
for _, issue := range issues {
|
||||
if issue.ID == issue1.ID {
|
||||
foundIssue1 = true
|
||||
}
|
||||
if issue.ID == issue2.ID {
|
||||
foundIssue2 = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundIssue1 || !foundIssue2 {
|
||||
t.Error("Expected both critical issues to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIssuesByLabelEmpty(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Get issues by nonexistent label
|
||||
issues, err := store.GetIssuesByLabel(ctx, "nonexistent")
|
||||
if err != nil {
|
||||
t.Fatalf("GetIssuesByLabel failed: %v", err)
|
||||
}
|
||||
|
||||
if len(issues) != 0 {
|
||||
t.Errorf("Expected 0 issues for nonexistent label, got %d", len(issues))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelMarksDirty(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)
|
||||
}
|
||||
|
||||
// Clear dirty issues
|
||||
err = store.ClearDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ClearDirtyIssues failed: %v", err)
|
||||
}
|
||||
|
||||
// Add label - should mark issue dirty
|
||||
err = store.AddLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("AddLabel failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify issue is dirty
|
||||
dirtyIssues, err := store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
if len(dirtyIssues) != 1 || dirtyIssues[0] != issue.ID {
|
||||
t.Error("Expected issue to be marked dirty after adding label")
|
||||
}
|
||||
|
||||
// Clear dirty again
|
||||
err = store.ClearDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ClearDirtyIssues failed: %v", err)
|
||||
}
|
||||
|
||||
// Remove label - should mark issue dirty
|
||||
err = store.RemoveLabel(ctx, issue.ID, "bug", "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("RemoveLabel failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify issue is dirty again
|
||||
dirtyIssues, err = store.GetDirtyIssues(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDirtyIssues failed: %v", err)
|
||||
}
|
||||
if len(dirtyIssues) != 1 || dirtyIssues[0] != issue.ID {
|
||||
t.Error("Expected issue to be marked dirty after removing label")
|
||||
}
|
||||
}
|
||||
@@ -499,3 +499,104 @@ func TestParallelIssueCreation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAndGetMetadata(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Set metadata
|
||||
err := store.SetMetadata(ctx, "import_hash", "abc123def456")
|
||||
if err != nil {
|
||||
t.Fatalf("SetMetadata failed: %v", err)
|
||||
}
|
||||
|
||||
// Get metadata
|
||||
value, err := store.GetMetadata(ctx, "import_hash")
|
||||
if err != nil {
|
||||
t.Fatalf("GetMetadata failed: %v", err)
|
||||
}
|
||||
|
||||
if value != "abc123def456" {
|
||||
t.Errorf("Expected 'abc123def456', got '%s'", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMetadataNotFound(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Get non-existent metadata
|
||||
value, err := store.GetMetadata(ctx, "nonexistent")
|
||||
if err != nil {
|
||||
t.Fatalf("GetMetadata failed: %v", err)
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
t.Errorf("Expected empty string for non-existent key, got '%s'", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMetadataUpdate(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Set initial value
|
||||
err := store.SetMetadata(ctx, "test_key", "initial_value")
|
||||
if err != nil {
|
||||
t.Fatalf("SetMetadata failed: %v", err)
|
||||
}
|
||||
|
||||
// Update value
|
||||
err = store.SetMetadata(ctx, "test_key", "updated_value")
|
||||
if err != nil {
|
||||
t.Fatalf("SetMetadata update failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify updated value
|
||||
value, err := store.GetMetadata(ctx, "test_key")
|
||||
if err != nil {
|
||||
t.Fatalf("GetMetadata failed: %v", err)
|
||||
}
|
||||
|
||||
if value != "updated_value" {
|
||||
t.Errorf("Expected 'updated_value', got '%s'", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataMultipleKeys(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Set multiple metadata keys
|
||||
keys := map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
for key, value := range keys {
|
||||
err := store.SetMetadata(ctx, key, value)
|
||||
if err != nil {
|
||||
t.Fatalf("SetMetadata failed for %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify all keys
|
||||
for key, expectedValue := range keys {
|
||||
value, err := store.GetMetadata(ctx, key)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMetadata failed for %s: %v", key, err)
|
||||
}
|
||||
if value != expectedValue {
|
||||
t.Errorf("For key %s, expected '%s', got '%s'", key, expectedValue, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user