Skip to content

Commit 00111c5

Browse files
Add tests for cmd.newCompletionCmd
Comprehensive test coverage for the newCompletionCmd function in internal/cmd/completion.go, which previously had zero test coverage. Tests added: - TestNewCompletionCmd_CommandStructure: verifies Use, Short, Long, DisableFlagsInUseLine, and ValidArgs metadata - TestNewCompletionCmd_PersistentPreRunE: verifies the override always returns nil, bypassing the root's config-validation preRun - TestNewCompletionCmd_ArgsValidation: table-driven test covering all four valid shells plus invalid cases (empty, unknown, too many args) - TestNewCompletionCmd_BashOutput: verifies bash completion generates non-empty output with expected shell tokens - TestNewCompletionCmd_ZshOutput: verifies zsh completion generates non-empty output - TestNewCompletionCmd_FishOutput: verifies fish completion generates non-empty output - TestNewCompletionCmd_PowerShellOutput: verifies powershell completion generates non-empty output - TestNewCompletionCmd_DefaultCaseFallback: exercises the defensive default branch directly via RunE to achieve 100% branch coverage - TestNewCompletionCmd_AllShellsProduceDifferentOutput: ensures each shell generates a distinct completion script - TestNewCompletionCmd_OverridesParentPersistentPreRunE: regression guard confirming completions work without a config file Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ac8733a commit 00111c5

1 file changed

Lines changed: 284 additions & 0 deletions

File tree

