summaryrefslogtreecommitdiff
path: root/internal/store
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store')
-rw-r--r--internal/store/sqlite.go18
-rw-r--r--internal/store/sqlite_test.go258
2 files changed, 276 insertions, 0 deletions
diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go
index c97d0af..79f5cc8 100644
--- a/internal/store/sqlite.go
+++ b/internal/store/sqlite.go
@@ -167,6 +167,12 @@ func (s *Store) GetTasks() ([]models.Task, error) {
return tasks, rows.Err()
}
+// DeleteTask removes a task from the cache by ID
+func (s *Store) DeleteTask(id string) error {
+ _, err := s.db.Exec(`DELETE FROM tasks WHERE id = ?`, id)
+ return err
+}
+
// Notes operations
// SaveNotes saves multiple notes to the database
@@ -403,6 +409,12 @@ func (s *Store) IsCacheValid(key string) (bool, error) {
return cm.IsCacheValid(), nil
}
+// InvalidateCache removes the cache metadata for a given key, forcing a refresh on next fetch
+func (s *Store) InvalidateCache(key string) error {
+ _, err := s.db.Exec(`DELETE FROM cache_metadata WHERE key = ?`, key)
+ return err
+}
+
// Boards operations
// SaveBoards saves multiple boards to the database
@@ -538,3 +550,9 @@ func (s *Store) GetBoards() ([]models.Board, error) {
return boards, cardRows.Err()
}
+
+// DeleteCard removes a card from the cache by ID
+func (s *Store) DeleteCard(id string) error {
+ _, err := s.db.Exec(`DELETE FROM cards WHERE id = ?`, id)
+ return err
+}
diff --git a/internal/store/sqlite_test.go b/internal/store/sqlite_test.go
index 3379a11..962a1bf 100644
--- a/internal/store/sqlite_test.go
+++ b/internal/store/sqlite_test.go
@@ -336,3 +336,261 @@ func TestGetNotes_SQLInjectionAttempt(t *testing.T) {
// and is not vulnerable to SQL injection via the LIMIT clause
fmt.Println("✓ LIMIT clause is properly parameterized")
}
+
+// setupTestStoreWithTasks creates a test store with tasks table
+func setupTestStoreWithTasks(t *testing.T) *Store {
+ t.Helper()
+
+ tempDir := t.TempDir()
+ dbPath := filepath.Join(tempDir, "test.db")
+
+ db, err := sql.Open("sqlite3", dbPath)
+ if err != nil {
+ t.Fatalf("Failed to open test database: %v", err)
+ }
+
+ db.SetMaxOpenConns(1)
+
+ store := &Store{db: db}
+
+ schema := `
+ CREATE TABLE IF NOT EXISTS tasks (
+ id TEXT PRIMARY KEY,
+ content TEXT NOT NULL,
+ description TEXT,
+ project_id TEXT,
+ project_name TEXT,
+ due_date DATETIME,
+ priority INTEGER DEFAULT 1,
+ completed BOOLEAN DEFAULT FALSE,
+ labels TEXT,
+ url TEXT,
+ created_at DATETIME,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ `
+ if _, err := db.Exec(schema); err != nil {
+ t.Fatalf("Failed to create schema: %v", err)
+ }
+
+ return store
+}
+
+// setupTestStoreWithCards creates a test store with boards and cards tables
+func setupTestStoreWithCards(t *testing.T) *Store {
+ t.Helper()
+
+ tempDir := t.TempDir()
+ dbPath := filepath.Join(tempDir, "test.db")
+
+ db, err := sql.Open("sqlite3", dbPath)
+ if err != nil {
+ t.Fatalf("Failed to open test database: %v", err)
+ }
+
+ db.SetMaxOpenConns(1)
+
+ store := &Store{db: db}
+
+ schema := `
+ CREATE TABLE IF NOT EXISTS boards (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ CREATE TABLE IF NOT EXISTS cards (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ board_id TEXT NOT NULL,
+ list_id TEXT,
+ list_name TEXT,
+ due_date DATETIME,
+ url TEXT,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ `
+ if _, err := db.Exec(schema); err != nil {
+ t.Fatalf("Failed to create schema: %v", err)
+ }
+
+ return store
+}
+
+// TestDeleteTask verifies that DeleteTask removes a task from the cache
+func TestDeleteTask(t *testing.T) {
+ store := setupTestStoreWithTasks(t)
+ defer store.Close()
+
+ // Save some tasks
+ tasks := []models.Task{
+ {
+ ID: "task1",
+ Content: "Task 1",
+ Description: "Description 1",
+ Priority: 1,
+ Completed: false,
+ Labels: []string{},
+ CreatedAt: time.Now(),
+ },
+ {
+ ID: "task2",
+ Content: "Task 2",
+ Description: "Description 2",
+ Priority: 2,
+ Completed: false,
+ Labels: []string{},
+ CreatedAt: time.Now(),
+ },
+ {
+ ID: "task3",
+ Content: "Task 3",
+ Description: "Description 3",
+ Priority: 3,
+ Completed: false,
+ Labels: []string{},
+ CreatedAt: time.Now(),
+ },
+ }
+
+ if err := store.SaveTasks(tasks); err != nil {
+ t.Fatalf("Failed to save tasks: %v", err)
+ }
+
+ // Verify all 3 tasks exist
+ result, err := store.GetTasks()
+ if err != nil {
+ t.Fatalf("Failed to get tasks: %v", err)
+ }
+ if len(result) != 3 {
+ t.Fatalf("Expected 3 tasks, got %d", len(result))
+ }
+
+ // Delete task2
+ if err := store.DeleteTask("task2"); err != nil {
+ t.Fatalf("DeleteTask failed: %v", err)
+ }
+
+ // Verify only 2 tasks remain
+ result, err = store.GetTasks()
+ if err != nil {
+ t.Fatalf("Failed to get tasks after delete: %v", err)
+ }
+ if len(result) != 2 {
+ t.Errorf("Expected 2 tasks after delete, got %d", len(result))
+ }
+
+ // Verify task2 is gone
+ for _, task := range result {
+ if task.ID == "task2" {
+ t.Errorf("task2 should have been deleted but was found")
+ }
+ }
+
+ // Verify task1 and task3 still exist
+ foundTask1, foundTask3 := false, false
+ for _, task := range result {
+ if task.ID == "task1" {
+ foundTask1 = true
+ }
+ if task.ID == "task3" {
+ foundTask3 = true
+ }
+ }
+ if !foundTask1 {
+ t.Error("task1 should still exist")
+ }
+ if !foundTask3 {
+ t.Error("task3 should still exist")
+ }
+}
+
+// TestDeleteTask_NonExistent verifies that deleting a non-existent task doesn't error
+func TestDeleteTask_NonExistent(t *testing.T) {
+ store := setupTestStoreWithTasks(t)
+ defer store.Close()
+
+ // Delete a task that doesn't exist - should not error
+ err := store.DeleteTask("nonexistent")
+ if err != nil {
+ t.Errorf("DeleteTask on non-existent task should not error, got: %v", err)
+ }
+}
+
+// TestDeleteCard verifies that DeleteCard removes a card from the cache
+func TestDeleteCard(t *testing.T) {
+ store := setupTestStoreWithCards(t)
+ defer store.Close()
+
+ // First create a board
+ _, err := store.db.Exec(`INSERT INTO boards (id, name) VALUES (?, ?)`, "board1", "Test Board")
+ if err != nil {
+ t.Fatalf("Failed to create board: %v", err)
+ }
+
+ // Insert cards directly
+ cards := []struct {
+ id string
+ name string
+ boardID string
+ }{
+ {"card1", "Card 1", "board1"},
+ {"card2", "Card 2", "board1"},
+ {"card3", "Card 3", "board1"},
+ }
+
+ for _, card := range cards {
+ _, err := store.db.Exec(
+ `INSERT INTO cards (id, name, board_id, list_id, list_name) VALUES (?, ?, ?, ?, ?)`,
+ card.id, card.name, card.boardID, "list1", "To Do",
+ )
+ if err != nil {
+ t.Fatalf("Failed to insert card: %v", err)
+ }
+ }
+
+ // Verify all 3 cards exist
+ var count int
+ err = store.db.QueryRow(`SELECT COUNT(*) FROM cards`).Scan(&count)
+ if err != nil {
+ t.Fatalf("Failed to count cards: %v", err)
+ }
+ if count != 3 {
+ t.Fatalf("Expected 3 cards, got %d", count)
+ }
+
+ // Delete card2
+ if err := store.DeleteCard("card2"); err != nil {
+ t.Fatalf("DeleteCard failed: %v", err)
+ }
+
+ // Verify only 2 cards remain
+ err = store.db.QueryRow(`SELECT COUNT(*) FROM cards`).Scan(&count)
+ if err != nil {
+ t.Fatalf("Failed to count cards after delete: %v", err)
+ }
+ if count != 2 {
+ t.Errorf("Expected 2 cards after delete, got %d", count)
+ }
+
+ // Verify card2 is gone
+ var exists int
+ err = store.db.QueryRow(`SELECT COUNT(*) FROM cards WHERE id = ?`, "card2").Scan(&exists)
+ if err != nil {
+ t.Fatalf("Failed to check card2: %v", err)
+ }
+ if exists != 0 {
+ t.Errorf("card2 should have been deleted")
+ }
+}
+
+// TestDeleteCard_NonExistent verifies that deleting a non-existent card doesn't error
+func TestDeleteCard_NonExistent(t *testing.T) {
+ store := setupTestStoreWithCards(t)
+ defer store.Close()
+
+ // Delete a card that doesn't exist - should not error
+ err := store.DeleteCard("nonexistent")
+ if err != nil {
+ t.Errorf("DeleteCard on non-existent card should not error, got: %v", err)
+ }
+}