summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-23 06:36:07 +0300
committerPaul Buetow <paul@buetow.org>2026-06-23 06:36:07 +0300
commitc525eb4c200a1bc20a8d866efaa3eaa3ce564376 (patch)
tree4f073e7db2ad18edaf4940f55df8a3681737eb87
parentcfedf447df07085eb5ef34a3dec9b28c5c4e2e69 (diff)
Fail fast on nil Taskwarrior injection for vq0
-rw-r--r--internal/ui/table.go5
-rw-r--r--internal/ui/table_test.go30
2 files changed, 31 insertions, 4 deletions
diff --git a/internal/ui/table.go b/internal/ui/table.go
index 1c526c0..774954c 100644
--- a/internal/ui/table.go
+++ b/internal/ui/table.go
@@ -2,6 +2,7 @@ package ui
import (
"context"
+ "errors"
"fmt"
"os"
"os/exec"
@@ -363,7 +364,7 @@ func blinkCmd() tea.Cmd {
func (m *Model) taskwarriorClient() task.Taskwarrior {
if m.taskwarrior == nil {
- m.taskwarrior = task.NewTaskwarrior()
+ panic("ui.Model Taskwarrior client is nil; use ui.New or ui.NewWithTaskwarrior")
}
return m.taskwarrior
}
@@ -453,7 +454,7 @@ func New(filters []string, browserCmd string) (Model, error) {
// NewWithTaskwarrior creates a UI model using the provided Taskwarrior client.
func NewWithTaskwarrior(filters []string, browserCmd string, tw task.Taskwarrior) (Model, error) {
if tw == nil {
- tw = task.NewTaskwarrior()
+ return Model{}, errors.New("taskwarrior client is nil")
}
m := Model{filters: filters, browserCmd: browserCmd, agentFilterHotkey: "3", taskwarrior: tw, blinkState: blinkState{blinkEnabled: true}}
m.initTaskContext()
diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go
index 9e190b7..a9348ba 100644
--- a/internal/ui/table_test.go
+++ b/internal/ui/table_test.go
@@ -187,6 +187,32 @@ func TestNewWithTaskwarriorUsesFakeForAddTask(t *testing.T) {
}
}
+func TestNewWithTaskwarriorRejectsNilClient(t *testing.T) {
+ _, err := NewWithTaskwarrior(nil, "firefox", nil)
+ if err == nil {
+ t.Fatalf("NewWithTaskwarrior with nil client succeeded")
+ }
+ if got, want := err.Error(), "taskwarrior client is nil"; got != want {
+ t.Fatalf("NewWithTaskwarrior error = %q, want %q", got, want)
+ }
+}
+
+func TestZeroValueModelTaskwarriorClientFailsFast(t *testing.T) {
+ defer func() {
+ r := recover()
+ if r == nil {
+ t.Fatalf("expected zero-value Model taskwarrior client access to panic")
+ }
+ want := "ui.Model Taskwarrior client is nil; use ui.New or ui.NewWithTaskwarrior"
+ if got := fmt.Sprint(r); got != want {
+ t.Fatalf("panic = %q, want %q", got, want)
+ }
+ }()
+
+ var m Model
+ _ = m.taskwarriorClient()
+}
+
func TestFakeTaskwarriorFailsFastOnUnexpectedCalls(t *testing.T) {
fake := &fakeTaskwarrior{}
@@ -848,7 +874,7 @@ func TestDeleteRecurringRollsBackCompletedDeletesAfterContextDeadline(t *testing
parentCtx, cancelParent := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancelParent()
- m := Model{taskContext: parentCtx, cancelTaskContext: cancelParent}
+ m := Model{taskContext: parentCtx, cancelTaskContext: cancelParent, taskwarrior: task.NewTaskwarrior()}
count, recurring, err := m.deleteTaskWithUndo(task.Task{
ID: 1,
@@ -912,7 +938,7 @@ func TestDeleteRecurringReportsRollbackFailure(t *testing.T) {
}
setupEnv(t, taskPath)
- m := Model{}
+ m := Model{taskwarrior: task.NewTaskwarrior()}
count, recurring, err := m.deleteTaskWithUndo(task.Task{
ID: 1,
UUID: "child",