internal/cmd/completion_test.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
"github.com/spf13/cobra"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
// captureStdoutDuring redirects os.Stdout to a pipe, calls fn, then restores
16+
// os.Stdout immediately and returns the captured output. A t.Cleanup safety net
17+
// ensures os.Stdout is always restored even if fn panics. Not safe for parallel
18+
// tests since os.Stdout is a process-global.
19+
func captureStdoutDuring(t *testing.T, fn func()) string {
20+
t.Helper()
21+
r, w, err := os.Pipe()
22+
require.NoError(t, err)
23+
24+
orig := os.Stdout
25+
os.Stdout = w
26+
// Safety net: restore if the function panics before we can restore manually.
27+
t.Cleanup(func() {
28+
if os.Stdout != orig {
29+
os.Stdout = orig
30+
}
31+
})
32+
33+
fn()
34+
35+
w.Close()
36+
os.Stdout = orig // restore immediately so repeated calls in the same test work
37+
38+
var buf bytes.Buffer
39+
_, err = io.Copy(&buf, r)
40+
r.Close()
41+
require.NoError(t, err)
42+
43+
return buf.String()
44+
}
45+
46+
// newTestRootWithCompletion creates an isolated root command with only the
47+
// completion sub-command attached. Keeping it isolated avoids accidentally
48+
// triggering the real rootCmd's PersistentPreRunE (which requires a config
49+
// file) during unit tests.
50+
func newTestRootWithCompletion() (*cobra.Command, *cobra.Command) {
51+
root := &cobra.Command{
52+
Use: "awmg",
53+
}
54+
completion := newCompletionCmd()
55+
root.AddCommand(completion)
56+
return root, completion
57+
}
58+
59+
// TestNewCompletionCmd_CommandStructure verifies metadata set on the command.
60+
func TestNewCompletionCmd_CommandStructure(t *testing.T) {
61+
cmd := newCompletionCmd()
62+
require.NotNil(t, cmd, "newCompletionCmd() must not return nil")
63+
64+
assert.Equal(t, "completion [bash|zsh|fish|powershell]", cmd.Use)
65+
assert.NotEmpty(t, cmd.Short, "Short description must not be empty")
66+
assert.NotEmpty(t, cmd.Long, "Long description must not be empty")
67+
assert.True(t, cmd.DisableFlagsInUseLine, "DisableFlagsInUseLine must be true")
68+
assert.ElementsMatch(t,
69+
[]string{"bash", "zsh", "fish", "powershell"},
70+
cmd.ValidArgs,
71+
"ValidArgs should contain exactly the four supported shells",
72+
)
73+
}
74+
75+
// TestNewCompletionCmd_PersistentPreRunE verifies the override returns nil so
76+
// the root's config-validation preRun is not triggered when running completions.
77+
func TestNewCompletionCmd_PersistentPreRunE(t *testing.T) {
78+
cmd := newCompletionCmd()
79+
require.NotNil(t, cmd.PersistentPreRunE,
80+
"PersistentPreRunE must be set to override parent validation")
81+
82+
// It must always succeed regardless of args/command context.
83+
err := cmd.PersistentPreRunE(cmd, nil)
84+
assert.NoError(t, err)
85+
86+
err = cmd.PersistentPreRunE(cmd, []string{"bash"})
87+
assert.NoError(t, err)
88+
}
89+
90+
// TestNewCompletionCmd_ArgsValidation exercises the cobra.MatchAll validator
91+
// (ExactArgs(1) + OnlyValidArgs) directly without executing the RunE handler.
92+
func TestNewCompletionCmd_ArgsValidation(t *testing.T) {
93+
tests := []struct {
94+
name string
95+
args []string
96+
wantErr bool
97+
}{
98+
{
99+
name: "bash is valid",
100+
args: []string{"bash"},
101+
wantErr: false,
102+
},
103+
{
104+
name: "zsh is valid",
105+
args: []string{"zsh"},
106+
wantErr: false,
107+
},
108+
{
109+
name: "fish is valid",
110+
args: []string{"fish"},
111+
wantErr: false,
112+
},
113+
{
114+
name: "powershell is valid",
115+
args: []string{"powershell"},
116+
wantErr: false,
117+
},
118+
{
119+
name: "no arguments",
120+
args: []string{},
121+
wantErr: true,
122+
},
123+
{
124+
name: "two arguments",
125+
args: []string{"bash", "zsh"},
126+
wantErr: true,
127+
},
128+
{
129+
name: "unknown shell tcsh",
130+
args: []string{"tcsh"},
131+
wantErr: true,
132+
},
133+
{
134+
name: "empty string argument",
135+
args: []string{""},
136+
wantErr: true,
137+
},
138+
}
139+
140+
for _, tt := range tests {
141+
t.Run(tt.name, func(t *testing.T) {
142+
_, completionCmd := newTestRootWithCompletion()
143+
err := completionCmd.Args(completionCmd, tt.args)
144+
if tt.wantErr {
145+
assert.Error(t, err, "expected an args validation error")
146+
} else {
147+
assert.NoError(t, err, "expected no args validation error")
148+
}
149+
})
150+
}
151+
}
152+
153+
// TestNewCompletionCmd_BashOutput verifies that running "completion bash"
154+
// produces non-empty Bash-completion script output.
155+
func TestNewCompletionCmd_BashOutput(t *testing.T) {
156+
root, _ := newTestRootWithCompletion()
157+
158+
output := captureStdoutDuring(t, func() {
159+
root.SetArgs([]string{"completion", "bash"})
160+
err := root.Execute()
161+
require.NoError(t, err)
162+
})
163+
164+
assert.NotEmpty(t, output, "bash completion output must not be empty")
165+
// Bash completion scripts contain the function keyword or compgen/complete.
166+
assert.True(t,
167+
strings.Contains(output, "bash") ||
168+
strings.Contains(output, "complete") ||
169+
strings.Contains(output, "__awmg"),
170+
"bash completion output should contain shell-specific tokens, got: %s", output)
171+
}
172+
173+
// TestNewCompletionCmd_ZshOutput verifies that running "completion zsh"
174+
// produces non-empty Zsh-completion script output.
175+
func TestNewCompletionCmd_ZshOutput(t *testing.T) {
176+
root, _ := newTestRootWithCompletion()
177+
178+
output := captureStdoutDuring(t, func() {
179+
root.SetArgs([]string{"completion", "zsh"})
180+
err := root.Execute()
181+
require.NoError(t, err)
182+
})
183+
184+
assert.NotEmpty(t, output, "zsh completion output must not be empty")
185+
}
186+
187+
// TestNewCompletionCmd_FishOutput verifies that running "completion fish"
188+
// produces non-empty Fish-completion script output.
189+
func TestNewCompletionCmd_FishOutput(t *testing.T) {
190+
root, _ := newTestRootWithCompletion()
191+
192+
output := captureStdoutDuring(t, func() {
193+
root.SetArgs([]string{"completion", "fish"})
194+
err := root.Execute()
195+
require.NoError(t, err)
196+
})
197+
198+
assert.NotEmpty(t, output, "fish completion output must not be empty")
199+
}
200+
201+
// TestNewCompletionCmd_PowerShellOutput verifies that running
202+
// "completion powershell" produces non-empty PowerShell-completion output.
203+
func TestNewCompletionCmd_PowerShellOutput(t *testing.T) {
204+
root, _ := newTestRootWithCompletion()
205+
206+
output := captureStdoutDuring(t, func() {
207+
root.SetArgs([]string{"completion", "powershell"})
208+
err := root.Execute()
209+
require.NoError(t, err)
210+
})
211+
212+
assert.NotEmpty(t, output, "powershell completion output must not be empty")
213+
}
214+
215+
// TestNewCompletionCmd_DefaultCaseFallback exercises the unreachable default
216+
// branch directly via RunE to satisfy branch coverage. In practice the
217+
// cobra.OnlyValidArgs validator prevents unknown shells from reaching RunE, but
218+
// the defensive error path must still produce a meaningful message.
219+
func TestNewCompletionCmd_DefaultCaseFallback(t *testing.T) {
220+
_, completionCmd := newTestRootWithCompletion()
221+
222+
// Bypass Args validation and call RunE directly.
223+
err := completionCmd.RunE(completionCmd, []string{"unsupported-shell"})
224+
require.Error(t, err, "unsupported shell should return an error")
225+
assert.Contains(t, err.Error(), "unsupported shell type",
226+
"error message should describe the unsupported shell type")
227+
assert.Contains(t, err.Error(), "unsupported-shell",
228+
"error message should include the provided shell name")
229+
}
230+
231+
// TestNewCompletionCmd_AllShellsProduceDifferentOutput verifies that each
232+
// shell produces distinct output — they must not accidentally share a handler.
233+
func TestNewCompletionCmd_AllShellsProduceDifferentOutput(t *testing.T) {
234+
shells := []string{"bash", "zsh", "fish", "powershell"}
235+
outputs := make(map[string]string, len(shells))
236+
237+
for _, shell := range shells {
238+
root, _ := newTestRootWithCompletion()
239+
shell := shell // capture loop variable
240+
output := captureStdoutDuring(t, func() {
241+
root.SetArgs([]string{"completion", shell})
242+
err := root.Execute()
243+
require.NoError(t, err, "shell=%s: completion should not error", shell)
244+
})
245+
assert.NotEmpty(t, output, "shell=%s: output must not be empty", shell)
246+
outputs[shell] = output
247+
}
248+
249+
// Each shell should produce unique output.
250+
seen := make(map[string]string, len(shells))
251+
for shell, out := range outputs {
252+
for prevShell, prevOut := range seen {
253+
assert.NotEqual(t, prevOut, out,
254+
"shells %q and %q must produce different completion scripts", prevShell, shell)
255+
}
256+
seen[shell] = out
257+
}
258+
}
259+
260+
// TestNewCompletionCmd_OverridesParentPersistentPreRunE confirms that the
261+
// completion command does not inherit the root's PersistentPreRunE — a real-
262+
// world regression guard ensuring completions work without a valid config file.
263+
func TestNewCompletionCmd_OverridesParentPersistentPreRunE(t *testing.T) {
264+
root := &cobra.Command{
265+
Use: "awmg",
266+
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
267+
// Simulate the real root's config validation, which would fail
268+
// when no config file is present.
269+
return assert.AnError
270+
},
271+
}
272+
completion := newCompletionCmd()
273+
root.AddCommand(completion)
274+
275+
output := captureStdoutDuring(t, func() {
276+
root.SetArgs([]string{"completion", "bash"})
277+
err := root.Execute()
278+
// Must succeed even though the root's PersistentPreRunE would fail.
279+
assert.NoError(t, err,
280+
"completion command must override parent PersistentPreRunE and succeed")
281+
})
282+
283+
assert.NotEmpty(t, output, "output must not be empty when pre-run override is active")
284+
}

0 commit comments

Comments
 (0)