Skip to content

Commit a397244

Browse files
authored
[test] Improve test coverage above 90% (#25)
* test: improve test coverage to 90% * test: refactor tests to use shared constants and reduce duplication
1 parent 2911ab1 commit a397244

29 files changed

Lines changed: 4451 additions & 49 deletions

cmd/add_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
10+
"github.com/lugassawan/rimba/internal/config"
11+
)
12+
13+
func TestAddSuccess(t *testing.T) {
14+
repoDir := t.TempDir()
15+
wtDir := filepath.Join(repoDir, "worktrees")
16+
_ = os.MkdirAll(wtDir, 0755)
17+
cfg := &config.Config{DefaultSource: branchMain, WorktreeDir: "worktrees"}
18+
19+
worktreeOut := wtPrefix + repoDir + headMainBlock
20+
21+
r := &mockRunner{
22+
run: func(args ...string) (string, error) {
23+
if len(args) >= 2 && args[1] == cmdShowToplevel {
24+
return repoDir, nil
25+
}
26+
if len(args) >= 1 && args[0] == cmdRevParse {
27+
return "", errGitFailed // BranchExists returns false
28+
}
29+
return worktreeOut, nil
30+
},
31+
runInDir: noopRunInDir,
32+
}
33+
restore := overrideNewRunner(r)
34+
defer restore()
35+
36+
cmd, buf := newTestCmd()
37+
cmd.Flags().StringP(flagSource, "s", "", "")
38+
cmd.Flags().Bool(flagSkipDeps, false, "")
39+
cmd.Flags().Bool(flagSkipHooks, false, "")
40+
addPrefixFlags(cmd)
41+
_ = cmd.Flags().Set(flagSkipDeps, "true")
42+
_ = cmd.Flags().Set(flagSkipHooks, "true")
43+
cmd.SetContext(config.WithConfig(context.Background(), cfg))
44+
45+
err := addCmd.RunE(cmd, []string{"my-task"})
46+
if err != nil {
47+
t.Fatalf("addCmd.RunE: %v", err)
48+
}
49+
out := buf.String()
50+
if !strings.Contains(out, "Created worktree") {
51+
t.Errorf("output = %q, want 'Created worktree'", out)
52+
}
53+
if !strings.Contains(out, "my-task") {
54+
t.Errorf("output = %q, want 'my-task'", out)
55+
}
56+
}
57+
58+
func TestAddBranchAlreadyExists(t *testing.T) {
59+
repoDir := t.TempDir()
60+
cfg := &config.Config{DefaultSource: branchMain, WorktreeDir: "worktrees"}
61+
62+
worktreeOut := wtPrefix + repoDir + headMainBlock
63+
64+
r := &mockRunner{
65+
run: func(args ...string) (string, error) {
66+
if len(args) >= 2 && args[1] == cmdShowToplevel {
67+
return repoDir, nil
68+
}
69+
if len(args) >= 1 && args[0] == cmdRevParse {
70+
return "", nil // BranchExists returns true
71+
}
72+
return worktreeOut, nil
73+
},
74+
runInDir: noopRunInDir,
75+
}
76+
restore := overrideNewRunner(r)
77+
defer restore()
78+
79+
cmd, _ := newTestCmd()
80+
cmd.Flags().StringP(flagSource, "s", "", "")
81+
cmd.Flags().Bool(flagSkipDeps, false, "")
82+
cmd.Flags().Bool(flagSkipHooks, false, "")
83+
addPrefixFlags(cmd)
84+
cmd.SetContext(config.WithConfig(context.Background(), cfg))
85+
86+
err := addCmd.RunE(cmd, []string{"existing"})
87+
if err == nil {
88+
t.Fatal("expected error for existing branch")
89+
}
90+
if !strings.Contains(err.Error(), "already exists") {
91+
t.Errorf("error = %q, want 'already exists'", err.Error())
92+
}
93+
}
94+
95+
func TestAddWithSource(t *testing.T) {
96+
repoDir := t.TempDir()
97+
wtDir := filepath.Join(repoDir, "worktrees")
98+
_ = os.MkdirAll(wtDir, 0755)
99+
cfg := &config.Config{DefaultSource: branchMain, WorktreeDir: "worktrees"}
100+
101+
worktreeOut := wtPrefix + repoDir + headMainBlock
102+
103+
r := &mockRunner{
104+
run: func(args ...string) (string, error) {
105+
if len(args) >= 2 && args[1] == cmdShowToplevel {
106+
return repoDir, nil
107+
}
108+
if len(args) >= 1 && args[0] == cmdRevParse {
109+
return "", errGitFailed
110+
}
111+
return worktreeOut, nil
112+
},
113+
runInDir: noopRunInDir,
114+
}
115+
restore := overrideNewRunner(r)
116+
defer restore()
117+
118+
cmd, buf := newTestCmd()
119+
cmd.Flags().StringP(flagSource, "s", "", "")
120+
cmd.Flags().Bool(flagSkipDeps, false, "")
121+
cmd.Flags().Bool(flagSkipHooks, false, "")
122+
addPrefixFlags(cmd)
123+
_ = cmd.Flags().Set(flagSource, "develop")
124+
_ = cmd.Flags().Set(flagSkipDeps, "true")
125+
_ = cmd.Flags().Set(flagSkipHooks, "true")
126+
cmd.SetContext(config.WithConfig(context.Background(), cfg))
127+
128+
err := addCmd.RunE(cmd, []string{"from-develop"})
129+
if err != nil {
130+
t.Fatalf("addCmd.RunE: %v", err)
131+
}
132+
if !strings.Contains(buf.String(), "Created worktree") {
133+
t.Errorf("output = %q, want 'Created worktree'", buf.String())
134+
}
135+
}

