Skip to content

Commit eb40e0c

Browse files
authored
Improve test coverage from ~85% to ~93% across all packages (#32)
1 parent 836b78e commit eb40e0c

18 files changed

Lines changed: 2942 additions & 71 deletions

src/cmd/doctor/doctor_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package doctor
22

33
import (
4+
"bytes"
5+
"fmt"
46
"os"
57
"os/exec"
68
"path/filepath"
9+
"strings"
710
"testing"
811

912
"github.com/8bitalex/raid/src/internal/lib"
@@ -64,3 +67,188 @@ func TestCommand_isConfigured(t *testing.T) {
6467
t.Error("Command.Run is nil")
6568
}
6669
}
70+
71+
// TestRunDoctor_allOK exercises the code path where every finding is SeverityOK,
72+
// which prints "No issues found." and exits normally (no os.Exit).
73+
func TestRunDoctor_allOK(t *testing.T) {
74+
dir := t.TempDir()
75+
old := lib.CfgPath
76+
t.Cleanup(func() {
77+
lib.CfgPath = old
78+
lib.ResetContext()
79+
viper.Reset()
80+
})
81+
lib.CfgPath = filepath.Join(dir, "config.toml")
82+
lib.ResetContext()
83+
if err := lib.InitConfig(); err != nil {
84+
t.Fatalf("InitConfig: %v", err)
85+
}
86+
87+
// Create a valid profile with a repo pointing to an existing directory
88+
// (so the doctor doesn't report any warnings or errors).
89+
repoDir := t.TempDir()
90+
// Make it look like a git repo by creating .git dir
91+
if err := os.MkdirAll(filepath.Join(repoDir, ".git"), 0755); err != nil {
92+
t.Fatal(err)
93+
}
94+
95+
profilePath := filepath.Join(dir, "ok.raid.yaml")
96+
content := fmt.Sprintf("name: ok\nrepositories:\n - name: repo1\n url: https://example.com/repo.git\n path: %s\n", repoDir)
97+
if err := os.WriteFile(profilePath, []byte(content), 0644); err != nil {
98+
t.Fatal(err)
99+
}
100+
101+
if err := lib.AddProfile(lib.Profile{Name: "ok", Path: profilePath}); err != nil {
102+
t.Fatal(err)
103+
}
104+
if err := lib.SetProfile("ok"); err != nil {
105+
t.Fatal(err)
106+
}
107+
if err := lib.ForceLoad(); err != nil {
108+
t.Fatalf("ForceLoad: %v", err)
109+
}
110+
111+
// Capture stdout (runDoctor uses fmt.Printf → os.Stdout)
112+
r, w, err := os.Pipe()
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
oldStdout := os.Stdout
117+
os.Stdout = w
118+
119+
cmd := &cobra.Command{}
120+
runDoctor(cmd, nil)
121+
122+
w.Close()
123+
os.Stdout = oldStdout
124+
125+
var buf bytes.Buffer
126+
buf.ReadFrom(r)
127+
got := buf.String()
128+
129+
if !strings.Contains(got, "[ok]") {
130+
t.Errorf("runDoctor allOK: expected '[ok]' in output, got %q", got)
131+
}
132+
if !strings.Contains(got, "No issues found") {
133+
t.Errorf("runDoctor allOK: expected 'No issues found', got %q", got)
134+
}
135+
}
136+
137+
// TestRunDoctor_warningsOnly exercises the path where warnings exist but no errors,
138+
// which prints the warning count and does NOT call os.Exit.
139+
func TestRunDoctor_warningsOnly(t *testing.T) {
140+
dir := t.TempDir()
141+
old := lib.CfgPath
142+
t.Cleanup(func() {
143+
lib.CfgPath = old
144+
lib.ResetContext()
145+
viper.Reset()
146+
})
147+
lib.CfgPath = filepath.Join(dir, "config.toml")
148+
lib.ResetContext()
149+
if err := lib.InitConfig(); err != nil {
150+
t.Fatalf("InitConfig: %v", err)
151+
}
152+
153+
// Create a profile with repos pointing to non-existent paths.
154+
// Doctor will report: git OK, profile OK, profile file OK, schema OK,
155+
// but repos not cloned → Warn findings only.
156+
profilePath := filepath.Join(dir, "warn.raid.yaml")
157+
content := "name: warn\nrepositories:\n - name: missing-repo\n url: https://example.com/repo.git\n path: /tmp/nonexistent-path-raid-test-12345\n"
158+
if err := os.WriteFile(profilePath, []byte(content), 0644); err != nil {
159+
t.Fatal(err)
160+
}
161+
162+
if err := lib.AddProfile(lib.Profile{Name: "warn", Path: profilePath}); err != nil {
163+
t.Fatal(err)
164+
}
165+
if err := lib.SetProfile("warn"); err != nil {
166+
t.Fatal(err)
167+
}
168+
if err := lib.ForceLoad(); err != nil {
169+
t.Fatalf("ForceLoad: %v", err)
170+
}
171+
172+
r, w, err := os.Pipe()
173+
if err != nil {
174+
t.Fatal(err)
175+
}
176+
oldStdout := os.Stdout
177+
os.Stdout = w
178+
179+
cmd := &cobra.Command{}
180+
runDoctor(cmd, nil)
181+
182+
w.Close()
183+
os.Stdout = oldStdout
184+
185+
var buf bytes.Buffer
186+
buf.ReadFrom(r)
187+
got := buf.String()
188+
189+
if !strings.Contains(got, "[warn]") {
190+
t.Errorf("runDoctor warnings: expected '[warn]' in output, got %q", got)
191+
}
192+
if !strings.Contains(got, "warning(s)") {
193+
t.Errorf("runDoctor warnings: expected 'warning(s)' in output, got %q", got)
194+
}
195+
// Should show the suggestion arrow
196+
if !strings.Contains(got, "→") {
197+
t.Errorf("runDoctor warnings: expected suggestion arrow '→' in output, got %q", got)
198+
}
199+
}
200+
201+
// TestRunDoctor_noReposWarning tests the path where the profile has no repositories
202+
// configured, which produces a warning finding.
203+
func TestRunDoctor_noReposWarning(t *testing.T) {
204+
dir := t.TempDir()
205+
old := lib.CfgPath
206+
t.Cleanup(func() {
207+
lib.CfgPath = old
208+
lib.ResetContext()
209+
viper.Reset()
210+
})
211+
lib.CfgPath = filepath.Join(dir, "config.toml")
212+
lib.ResetContext()
213+
if err := lib.InitConfig(); err != nil {
214+
t.Fatalf("InitConfig: %v", err)
215+
}
216+
217+
profilePath := filepath.Join(dir, "norepo.raid.yaml")
218+
if err := os.WriteFile(profilePath, []byte("name: norepo\n"), 0644); err != nil {
219+
t.Fatal(err)
220+
}
221+
if err := lib.AddProfile(lib.Profile{Name: "norepo", Path: profilePath}); err != nil {
222+
t.Fatal(err)
223+
}
224+
if err := lib.SetProfile("norepo"); err != nil {
225+
t.Fatal(err)
226+
}
227+
if err := lib.ForceLoad(); err != nil {
228+
t.Fatalf("ForceLoad: %v", err)
229+
}
230+
231+
r, w, err := os.Pipe()
232+
if err != nil {
233+
t.Fatal(err)
234+
}
235+
oldStdout := os.Stdout
236+
os.Stdout = w
237+
238+
cmd := &cobra.Command{}
239+
runDoctor(cmd, nil)
240+
241+
w.Close()
242+
os.Stdout = oldStdout
243+
244+
var buf bytes.Buffer
245+
buf.ReadFrom(r)
246+
got := buf.String()
247+
248+
if !strings.Contains(got, "[warn]") {
249+
t.Errorf("runDoctor no repos: expected '[warn]' in output, got %q", got)
250+
}
251+
if !strings.Contains(got, "none configured") {
252+
t.Errorf("runDoctor no repos: expected 'none configured' in output, got %q", got)
253+
}
254+
}

src/cmd/env/env_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9+
"runtime"
910
"strings"
1011
"testing"
1112

@@ -222,3 +223,124 @@ func TestListEnvCmd_withEnvironments(t *testing.T) {
222223
t.Errorf("ListEnvCmd with envs: got %q, want 'staging'", got)
223224
}
224225
}
226+
227+
func TestCommand_envFound_fullSuccess(t *testing.T) {
228+
setupConfigWithEnv(t, "success-profile", "prod")
229+
230+
var buf bytes.Buffer
231+
fakeCmd := &cobra.Command{}
232+
fakeCmd.SetOut(&buf)
233+
fakeCmd.SetErr(&buf)
234+
Command.Run(fakeCmd, []string{"prod"})
235+
236+
got := buf.String()
237+
if !strings.Contains(got, "Environment executed successfully") {
238+
t.Errorf("Command env success: got %q, want 'Environment executed successfully'", got)
239+
}
240+
}
241+
242+
func TestCommand_envFound_forceLoadError(t *testing.T) {
243+
setupConfigWithEnv(t, "exec-err-profile", "failing")
244+
245+
// Delete the profile file so that ForceLoad (which re-reads it) fails,
246+
// while the cached context still has the env for Contains to succeed.
247+
profilePath := lib.GetProfile().Path
248+
if err := os.Remove(profilePath); err != nil {
249+
t.Fatalf("remove profile file: %v", err)
250+
}
251+
252+
var buf bytes.Buffer
253+
fakeCmd := &cobra.Command{}
254+
fakeCmd.SetOut(&buf)
255+
fakeCmd.SetErr(&buf)
256+
Command.Run(fakeCmd, []string{"failing"})
257+
258+
got := buf.String()
259+
if !strings.Contains(got, "Failed to reload profile") {
260+
t.Errorf("Command env forceLoad error: got %q, want 'Failed to reload profile'", got)
261+
}
262+
}
263+
264+
// TestCommand_envFound_setError covers the env.Set error path by calling
265+
// with an env name that doesn't exist in the config but passes Contains
266+
// via a direct context manipulation.
267+
func TestCommand_envFound_executeError(t *testing.T) {
268+
// Set up an env with a task that will fail.
269+
repoRoot := repoRootForEnv(t)
270+
271+
dir := t.TempDir()
272+
old := lib.CfgPath
273+
t.Cleanup(func() {
274+
lib.CfgPath = old
275+
lib.ResetContext()
276+
viper.Reset()
277+
})
278+
lib.CfgPath = filepath.Join(dir, "config.toml")
279+
lib.ResetContext()
280+
if err := lib.InitConfig(); err != nil {
281+
t.Fatalf("InitConfig: %v", err)
282+
}
283+
284+
// Profile with env that has a failing task.
285+
profilePath := filepath.Join(dir, "failenv.raid.yaml")
286+
content := "name: failenv\nenvironments:\n - name: badenv\n tasks:\n - type: Shell\n cmd: exit 1\n"
287+
if err := os.WriteFile(profilePath, []byte(content), 0644); err != nil {
288+
t.Fatal(err)
289+
}
290+
if err := lib.AddProfile(lib.Profile{Name: "failenv", Path: profilePath}); err != nil {
291+
t.Fatal(err)
292+
}
293+
if err := lib.SetProfile("failenv"); err != nil {
294+
t.Fatal(err)
295+
}
296+
297+
wd, _ := os.Getwd()
298+
if err := os.Chdir(repoRoot); err != nil {
299+
t.Fatalf("chdir: %v", err)
300+
}
301+
t.Cleanup(func() { os.Chdir(wd) })
302+
303+
if err := lib.ForceLoad(); err != nil {
304+
t.Fatalf("ForceLoad: %v", err)
305+
}
306+
307+
var buf bytes.Buffer
308+
fakeCmd := &cobra.Command{}
309+
fakeCmd.SetOut(&buf)
310+
fakeCmd.SetErr(&buf)
311+
Command.Run(fakeCmd, []string{"badenv"})
312+
313+
got := buf.String()
314+
if !strings.Contains(got, "Failed to execute environment") {
315+
t.Errorf("Command env execute error: got %q, want 'Failed to execute environment'", got)
316+
}
317+
}
318+
319+
// TestCommand_envSetError covers the env.Set error path by making the config
320+
// file read-only after setup so viper.WriteConfig fails.
321+
func TestCommand_envSetError(t *testing.T) {
322+
if os.Getuid() == 0 {
323+
t.Skip("file permissions not enforced as root")
324+
}
325+
if runtime.GOOS == "windows" {
326+
t.Skip("chmod file permissions behave differently on Windows")
327+
}
328+
setupConfigWithEnv(t, "setfail-profile", "dev")
329+
330+
// Make the config file read-only so Set (which calls viper.WriteConfig) fails.
331+
if err := os.Chmod(lib.CfgPath, 0444); err != nil {
332+
t.Fatal(err)
333+
}
334+
t.Cleanup(func() { os.Chmod(lib.CfgPath, 0644) })
335+
336+
var buf bytes.Buffer
337+
fakeCmd := &cobra.Command{}
338+
fakeCmd.SetOut(&buf)
339+
fakeCmd.SetErr(&buf)
340+
Command.Run(fakeCmd, []string{"dev"})
341+
342+
got := buf.String()
343+
if !strings.Contains(got, "Failed to switch environment") {
344+
t.Errorf("Command env setError: got %q, want 'Failed to switch environment'", got)
345+
}
346+
}

0 commit comments

Comments
 (0)