diff options
| -rw-r--r-- | cmd/ask/main_test.go | 4 | ||||
| -rw-r--r-- | integrationtests/ask_test.go | 66 | ||||
| -rw-r--r-- | internal/askcli/command_info_add.go | 26 | ||||
| -rw-r--r-- | internal/askcli/command_info_add_test.go | 79 | ||||
| -rw-r--r-- | internal/askcli/taskexport.go | 34 | ||||
| -rw-r--r-- | internal/askcli/taskexport_test.go | 39 |
6 files changed, 86 insertions, 162 deletions
diff --git a/cmd/ask/main_test.go b/cmd/ask/main_test.go index f91afbd..db6b436 100644 --- a/cmd/ask/main_test.go +++ b/cmd/ask/main_test.go @@ -25,8 +25,8 @@ func TestMain_WiresDispatcher(t *testing.T) { if code != 0 { t.Fatalf("exitCode = %d, want 0", code) } - if len(gotArgs) < 2 || gotArgs[0] != "export" { - t.Fatalf("args = %v, want [export, ...]", gotArgs) + if len(gotArgs) < 1 || gotArgs[len(gotArgs)-1] != "export" { + t.Fatalf("args = %v, want [..., export]", gotArgs) } } diff --git a/integrationtests/ask_test.go b/integrationtests/ask_test.go index e18aa10..42825dd 100644 --- a/integrationtests/ask_test.go +++ b/integrationtests/ask_test.go @@ -9,7 +9,6 @@ import ( "os/exec" "path/filepath" "regexp" - "strconv" "strings" "testing" "time" @@ -114,59 +113,20 @@ func runTaskWithStdin(ctx context.Context, args []string, stdin string) (stdout, return stdout, stderr, ee.ExitCode() } +// createTask creates a new task via ask add and returns its UUID. +// ask add now outputs the UUID directly, so no follow-up lookup is needed. func createTask(ctx context.Context, desc string) (string, error) { - stdout, _, code := runAskWithStdin(ctx, []string{"add", "+integrationtest", desc}, "yes\n") + stdout, stderr, code := runAsk(ctx, []string{"add", "+integrationtest", desc}) if code != 0 { - return "", fmt.Errorf("create task failed (code %d): %s", code, stdout.String()) + return "", fmt.Errorf("create task failed (code %d): stdout=%s stderr=%s", code, stdout.String(), stderr.String()) } - - taskID := extractTaskID(stdout.String()) - if taskID == "" { - return "", fmt.Errorf("could not extract task ID from output: %s", stdout.String()) - } - - time.Sleep(100 * time.Millisecond) - - infoOut, _, _ := runTask(ctx, []string{taskID, "info"}) - uuid := extractUUIDFromTaskInfo(infoOut.String()) + uuid := strings.TrimSpace(stdout.String()) if uuid == "" { - return "", fmt.Errorf("could not extract UUID from task info") + return "", fmt.Errorf("could not extract UUID from ask add output: %s", stdout.String()) } return uuid, nil } -var taskUUIDRx = regexp.MustCompile(`UUID\s+(.+)`) - -func extractUUIDFromTaskInfo(output string) string { - if m := taskUUIDRx.FindStringSubmatch(output); len(m) > 1 { - return strings.TrimSpace(m[1]) - } - return "" -} - -func extractTaskID(output string) string { - output = strings.TrimSpace(output) - lines := strings.Split(output, "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.Contains(line, "Created task") { - fields := strings.Fields(line) - for i, f := range fields { - if f == "task" && i+1 < len(fields) { - return strings.TrimSuffix(fields[i+1], ".") - } - } - } - } - for _, line := range lines { - line = strings.TrimSpace(line) - if _, err := strconv.Atoi(line); err == nil { - return line - } - } - return "" -} - func deleteTask(ctx context.Context, uuid string) { runTaskWithStdin(ctx, []string{"uuid:" + uuid, "delete"}, "yes\n") } @@ -201,12 +161,14 @@ type taskInfo struct { Start string } -var uuidFieldRx = regexp.MustCompile(`UUID:\s+(.+)`) -var descFieldRx = regexp.MustCompile(`Description:\s+(.+)`) -var statusFieldRx = regexp.MustCompile(`Status:\s+(.+)`) -var priorityFieldRx = regexp.MustCompile(`Priority:\s+(.+)`) -var tagsFieldRx = regexp.MustCompile(`Tags:\s+(.+)`) -var startFieldRx = regexp.MustCompile(`Started:\s+(.+)`) +var ( + uuidFieldRx = regexp.MustCompile(`UUID:\s+(.+)`) + descFieldRx = regexp.MustCompile(`Description:\s+(.+)`) + statusFieldRx = regexp.MustCompile(`Status:\s+(.+)`) + priorityFieldRx = regexp.MustCompile(`Priority:\s+(.+)`) + tagsFieldRx = regexp.MustCompile(`Tags:\s+(.+)`) + startFieldRx = regexp.MustCompile(`Started:\s+(.+)`) +) func parseTaskInfoText(output string, uuid string) taskInfo { ti := taskInfo{UUID: uuid} diff --git a/internal/askcli/command_info_add.go b/internal/askcli/command_info_add.go index 477ad62..5b76b2b 100644 --- a/internal/askcli/command_info_add.go +++ b/internal/askcli/command_info_add.go @@ -38,22 +38,38 @@ func (d Dispatcher) handleAdd(ctx context.Context, args []string, stdout, stderr } modifiers, description := parseAddArgs(args[1:]) var outBuf bytes.Buffer - taskArgs := []string{"add"} + // rc.verbose=new-uuid instructs taskwarrior to emit "Created task <uuid>." + // so we get the UUID directly from the add output without a follow-up export. + taskArgs := []string{"add", "rc.verbose=new-uuid"} taskArgs = append(taskArgs, modifiers...) taskArgs = append(taskArgs, description) code, err := d.runner.Run(ctx, taskArgs, nil, &outBuf, stderr) if code != 0 { return code, err } - createdUUID := ExtractUUIDFromOutput(outBuf.String()) - if createdUUID == "" { - io.WriteString(stderr, "error: could not extract UUID from task creation output\n") + uuid := extractUUIDFromAddOutput(outBuf.String()) + if uuid == "" { + io.WriteString(stderr, "error: could not parse UUID from task creation output\n") return 1, nil } - io.WriteString(stdout, createdUUID+"\n") + io.WriteString(stdout, uuid+"\n") return 0, nil } +// extractUUIDFromAddOutput parses the UUID from taskwarrior's +// "Created task <uuid>." output (produced when rc.verbose=new-uuid is set). +func extractUUIDFromAddOutput(output string) string { + for _, line := range strings.Split(strings.TrimSpace(output), "\n") { + if strings.HasPrefix(line, "Created task ") { + parts := strings.Fields(line) + if len(parts) >= 3 { + return strings.TrimSuffix(parts[2], ".") + } + } + } + return "" +} + func parseAddArgs(args []string) (modifiers []string, description string) { for i, arg := range args { if strings.HasPrefix(arg, "priority:") || strings.HasPrefix(arg, "+") || strings.HasPrefix(arg, "-") { diff --git a/internal/askcli/command_info_add_test.go b/internal/askcli/command_info_add_test.go index 47cd790..b809097 100644 --- a/internal/askcli/command_info_add_test.go +++ b/internal/askcli/command_info_add_test.go @@ -53,8 +53,9 @@ func TestHandleInfo_MissingUUID(t *testing.T) { } func TestHandleAdd_Success(t *testing.T) { + // With rc.verbose=new-uuid, task add outputs "Created task <uuid>." directly. d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { - io.WriteString(stdout, "Created task 123.\nUUID: abc-123-def") + io.WriteString(stdout, "Created task abc-123-def.") return 0, nil }}) var stdout, stderr bytes.Buffer @@ -62,9 +63,8 @@ func TestHandleAdd_Success(t *testing.T) { if code != 0 { t.Fatalf("add code = %d, want 0", code) } - output := stdout.String() - if !strings.Contains(output, "abc-123-def") { - t.Fatalf("output missing UUID: %s", output) + if !strings.Contains(stdout.String(), "abc-123-def") { + t.Fatalf("output missing UUID: %s", stdout.String()) } } @@ -79,37 +79,44 @@ func TestHandleAdd_MissingDescription(t *testing.T) { } } +func makeAddRunner(onAdd func(args []string, stdout io.Writer)) *spyRunner { + return &spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + onAdd(args, stdout) + return 0, nil + }} +} + func TestHandleAdd_MultipleWords(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) { + d := NewDispatcher(makeAddRunner(func(args []string, stdout io.Writer) { capturedArgs = args - io.WriteString(stdout, "Created task 123.\nUUID: xyz-789") - return 0, nil - }}) + io.WriteString(stdout, "Created task test-uuid.") + })) var stdout, stderr bytes.Buffer d.Dispatch(context.Background(), []string{"add", "Multi", "word", "description"}, nil, &stdout, &stderr) - if len(capturedArgs) < 2 || capturedArgs[0] != "add" { - t.Fatalf("capturedArgs = %v, want [add, Multi word description]", capturedArgs) + // args[0]="add", args[1]="rc.verbose=new-uuid", then description + if len(capturedArgs) < 3 || capturedArgs[0] != "add" || capturedArgs[1] != "rc.verbose=new-uuid" { + t.Fatalf("capturedArgs = %v, want [add, rc.verbose=new-uuid, ...]", capturedArgs) } } func TestHandleAdd_WithPriority(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) { + d := NewDispatcher(makeAddRunner(func(args []string, stdout io.Writer) { capturedArgs = args - io.WriteString(stdout, "Created task 123.\nUUID: uuid-priority") - return 0, nil - }}) + io.WriteString(stdout, "Created task test-uuid.") + })) var stdout, stderr bytes.Buffer code, _ := d.Dispatch(context.Background(), []string{"add", "priority:H", "Fix critical bug"}, nil, &stdout, &stderr) if code != 0 { t.Fatalf("add code = %d, want 0", code) } - if len(capturedArgs) < 3 { - t.Fatalf("capturedArgs = %v, want at least [add, priority:H, Fix critical bug]", capturedArgs) + // args: [add, rc.verbose=new-uuid, priority:H, Fix critical bug] + if len(capturedArgs) < 4 { + t.Fatalf("capturedArgs = %v, want at least 4 elements", capturedArgs) } - if capturedArgs[1] != "priority:H" { - t.Errorf("capturedArgs[1] = %s, want priority:H", capturedArgs[1]) + if capturedArgs[2] != "priority:H" { + t.Errorf("capturedArgs[2] = %s, want priority:H", capturedArgs[2]) } if capturedArgs[len(capturedArgs)-1] != "Fix critical bug" { t.Errorf("last arg = %s, want 'Fix critical bug'", capturedArgs[len(capturedArgs)-1]) @@ -118,35 +125,47 @@ func TestHandleAdd_WithPriority(t *testing.T) { func TestHandleAdd_WithTag(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) { + d := NewDispatcher(makeAddRunner(func(args []string, stdout io.Writer) { capturedArgs = args - io.WriteString(stdout, "Created task 123.\nUUID: uuid-tag") - return 0, nil - }}) + io.WriteString(stdout, "Created task test-uuid.") + })) var stdout, stderr bytes.Buffer code, _ := d.Dispatch(context.Background(), []string{"add", "+cli", "New feature"}, nil, &stdout, &stderr) if code != 0 { t.Fatalf("add code = %d, want 0", code) } - if capturedArgs[1] != "+cli" { - t.Errorf("capturedArgs[1] = %s, want +cli", capturedArgs[1]) + // args: [add, rc.verbose=new-uuid, +cli, New feature] + if capturedArgs[2] != "+cli" { + t.Errorf("capturedArgs[2] = %s, want +cli", capturedArgs[2]) } } func TestHandleAdd_WithPriorityAndTag(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) { + d := NewDispatcher(makeAddRunner(func(args []string, stdout io.Writer) { capturedArgs = args - io.WriteString(stdout, "Created task 123.\nUUID: uuid-combined") - return 0, nil - }}) + io.WriteString(stdout, "Created task test-uuid.") + })) var stdout, stderr bytes.Buffer code, _ := d.Dispatch(context.Background(), []string{"add", "priority:H", "+cli", "Complex task"}, nil, &stdout, &stderr) if code != 0 { t.Fatalf("add code = %d, want 0", code) } - if capturedArgs[1] != "priority:H" || capturedArgs[2] != "+cli" { - t.Errorf("capturedArgs = %v, want [add, priority:H, +cli, Complex task]", capturedArgs) + // args: [add, rc.verbose=new-uuid, priority:H, +cli, Complex task] + if capturedArgs[2] != "priority:H" || capturedArgs[3] != "+cli" { + t.Errorf("capturedArgs = %v, want [add, rc.verbose=new-uuid, priority:H, +cli, Complex task]", capturedArgs) + } +} + +func TestExtractUUIDFromAddOutput(t *testing.T) { + if uuid := extractUUIDFromAddOutput("Created task abc-123-def."); uuid != "abc-123-def" { + t.Fatalf("got %q, want abc-123-def", uuid) + } + if uuid := extractUUIDFromAddOutput("Created task abc-123-def.\nsome other line"); uuid != "abc-123-def" { + t.Fatalf("got %q, want abc-123-def", uuid) + } + if uuid := extractUUIDFromAddOutput("no match here"); uuid != "" { + t.Fatalf("got %q, want empty", uuid) } } diff --git a/internal/askcli/taskexport.go b/internal/askcli/taskexport.go index c18cd4e..9841821 100644 --- a/internal/askcli/taskexport.go +++ b/internal/askcli/taskexport.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io" - "strings" ) type TaskExport struct { @@ -41,36 +40,3 @@ func MustParseTaskExport(data []byte) []TaskExport { } return tasks } - -func ExtractUUIDFromOutput(output string) string { - lines := strings.Split(strings.TrimSpace(output), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "UUID:") { - parts := strings.Fields(line) - if len(parts) >= 2 { - return parts[1] - } - } - } - for _, line := range lines { - if strings.HasPrefix(line, "Created task ") { - parts := strings.Fields(line) - if len(parts) >= 3 { - return strings.TrimSuffix(parts[2], ".") - } - } - } - fields := strings.Fields(output) - for i, f := range fields { - if f == "uuid" && i+1 < len(fields) { - return fields[i+1] - } - if strings.HasPrefix(f, "Created task") { - parts := strings.Split(f, " ") - if len(parts) >= 2 { - return strings.TrimSuffix(parts[1], ".") - } - } - } - return strings.TrimSpace(output) -} diff --git a/internal/askcli/taskexport_test.go b/internal/askcli/taskexport_test.go index af468e2..e7779aa 100644 --- a/internal/askcli/taskexport_test.go +++ b/internal/askcli/taskexport_test.go @@ -51,38 +51,6 @@ func TestMustParseTaskExport_ValidJSON(t *testing.T) { } } -func TestExtractUUIDFromOutput_CreatedTask(t *testing.T) { - output := "Created task 123.\nUUID: abc-123-def" - uuid := ExtractUUIDFromOutput(output) - if uuid != "abc-123-def" { - t.Fatalf("ExtractUUIDFromOutput = %q, want %q", uuid, "abc-123-def") - } -} - -func TestExtractUUIDFromOutput_CreatedTaskOnly(t *testing.T) { - output := "Created task 123." - uuid := ExtractUUIDFromOutput(output) - if uuid != "123" { - t.Fatalf("ExtractUUIDFromOutput = %q, want %q", uuid, "123") - } -} - -func TestExtractUUIDFromOutput_UUIDField(t *testing.T) { - output := "Some text\nuuid abc-123-def\nmore text" - uuid := ExtractUUIDFromOutput(output) - if uuid != "abc-123-def" { - t.Fatalf("ExtractUUIDFromOutput = %q, want %q", uuid, "abc-123-def") - } -} - -func TestExtractUUIDFromOutput_PlainText(t *testing.T) { - output := "abc-456-xyz" - uuid := ExtractUUIDFromOutput(output) - if uuid != output { - t.Fatalf("ExtractUUIDFromOutput = %q, want %q", uuid, output) - } -} - func TestTaskExport_JSONRoundTrip(t *testing.T) { original := TaskExport{ UUID: "test-uuid", @@ -133,13 +101,6 @@ func TestParseTaskExport_MultipleTasks(t *testing.T) { } } -func TestExtractUUIDFromOutput_NilOutput(t *testing.T) { - uuid := ExtractUUIDFromOutput("") - if uuid != "" { - t.Fatalf("ExtractUUIDFromOutput = %q, want empty string", uuid) - } -} - func TestParseTaskExport_ReadError(t *testing.T) { _, err := ParseTaskExport(&errReader{}) if err == nil { |
