summaryrefslogtreecommitdiff
path: root/internal/askcli
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-22 19:45:02 +0200
committerPaul Buetow <paul@buetow.org>2026-03-22 19:45:02 +0200
commit97cb0462aad67bf1e9558b340f5ef2f5a72eb662 (patch)
treea0c3a7912adbe05c75c9a6d290667976d7054149 /internal/askcli
parent3f06d7dadb83d78f0913b1c1c9a9297826e107b1 (diff)
Implement 'ask delete' subcommand: forward to Taskwarrior, suppress output, print UUID+success
Diffstat (limited to 'internal/askcli')
-rw-r--r--internal/askcli/command_delete.go26
-rw-r--r--internal/askcli/command_delete_test.go92
-rw-r--r--internal/askcli/dispatch.go4
-rw-r--r--internal/askcli/dispatch_test.go11
4 files changed, 130 insertions, 3 deletions
diff --git a/internal/askcli/command_delete.go b/internal/askcli/command_delete.go
new file mode 100644
index 0000000..1da0498
--- /dev/null
+++ b/internal/askcli/command_delete.go
@@ -0,0 +1,26 @@
+package askcli
+
+import (
+ "bytes"
+ "context"
+ "io"
+)
+
+func (d Dispatcher) handleDelete(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) {
+ if len(args) < 2 {
+ io.WriteString(stderr, "error: ask delete requires a UUID argument\n")
+ return 1, nil
+ }
+ uuid := args[1]
+ if IsNumericID(uuid) {
+ io.WriteString(stderr, RejectNumericID())
+ return 1, nil
+ }
+ var outBuf bytes.Buffer
+ code, err := d.runner.Run(ctx, []string{"delete", uuid}, nil, &outBuf, io.Discard)
+ if code != 0 {
+ return code, err
+ }
+ io.WriteString(stdout, FormatSuccess(uuid))
+ return 0, nil
+}
diff --git a/internal/askcli/command_delete_test.go b/internal/askcli/command_delete_test.go
new file mode 100644
index 0000000..e07205f
--- /dev/null
+++ b/internal/askcli/command_delete_test.go
@@ -0,0 +1,92 @@
+package askcli
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "strings"
+ "testing"
+)
+
+func TestHandleDelete_Success(t *testing.T) {
+ d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
+ return 0, nil
+ }})
+ var stdout, stderr bytes.Buffer
+ code, err := d.Dispatch(context.Background(), []string{"delete", "test-uuid-123"}, &bytes.Buffer{}, &stdout, &stderr)
+ if code != 0 {
+ t.Fatalf("delete code = %d, want 0", code)
+ }
+ if err != nil {
+ t.Fatalf("delete returned error: %v", err)
+ }
+ if !strings.Contains(stdout.String(), "ok") || !strings.Contains(stdout.String(), "test-uuid-123") {
+ t.Fatalf("stdout = %q, want ok + uuid", stdout.String())
+ }
+ if stderr.Len() > 0 {
+ t.Fatalf("stderr should be empty, got %q", stderr.String())
+ }
+}
+
+func TestHandleDelete_NumericID(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 for numeric ID")
+ return 0, nil
+ }})
+ var stdout, stderr bytes.Buffer
+ code, err := d.Dispatch(context.Background(), []string{"delete", "123"}, &bytes.Buffer{}, &stdout, &stderr)
+ if code != 1 {
+ t.Fatalf("delete code = %d, want 1 for numeric ID", code)
+ }
+ if err != nil {
+ t.Fatalf("delete returned unexpected error: %v", err)
+ }
+ if !strings.Contains(stderr.String(), "use UUID") {
+ t.Fatalf("stderr = %q, want 'use UUID' message", stderr.String())
+ }
+}
+
+func TestHandleDelete_MissingUUID(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 for missing UUID")
+ return 0, nil
+ }})
+ var stdout, stderr bytes.Buffer
+ code, err := d.Dispatch(context.Background(), []string{"delete"}, &bytes.Buffer{}, &stdout, &stderr)
+ if code != 1 {
+ t.Fatalf("delete code = %d, want 1 for missing UUID", code)
+ }
+ if err != nil {
+ t.Fatalf("delete returned unexpected error: %v", err)
+ }
+ if !strings.Contains(stderr.String(), "requires a UUID") {
+ t.Fatalf("stderr = %q, want 'requires a UUID' message", stderr.String())
+ }
+}
+
+func TestHandleDelete_CommandFails(t *testing.T) {
+ d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
+ return 1, nil
+ }})
+ var stdout, stderr bytes.Buffer
+ code, _ := d.Dispatch(context.Background(), []string{"delete", "test-uuid-456"}, &bytes.Buffer{}, &stdout, &stderr)
+ if code != 1 {
+ t.Fatalf("delete code = %d, want 1 for failed command", code)
+ }
+ if stdout.Len() > 0 {
+ t.Fatalf("stdout should be empty on failure, got %q", stdout.String())
+ }
+}
+
+func TestHandleDelete_PassesCorrectArgs(t *testing.T) {
+ var capturedArgs []string
+ d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
+ capturedArgs = args
+ return 0, nil
+ }})
+ var stdout, stderr bytes.Buffer
+ d.Dispatch(context.Background(), []string{"delete", "my-uuid"}, &bytes.Buffer{}, &stdout, &stderr)
+ if len(capturedArgs) != 2 || capturedArgs[0] != "delete" || capturedArgs[1] != "my-uuid" {
+ t.Fatalf("capturedArgs = %v, want [delete, my-uuid]", capturedArgs)
+ }
+}
diff --git a/internal/askcli/dispatch.go b/internal/askcli/dispatch.go
index b764618..fc1c67b 100644
--- a/internal/askcli/dispatch.go
+++ b/internal/askcli/dispatch.go
@@ -29,8 +29,10 @@ func (d Dispatcher) Dispatch(ctx context.Context, args []string, stdin io.Reader
subcommand := args[0]
switch subcommand {
case "add", "list", "info", "annotate", "start", "stop", "done",
- "priority", "tag", "dep", "urgency", "modify", "denotate", "delete", "export":
+ "priority", "tag", "dep", "urgency", "modify", "denotate", "export":
return d.runner.Run(ctx, args, stdin, stdout, stderr)
+ case "delete":
+ return d.handleDelete(ctx, args, stdout, stderr)
default:
return d.unknownCommand(stderr, subcommand)
}
diff --git a/internal/askcli/dispatch_test.go b/internal/askcli/dispatch_test.go
index f4c27a4..d2a4e52 100644
--- a/internal/askcli/dispatch_test.go
+++ b/internal/askcli/dispatch_test.go
@@ -59,7 +59,10 @@ func TestDispatcher_LongHelp(t *testing.T) {
}
func TestDispatcher_AllSubcommandsReachExecutor(t *testing.T) {
- subcommands := []string{"add", "list", "info", "annotate", "start", "stop", "done", "priority", "tag", "dep", "urgency", "modify", "denotate", "delete", "export"}
+ subcommands := []string{"add", "list", "info", "annotate", "start", "stop", "done", "priority", "tag", "dep", "urgency", "modify", "denotate", "export"}
+ subcommandArgs := map[string][]string{
+ "delete": {"delete", "test-uuid"},
+ }
for _, sub := range subcommands {
var stdout, stderr bytes.Buffer
calls := 0
@@ -67,7 +70,11 @@ func TestDispatcher_AllSubcommandsReachExecutor(t *testing.T) {
calls++
return 0, nil
}})
- code, _ := d.Dispatch(context.Background(), []string{sub}, nil, &stdout, &stderr)
+ args := []string{sub}
+ if extra, ok := subcommandArgs[sub]; ok {
+ args = extra
+ }
+ code, _ := d.Dispatch(context.Background(), args, nil, &stdout, &stderr)
if code != 0 {
t.Errorf("subcommand %q code = %d, want 0", sub, code)
}