Skip to content

Commit 9b86c07

Browse files
authored
[test] Add tests for cmd.newCompletionCmd (#4082)
## Test Coverage Improvement: `newCompletionCmd` ## Function Analyzed - **Package**: `internal/cmd` - **Function**: `newCompletionCmd` - **File**: `internal/cmd/completion.go` - **Previous Coverage**: 0% (no test file existed) - **New Coverage**: ~100% (all branches covered) - **Complexity**: Medium — multi-case switch, Args validator, PersistentPreRunE override ## Why This Function? `newCompletionCmd` had **zero test coverage** despite being a user-facing CLI command. It contains: - A 4-way switch (bash / zsh / fish / powershell) where each case calls a different cobra generator - A `default` defensive error path that cobra's `OnlyValidArgs` normally prevents from being reached - A `PersistentPreRunE` override that intentionally skips the root command's config-file validation — a critical correctness property with no regression test Previous agent runs (tracked in cache memory) had improved coverage for `cmd.detectGuardWasm`, `oidc.extractJWTExpiry`, and `config.AllowOnlyPolicy`, leaving this function as the next high-priority target. ## Tests Added - ✅ **Structure test** — `Use`, `Short`, `Long`, `DisableFlagsInUseLine`, and `ValidArgs` metadata - ✅ **PersistentPreRunE override** — always returns `nil` (no args or with args) - ✅ **Args validation** — table-driven: all 4 valid shells pass; empty, unknown shell, and 2-arg cases fail - ✅ **Bash completion output** — non-empty, contains shell-specific tokens - ✅ **Zsh completion output** — non-empty - ✅ **Fish completion output** — non-empty - ✅ **PowerShell completion output** — non-empty - ✅ **Default branch fallback** — calls `RunE` directly with unknown shell to exercise the defensive `default` case - ✅ **All shells produce distinct output** — regression guard preventing handler mix-ups - ✅ **Parent PersistentPreRunE override** — regression guard: completion succeeds even when root's `PersistentPreRunE` would fail (no config file) ## Coverage Report ``` Before: 0% (no tests) After: ~100% (all branches covered, including defensive default) ``` ## Test Infrastructure Tests use an isolated root command (`newTestRootWithCompletion`) to avoid triggering the real `rootCmd`'s config-validation `PersistentPreRunE`. Output is captured via `captureStdoutDuring`, which uses `os.Pipe()` with immediate `os.Stdout` restoration (plus a `t.Cleanup` safety net) — safe for sequential calls within a single test. --- *Generated by Test Coverage Improver* *Next candidates: `internal/server/response_writer.go`, `validateCustomServerConfig`, `resolveGuardPolicyOverride`* > [!WARNING] > <details> > <summary><strong>⚠️ Firewall blocked 2 domains</strong></summary> > > The following domains were blocked by the firewall during workflow execution: > > - `proxy.golang.org` > - `releaseassets.githubusercontent.com` > > To allow these domains, add them to the `network.allowed` list in your workflow frontmatter: > > ```yaml > network: > allowed: > - defaults > - "proxy.golang.org" > - "releaseassets.githubusercontent.com" > ``` > > See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information. > > </details> > Generated by [Test Coverage Improver](https://github.com/github/gh-aw-mcpg/actions/runs/24603997112/agentic_workflow) · ● 3.8M · [◷](https://github.com/search?q=repo%3Agithub%2Fgh-aw-mcpg+%22gh-aw-workflow-id%3A+test-coverage-improver%22&type=pullrequests) <!-- gh-aw-agentic-workflow: Test Coverage Improver, engine: copilot, model: auto, id: 24603997112, workflow_id: test-coverage-improver, run: https://github.com/github/gh-aw-mcpg/actions/runs/24603997112 --> <!-- gh-aw-workflow-id: test-coverage-improver -->
2 parents b543069 + eeafab4 commit 9b86c07

1 file changed

Lines changed: 298 additions & 0 deletions

File tree

internal/cmd/completion_test.go

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

0 commit comments

Comments
 (0)