Skip to content

Commit

Permalink
Improve install coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
XiaoConstantine committed Jul 9, 2024
1 parent 9833b2e commit 366cfcb
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 62 deletions.
2 changes: 2 additions & 0 deletions pkg/commands/install/homebrew/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func NewInstallToolsCmd(iostream *iostreams.IOStreams) *cobra.Command {
cs := iostream.ColorScheme()
var configFile string
var force bool
var nonInteractive bool

cmd := &cobra.Command{
Use: "tools",
Expand All @@ -40,6 +41,7 @@ func NewInstallToolsCmd(iostream *iostreams.IOStreams) *cobra.Command {
}
cmd.Flags().StringVarP(&configFile, "config", "c", "config.yaml", "Path to the configuration file")
cmd.Flags().BoolVarP(&force, "force", "f", false, "Force reinstall of casks")
cmd.Flags().BoolVar(&nonInteractive, "non-interactive", false, "Run in non-interactive mode")
return cmd
}

Expand Down
4 changes: 3 additions & 1 deletion pkg/commands/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ func NewInstallCmd(iostream *iostreams.IOStreams) *cobra.Command {
fmt.Fprintf(iostream.ErrOut, "failed to set force flag: %s\n", err)
return err
}

}

fmt.Fprintln(iostream.Out, cs.GreenBold("Running all installation subcommands..."))
for _, subcmd := range cmd.Commands() {
fmt.Printf("Running installation for %s...\n", subcmd.Use)
if len(subcmd.Use) == 0 {
continue
}
subSpan, subCtx := tracer.StartSpanFromContext(ctx, "install_"+subcmd.Use)
subcmd.SetContext(subCtx)
if subcmd.Use == "tools" {
Expand Down
230 changes: 169 additions & 61 deletions pkg/commands/install/install_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package install

import (
"errors"
"os"
"path/filepath"
"testing"

"github.com/XiaoConstantine/mycli/pkg/iostreams"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)

Expand All @@ -15,66 +19,170 @@ func TestNewInstallCmd(t *testing.T) {
assert.Equal(t, "install", cmd.Use)
assert.Equal(t, "Install software", cmd.Short)
})
}

// Helper function to replace subcommands with mocks.
func mockSubcommands(cmd *cobra.Command, outcomes map[string]error) {
for _, subcmd := range cmd.Commands() {
// Capture the subcommand name and the intended mock outcome
if outcome, ok := outcomes[subcmd.Use]; ok {
subcmd.RunE = func(cmd *cobra.Command, args []string) error {
return outcome // Return the mock outcome when the subcommand is run
}
}
}
}

func TestNewInstallCmd_NonInteractiveMode_Force(t *testing.T) {
ios, _, outBuf, errBuf := iostreams.Test()
cmd := NewInstallCmd(ios)

// Create a temporary directory
tempDir, err := os.MkdirTemp("", "test-config-install")
assert.NoError(t, err)
defer os.RemoveAll(tempDir) // Clean up

// Create a temporary config file
configContent := []byte(`
tools:
- name: example-tool
`)
configPath := filepath.Join(tempDir, "test-config.yaml")
err = os.WriteFile(configPath, configContent, 0644)
assert.NoError(t, err)

// Set flags to simulate non-interactive mode
cmd.SetArgs([]string{"--non-interactive", "--config", configPath, "--force"})

// Disable base commands
cmd.Root().CompletionOptions.DisableDefaultCmd = true
cmd.SetHelpCommand(&cobra.Command{Hidden: true})

// Map of subcommand uses to their desired mock outcomes
outcomes := map[string]error{
"homebrew": nil, // Mock homebrew command to succeed
"xcode": errors.New("xcode installation failed"), // Mock xcode command to fail
"tools": nil,
}

mockSubcommands(cmd, outcomes)

err = cmd.Execute()
assert.Error(t, err) // Expect an error because xcode installation is mocked to fail
assert.Contains(t, errBuf.String(), "xcode installation failed")
assert.Contains(t, outBuf.String(), "Running all installation subcommands...\n") // Check output for successful mock
}

func TestNewInstallCmd_FileNotExist(t *testing.T) {
ios, _, _, errBuf := iostreams.Test()
cmd := NewInstallCmd(ios)

// Set flags to simulate file not found
cmd.SetArgs([]string{"--non-interactive", "--config", "/non/existent/path.yaml"})

err := cmd.Execute()
assert.Error(t, err)
assert.Contains(t, errBuf.String(), "Error: Config file does not exist at path")
}

