summaryrefslogtreecommitdiff
path: root/internal/askcli/taskexec_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-22 19:39:33 +0200
committerPaul Buetow <paul@buetow.org>2026-03-22 19:39:33 +0200
commit3f06d7dadb83d78f0913b1c1c9a9297826e107b1 (patch)
tree0b2bf1466c3cd6d949abd00d70c6f07b4588200a /internal/askcli/taskexec_test.go
parent487ee8b3262ca1845b931d1f0b9df9966fbedea3 (diff)
Scaffold internal/askcli package: dispatch, taskexec, taskexport, formatter
Diffstat (limited to 'internal/askcli/taskexec_test.go')
-rw-r--r--internal/askcli/taskexec_test.go153
1 files changed, 153 insertions, 0 deletions
diff --git a/internal/askcli/taskexec_test.go b/internal/askcli/taskexec_test.go
new file mode 100644
index 0000000..4450a28
--- /dev/null
+++ b/internal/askcli/taskexec_test.go
@@ -0,0 +1,153 @@
+package askcli
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "io"
+ "os/exec"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestExecutorTaskArgs(t *testing.T) {
+ exec_ := NewExecutor("ask")
+ args, err := exec_.taskArgs("/tmp/work/hexai", []string{"list", "limit:1"})
+ if err != nil {
+ t.Fatalf("taskArgs returned error: %v", err)
+ }
+ want := []string{"project:hexai", "+agent", "list", "limit:1"}
+ if !reflect.DeepEqual(args, want) {
+ t.Fatalf("task args = %v, want %v", args, want)
+ }
+}
+
+func TestExecutorRun_InjectsProjectFilterAndAgentTag(t *testing.T) {
+ var gotName string
+ var gotArgs []string
+ exec_ := Executor{
+ commandName: "ask",
+ findBinary: func() (string, error) { return "/usr/bin/task", nil },
+ detectRepoRoot: func(context.Context) (string, error) { return "/tmp/work/hexai", nil },
+ runCommand: func(_ context.Context, name string, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
+ gotName = name
+ gotArgs = append([]string(nil), args...)
+ return nil
+ },
+ }
+
+ exitCode, err := exec_.Run(context.Background(), []string{"list", "limit:1"}, strings.NewReader("in"), &bytes.Buffer{}, &bytes.Buffer{})
+ if err != nil {
+ t.Fatalf("Run returned error: %v", err)
+ }
+ if exitCode != 0 {
+ t.Fatalf("exitCode = %d, want 0", exitCode)
+ }
+ if gotName != "/usr/bin/task" {
+ t.Fatalf("task binary = %q, want /usr/bin/task", gotName)
+ }
+ wantArgs := []string{"project:hexai", "+agent", "list", "limit:1"}
+ if !reflect.DeepEqual(gotArgs, wantArgs) {
+ t.Fatalf("task args = %v, want %v", gotArgs, wantArgs)
+ }
+}
+
+func TestExecutorRun_OutsideGitRepo_IsActionable(t *testing.T) {
+ exec_ := Executor{
+ commandName: "ask",
+ findBinary: func() (string, error) { return "/usr/bin/task", nil },
+ detectRepoRoot: func(context.Context) (string, error) { return "", errors.New("git failed") },
+ runCommand: func(context.Context, string, []string, io.Reader, io.Writer, io.Writer) error {
+ t.Fatal("runCommand should not be called when repo detection fails")
+ return nil
+ },
+ }
+
+ exitCode, err := exec_.Run(context.Background(), []string{"list"}, strings.NewReader(""), &bytes.Buffer{}, &bytes.Buffer{})
+ if exitCode != 1 {
+ t.Fatalf("exitCode = %d, want 1", exitCode)
+ }
+ if err == nil || !strings.Contains(err.Error(), "must be run inside a git repository") {
+ t.Fatalf("expected actionable git-repo error, got %v", err)
+ }
+}
+
+func TestExecutorRun_PreservesTaskwarriorExitCode(t *testing.T) {
+ exec_ := Executor{
+ commandName: "ask",
+ findBinary: func() (string, error) { return "/usr/bin/task", nil },
+ detectRepoRoot: func(context.Context) (string, error) { return "/tmp/work/hexai", nil },
+ runCommand: func(context.Context, string, []string, io.Reader, io.Writer, io.Writer) error {
+ return exec.Command("sh", "-c", "exit 7").Run()
+ },
+ }
+
+ exitCode, err := exec_.Run(context.Background(), []string{"list"}, strings.NewReader(""), &bytes.Buffer{}, &bytes.Buffer{})
+ if err != nil {
+ t.Fatalf("expected nil error for subprocess exit, got %v", err)
+ }
+ if exitCode != 7 {
+ t.Fatalf("exitCode = %d, want 7", exitCode)
+ }
+}
+
+func TestExecutorRun_PreservesStdoutAndStderr(t *testing.T) {
+ var stdout bytes.Buffer
+ var stderr bytes.Buffer
+ exec_ := Executor{
+ commandName: "ask",
+ findBinary: func() (string, error) { return "/usr/bin/task", nil },
+ detectRepoRoot: func(context.Context) (string, error) { return "/tmp/work/hexai", nil },
+ runCommand: func(_ context.Context, name string, args []string, stdin io.Reader, out, errOut io.Writer) error {
+ _, _ = io.WriteString(out, "task stdout")
+ _, _ = io.WriteString(errOut, "task stderr")
+ return nil
+ },
+ }
+
+ exitCode, err := exec_.Run(context.Background(), []string{"list"}, strings.NewReader(""), &stdout, &stderr)
+ if err != nil {
+ t.Fatalf("Run returned error: %v", err)
+ }
+ if exitCode != 0 {
+ t.Fatalf("exitCode = %d, want 0", exitCode)
+ }
+ if stdout.String() != "task stdout" {
+ t.Fatalf("stdout = %q, want %q", stdout.String(), "task stdout")
+ }
+ if stderr.String() != "task stderr" {
+ t.Fatalf("stderr = %q, want %q", stderr.String(), "task stderr")
+ }
+}
+
+func TestExecutorRun_TaskLookupFailure_IsActionable(t *testing.T) {
+ exec_ := Executor{
+ commandName: "ask",
+ findBinary: func() (string, error) { return "", errors.New("not found") },
+ }
+
+ exitCode, err := exec_.Run(context.Background(), []string{"list"}, strings.NewReader(""), &bytes.Buffer{}, &bytes.Buffer{})
+ if exitCode != 1 {
+ t.Fatalf("exitCode = %d, want 1", exitCode)
+ }
+ if err == nil || !strings.Contains(err.Error(), "task binary lookup failed") {
+ t.Fatalf("expected actionable task lookup error, got %v", err)
+ }
+}
+
+func TestExecutorRun_EmptyRepoName_IsActionable(t *testing.T) {
+ exec_ := Executor{
+ commandName: "ask",
+ findBinary: func() (string, error) { return "/usr/bin/task", nil },
+ detectRepoRoot: func(context.Context) (string, error) { return "/", nil },
+ }
+
+ exitCode, err := exec_.Run(context.Background(), []string{"list"}, strings.NewReader(""), &bytes.Buffer{}, &bytes.Buffer{})
+ if exitCode != 1 {
+ t.Fatalf("exitCode = %d, want 1", exitCode)
+ }
+ if err == nil || !strings.Contains(err.Error(), "could not derive project name") {
+ t.Fatalf("expected actionable project-name error, got %v", err)
+ }
+}