Skip to content

Commit 6cd460b

Browse files
julianknutsenclaude
andcommitted
Backfill unit test coverage from 35.5% to 46.7%
Add tests for table rendering, command handlers (list, leave, join), isNothingToCommit, and error propagation paths in post/claim/done. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8ce276d commit 6cd460b

8 files changed

Lines changed: 561 additions & 0 deletions

File tree

cmd/wl/cmd_claim_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package main
22

33
import (
4+
"fmt"
5+
"strings"
46
"testing"
57

68
"github.com/steveyegge/wasteland/internal/commons"
@@ -32,6 +34,24 @@ func TestClaimWanted_Success(t *testing.T) {
3234
}
3335
}
3436

37+
func TestClaimWanted_StoreError(t *testing.T) {
38+
t.Parallel()
39+
store := newFakeWLCommonsStore()
40+
store.ClaimWantedErr = fmt.Errorf("claim store error")
41+
_ = store.InsertWanted(&commons.WantedItem{
42+
ID: "w-abc123",
43+
Title: "Fix auth bug",
44+
})
45+
46+
_, err := claimWanted(store, "w-abc123", "my-rig")
47+
if err == nil {
48+
t.Fatal("claimWanted() expected error when ClaimWanted fails")
49+
}
50+
if !strings.Contains(err.Error(), "claim store error") {
51+
t.Errorf("error = %q, want to contain 'claim store error'", err.Error())
52+
}
53+
}
54+
3555
func TestClaimWanted_NotOpen(t *testing.T) {
3656
t.Parallel()
3757
store := newFakeWLCommonsStore()

cmd/wl/cmd_done_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"fmt"
45
"strings"
56
"testing"
67

@@ -59,6 +60,25 @@ func TestSubmitDone_Success(t *testing.T) {
5960
}
6061
}
6162

63+
func TestSubmitDone_StoreError(t *testing.T) {
64+
t.Parallel()
65+
store := newFakeWLCommonsStore()
66+
store.SubmitCompletionErr = fmt.Errorf("completion store error")
67+
_ = store.InsertWanted(&commons.WantedItem{
68+
ID: "w-abc",
69+
Title: "Fix bug",
70+
})
71+
_ = store.ClaimWanted("w-abc", "my-rig")
72+
73+
err := submitDone(store, "w-abc", "my-rig", "https://github.com/pr/1", "c-test")
74+
if err == nil {
75+
t.Fatal("submitDone() expected error when SubmitCompletion fails")
76+
}
77+
if !strings.Contains(err.Error(), "completion store error") {
78+
t.Errorf("error = %q, want to contain 'completion store error'", err.Error())
79+
}
80+
}
81+
6282
func TestSubmitDone_NotClaimed(t *testing.T) {
6383
t.Parallel()
6484
store := newFakeWLCommonsStore()

cmd/wl/cmd_join_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"github.com/steveyegge/wasteland/internal/remote"
9+
)
10+
11+
func TestGitConfigValue_MissingKey(t *testing.T) {
12+
t.Parallel()
13+
got := gitConfigValue("wasteland.nonexistent.key.12345")
14+
if got != "" {
15+
t.Errorf("gitConfigValue(missing) = %q, want empty string", got)
16+
}
17+
}
18+
19+
func TestGitConfigValue_UserName(t *testing.T) {
20+
t.Parallel()
21+
// git config user.name may or may not be set in CI; just verify it doesn't panic
22+
_ = gitConfigValue("user.name")
23+
}
24+
25+
func TestPrintForkInstructions(t *testing.T) {
26+
t.Parallel()
27+
var buf bytes.Buffer
28+
forkErr := &remote.ForkRequiredError{
29+
UpstreamOrg: "hop",
30+
UpstreamDB: "wl-commons",
31+
ForkOrg: "alice",
32+
}
33+
34+
printForkInstructions(&buf, forkErr)
35+
got := buf.String()
36+
37+
if !strings.Contains(got, "Fork required") {
38+
t.Errorf("output missing 'Fork required': %q", got)
39+
}
40+
if !strings.Contains(got, forkErr.ForkURL()) {
41+
t.Errorf("output missing fork URL %q: %q", forkErr.ForkURL(), got)
42+
}
43+
if !strings.Contains(got, "alice") {
44+
t.Errorf("output missing org name 'alice': %q", got)
45+
}
46+
if !strings.Contains(got, "wl join") {
47+
t.Errorf("output missing 'wl join': %q", got)
48+
}
49+
}

