summaryrefslogtreecommitdiff
path: root/internal/store/sqlite_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/sqlite_test.go')
-rw-r--r--internal/store/sqlite_test.go189
1 files changed, 189 insertions, 0 deletions
diff --git a/internal/store/sqlite_test.go b/internal/store/sqlite_test.go
index 2467f96..4d3c8f8 100644
--- a/internal/store/sqlite_test.go
+++ b/internal/store/sqlite_test.go
@@ -103,6 +103,7 @@ func setupTestStoreWithCards(t *testing.T) *Store {
CREATE TABLE IF NOT EXISTS cards (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
+ description TEXT DEFAULT '',
board_id TEXT NOT NULL,
list_id TEXT,
list_name TEXT,
@@ -118,6 +119,194 @@ func setupTestStoreWithCards(t *testing.T) *Store {
return store
}
+func setupTestStoreWithGoogleTasks(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 google_tasks (
+ id TEXT PRIMARY KEY,
+ title TEXT NOT NULL,
+ notes TEXT,
+ status TEXT NOT NULL,
+ completed BOOLEAN NOT NULL DEFAULT 0,
+ due_date TEXT,
+ updated_at TEXT,
+ list_id TEXT NOT NULL,
+ url TEXT
+ );
+ `
+ if _, err := db.Exec(schema); err != nil {
+ t.Fatalf("Failed to create schema: %v", err)
+ }
+
+ return store
+}
+
+func setupTestStoreWithNativeTasks(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 native_tasks (
+ id TEXT PRIMARY KEY,
+ content TEXT NOT NULL,
+ description TEXT DEFAULT '',
+ project_name TEXT DEFAULT '',
+ due_date DATETIME,
+ priority INTEGER DEFAULT 1,
+ completed BOOLEAN DEFAULT 0,
+ labels TEXT DEFAULT '[]',
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ `
+ if _, err := db.Exec(schema); err != nil {
+ t.Fatalf("Failed to create schema: %v", err)
+ }
+
+ return store
+}
+
+// TestGetNativeTasksByDateRange_IncludesOverdue guards against a regression where a native task
+// due before the window's start (e.g. yesterday, still incomplete) silently dropped out of the
+// widget/timeline the moment the day rolled over, because the query required due_date >= start.
+func TestGetNativeTasksByDateRange_IncludesOverdue(t *testing.T) {
+ store := setupTestStoreWithNativeTasks(t)
+
+ now := time.Now()
+ overdue := now.Add(-48 * time.Hour)
+ today := now
+ future := now.Add(72 * time.Hour) // outside the window
+
+ for _, task := range []models.Task{
+ {ID: "t-overdue", Content: "Overdue task", DueDate: &overdue},
+ {ID: "t-today", Content: "Today task", DueDate: &today},
+ {ID: "t-future", Content: "Future task", DueDate: &future},
+ } {
+ if err := store.CreateNativeTask(task); err != nil {
+ t.Fatalf("CreateNativeTask(%s) failed: %v", task.ID, err)
+ }
+ }
+
+ start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ end := start.Add(48 * time.Hour)
+
+ results, err := store.GetNativeTasksByDateRange(start, end)
+ if err != nil {
+ t.Fatalf("GetNativeTasksByDateRange failed: %v", err)
+ }
+
+ ids := make(map[string]bool)
+ for _, r := range results {
+ ids[r.ID] = true
+ }
+ if !ids["t-overdue"] {
+ t.Error("expected overdue task to be included, but it was excluded")
+ }
+ if !ids["t-today"] {
+ t.Error("expected today's task to be included")
+ }
+ if ids["t-future"] {
+ t.Error("expected far-future task to be excluded")
+ }
+}
+
+// TestSaveAndGetGoogleTasks_RoundTripsTimestamps guards against a regression where
+// due_date/updated_at (TEXT columns, not DATETIME) failed to scan back into time.Time
+// via sql.NullTime whenever a row had a non-null timestamp.
+func TestSaveAndGetGoogleTasks_RoundTripsTimestamps(t *testing.T) {
+ store := setupTestStoreWithGoogleTasks(t)
+
+ due := time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)
+ updated := time.Now()
+
+ err := store.SaveGoogleTasks([]models.GoogleTask{
+ {ID: "g1", Title: "Renew passport", Notes: "bring photo", Status: "needsAction", ListID: "list-a", DueDate: &due, UpdatedAt: updated},
+ })
+ if err != nil {
+ t.Fatalf("SaveGoogleTasks failed: %v", err)
+ }
+
+ tasks, err := store.GetGoogleTasks()
+ if err != nil {
+ t.Fatalf("GetGoogleTasks failed: %v", err)
+ }
+ if len(tasks) != 1 {
+ t.Fatalf("expected 1 task, got %d", len(tasks))
+ }
+ got := tasks[0]
+ if got.DueDate == nil || !got.DueDate.Equal(due) {
+ t.Errorf("DueDate: got %v, want %v", got.DueDate, due)
+ }
+ if got.UpdatedAt.IsZero() {
+ t.Error("UpdatedAt should have round-tripped, got zero value")
+ }
+}
+
+// TestGetGoogleTasksByDateRange_IncludesOverdue guards against a regression where a task due
+// before the window's start (e.g. yesterday, still incomplete) silently dropped out of the
+// widget/timeline the moment the day rolled over, because the query required due_date >= start.
+func TestGetGoogleTasksByDateRange_IncludesOverdue(t *testing.T) {
+ store := setupTestStoreWithGoogleTasks(t)
+
+ now := time.Now()
+ overdue := now.Add(-48 * time.Hour)
+ today := now
+ future := now.Add(72 * time.Hour) // outside the window
+
+ err := store.SaveGoogleTasks([]models.GoogleTask{
+ {ID: "g-overdue", Title: "Overdue task", ListID: "list-a", DueDate: &overdue, UpdatedAt: now},
+ {ID: "g-today", Title: "Today task", ListID: "list-a", DueDate: &today, UpdatedAt: now},
+ {ID: "g-future", Title: "Future task", ListID: "list-a", DueDate: &future, UpdatedAt: now},
+ })
+ if err != nil {
+ t.Fatalf("SaveGoogleTasks failed: %v", err)
+ }
+
+ start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ end := start.Add(48 * time.Hour)
+
+ results, err := store.GetGoogleTasksByDateRange(start, end)
+ if err != nil {
+ t.Fatalf("GetGoogleTasksByDateRange failed: %v", err)
+ }
+
+ ids := make(map[string]bool)
+ for _, r := range results {
+ ids[r.ID] = true
+ }
+ if !ids["g-overdue"] {
+ t.Error("expected overdue task to be included, but it was excluded")
+ }
+ if !ids["g-today"] {
+ t.Error("expected today's task to be included")
+ }
+ if ids["g-future"] {
+ t.Error("expected far-future task to be excluded")
+ }
+}
+
// setupTestStoreWithMeals creates a test store with meals table
func setupTestStoreWithMeals(t *testing.T) *Store {
t.Helper()