summaryrefslogtreecommitdiff
path: root/cmd/ask/main_test.go
blob: 643dde4d45b7fb599951007afed208b99d2eeac1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package main

import (
	"bytes"
	"context"
	"errors"
	"io"
	"strings"
	"testing"

	"codeberg.org/snonux/hexai/internal/askcli"
)

func TestMain_WiresDispatcher(t *testing.T) {
	var gotArgs []string
	d := askcli.NewDispatcher(&spyRunner{
		runFn: func(_ context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
			gotArgs = append([]string(nil), args...)
			io.WriteString(stdout, `[{"uuid":"test-uuid","description":"Test","status":"pending","priority":"H","tags":["cli"],"urgency":15.0,"depends":[]}]`)
			return 0, nil
		},
	})
	code, err := d.Dispatch(context.Background(), []string{"list", "limit:1"}, nil, &bytes.Buffer{}, &bytes.Buffer{})
	if err != nil {
		t.Fatalf("dispatch returned error: %v", err)
	}
	if code != 0 {
		t.Fatalf("exitCode = %d, want 0", code)
	}
	if len(gotArgs) < 1 || gotArgs[len(gotArgs)-1] != "export" {
		t.Fatalf("args = %v, want [..., export]", gotArgs)
	}
}

func TestMain_ExitsNonZero(t *testing.T) {
	d := askcli.NewDispatcher(&spyRunner{
		runFn: func(context.Context, []string, io.Reader, io.Writer, io.Writer) (int, error) {
			return 1, nil
		},
	})
	code, _ := d.Dispatch(context.Background(), []string{"list"}, nil, &bytes.Buffer{}, &bytes.Buffer{})
	if code == 0 {
		t.Fatalf("exitCode = 0, want non-zero")
	}
}

type spyRunner struct {
	runFn func(context.Context, []string, io.Reader, io.Writer, io.Writer) (int, error)
}

func (s *spyRunner) Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
	return s.runFn(ctx, args, stdin, stdout, stderr)
}

// fakeDispatcher captures Dispatch arguments and returns canned values; used
// to exercise runMain without a real Taskwarrior binary on PATH.
type fakeDispatcher struct {
	gotArgs []string
	code    int
	err     error
}

func (f *fakeDispatcher) Dispatch(_ context.Context, args []string, _ io.Reader, _, _ io.Writer) (int, error) {
	f.gotArgs = append([]string(nil), args...)
	return f.code, f.err
}

// Driving runMain through a fake dispatcher proves the wiring (args
// forwarded, exit code returned) without touching the real ask CLI.
func TestRunMain_DelegatesAndReturnsCode(t *testing.T) {
	old := dispatcherFactory
	t.Cleanup(func() { dispatcherFactory = old })

	fake := &fakeDispatcher{code: 0}
	dispatcherFactory = func() dispatcher { return fake }

	var stdout, stderr bytes.Buffer
	got := runMain([]string{"list", "limit:1"}, nil, &stdout, &stderr)
	if got != 0 {
		t.Fatalf("runMain code = %d, want 0", got)
	}
	if len(fake.gotArgs) != 2 || fake.gotArgs[0] != "list" || fake.gotArgs[1] != "limit:1" {
		t.Fatalf("Dispatch args = %v", fake.gotArgs)
	}
	if stderr.Len() != 0 {
		t.Fatalf("stderr should be empty on success, got %q", stderr.String())
	}
}

// The default dispatcherFactory must return a working real dispatcher (this
// is the path main() uses in production); fakes used elsewhere don't cover
// it, so verify it explicitly.
func TestDispatcherFactory_DefaultReturnsRealDispatcher(t *testing.T) {
	d := dispatcherFactory()
	if d == nil {
		t.Fatal("default dispatcherFactory returned nil")
	}
	if _, ok := d.(*askcli.Dispatcher); !ok {
		t.Fatalf("default dispatcherFactory returned %T, want *askcli.Dispatcher", d)
	}
}

// On a dispatcher error, runMain must print the error to stderr AND surface
// the dispatcher's exit code so the shell sees Taskwarrior's own status.
func TestRunMain_PrintsErrorAndPropagatesExitCode(t *testing.T) {
	old := dispatcherFactory
	t.Cleanup(func() { dispatcherFactory = old })

	fake := &fakeDispatcher{code: 7, err: errors.New("dispatch boom")}
	dispatcherFactory = func() dispatcher { return fake }

	var stdout, stderr bytes.Buffer
	got := runMain(nil, nil, &stdout, &stderr)
	if got != 7 {
		t.Fatalf("runMain code = %d, want 7", got)
	}
	if !strings.Contains(stderr.String(), "dispatch boom") {
		t.Fatalf("stderr missing error text: %q", stderr.String())
	}
}