cmd/wl/cmd_leave_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
"time"
8+
9+
"github.com/spf13/cobra"
10+
"github.com/steveyegge/wasteland/internal/federation"
11+
)
12+
13+
func TestRunLeave_Success(t *testing.T) {
14+
tmpDir := t.TempDir()
15+
t.Setenv("XDG_CONFIG_HOME", tmpDir)
16+
17+
store := federation.NewConfigStore()
18+
cfg := &federation.Config{
19+
Upstream: "hop/wl-commons",
20+
ForkOrg: "alice",
21+
ForkDB: "wl-commons",
22+
LocalDir: "/tmp/test/wl-commons",
23+
RigHandle: "alice",
24+
JoinedAt: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
25+
}
26+
if err := store.Save(cfg); err != nil {
27+
t.Fatalf("Save() error: %v", err)
28+
}
29+
30+
// Build a minimal cobra command with the --wasteland flag
31+
cmd := &cobra.Command{}
32+
cmd.Flags().String("wasteland", "", "")
33+
34+
var stdout, stderr bytes.Buffer
35+
err := runLeave(cmd, &stdout, &stderr, "hop/wl-commons")
36+
if err != nil {
37+
t.Fatalf("runLeave() error: %v", err)
38+
}
39+
40+
got := stdout.String()
41+
if !strings.Contains(got, "Left wasteland") {
42+
t.Errorf("output missing 'Left wasteland': %q", got)
43+
}
44+
if !strings.Contains(got, "hop/wl-commons") {
45+
t.Errorf("output missing upstream: %q", got)
46+
}
47+
48+
// Verify config was deleted
49+
_, err = store.Load("hop/wl-commons")
50+
if err == nil {
51+
t.Error("config should be deleted after leave")
52+
}
53+
}
54+
55+
func TestRunLeave_NotJoined(t *testing.T) {
56+
tmpDir := t.TempDir()
57+
t.Setenv("XDG_CONFIG_HOME", tmpDir)
58+
59+
cmd := &cobra.Command{}
60+
cmd.Flags().String("wasteland", "", "")
61+
62+
var stdout, stderr bytes.Buffer
63+
err := runLeave(cmd, &stdout, &stderr, "hop/wl-commons")
64+
if err == nil {
65+
t.Fatal("runLeave() expected error for non-joined wasteland")
66+
}
67+
}
68+
69+
func TestRunLeave_AutoResolvesSingleWasteland(t *testing.T) {
70+
tmpDir := t.TempDir()
71+
t.Setenv("XDG_CONFIG_HOME", tmpDir)
72+
73+
store := federation.NewConfigStore()
74+
cfg := &federation.Config{
75+
Upstream: "hop/wl-commons",
76+
ForkOrg: "alice",
77+
ForkDB: "wl-commons",
78+
LocalDir: "/tmp/test/wl-commons",
79+
RigHandle: "alice",
80+
JoinedAt: time.Now(),
81+
}
82+
if err := store.Save(cfg); err != nil {
83+
t.Fatalf("Save() error: %v", err)
84+
}
85+
86+
// No positional arg, no --wasteland flag — should auto-resolve
87+
cmd := &cobra.Command{}
88+
cmd.Flags().String("wasteland", "", "")
89+
90+
var stdout, stderr bytes.Buffer
91+
err := runLeave(cmd, &stdout, &stderr, "")
92+
if err != nil {
93+
t.Fatalf("runLeave() error: %v", err)
94+
}
95+
if !strings.Contains(stdout.String(), "Left wasteland") {
96+
t.Errorf("output missing 'Left wasteland': %q", stdout.String())
97+
}
98+
}

