mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2026-01-27 03:04:21 +01:00
- Add ValidArgsFunction to RootCmd for dynamic host completion - Add 'sshm completion' subcommand for bash/zsh/fish/powershell - Support prefix matching and case-insensitive filtering - Respect --config flag for custom SSH config files - Add comprehensive tests for completion functionality - Document setup instructions in README Co-authored-by: Guillaume Archambault <67098259+Gu1llaum-3@users.noreply.github.com>
286 lines
6.6 KiB
Go
286 lines
6.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestCompletionCommand(t *testing.T) {
|
|
if completionCmd.Use != "completion [bash|zsh|fish|powershell]" {
|
|
t.Errorf("Expected Use 'completion [bash|zsh|fish|powershell]', got '%s'", completionCmd.Use)
|
|
}
|
|
|
|
if completionCmd.Short != "Generate shell completion script" {
|
|
t.Errorf("Expected Short description, got '%s'", completionCmd.Short)
|
|
}
|
|
}
|
|
|
|
func TestCompletionCommandValidArgs(t *testing.T) {
|
|
expected := []string{"bash", "zsh", "fish", "powershell"}
|
|
|
|
if len(completionCmd.ValidArgs) != len(expected) {
|
|
t.Errorf("Expected %d valid args, got %d", len(expected), len(completionCmd.ValidArgs))
|
|
}
|
|
|
|
for i, arg := range expected {
|
|
if completionCmd.ValidArgs[i] != arg {
|
|
t.Errorf("Expected ValidArgs[%d] to be '%s', got '%s'", i, arg, completionCmd.ValidArgs[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompletionCommandRegistered(t *testing.T) {
|
|
found := false
|
|
for _, cmd := range RootCmd.Commands() {
|
|
if cmd.Name() == "completion" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Error("Expected 'completion' command to be registered")
|
|
}
|
|
}
|
|
|
|
func TestCompletionBashOutput(t *testing.T) {
|
|
oldStdout := os.Stdout
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
|
|
RootCmd.SetArgs([]string{"completion", "bash"})
|
|
err := RootCmd.Execute()
|
|
|
|
w.Close()
|
|
os.Stdout = oldStdout
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error for bash completion, got %v", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "bash completion") || !strings.Contains(output, "sshm") {
|
|
t.Error("Bash completion output should contain bash completion markers and sshm")
|
|
}
|
|
}
|
|
|
|
func TestCompletionZshOutput(t *testing.T) {
|
|
oldStdout := os.Stdout
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
|
|
RootCmd.SetArgs([]string{"completion", "zsh"})
|
|
err := RootCmd.Execute()
|
|
|
|
w.Close()
|
|
os.Stdout = oldStdout
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error for zsh completion, got %v", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "compdef") || !strings.Contains(output, "sshm") {
|
|
t.Error("Zsh completion output should contain compdef and sshm")
|
|
}
|
|
}
|
|
|
|
func TestCompletionFishOutput(t *testing.T) {
|
|
oldStdout := os.Stdout
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
|
|
RootCmd.SetArgs([]string{"completion", "fish"})
|
|
err := RootCmd.Execute()
|
|
|
|
w.Close()
|
|
os.Stdout = oldStdout
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error for fish completion, got %v", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "complete") || !strings.Contains(output, "sshm") {
|
|
t.Error("Fish completion output should contain complete command and sshm")
|
|
}
|
|
}
|
|
|
|
func TestCompletionPowershellOutput(t *testing.T) {
|
|
oldStdout := os.Stdout
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
|
|
RootCmd.SetArgs([]string{"completion", "powershell"})
|
|
err := RootCmd.Execute()
|
|
|
|
w.Close()
|
|
os.Stdout = oldStdout
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error for powershell completion, got %v", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "Register-ArgumentCompleter") || !strings.Contains(output, "sshm") {
|
|
t.Error("PowerShell completion output should contain Register-ArgumentCompleter and sshm")
|
|
}
|
|
}
|
|
|
|
func TestCompletionInvalidShell(t *testing.T) {
|
|
RootCmd.SetArgs([]string{"completion", "invalid"})
|
|
err := RootCmd.Execute()
|
|
|
|
if err == nil {
|
|
t.Error("Expected error for invalid shell type")
|
|
}
|
|
}
|
|
|
|
func TestCompletionNoArgs(t *testing.T) {
|
|
RootCmd.SetArgs([]string{"completion"})
|
|
err := RootCmd.Execute()
|
|
|
|
if err == nil {
|
|
t.Error("Expected error when no shell type provided")
|
|
}
|
|
}
|
|
|
|
func TestValidArgsFunction(t *testing.T) {
|
|
if RootCmd.ValidArgsFunction == nil {
|
|
t.Fatal("Expected ValidArgsFunction to be set on RootCmd")
|
|
}
|
|
}
|
|
|
|
func TestValidArgsFunctionWithSSHConfig(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testConfigFile := filepath.Join(tmpDir, "config")
|
|
|
|
sshConfig := `Host prod-server
|
|
HostName 192.168.1.1
|
|
User admin
|
|
|
|
Host dev-server
|
|
HostName 192.168.1.2
|
|
User developer
|
|
|
|
Host staging-db
|
|
HostName 192.168.1.3
|
|
User dbadmin
|
|
`
|
|
err := os.WriteFile(testConfigFile, []byte(sshConfig), 0600)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write test config: %v", err)
|
|
}
|
|
|
|
originalConfigFile := configFile
|
|
defer func() { configFile = originalConfigFile }()
|
|
configFile = testConfigFile
|
|
|
|
tests := []struct {
|
|
name string
|
|
toComplete string
|
|
args []string
|
|
wantCount int
|
|
wantHosts []string
|
|
}{
|
|
{
|
|
name: "empty prefix returns all hosts",
|
|
toComplete: "",
|
|
args: []string{},
|
|
wantCount: 3,
|
|
wantHosts: []string{"prod-server", "dev-server", "staging-db"},
|
|
},
|
|
{
|
|
name: "prefix filters hosts",
|
|
toComplete: "prod",
|
|
args: []string{},
|
|
wantCount: 1,
|
|
wantHosts: []string{"prod-server"},
|
|
},
|
|
{
|
|
name: "prefix case insensitive",
|
|
toComplete: "DEV",
|
|
args: []string{},
|
|
wantCount: 1,
|
|
wantHosts: []string{"dev-server"},
|
|
},
|
|
{
|
|
name: "no match returns empty",
|
|
toComplete: "nonexistent",
|
|
args: []string{},
|
|
wantCount: 0,
|
|
wantHosts: []string{},
|
|
},
|
|
{
|
|
name: "already has host arg returns nothing",
|
|
toComplete: "",
|
|
args: []string{"existing-host"},
|
|
wantCount: 0,
|
|
wantHosts: []string{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
completions, directive := RootCmd.ValidArgsFunction(RootCmd, tt.args, tt.toComplete)
|
|
|
|
if len(completions) != tt.wantCount {
|
|
t.Errorf("Expected %d completions, got %d: %v", tt.wantCount, len(completions), completions)
|
|
}
|
|
|
|
if directive != cobra.ShellCompDirectiveNoFileComp {
|
|
t.Errorf("Expected ShellCompDirectiveNoFileComp, got %v", directive)
|
|
}
|
|
|
|
for _, wantHost := range tt.wantHosts {
|
|
found := false
|
|
for _, comp := range completions {
|
|
if comp == wantHost {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Expected completion '%s' not found in %v", wantHost, completions)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidArgsFunctionWithNonExistentConfig(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
nonExistentConfig := filepath.Join(tmpDir, "nonexistent")
|
|
|
|
originalConfigFile := configFile
|
|
defer func() { configFile = originalConfigFile }()
|
|
configFile = nonExistentConfig
|
|
|
|
completions, directive := RootCmd.ValidArgsFunction(RootCmd, []string{}, "")
|
|
|
|
if directive != cobra.ShellCompDirectiveNoFileComp {
|
|
t.Errorf("Expected ShellCompDirectiveNoFileComp for non-existent config, got %v", directive)
|
|
}
|
|
|
|
if len(completions) != 0 {
|
|
t.Errorf("Expected empty completions for non-existent config, got %v", completions)
|
|
}
|
|
}
|