diff options
Diffstat (limited to 'internal/store/native_tasks_test.go')
| -rw-r--r-- | internal/store/native_tasks_test.go | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/internal/store/native_tasks_test.go b/internal/store/native_tasks_test.go index 0a87bfa..fae7e90 100644 --- a/internal/store/native_tasks_test.go +++ b/internal/store/native_tasks_test.go @@ -168,3 +168,193 @@ func TestGetNativeTasks_NonRecurringTask_HasEmptyRecurrenceFields(t *testing.T) t.Errorf("expected nil NextOccurrenceOverride for non-recurring task, got %v", found.NextOccurrenceOverride) } } + +func TestGetNativeTaskByID_UnknownID_ReturnsErrNotFound(t *testing.T) { + s := newNativeTasksTestStore(t) + + _, err := s.GetNativeTaskByID("does-not-exist") + if !errors.Is(err, ErrNativeTaskNotFound) { + t.Fatalf("expected ErrNativeTaskNotFound, got %v", err) + } +} + +func TestGetNativeTaskByID_RealID_ReturnsTask(t *testing.T) { + s := newNativeTasksTestStore(t) + + task, err := s.GetNativeTaskByID("real-1") + if err != nil { + t.Fatalf("GetNativeTaskByID: %v", err) + } + if task.Content != "Real task" { + t.Errorf("Content = %q, want %q", task.Content, "Real task") + } +} + +func TestCompleteNativeTask_NonRecurring_JustCompletes(t *testing.T) { + s := newNativeTasksTestStore(t) + + if err := s.CompleteNativeTask("real-1"); err != nil { + t.Fatalf("CompleteNativeTask: %v", err) + } + + tasks, err := s.db.Query(`SELECT id FROM native_tasks`) + if err != nil { + t.Fatal(err) + } + defer tasks.Close() + count := 0 + for tasks.Next() { + count++ + } + if count != 1 { + t.Errorf("expected exactly 1 row (no iteration created for a non-recurring task), got %d", count) + } +} + +func TestCompleteNativeTask_LatestInSeries_CreatesNextIteration(t *testing.T) { + s := newNativeTasksTestStore(t) + if _, err := s.db.Exec(` + INSERT INTO native_tasks (id, content, due_date, recurrence_freq, recurrence_interval, recurrence_series_id) + VALUES ('rec-1', 'Water plants', '2026-07-13', 'weekly', 1, 'series-1') + `); err != nil { + t.Fatal(err) + } + + if err := s.CompleteNativeTask("rec-1"); err != nil { + t.Fatalf("CompleteNativeTask: %v", err) + } + + var completed bool + if err := s.db.QueryRow(`SELECT completed FROM native_tasks WHERE id = 'rec-1'`).Scan(&completed); err != nil { + t.Fatal(err) + } + if !completed { + t.Error("expected rec-1 to be marked completed") + } + + var nextCount int + var nextDue string + if err := s.db.QueryRow(` + SELECT COUNT(*), COALESCE(MAX(due_date), '') FROM native_tasks + WHERE recurrence_series_id = 'series-1' AND id != 'rec-1' + `).Scan(&nextCount, &nextDue); err != nil { + t.Fatal(err) + } + if nextCount != 1 { + t.Fatalf("expected exactly 1 new iteration, got %d", nextCount) + } + if nextDue[:10] != "2026-07-20" { + t.Errorf("next iteration due_date = %q, want 2026-07-20", nextDue) + } +} + +func TestCompleteNativeTask_UsesNextOccurrenceOverride(t *testing.T) { + s := newNativeTasksTestStore(t) + if _, err := s.db.Exec(` + INSERT INTO native_tasks (id, content, due_date, recurrence_freq, recurrence_interval, recurrence_series_id, next_occurrence_override) + VALUES ('rec-1', 'Water plants', '2026-07-13', 'weekly', 1, 'series-1', '2026-08-01') + `); err != nil { + t.Fatal(err) + } + + if err := s.CompleteNativeTask("rec-1"); err != nil { + t.Fatalf("CompleteNativeTask: %v", err) + } + + var nextDue string + if err := s.db.QueryRow(` + SELECT due_date FROM native_tasks WHERE recurrence_series_id = 'series-1' AND id != 'rec-1' + `).Scan(&nextDue); err != nil { + t.Fatal(err) + } + if nextDue[:10] != "2026-08-01" { + t.Errorf("next iteration due_date = %q, want 2026-08-01 (the override)", nextDue) + } +} + +func TestCompleteNativeTask_AlreadySuperseded_DoesNotDoubleCreate(t *testing.T) { + s := newNativeTasksTestStore(t) + if _, err := s.db.Exec(` + INSERT INTO native_tasks (id, content, due_date, recurrence_freq, recurrence_interval, recurrence_series_id) + VALUES ('rec-1', 'Water plants', '2026-07-13', 'weekly', 1, 'series-1') + `); err != nil { + t.Fatal(err) + } + // Simulate the periodic due-check having already created the successor + // before the user got around to completing rec-1. + if _, err := s.db.Exec(` + INSERT INTO native_tasks (id, content, due_date, recurrence_freq, recurrence_interval, recurrence_series_id) + VALUES ('rec-2', 'Water plants', '2026-07-20', 'weekly', 1, 'series-1') + `); err != nil { + t.Fatal(err) + } + + if err := s.CompleteNativeTask("rec-1"); err != nil { + t.Fatalf("CompleteNativeTask: %v", err) + } + + var count int + if err := s.db.QueryRow(`SELECT COUNT(*) FROM native_tasks WHERE recurrence_series_id = 'series-1'`).Scan(&count); err != nil { + t.Fatal(err) + } + if count != 2 { + t.Errorf("expected still exactly 2 rows in the series (no double-create), got %d", count) + } +} + +func TestSetTaskRecurrence_FirstTimeGeneratesSeriesID(t *testing.T) { + s := newNativeTasksTestStore(t) + + if err := s.SetTaskRecurrence("real-1", "weekly", 1, []int{1, 3}); err != nil { + t.Fatalf("SetTaskRecurrence: %v", err) + } + + task, err := s.GetNativeTaskByID("real-1") + if err != nil { + t.Fatal(err) + } + if task.RecurrenceFreq != "weekly" { + t.Errorf("RecurrenceFreq = %q, want weekly", task.RecurrenceFreq) + } + if task.RecurrenceSeriesID == "" { + t.Error("expected a generated RecurrenceSeriesID, got empty string") + } + if len(task.RecurrenceWeekdays) != 2 || task.RecurrenceWeekdays[0] != 1 || task.RecurrenceWeekdays[1] != 3 { + t.Errorf("RecurrenceWeekdays = %v, want [1 3]", task.RecurrenceWeekdays) + } +} + +func TestSetTaskRecurrence_ClearingKeepsSeriesID(t *testing.T) { + s := newNativeTasksTestStore(t) + if err := s.SetTaskRecurrence("real-1", "weekly", 1, nil); err != nil { + t.Fatal(err) + } + task, err := s.GetNativeTaskByID("real-1") + if err != nil { + t.Fatal(err) + } + seriesID := task.RecurrenceSeriesID + + if err := s.SetTaskRecurrence("real-1", "", 1, nil); err != nil { + t.Fatalf("SetTaskRecurrence (clear): %v", err) + } + + task, err = s.GetNativeTaskByID("real-1") + if err != nil { + t.Fatal(err) + } + if task.RecurrenceFreq != "" { + t.Errorf("RecurrenceFreq = %q, want empty after clearing", task.RecurrenceFreq) + } + if task.RecurrenceSeriesID != seriesID { + t.Errorf("RecurrenceSeriesID = %q, want unchanged %q after clearing", task.RecurrenceSeriesID, seriesID) + } +} + +func TestSetNextOccurrenceOverride_UnknownID_ReturnsErrNotFound(t *testing.T) { + s := newNativeTasksTestStore(t) + err := s.SetNextOccurrenceOverride("does-not-exist", time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)) + if !errors.Is(err, ErrNativeTaskNotFound) { + t.Fatalf("expected ErrNativeTaskNotFound, got %v", err) + } +} |