cmd/wl/cmd_list_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/steveyegge/wasteland/internal/federation"
12+
)
13+
14+
func TestRunList_NoWastelands(t *testing.T) {
15+
tmpDir := t.TempDir()
16+
t.Setenv("XDG_CONFIG_HOME", tmpDir)
17+
18+
var stdout, stderr bytes.Buffer
19+
err := runList(&stdout, &stderr)
20+
if err != nil {
21+
t.Fatalf("runList() error: %v", err)
22+
}
23+
if !strings.Contains(stdout.String(), "No wastelands joined") {
24+
t.Errorf("output = %q, want to contain 'No wastelands joined'", stdout.String())
25+
}
26+
}
27+
28+
func TestRunList_SingleWasteland(t *testing.T) {
29+
tmpDir := t.TempDir()
30+
t.Setenv("XDG_CONFIG_HOME", tmpDir)
31+
32+
store := federation.NewConfigStore()
33+
cfg := &federation.Config{
34+
Upstream: "hop/wl-commons",
35+
ForkOrg: "alice",
36+
ForkDB: "wl-commons",
37+
LocalDir: "/tmp/test/wl-commons",
38+
RigHandle: "alice",
39+
JoinedAt: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
40+
}
41+
if err := store.Save(cfg); err != nil {
42+
t.Fatalf("Save() error: %v", err)
43+
}
44+
45+
var stdout, stderr bytes.Buffer
46+
err := runList(&stdout, &stderr)
47+
if err != nil {
48+
t.Fatalf("runList() error: %v", err)
49+
}
50+
51+
got := stdout.String()
52+
if !strings.Contains(got, "hop/wl-commons") {
53+
t.Errorf("output missing upstream: %q", got)
54+
}
55+
if !strings.Contains(got, "alice") {
56+
t.Errorf("output missing handle: %q", got)
57+
}
58+
if !strings.Contains(got, "1") {
59+
t.Errorf("output missing count: %q", got)
60+
}
61+
}
62+
63+
func TestRunList_MultipleWastelands(t *testing.T) {
64+
tmpDir := t.TempDir()
65+
t.Setenv("XDG_CONFIG_HOME", tmpDir)
66+
67+
store := federation.NewConfigStore()
68+
for _, cfg := range []*federation.Config{
69+
{
70+
Upstream: "hop/wl-commons",
71+
ForkOrg: "alice",
72+
ForkDB: "wl-commons",
73+
LocalDir: "/tmp/test1",
74+
RigHandle: "alice",
75+
JoinedAt: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
76+
},
77+
{
78+
Upstream: "bob/wl-commons",
79+
ForkOrg: "alice",
80+
ForkDB: "wl-commons",
81+
LocalDir: "/tmp/test2",
82+
RigHandle: "alice",
83+
JoinedAt: time.Date(2025, 2, 20, 0, 0, 0, 0, time.UTC),
84+
},
85+
} {
86+
if err := store.Save(cfg); err != nil {
87+
t.Fatalf("Save() error: %v", err)
88+
}
89+
}
90+
91+
var stdout, stderr bytes.Buffer
92+
err := runList(&stdout, &stderr)
93+
if err != nil {
94+
t.Fatalf("runList() error: %v", err)
95+
}
96+
97+
got := stdout.String()
98+
if !strings.Contains(got, "hop/wl-commons") {
99+
t.Errorf("output missing first upstream: %q", got)
100+
}
101+
if !strings.Contains(got, "bob/wl-commons") {
102+
t.Errorf("output missing second upstream: %q", got)
103+
}
104+
if !strings.Contains(got, "2") {
105+
t.Errorf("output missing count '2': %q", got)
106+
}
107+
}
108+
109+
func TestRunList_CorruptConfig(t *testing.T) {
110+
tmpDir := t.TempDir()
111+
t.Setenv("XDG_CONFIG_HOME", tmpDir)
112+
113+
// Create a valid config first so List() finds an upstream
114+
dir := filepath.Join(tmpDir, "wasteland", "wastelands", "hop")
115+
if err := os.MkdirAll(dir, 0o755); err != nil {
116+
t.Fatal(err)
117+
}
118+
// Write corrupt JSON
119+
if err := os.WriteFile(filepath.Join(dir, "wl-commons.json"), []byte("{bad json"), 0o644); err != nil {
120+
t.Fatal(err)
121+
}
122+
123+
var stdout, stderr bytes.Buffer
124+
err := runList(&stdout, &stderr)
125+
if err != nil {
126+
t.Fatalf("runList() should not error on corrupt config: %v", err)
127+
}
128+
// Error should be printed to stderr
129+
if !strings.Contains(stderr.String(), "error loading config") {
130+
t.Errorf("stderr = %q, want error message about corrupt config", stderr.String())
131+
}
132+
}

cmd/wl/cmd_post_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package main
22

33
import (
4+
"fmt"
5+
"strings"
46
"testing"
57

68
"github.com/steveyegge/wasteland/internal/commons"
@@ -68,6 +70,25 @@ func TestPostWanted_EmptyTitle(t *testing.T) {
6870
}
6971
}
7072

73+
func TestPostWanted_InsertFails(t *testing.T) {
74+
t.Parallel()
75+
store := newFakeWLCommonsStore()
76+
store.InsertWantedErr = fmt.Errorf("database write error")
77+
78+
item := &commons.WantedItem{
79+
ID: "w-test",
80+
Title: "Test item",
81+
}
82+
83+
err := postWanted(store, item)
84+
if err == nil {
85+
t.Fatal("postWanted() expected error when InsertWanted fails")
86+
}
87+
if !strings.Contains(err.Error(), "database write error") {
88+
t.Errorf("error = %q, want to contain 'database write error'", err.Error())
89+
}
90+
}
91+
7192
func TestValidatePostInputs_ValidType(t *testing.T) {
7293
t.Parallel()
7394
for _, typ := range []string{"feature", "bug", "design", "rfc", "docs", ""} {

0 commit comments

Comments
 (0)