diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-27 07:21:49 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-27 07:21:49 +0200 |
| commit | a9d00ab5e5b4ff3d08f71f24f00d6b3bc4579d02 (patch) | |
| tree | 194b1de831a8ee6bcbb0b47caa595f3c69d88c6e | |
| parent | d3586ad2b2b140435055c349393154ae7cfffaea (diff) | |
refactor: extract runSingleTaskCommand helper (715cc01f-1630-4d11-b6fd-18d64a8c2035)
| -rw-r--r-- | internal/askcli/command_write.go | 138 | ||||
| -rw-r--r-- | internal/askcli/command_write_test.go | 68 |
2 files changed, 112 insertions, 94 deletions
diff --git a/internal/askcli/command_write.go b/internal/askcli/command_write.go index efc634a..023251c 100644 --- a/internal/askcli/command_write.go +++ b/internal/askcli/command_write.go @@ -7,44 +7,48 @@ import ( "strings" ) -func (d *Dispatcher) handleDenotate(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { - if len(args) < 3 { - io.WriteString(stderr, "error: ask denotate requires an ID or UUID and text argument\n") - return 1, nil - } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) +func (d *Dispatcher) runSingleTaskCommand( + ctx context.Context, + selector string, + stdout, stderr io.Writer, + buildArgs func(resolvedTaskSelector) []string, +) (int, error) { + resolved, _, code, err := d.resolveTaskSelector(ctx, selector, stderr) if err != nil { writeInfoError(stderr, err) return code, nil } - text := args[2] + var outBuf bytes.Buffer - code, err = d.runner.Run(ctx, []string{"uuid:" + resolved.UUID, "denotate", text}, nil, &outBuf, io.Discard) + code, err = d.runner.Run(ctx, buildArgs(resolved), nil, &outBuf, io.Discard) if code != 0 { return code, err } + io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) return 0, nil } +func (d *Dispatcher) handleDenotate(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { + if len(args) < 3 { + io.WriteString(stderr, "error: ask denotate requires an ID or UUID and text argument\n") + return 1, nil + } + text := args[2] + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + return []string{"uuid:" + resolved.UUID, "denotate", text} + }) +} + func (d *Dispatcher) handleModify(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 3 { io.WriteString(stderr, "error: ask modify requires an ID or UUID and modification args\n") return 1, nil } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) - if err != nil { - writeInfoError(stderr, err) - return code, nil - } modArgs := args[2:] - var outBuf bytes.Buffer - code, err = d.runner.Run(ctx, append([]string{"uuid:" + resolved.UUID, "modify"}, modArgs...), nil, &outBuf, io.Discard) - if code != 0 { - return code, err - } - io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) - return 0, nil + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + return append([]string{"uuid:" + resolved.UUID, "modify"}, modArgs...) + }) } func (d *Dispatcher) handleAnnotate(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { @@ -52,19 +56,10 @@ func (d *Dispatcher) handleAnnotate(ctx context.Context, args []string, stdout, io.WriteString(stderr, "error: ask annotate requires an ID or UUID and note argument\n") return 1, nil } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) - if err != nil { - writeInfoError(stderr, err) - return code, nil - } note := strings.Join(args[2:], " ") - var outBuf bytes.Buffer - code, err = d.runner.Run(ctx, []string{"uuid:" + resolved.UUID, "annotate", note}, nil, &outBuf, io.Discard) - if code != 0 { - return code, err - } - io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) - return 0, nil + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + return []string{"uuid:" + resolved.UUID, "annotate", note} + }) } func (d *Dispatcher) handleStart(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { @@ -72,20 +67,11 @@ func (d *Dispatcher) handleStart(ctx context.Context, args []string, stdout, std io.WriteString(stderr, "error: ask start requires an ID or UUID argument\n") return 1, nil } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) - if err != nil { - writeInfoError(stderr, err) - return code, nil - } - var outBuf bytes.Buffer - // uuid:<uuid> is used as the filter so taskwarrior selects the exact task; - // the action verb follows the filter. - code, err = d.runner.Run(ctx, []string{"uuid:" + resolved.UUID, "start"}, nil, &outBuf, io.Discard) - if code != 0 { - return code, err - } - io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) - return 0, nil + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + // uuid:<uuid> is used as the filter so taskwarrior selects the exact task; + // the action verb follows the filter. + return []string{"uuid:" + resolved.UUID, "start"} + }) } func (d *Dispatcher) handleStop(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { @@ -93,18 +79,9 @@ func (d *Dispatcher) handleStop(ctx context.Context, args []string, stdout, stde io.WriteString(stderr, "error: ask stop requires an ID or UUID argument\n") return 1, nil } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) - if err != nil { - writeInfoError(stderr, err) - return code, nil - } - var outBuf bytes.Buffer - code, err = d.runner.Run(ctx, []string{"uuid:" + resolved.UUID, "stop"}, nil, &outBuf, io.Discard) - if code != 0 { - return code, err - } - io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) - return 0, nil + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + return []string{"uuid:" + resolved.UUID, "stop"} + }) } func (d *Dispatcher) handleDone(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { @@ -112,18 +89,9 @@ func (d *Dispatcher) handleDone(ctx context.Context, args []string, stdout, stde io.WriteString(stderr, "error: ask done requires an ID or UUID argument\n") return 1, nil } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) - if err != nil { - writeInfoError(stderr, err) - return code, nil - } - var outBuf bytes.Buffer - code, err = d.runner.Run(ctx, []string{"uuid:" + resolved.UUID, "done"}, nil, &outBuf, io.Discard) - if code != 0 { - return code, err - } - io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) - return 0, nil + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + return []string{"uuid:" + resolved.UUID, "done"} + }) } func (d *Dispatcher) handlePriority(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { @@ -131,19 +99,10 @@ func (d *Dispatcher) handlePriority(ctx context.Context, args []string, stdout, io.WriteString(stderr, "error: ask priority requires an ID or UUID and priority (H/M/L)\n") return 1, nil } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) - if err != nil { - writeInfoError(stderr, err) - return code, nil - } priority := args[2] - var outBuf bytes.Buffer - code, err = d.runner.Run(ctx, []string{"uuid:" + resolved.UUID, "modify", "priority:" + priority}, nil, &outBuf, io.Discard) - if code != 0 { - return code, err - } - io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) - return 0, nil + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + return []string{"uuid:" + resolved.UUID, "modify", "priority:" + priority} + }) } func (d *Dispatcher) handleTag(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { @@ -151,17 +110,8 @@ func (d *Dispatcher) handleTag(ctx context.Context, args []string, stdout, stder io.WriteString(stderr, "error: ask tag requires an ID or UUID and +/-tag\n") return 1, nil } - resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) - if err != nil { - writeInfoError(stderr, err) - return code, nil - } tag := args[2] - var outBuf bytes.Buffer - code, err = d.runner.Run(ctx, []string{"uuid:" + resolved.UUID, "modify", tag}, nil, &outBuf, io.Discard) - if code != 0 { - return code, err - } - io.WriteString(stdout, FormatSuccess(displayResolvedTaskID(resolved))) - return 0, nil + return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { + return []string{"uuid:" + resolved.UUID, "modify", tag} + }) } diff --git a/internal/askcli/command_write_test.go b/internal/askcli/command_write_test.go index e6b6dd2..17935e3 100644 --- a/internal/askcli/command_write_test.go +++ b/internal/askcli/command_write_test.go @@ -3,6 +3,7 @@ package askcli import ( "bytes" "context" + "errors" "io" "path/filepath" "strings" @@ -421,3 +422,70 @@ func TestAllWriteHandlers_AcceptUUIDPrefix(t *testing.T) { }) } } + +func TestRunSingleTaskCommand_ResolveErrorWritesInfoError(t *testing.T) { + d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + t.Fatal("runFn should not be called when selector resolution fails") + return 0, nil + }}) + + var stdout, stderr bytes.Buffer + code, err := d.runSingleTaskCommand( + context.Background(), + "123", + &stdout, + &stderr, + func(resolved resolvedTaskSelector) []string { + t.Fatal("buildArgs should not be called when selector resolution fails") + return nil + }, + ) + if code != 1 { + t.Fatalf("runSingleTaskCommand code = %d, want 1", code) + } + if err != nil { + t.Fatalf("runSingleTaskCommand returned error: %v", err) + } + if stdout.Len() != 0 { + t.Fatalf("stdout = %q, want empty output", stdout.String()) + } + if !strings.Contains(stderr.String(), "use a task alias ID or UUID") { + t.Fatalf("stderr = %q, want numeric task ID error", stderr.String()) + } +} + +func TestRunSingleTaskCommand_RunFailureDoesNotWriteSuccess(t *testing.T) { + useIsolatedTaskAliasCache(t) + + runErr := errors.New("runner failed") + d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + if len(args) == 2 && args[0] == "uuid:test-uuid" && args[1] == "export" { + io.WriteString(stdout, `[{"uuid":"test-uuid","description":"Task","status":"pending","priority":"M","tags":[],"urgency":0,"depends":[]}]`) + return 0, nil + } + return 2, runErr + }}) + + var stdout, stderr bytes.Buffer + code, err := d.runSingleTaskCommand( + context.Background(), + "test-uuid", + &stdout, + &stderr, + func(resolved resolvedTaskSelector) []string { + return []string{"uuid:" + resolved.UUID, "start"} + }, + ) + if code != 2 { + t.Fatalf("runSingleTaskCommand code = %d, want 2", code) + } + if !errors.Is(err, runErr) { + t.Fatalf("runSingleTaskCommand error = %v, want %v", err, runErr) + } + if stdout.Len() != 0 { + t.Fatalf("stdout = %q, want empty output", stdout.String()) + } + if stderr.Len() != 0 { + t.Fatalf("stderr = %q, want empty output", stderr.String()) + } +} |