func TestNewInstallCmdFlagsErrorsEdgeCases(t *testing.T) {
ios, _, outBuf, errBuf := iostreams.Test()
cmd := NewInstallCmd(ios)

// Create a temporary directory
tempDir, err := os.MkdirTemp("", "test-config-install")
assert.NoError(t, err)
defer os.RemoveAll(tempDir) // Clean up

// Create a temporary config file
configContent := []byte(`
tools:
- name: example-tool
`)
configPath := filepath.Join(tempDir, "test-config.yaml")
err = os.WriteFile(configPath, configContent, 0644)
assert.NoError(t, err)

// Setup mock outcomes with no specific subcommand behavior
// Map of subcommand uses to their desired mock outcomes
outcomes := map[string]error{
"homebrew": nil, // Mock homebrew command to succeed
"xcode": errors.New("xcode installation failed"), // Mock xcode command to fail
"tools": nil,
}

mockSubcommands(cmd, outcomes)

tests := []struct {
name string
args []string
setupFunc func()
cleanupFunc func()
expectErr bool
errorMsg string
successMsg string
}{
{
name: "Non-interactive with missing config file",
args: []string{"--non-interactive", "--config", "/fake/path.yaml"},
expectErr: true,
errorMsg: "Config file does not exist at path",
},
{
name: "Valid non-interactive force install",
args: []string{"--non-interactive", "--force", "--config", configPath},
setupFunc: func() {
// Mock environment variable and file path resolution
os.Setenv("HOME", "/tmp")
outcomes := map[string]error{
"homebrew": nil, // Mock homebrew command to succeed
"xcode": nil, // Mock xcode command to fail
"tools": nil,
}
mockSubcommands(cmd, outcomes)

},
cleanupFunc: func() {
os.Unsetenv("HOME")
mockSubcommands(cmd, map[string]error{})
},
expectErr: false,
successMsg: "All installations completed successfully.",
},
// {
// name: "Edge case with unexpected arguments",
// args: []string{"--non-interactive", "--unexpected_arg"},
// expectErr: true,
// errorMsg: "unknown flag: --unexpected_arg",
// },
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if tc.setupFunc != nil {
tc.setupFunc()
}
outBuf.Reset()
errBuf.Reset()

// Disable base commands
cmd.Root().CompletionOptions.DisableDefaultCmd = true
cmd.SetHelpCommand(&cobra.Command{Hidden: true})

cmd.SetArgs(tc.args)
err := cmd.Execute()

if tc.expectErr {
assert.Error(t, err)
assert.Contains(t, errBuf.String(), tc.errorMsg)
} else {
assert.NoError(t, err)
assert.Contains(t, outBuf.String(), tc.successMsg)
}

// t.Run("Non-interactive mode with valid config", func(t *testing.T) {
// ios, _, stdout, stderr := iostreams.Test()
// cmd := NewInstallCmd(ios)

// // Create a temporary directory
// tempDir, err := os.MkdirTemp("", "test-config")
// assert.NoError(t, err)
// defer os.RemoveAll(tempDir) // Clean up

// // Create a temporary config file
// configContent := []byte(`
// tools:
// - name: example-tool
// `)
// configPath := filepath.Join(tempDir, "config.yaml")
// err = os.WriteFile(configPath, configContent, 0644)
// assert.NoError(t, err)

// // Set flags
// cmd.Flags().Set("non-interactive", "true")
// cmd.Flags().Set("config", configPath)
// cmd.Flags().Set("force", "true")

// // Execute the command
// err = cmd.Execute()

// assert.NoError(t, err)
// assert.Contains(t, stdout.String(), "Running all installation subcommands...")
// assert.NotContains(t, stderr.String(), "Error:")
// })

// t.Run("Non-interactive mode with invalid config", func(t *testing.T) {
// ios, _, _, stderr := iostreams.Test()
// cmd := NewInstallCmd(ios)
// cmd.SetArgs([]string{"--non-interactive", "--config", "testdata/nonexistent_config.yaml"})
// err := cmd.Execute()
// assert.Error(t, err)
// assert.Contains(t, stderr.String(), "Error: Config file does not exist at path:")
// })

// t.Run("Interactive mode with Everything choice", func(t *testing.T) {
// ios, stdin, stdout, _ := iostreams.Test()
// cmd := NewInstallCmd(ios)
// stdin.WriteString("Everything\n")
// stdin.WriteString("testdata/valid_config.yaml\n")
// stdin.WriteString("n\n") // No force reinstall
// err := cmd.Execute()
// assert.NoError(t, err)
// assert.Contains(t, stdout.String(), "Running all installation subcommands...")
// })

// t.Run("Interactive mode with specific subcommand choice", func(t *testing.T) {
// ios, stdin, stdout, _ := iostreams.Test()
// cmd := NewInstallCmd(ios)
// stdin.WriteString("tools\n")
// stdin.WriteString("testdata/valid_config.yaml\n")
// stdin.WriteString("n\n") // No force reinstall
// err := cmd.Execute()
// assert.NoError(t, err)
// assert.Contains(t, stdout.String(), "Running installation for: tools...")
// })
if tc.cleanupFunc != nil {
tc.cleanupFunc()
}
})
}
}

0 comments on commit 366cfcb

Please sign in to comment.