diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-27 06:44:25 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-27 06:44:25 +0200 |
| commit | 21c1d5c9cc4974ba490b329acae735ff6bde9973 (patch) | |
| tree | 6eceefd9094eebb9c03eb2fa42d406c9cb2aea9f | |
| parent | 1f4c09e0f5a2a38c0b133264ec24e8ae5e5a3c22 (diff) | |
fix ask add alias output for 1a1731dc-1f11-42bd-be12-4c1af7f7e673
| -rw-r--r-- | docs/usage.md | 2 | ||||
| -rw-r--r-- | integrationtests/ask_test.go | 42 | ||||
| -rw-r--r-- | internal/askcli/command_info_add.go | 7 | ||||
| -rw-r--r-- | internal/askcli/command_info_add_test.go | 41 | ||||
| -rw-r--r-- | internal/askcli/dispatch.go | 2 |
5 files changed, 77 insertions, 17 deletions
diff --git a/docs/usage.md b/docs/usage.md index 3447cd3..c43f406 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -135,7 +135,7 @@ cat SOMEFILE.txt | hexai --tps-simulation 20 | Subcommand | Description | |---|---| -| `ask add "description"` | Create a new task | +| `ask add "description"` | Create a new task and print its alias ID | | `ask add priority:H "description"` | Create task with priority | | `ask add +tag "description"` | Create task with tag | | `ask list` | List pending tasks only (alias-ID table) | diff --git a/integrationtests/ask_test.go b/integrationtests/ask_test.go index 337a486..0e56bf3 100644 --- a/integrationtests/ask_test.go +++ b/integrationtests/ask_test.go @@ -117,17 +117,24 @@ func runTaskWithStdin(ctx context.Context, args []string, stdin string) (stdout, } // createTask creates a new task via ask add and returns its UUID. -// ask add outputs the UUID directly (via rc.verbose=new-uuid), so no follow-up lookup is needed. +// ask add prints the human-facing alias ID, so we resolve the created UUID via ask info. func createTask(ctx context.Context, desc string) (string, error) { stdout, stderr, code := runAsk(ctx, []string{"add", "+integrationtest", desc}) if code != 0 { return "", fmt.Errorf("create task failed (code %d): stdout=%s stderr=%s", code, stdout.String(), stderr.String()) } - uuid := strings.TrimSpace(stdout.String()) - if uuid == "" { - return "", fmt.Errorf("could not extract UUID from ask add output: %s", stdout.String()) + id := strings.TrimSpace(stdout.String()) + if id == "" { + return "", fmt.Errorf("could not extract task ID from ask add output: %s", stdout.String()) } - return uuid, nil + info, ok := getTaskInfoFast(ctx, id) + if !ok { + return "", fmt.Errorf("could not resolve task ID %q after ask add", id) + } + if info.UUID == "" { + return "", fmt.Errorf("ask info %q did not return a UUID", id) + } + return info.UUID, nil } func deleteTask(ctx context.Context, uuid string) { @@ -291,8 +298,8 @@ func TestAdd(t *testing.T) { } } -// TestAddReturnsUUID verifies that ask add outputs a UUID, never a numeric task ID. -func TestAddReturnsUUID(t *testing.T) { +// TestAddReturnsAlias verifies that ask add outputs the human-facing alias ID. +func TestAddReturnsAlias(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -300,11 +307,24 @@ func TestAddReturnsUUID(t *testing.T) { if code != 0 { t.Fatalf("ask add failed with code %d", code) } - uuid := strings.TrimSpace(stdout.String()) - defer deleteTask(ctx, uuid) + id := strings.TrimSpace(stdout.String()) + info, ok := getTaskInfoFast(ctx, id) + if !ok { + t.Fatalf("ask info %q failed after add", id) + } + defer deleteTask(ctx, info.UUID) - if !uuidFormatRx.MatchString(uuid) { - t.Errorf("ask add output %q is not a valid UUID", uuid) + if id == "" { + t.Fatal("ask add returned an empty task ID") + } + if uuidFormatRx.MatchString(id) { + t.Fatalf("ask add output %q leaked a UUID, want alias ID", id) + } + if info.ID != id { + t.Fatalf("ask info ID = %q, want %q", info.ID, id) + } + if !uuidFormatRx.MatchString(info.UUID) { + t.Fatalf("ask info UUID = %q, want valid UUID", info.UUID) } } diff --git a/internal/askcli/command_info_add.go b/internal/askcli/command_info_add.go index 5332e71..4fa0941 100644 --- a/internal/askcli/command_info_add.go +++ b/internal/askcli/command_info_add.go @@ -104,7 +104,12 @@ func (d Dispatcher) handleAdd(ctx context.Context, args []string, stdout, stderr io.WriteString(stderr, "error: could not parse UUID from task creation output\n") return 1, nil } - io.WriteString(stdout, uuid+"\n") + aliases, err := ensureTaskAliasesForUUIDs([]string{uuid}) + if err != nil { + fmt.Fprintf(stderr, "error: failed to assign task alias: %v\n", err) + return 1, nil + } + io.WriteString(stdout, displayTaskAlias(uuid, aliases)+"\n") return 0, nil } diff --git a/internal/askcli/command_info_add_test.go b/internal/askcli/command_info_add_test.go index bd95de4..11bc451 100644 --- a/internal/askcli/command_info_add_test.go +++ b/internal/askcli/command_info_add_test.go @@ -211,7 +211,14 @@ func TestHandleInfo_MissingUUID_MultipleStartedTasks(t *testing.T) { } func TestHandleAdd_Success(t *testing.T) { - // With rc.verbose=new-uuid, task add outputs "Created task <uuid>." directly. + now := useIsolatedTaskAliasCache(t) + writeTaskAliasCacheForTest(t, taskAliasCache{ + NextID: 1, + Entries: []taskAliasCacheEntry{ + {UUID: "existing-uuid", Alias: "0", CreatedAt: now}, + }, + }) + d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { io.WriteString(stdout, "Created task abc-123-def.") return 0, nil @@ -221,8 +228,36 @@ func TestHandleAdd_Success(t *testing.T) { if code != 0 { t.Fatalf("add code = %d, want 0", code) } - if !strings.Contains(stdout.String(), "abc-123-def") { - t.Fatalf("output missing UUID: %s", stdout.String()) + if got := strings.TrimSpace(stdout.String()); got != "1" { + t.Fatalf("stdout = %q, want alias 1", stdout.String()) + } + cache := readTaskAliasCacheSnapshot(t) + entry := findTaskAliasEntry(t, cache, "abc-123-def") + if entry.Alias != "1" { + t.Fatalf("created task alias = %q, want 1", entry.Alias) + } +} + +func TestHandleAdd_AliasAssignmentFailure(t *testing.T) { + oldRoot := taskAliasCacheRoot + taskAliasCacheRoot = func() (string, error) { return "", io.ErrUnexpectedEOF } + defer func() { taskAliasCacheRoot = oldRoot }() + + d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + io.WriteString(stdout, "Created task abc-123-def.") + return 0, nil + }}) + + var stdout, stderr bytes.Buffer + code, _ := d.Dispatch(context.Background(), []string{"add", "New task description"}, nil, &stdout, &stderr) + if code != 1 { + t.Fatalf("add code = %d, want 1", code) + } + if stdout.Len() != 0 { + t.Fatalf("stdout = %q, want empty output on alias assignment failure", stdout.String()) + } + if !strings.Contains(stderr.String(), "failed to assign task alias") { + t.Fatalf("stderr = %q, want alias assignment failure", stderr.String()) } } diff --git a/internal/askcli/dispatch.go b/internal/askcli/dispatch.go index 7cb217b..12407e3 100644 --- a/internal/askcli/dispatch.go +++ b/internal/askcli/dispatch.go @@ -88,7 +88,7 @@ func (d Dispatcher) Dispatch(ctx context.Context, args []string, stdin io.Reader func (d Dispatcher) help(w io.Writer) (int, error) { io.WriteString(w, "ask - task management CLI\n") io.WriteString(w, "\nSubcommands:\n") - io.WriteString(w, " ask add \"description\" Create a new task\n") + io.WriteString(w, " ask add \"description\" Create a new task and print its ID\n") io.WriteString(w, " ask list [filters] List active tasks (default)\n") io.WriteString(w, " ask ready List READY tasks (not blocked)\n") io.WriteString(w, " ask all [filters] List all tasks including completed/deleted\n") |
