diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-23 06:36:07 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-23 06:36:07 +0300 |
| commit | c525eb4c200a1bc20a8d866efaa3eaa3ce564376 (patch) | |
| tree | 4f073e7db2ad18edaf4940f55df8a3681737eb87 | |
| parent | cfedf447df07085eb5ef34a3dec9b28c5c4e2e69 (diff) | |
Fail fast on nil Taskwarrior injection for vq0
| -rw-r--r-- | internal/ui/table.go | 5 | ||||
| -rw-r--r-- | internal/ui/table_test.go | 30 |
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", |
