package store import ( "database/sql" "errors" "path/filepath" "testing" "time" _ "github.com/mattn/go-sqlite3" ) // newNativeTasksTestStore creates a Store backed by a fresh temp sqlite DB // with just the native_tasks table -- enough to exercise // CompleteNativeTask/UncompleteNativeTask/RescheduleNativeTask without // running the full migration set. func newNativeTasksTestStore(t *testing.T) *Store { t.Helper() dbPath := filepath.Join(t.TempDir(), "test.db") db, err := sql.Open("sqlite3", dbPath) if err != nil { t.Fatal(err) } t.Cleanup(func() { db.Close() }) if _, err := db.Exec(` CREATE TABLE 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 ) `); err != nil { t.Fatal(err) } if _, err := db.Exec(`INSERT INTO native_tasks (id, content) VALUES ('real-1', 'Real task')`); err != nil { t.Fatal(err) } return &Store{db: db} } // TestCompleteNativeTask_UnknownID_ReturnsErrNotFound proves the 2026-07-12 // fix: a plain UPDATE ... WHERE id = ? silently "succeeds" with a nil error // when 0 rows match (this is how database/sql's Exec behaves for an UPDATE // that matches nothing -- no error, just RowsAffected() == 0). Before this // fix, CompleteNativeTask returned that nil error straight through, so a // stale/wrong id from a caller (the Android widget, in the real incident // this was found from) looked identical to a real completion: HTTP 200, // nothing changed in the database. func TestCompleteNativeTask_UnknownID_ReturnsErrNotFound(t *testing.T) { s := newNativeTasksTestStore(t) err := s.CompleteNativeTask("does-not-exist") if !errors.Is(err, ErrNativeTaskNotFound) { t.Fatalf("expected ErrNativeTaskNotFound, got %v", err) } } func TestCompleteNativeTask_RealID_Succeeds(t *testing.T) { s := newNativeTasksTestStore(t) if err := s.CompleteNativeTask("real-1"); err != nil { t.Fatalf("CompleteNativeTask: %v", err) } var completed bool if err := s.db.QueryRow(`SELECT completed FROM native_tasks WHERE id = 'real-1'`).Scan(&completed); err != nil { t.Fatal(err) } if !completed { t.Error("expected task to be marked completed") } } func TestUncompleteNativeTask_UnknownID_ReturnsErrNotFound(t *testing.T) { s := newNativeTasksTestStore(t) err := s.UncompleteNativeTask("does-not-exist") if !errors.Is(err, ErrNativeTaskNotFound) { t.Fatalf("expected ErrNativeTaskNotFound, got %v", err) } } func TestRescheduleNativeTask_UnknownID_ReturnsErrNotFound(t *testing.T) { s := newNativeTasksTestStore(t) err := s.RescheduleNativeTask("does-not-exist", time.Now()) if !errors.Is(err, ErrNativeTaskNotFound) { t.Fatalf("expected ErrNativeTaskNotFound, got %v", err) } }