diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-22 19:45:02 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-22 19:45:02 +0200 |
| commit | 97cb0462aad67bf1e9558b340f5ef2f5a72eb662 (patch) | |
| tree | a0c3a7912adbe05c75c9a6d290667976d7054149 /internal/askcli | |
| parent | 3f06d7dadb83d78f0913b1c1c9a9297826e107b1 (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.go | 26 | ||||
| -rw-r--r-- | internal/askcli/command_delete_test.go | 92 | ||||
| -rw-r--r-- | internal/askcli/dispatch.go | 4 | ||||
| -rw-r--r-- | internal/askcli/dispatch_test.go | 11 |
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) } |
