From 1ff5d1f05a294fc0356e5c15b7f9dd10160b806c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 31 Jul 2025 16:51:10 +0700 Subject: [PATCH] test: add comprehensive CLI command tests Add comprehensive test suite for all Cobra CLI commands in cmd/cli/commands_test.go. The test suite includes: - Basic command structure validation - Service command creation and subcommand testing - Help and version command functionality - Error handling for invalid flags - Flag validation (verbose, silent) - Command execution and argument handling - Subcommand validation Key features: - Uses sync.Once for thread-safe CLI initialization - Tests the actual global rootCmd instead of isolated instances - Provides realistic test coverage of the application's command structure - All tests pass and project builds successfully --- cmd/cli/commands_test.go | 208 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 cmd/cli/commands_test.go diff --git a/cmd/cli/commands_test.go b/cmd/cli/commands_test.go new file mode 100644 index 0000000..683aa79 --- /dev/null +++ b/cmd/cli/commands_test.go @@ -0,0 +1,208 @@ +package cli + +import ( + "bytes" + "sync" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// setupTestCLI initializes the CLI for testing, ensuring it's only done once +var cliInitOnce sync.Once + +func setupTestCLI() { + cliInitOnce.Do(func() { + initCLI() + }) +} + +// TestBasicCommandStructure tests the actual root command structure +func TestBasicCommandStructure(t *testing.T) { + // Test the actual global rootCmd that's used in the application + // Initialize the CLI to set up the root command + setupTestCLI() + + // Test that root command has basic properties + assert.Equal(t, "ctrld", rootCmd.Use) + assert.NotEmpty(t, rootCmd.Short, "Root command should have a short description") + + // Test that root command has subcommands + commands := rootCmd.Commands() + assert.NotNil(t, commands, "Root command should have subcommands") + assert.Greater(t, len(commands), 0, "Root command should have at least one subcommand") + + // Test that expected commands exist + expectedCommands := []string{"run", "service", "clients", "upgrade", "log"} + for _, cmdName := range expectedCommands { + found := false + for _, cmd := range commands { + if cmd.Name() == cmdName { + found = true + break + } + } + assert.True(t, found, "Expected command %s not found in root command", cmdName) + } +} + +// TestServiceCommandCreation tests service command creation +func TestServiceCommandCreation(t *testing.T) { + sc := NewServiceCommand() + require.NotNil(t, sc, "ServiceCommand should be created") + + // Test service config creation + config := sc.createServiceConfig() + require.NotNil(t, config, "Service config should be created") + assert.Equal(t, ctrldServiceName, config.Name) + assert.Equal(t, "Control-D Helper Service", config.DisplayName) + assert.Equal(t, "A highly configurable, multi-protocol DNS forwarding proxy", config.Description) +} + +// TestServiceCommandSubCommands tests service command sub commands +func TestServiceCommandSubCommands(t *testing.T) { + rootCmd := &cobra.Command{ + Use: "ctrld", + Short: "DNS forwarding proxy", + } + + serviceCmd := InitServiceCmd(rootCmd) + require.NotNil(t, serviceCmd, "Service command should be created") + + // Test that service command has subcommands + subcommands := serviceCmd.Commands() + assert.Greater(t, len(subcommands), 0, "Service command should have subcommands") + + // Test specific subcommands exist + expectedCommands := []string{"start", "stop", "restart", "reload", "status", "uninstall", "interfaces"} + + for _, cmdName := range expectedCommands { + found := false + for _, cmd := range subcommands { + if cmd.Name() == cmdName { + found = true + break + } + } + assert.True(t, found, "Expected service subcommand %s not found", cmdName) + } +} + +// TestCommandHelp tests basic help functionality +func TestCommandHelp(t *testing.T) { + // Initialize the CLI to set up the root command + setupTestCLI() + + // Test help command execution + var buf bytes.Buffer + rootCmd.SetOut(&buf) + rootCmd.SetErr(&buf) + + rootCmd.SetArgs([]string{"--help"}) + err := rootCmd.Execute() + assert.NoError(t, err, "Help command should execute without error") + assert.Contains(t, buf.String(), "dns forwarding proxy", "Help output should contain description") +} + +// TestCommandVersion tests version command +func TestCommandVersion(t *testing.T) { + // Initialize the CLI to set up the root command + setupTestCLI() + + var buf bytes.Buffer + rootCmd.SetOut(&buf) + rootCmd.SetErr(&buf) + + // Test version command + rootCmd.SetArgs([]string{"--version"}) + err := rootCmd.Execute() + assert.NoError(t, err, "Version command should execute without error") + assert.Contains(t, buf.String(), "version", "Version output should contain version information") +} + +// TestCommandErrorHandling tests error handling +func TestCommandErrorHandling(t *testing.T) { + // Initialize the CLI to set up the root command + setupTestCLI() + + // Test invalid flag instead of invalid command + rootCmd.SetArgs([]string{"--invalid-flag"}) + err := rootCmd.Execute() + assert.Error(t, err, "Invalid flag should return error") +} + +// TestCommandFlags tests flag functionality +func TestCommandFlags(t *testing.T) { + // Initialize the CLI to set up the root command + setupTestCLI() + + // Test that root command has expected flags + verboseFlag := rootCmd.PersistentFlags().Lookup("verbose") + assert.NotNil(t, verboseFlag, "Verbose flag should exist") + assert.Equal(t, "v", verboseFlag.Shorthand) + + silentFlag := rootCmd.PersistentFlags().Lookup("silent") + assert.NotNil(t, silentFlag, "Silent flag should exist") + assert.Equal(t, "s", silentFlag.Shorthand) +} + +// TestCommandExecution tests basic command execution +func TestCommandExecution(t *testing.T) { + // Initialize the CLI to set up the root command + setupTestCLI() + + // Test that root command can be executed (help command) + var buf bytes.Buffer + rootCmd.SetOut(&buf) + rootCmd.SetErr(&buf) + + rootCmd.SetArgs([]string{"--help"}) + err := rootCmd.Execute() + assert.NoError(t, err, "Root command should execute without error") + assert.Contains(t, buf.String(), "dns forwarding proxy", "Help output should contain description") +} + +// TestCommandArgs tests argument handling +func TestCommandArgs(t *testing.T) { + // Initialize the CLI to set up the root command + setupTestCLI() + + // Test that root command can handle arguments properly + // Test with no args (should succeed) + err := rootCmd.Execute() + assert.NoError(t, err, "Root command with no args should execute") + + // Test with help flag (should succeed) + rootCmd.SetArgs([]string{"--help"}) + err = rootCmd.Execute() + assert.NoError(t, err, "Root command with help flag should execute") +} + +// TestCommandSubcommands tests subcommand functionality +func TestCommandSubcommands(t *testing.T) { + // Initialize the CLI to set up the root command + setupTestCLI() + + // Test that root command has subcommands + commands := rootCmd.Commands() + assert.Greater(t, len(commands), 0, "Root command should have subcommands") + + // Test that specific subcommands exist and can be executed + expectedSubcommands := []string{"run", "service", "clients", "upgrade", "log"} + for _, subCmdName := range expectedSubcommands { + // Find the subcommand + var subCmd *cobra.Command + for _, cmd := range commands { + if cmd.Name() == subCmdName { + subCmd = cmd + break + } + } + assert.NotNil(t, subCmd, "Subcommand %s should exist", subCmdName) + + // Test that subcommand has help + assert.NotEmpty(t, subCmd.Short, "Subcommand %s should have a short description", subCmdName) + } +}