cmd/clean_test.go

Lines changed: 211 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"errors"
6+
"path/filepath"
57
"strings"
68
"testing"
9+
10+
"github.com/lugassawan/rimba/internal/config"
11+
"github.com/spf13/cobra"
712
)
813

914
// mergedWorktreeRunner returns a mockRunner that supports MergedBranches and ListWorktrees.
@@ -24,7 +29,7 @@ func mergedWorktreeRunner(mergedOut, worktreeOut string) *mockRunner {
2429

2530
func TestFindMergedCandidatesFound(t *testing.T) {
2631
worktreeOut := strings.Join([]string{
27-
"worktree /repo",
32+
wtRepo,
2833
"HEAD abc123",
2934
"branch refs/heads/main",
3035
"",
@@ -52,7 +57,7 @@ func TestFindMergedCandidatesFound(t *testing.T) {
5257
}
5358

5459
func TestFindMergedCandidatesNone(t *testing.T) {
55-
r := mergedWorktreeRunner("", "worktree /repo\nHEAD abc\nbranch refs/heads/main\n")
60+
r := mergedWorktreeRunner("", wtRepo + headMainBlock)
5661
candidates, err := findMergedCandidates(r, branchMain)
5762
if err != nil {
5863
t.Fatalf("findMergedCandidates: %v", err)
@@ -120,7 +125,7 @@ func TestRemoveMergedWorktreesRemoveFails(t *testing.T) {
120125
cmd, buf := newTestCmd()
121126
r := &mockRunner{
122127
run: func(args ...string) (string, error) {
123-
if len(args) >= 2 && args[0] == cmdWorktreeTest && args[1] == "remove" {
128+
if len(args) >= 2 && args[0] == cmdWorktreeTest && args[1] == cmdRemove {
124129
return "", errors.New("locked")
125130
}
126131
return "", nil
@@ -181,3 +186,206 @@ func TestConfirmRemoval(t *testing.T) {
181186
})
182187
}
183188
}
189+
190+
func newCleanPruneCmd() (*cobra.Command, *bytes.Buffer) {
191+
cmd, buf := newTestCmd()
192+
cmd.Flags().Bool(flagDryRun, false, "")
193+
cmd.Flags().Bool(flagMerged, false, "")
194+
return cmd, buf
195+
}
196+
197+
func TestCleanPruneSuccess(t *testing.T) {
198+
cmd, buf := newCleanPruneCmd()
199+
r := &mockRunner{
200+
run: func(args ...string) (string, error) {
201+
if len(args) >= 1 && args[0] == cmdWorktreeTest {
202+
return "Removing worktrees/stale", nil
203+
}
204+
return "", nil
205+
},
206+
runInDir: noopRunInDir,
207+
}
208+
209+
err := cleanPrune(cmd, r)
210+
if err != nil {
211+
t.Fatalf(fatalCleanPrune, err)
212+
}
213+
if !strings.Contains(buf.String(), "Removing worktrees/stale") {
214+
t.Errorf("output = %q, want prune output", buf.String())
215+
}
216+
}
217+
218+
func TestCleanPruneDryRunEmpty(t *testing.T) {
219+
cmd, buf := newCleanPruneCmd()
220+
_ = cmd.Flags().Set(flagDryRun, "true")
221+
r := &mockRunner{
222+
run: func(_ ...string) (string, error) { return "", nil },
223+
runInDir: noopRunInDir,
224+
}
225+
226+
err := cleanPrune(cmd, r)
227+
if err != nil {
228+
t.Fatalf(fatalCleanPrune, err)
229+
}
230+
if !strings.Contains(buf.String(), "Nothing to prune") {
231+
t.Errorf("output = %q, want 'Nothing to prune'", buf.String())
232+
}
233+
}
234+
235+
func TestCleanPruneNoDryRunEmpty(t *testing.T) {
236+
cmd, buf := newCleanPruneCmd()
237+
r := &mockRunner{
238+
run: func(_ ...string) (string, error) { return "", nil },
239+
runInDir: noopRunInDir,
240+
}
241+
242+
err := cleanPrune(cmd, r)
243+
if err != nil {
244+
t.Fatalf(fatalCleanPrune, err)
245+
}
246+
if !strings.Contains(buf.String(), "Pruned stale worktree references") {
247+
t.Errorf("output = %q, want 'Pruned stale worktree references'", buf.String())
248+
}
249+
}
250+
251+
func TestCleanPruneError(t *testing.T) {
252+
cmd, _ := newCleanPruneCmd()
253+
r := &mockRunner{
254+
run: func(args ...string) (string, error) {
255+
if len(args) >= 1 && args[0] == cmdWorktreeTest {
256+
return "", errGitFailed
257+
}
258+
return "", nil
259+
},
260+
runInDir: noopRunInDir,
261+
}
262+
263+
err := cleanPrune(cmd, r)
264+
if err == nil {
265+
t.Fatal("expected error from prune failure")
266+
}
267+
}
268+
269+
func cleanMergedTestRunner(t *testing.T, mergedOut, worktreeOut string) *mockRunner {
270+
dir := t.TempDir()
271+
cfg := &config.Config{DefaultSource: branchMain}
272+
_ = config.Save(filepath.Join(dir, config.FileName), cfg)
273+
274+
return &mockRunner{
275+
run: func(args ...string) (string, error) {
276+
if len(args) >= 2 && args[1] == cmdShowToplevel {
277+
return dir, nil
278+
}
279+
if len(args) >= 1 && args[0] == cmdBranch {
280+
return mergedOut, nil
281+
}
282+
if len(args) >= 1 && args[0] == cmdWorktreeTest {
283+
return worktreeOut, nil
284+
}
285+
if len(args) >= 1 && args[0] == "fetch" {
286+
return "", errors.New("no remote")
287+
}
288+
return "", nil
289+
},
290+
runInDir: noopRunInDir,
291+
}
292+
}
293+
294+
func newCleanMergedCmd() (*cobra.Command, *bytes.Buffer) {
295+
cmd, buf := newTestCmd()
296+
cmd.Flags().Bool(flagDryRun, false, "")
297+
cmd.Flags().Bool(flagForce, false, "")
298+
cmd.Flags().Bool(flagMerged, false, "")
299+
return cmd, buf
300+
}
301+
302+
func cleanMergedWorktreeOut() string {
303+
return strings.Join([]string{
304+
wtRepo,
305+
"HEAD abc123",
306+
"branch refs/heads/main",
307+
"",
308+
"worktree " + pathWtDone,
309+
"HEAD def456",
310+
"branch refs/heads/" + branchDone,
311+
"",
312+
}, "\n")
313+
}
314+
315+
func TestCleanMergedNoCandidates(t *testing.T) {
316+
worktreeOut := cleanMergedWorktreeOut()
317+
cmd, buf := newCleanMergedCmd()
318+
r := cleanMergedTestRunner(t, "", worktreeOut)
319+
320+
err := cleanMerged(cmd, r)
321+
if err != nil {
322+
t.Fatalf(fatalCleanMerged, err)
323+
}
324+
if !strings.Contains(buf.String(), "No merged worktrees found") {
325+
t.Errorf("output = %q, want 'No merged worktrees found'", buf.String())
326+
}
327+
}
328+
329+
func TestCleanMergedDryRun(t *testing.T) {
330+
worktreeOut := cleanMergedWorktreeOut()
331+
cmd, buf := newCleanMergedCmd()
332+
_ = cmd.Flags().Set(flagDryRun, "true")
333+
r := cleanMergedTestRunner(t, " "+branchDone, worktreeOut)
334+
335+
err := cleanMerged(cmd, r)
336+
if err != nil {
337+
t.Fatalf(fatalCleanMerged, err)
338+
}
339+
out := buf.String()
340+
if !strings.Contains(out, "Merged worktrees:") {
341+
t.Errorf("output = %q, want 'Merged worktrees:'", out)
342+
}
343+
if strings.Contains(out, "Cleaned") {
344+
t.Errorf("dry-run should not show 'Cleaned'")
345+
}
346+
}
347+
348+
func TestCleanMergedAbort(t *testing.T) {
349+
worktreeOut := cleanMergedWorktreeOut()
350+
cmd, buf := newCleanMergedCmd()
351+
cmd.SetIn(strings.NewReader("n\n"))
352+
r := cleanMergedTestRunner(t, " "+branchDone, worktreeOut)
353+
354+
err := cleanMerged(cmd, r)
355+
if err != nil {
356+
t.Fatalf(fatalCleanMerged, err)
357+
}
358+
if !strings.Contains(buf.String(), "Aborted") {
359+
t.Errorf("output = %q, want 'Aborted'", buf.String())
360+
}
361+
}
362+
363+
func TestCleanMergedForce(t *testing.T) {
364+
worktreeOut := cleanMergedWorktreeOut()
365+
cmd, buf := newCleanMergedCmd()
366+
_ = cmd.Flags().Set(flagForce, "true")
367+
r := cleanMergedTestRunner(t, " "+branchDone, worktreeOut)
368+
369+
err := cleanMerged(cmd, r)
370+
if err != nil {
371+
t.Fatalf(fatalCleanMerged, err)
372+
}
373+
out := buf.String()
374+
if !strings.Contains(out, "Cleaned") {
375+
t.Errorf("output = %q, want 'Cleaned'", out)
376+
}
377+
}
378+
379+
func TestCleanMergedResolveError(t *testing.T) {
380+
cmd, _ := newCleanMergedCmd()
381+
r := &mockRunner{
382+
run: func(_ ...string) (string, error) { return "", errGitFailed },
383+
runInDir: noopRunInDir,
384+
}
385+
386+
err := cleanMerged(cmd, r)
387+
if err == nil {
388+
t.Fatal("expected error from resolveMainBranch failure")
389+
}
390+
}
391+

0 commit comments

Comments
 (0)