summaryrefslogtreecommitdiff
path: root/internal/askcli/command_edit_test.go
blob: 91569c89e9c2bcb11dee793d381c6100407513e8 (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
121
122
123
124
package askcli

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

func stubEditorCapture(d *Dispatcher, content string, err error) {
	d.capture = func(_ context.Context, initial []byte) (string, error) {
		return content, err
	}
}

func TestHandleEdit_Success(t *testing.T) {
	now := useIsolatedTaskAliasCache(t)
	writeTaskAliasCacheForTest(t, taskAliasCache{
		NextID: 1,
		Entries: []taskAliasCacheEntry{
			{UUID: "existing-uuid", Alias: "0", CreatedAt: now},
		},
	})
	var capturedArgs []string
	d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
		capturedArgs = args
		_, _ = io.WriteString(stdout, "Created task abc-123-def.")
		return 0, nil
	}})
	// editor.OpenTempAndEdit trims content, so mimic that here.
	stubEditorCapture(d, "Multi line\ntask description", nil)

	var stdout, stderr bytes.Buffer
	code, _ := d.Dispatch(context.Background(), []string{"edit"}, nil, &stdout, &stderr)
	if code != 0 {
		t.Fatalf("edit code = %d stderr = %q", code, stderr.String())
	}
	if got := strings.TrimSpace(stdout.String()); got != "created task 1" {
		t.Fatalf("stdout = %q, want created task 1", stdout.String())
	}
	if len(capturedArgs) == 0 || capturedArgs[len(capturedArgs)-1] != "Multi line\ntask description" {
		t.Fatalf("description arg = %v, want trimmed multi-line content", capturedArgs)
	}
}

func TestHandleEdit_ExistingTaskModifiesDescription(t *testing.T) {
	now := useIsolatedTaskAliasCache(t)
	writeTaskAliasCacheForTest(t, taskAliasCache{
		NextID: 1,
		Entries: []taskAliasCacheEntry{
			{UUID: "existing-uuid", Alias: "0", CreatedAt: now},
		},
	})

	var capturedArgs []string
	var initialContent []byte
	d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
		// First call: export to resolve selector. Subsequent: modify.
		if len(args) >= 2 && args[1] == "export" {
			_, _ = io.WriteString(stdout, `[{"uuid":"existing-uuid","description":"old description","status":"pending"}]`)
			return 0, nil
		}
		capturedArgs = args
		return 0, nil
	}})
	d.capture = func(_ context.Context, initial []byte) (string, error) {
		initialContent = initial
		return "updated description", nil
	}

	var stdout, stderr bytes.Buffer
	code, _ := d.Dispatch(context.Background(), []string{"edit", "0"}, nil, &stdout, &stderr)
	if code != 0 {
		t.Fatalf("edit code = %d stderr = %q", code, stderr.String())
	}
	if string(initialContent) != "old description" {
		t.Fatalf("editor initial content = %q, want old description", initialContent)
	}
	want := []string{"uuid:existing-uuid", "modify", "updated description"}
	if len(capturedArgs) != len(want) {
		t.Fatalf("modify args = %v, want %v", capturedArgs, want)
	}
	for i := range want {
		if capturedArgs[i] != want[i] {
			t.Fatalf("modify args = %v, want %v", capturedArgs, want)
		}
	}
}

func TestHandleEdit_EmptyContentAborts(t *testing.T) {
	d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
		t.Fatalf("runner should not be called on empty content")
		return 0, nil
	}})
	stubEditorCapture(d, "", nil)

	var stdout, stderr bytes.Buffer
	code, _ := d.Dispatch(context.Background(), []string{"edit"}, nil, &stdout, &stderr)
	if code != 1 {
		t.Fatalf("edit code = %d, want 1", code)
	}
	if !strings.Contains(stderr.String(), "empty description") {
		t.Fatalf("stderr = %q, want empty description error", stderr.String())
	}
}

func TestHandleEdit_EditorError(t *testing.T) {
	d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
		t.Fatalf("runner should not be called when editor fails")
		return 0, nil
	}})
	stubEditorCapture(d, "", errors.New("boom"))

	var stdout, stderr bytes.Buffer
	code, _ := d.Dispatch(context.Background(), []string{"edit"}, nil, &stdout, &stderr)
	if code != 1 {
		t.Fatalf("edit code = %d, want 1", code)
	}
	if !strings.Contains(stderr.String(), "boom") {
		t.Fatalf("stderr = %q, want editor error", stderr.String())
	}
}