From f45c373097ec6b374cc52eccf4b1f908d5a6fe37 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Tue, 15 Jul 2025 21:12:44 -0400 Subject: [PATCH 1/6] fix(cmd): Implement trust properly across required commands --- cmd/down.go | 29 ++-------- cmd/down_test.go | 24 -------- cmd/env.go | 9 +-- cmd/exec.go | 30 ++-------- cmd/exec_test.go | 39 ------------- cmd/install.go | 7 ++- cmd/root.go | 24 ++++++++ cmd/root_test.go | 118 ++++++++++++++++++++++++++++++++++++++++ cmd/up.go | 27 ++------- cmd/up_test.go | 24 -------- pkg/shell/shell.go | 39 +++++++++++++ pkg/shell/shell_test.go | 101 ++++++++++++++++++++++++++++++++++ 12 files changed, 307 insertions(+), 164 deletions(-) diff --git a/cmd/down.go b/cmd/down.go index 3bd67a47d..fea6503a2 100644 --- a/cmd/down.go +++ b/cmd/down.go @@ -7,7 +7,6 @@ import ( "github.com/spf13/cobra" "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/pipelines" - "github.com/windsorcli/cli/pkg/shell" ) var ( @@ -17,34 +16,18 @@ var ( ) var downCmd = &cobra.Command{ - Use: "down", - Short: "Tear down the Windsor environment", - Long: "Tear down the Windsor environment by executing necessary shell commands.", - SilenceUsage: true, + Use: "down", + Short: "Tear down the Windsor environment", + Long: "Tear down the Windsor environment by executing necessary shell commands.", + SilenceUsage: true, + PersistentPreRunE: checkTrust, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) - // First, initialize a base pipeline to set up core dependencies (shell, config, etc.) - _, err := pipelines.WithPipeline(injector, cmd.Context(), "basePipeline") - if err != nil { - return fmt.Errorf("failed to initialize dependencies: %w", err) - } - - // Now check if directory is trusted using the initialized shell - shellInstance := injector.Resolve("shell") - if shellInstance != nil { - if s, ok := shellInstance.(shell.Shell); ok { - if err := s.CheckTrustedDirectory(); err != nil { - return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") - } - } - } - - // Directory is trusted, proceed with normal pipeline execution // First, run the env pipeline in quiet mode to set up environment variables var envPipeline pipelines.Pipeline - envPipeline, err = pipelines.WithPipeline(injector, cmd.Context(), "envPipeline") + envPipeline, err := pipelines.WithPipeline(injector, cmd.Context(), "envPipeline") if err != nil { return fmt.Errorf("failed to set up env pipeline: %w", err) } diff --git a/cmd/down_test.go b/cmd/down_test.go index 0bda8baf7..d2af55e57 100644 --- a/cmd/down_test.go +++ b/cmd/down_test.go @@ -333,28 +333,4 @@ func TestDownCmd(t *testing.T) { } }) - t.Run("FailsWhenDirectoryNotTrusted", func(t *testing.T) { - // Given a temporary directory with mocked dependencies - mocks := setupDownMocks(t) - - // And shell CheckTrustedDirectory returns an error - mocks.Shell.CheckTrustedDirectoryFunc = func() error { - return fmt.Errorf("directory not trusted") - } - - // When executing the down command - cmd := createTestDownCmd() - ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) - cmd.SetArgs([]string{}) - cmd.SetContext(ctx) - err := cmd.Execute() - - // Then an error should occur about untrusted directory - if err == nil { - t.Error("Expected error when directory is not trusted, got nil") - } - if !strings.Contains(err.Error(), "not in a trusted directory") { - t.Errorf("Expected error about untrusted directory, got: %v", err) - } - }) } diff --git a/cmd/env.go b/cmd/env.go index 55833ef1f..9199b66cb 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -10,10 +10,11 @@ import ( ) var envCmd = &cobra.Command{ - Use: "env", - Short: "Output commands to set environment variables", - Long: "Output commands to set environment variables for the application.", - SilenceUsage: true, + Use: "env", + Short: "Output commands to set environment variables", + Long: "Output commands to set environment variables for the application.", + SilenceUsage: true, + PersistentPreRunE: checkTrust, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) diff --git a/cmd/exec.go b/cmd/exec.go index 6c173ac12..d8352fb25 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -7,15 +7,15 @@ import ( "github.com/spf13/cobra" "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/pipelines" - "github.com/windsorcli/cli/pkg/shell" ) // execCmd represents the exec command var execCmd = &cobra.Command{ - Use: "exec [command] [args...]", - Short: "Execute a command with environment variables", - Long: "Execute a command with environment variables loaded from configuration and secrets", - Args: cobra.MinimumNArgs(1), + Use: "exec [command] [args...]", + Short: "Execute a command with environment variables", + Long: "Execute a command with environment variables loaded from configuration and secrets", + Args: cobra.MinimumNArgs(1), + PersistentPreRunE: checkTrust, RunE: func(cmd *cobra.Command, args []string) error { // Safety check for arguments if len(args) == 0 { @@ -25,26 +25,6 @@ var execCmd = &cobra.Command{ // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) - // Initialize base pipeline to set up dependencies - basePipeline, err := pipelines.WithPipeline(injector, cmd.Context(), "basePipeline") - if err != nil { - return fmt.Errorf("failed to set up base pipeline: %w", err) - } - - if err := basePipeline.Execute(cmd.Context()); err != nil { - return fmt.Errorf("failed to initialize base pipeline: %w", err) - } - - // Now check if directory is trusted using the initialized shell - shellInstance := injector.Resolve("shell") - if shellInstance != nil { - if s, ok := shellInstance.(shell.Shell); ok { - if err := s.CheckTrustedDirectory(); err != nil { - return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") - } - } - } - // First, run the env pipeline in quiet mode to set up environment variables envPipeline, err := pipelines.WithPipeline(injector, cmd.Context(), "envPipeline") if err != nil { diff --git a/cmd/exec_test.go b/cmd/exec_test.go index 741fc3ea0..2e8010760 100644 --- a/cmd/exec_test.go +++ b/cmd/exec_test.go @@ -63,45 +63,6 @@ func TestExecCmd(t *testing.T) { } }) - t.Run("UntrustedDirectory", func(t *testing.T) { - tmpDir := t.TempDir() - originalDir, _ := os.Getwd() - defer func() { - os.Chdir(originalDir) - }() - os.Chdir(tmpDir) - - injector := di.NewInjector() - - // Register mock shell that fails trust check - mockShell := shell.NewMockShell() - mockShell.CheckTrustedDirectoryFunc = func() error { - return fmt.Errorf("directory not trusted") - } - injector.Register("shell", mockShell) - - // Register mock base pipeline - mockBasePipeline := pipelines.NewMockBasePipeline() - injector.Register("basePipeline", mockBasePipeline) - - cmd := createTestCmd() - ctx := context.WithValue(context.Background(), injectorKey, injector) - cmd.SetContext(ctx) - - args := []string{"go", "version"} - cmd.SetArgs(args) - - err := cmd.Execute() - - if err == nil { - t.Error("Expected error for untrusted directory, got nil") - } - expectedMsg := "not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve" - if fmt.Sprintf("%v", err) != expectedMsg { - t.Errorf("Expected error message '%s', got '%v'", expectedMsg, err) - } - }) - t.Run("NoCommandProvided", func(t *testing.T) { tmpDir := t.TempDir() originalDir, _ := os.Getwd() diff --git a/cmd/install.go b/cmd/install.go index 856e80496..bcacb5ea8 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -12,9 +12,10 @@ import ( var installWaitFlag bool var installCmd = &cobra.Command{ - Use: "install", - Short: "Install the blueprint's cluster-level services", - SilenceUsage: true, + Use: "install", + Short: "Install the blueprint's cluster-level services", + SilenceUsage: true, + PersistentPreRunE: checkTrust, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) diff --git a/cmd/root.go b/cmd/root.go index 641c4b9e1..31874a796 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,9 +2,11 @@ package cmd import ( "context" + "fmt" "github.com/spf13/cobra" "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/shell" ) // verbose is a flag for verbose output @@ -50,3 +52,25 @@ func init() { // Define the --verbose flag rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") } + +// checkTrust performs trust validation for Windsor CLI commands requiring a trusted project directory. +// It verifies directory trust status by checking if the current project directory is in the trusted file list. +// For the "init" command, or for the "env" command with the --hook flag set, trust validation is skipped. +// Returns an error if the directory is untrusted. +func checkTrust(cmd *cobra.Command, args []string) error { + if cmd.Name() == "init" { + return nil + } + + if cmd.Name() == "env" { + if hook, _ := cmd.Flags().GetBool("hook"); hook { + return nil + } + } + + if err := shell.CheckTrustedDirectory(); err != nil { + return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") + } + + return nil +} diff --git a/cmd/root_test.go b/cmd/root_test.go index b470e1468..f6dc1e123 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -9,8 +9,11 @@ import ( "bytes" "os" "os/exec" + "path/filepath" + "strings" "testing" + "github.com/spf13/cobra" blueprintpkg "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" @@ -259,3 +262,118 @@ func TestRootCmd_PersistentPreRunE(t *testing.T) { } }) } + +func TestCheckTrust(t *testing.T) { + createMockCmd := func(name string) *cobra.Command { + return &cobra.Command{ + Use: name, + } + } + + t.Run("SkipsTrustCheckForInitCommand", func(t *testing.T) { + // Given an init command + cmd := createMockCmd("init") + + // When checking trust + err := checkTrust(cmd, []string{}) + + // Then no error should occur (trust check is skipped) + if err != nil { + t.Errorf("Expected no error for init command, got: %v", err) + } + }) + + t.Run("SkipsTrustCheckForEnvCommandWithHookFlag", func(t *testing.T) { + // Given an env command with hook flag + cmd := createMockCmd("env") + cmd.Flags().Bool("hook", false, "hook flag") + cmd.Flags().Set("hook", "true") + + // When checking trust + err := checkTrust(cmd, []string{}) + + // Then no error should occur (trust check is skipped for env --hook) + if err != nil { + t.Errorf("Expected no error for env --hook, got: %v", err) + } + }) + + t.Run("ChecksTrustForEnvCommandWithoutHookFlag", func(t *testing.T) { + // Given an env command without hook flag in an untrusted directory + cmd := createMockCmd("env") + cmd.Flags().Bool("hook", false, "hook flag") + + // Set up a temporary directory that's not trusted + tmpDir := t.TempDir() + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + defer os.Chdir(originalDir) + + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + // When checking trust + err = checkTrust(cmd, []string{}) + + // Then an error should occur about untrusted directory + if err == nil { + t.Error("Expected error for untrusted directory, got nil") + } + if !strings.Contains(err.Error(), "not in a trusted directory") { + t.Errorf("Expected trust error message, got: %v", err) + } + }) + + t.Run("PassesTrustCheckForTrustedDirectory", func(t *testing.T) { + // Given a command in a trusted directory + cmd := createMockCmd("down") + + // Set up a temporary directory structure with trusted file + tmpDir := t.TempDir() + testDir := filepath.Join(tmpDir, "project") + if err := os.MkdirAll(testDir, 0755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + + // Create trusted file + trustedDir := filepath.Join(tmpDir, ".config", "windsor") + if err := os.MkdirAll(trustedDir, 0755); err != nil { + t.Fatalf("Failed to create trusted directory: %v", err) + } + + trustedFile := filepath.Join(trustedDir, ".trusted") + realTestDir, _ := filepath.EvalSymlinks(testDir) + trustedContent := realTestDir + "\n" + if err := os.WriteFile(trustedFile, []byte(trustedContent), 0644); err != nil { + t.Fatalf("Failed to create trusted file: %v", err) + } + + // Change to test directory + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + defer os.Chdir(originalDir) + + if err := os.Chdir(testDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + // Mock home directory + originalHome := os.Getenv("HOME") + defer os.Setenv("HOME", originalHome) + os.Setenv("HOME", tmpDir) + + // When checking trust + err = checkTrust(cmd, []string{}) + + // Then no error should occur + if err != nil { + t.Errorf("Expected no error for trusted directory, got: %v", err) + } + }) + +} diff --git a/cmd/up.go b/cmd/up.go index 75928af52..c1b4b4dd6 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -7,7 +7,6 @@ import ( "github.com/spf13/cobra" "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/pipelines" - "github.com/windsorcli/cli/pkg/shell" ) var ( @@ -16,31 +15,15 @@ var ( ) var upCmd = &cobra.Command{ - Use: "up", - Short: "Set up the Windsor environment", - Long: "Set up the Windsor environment by executing necessary shell commands.", - SilenceUsage: true, + Use: "up", + Short: "Set up the Windsor environment", + Long: "Set up the Windsor environment by executing necessary shell commands.", + SilenceUsage: true, + PersistentPreRunE: checkTrust, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) - // First, initialize a base pipeline to set up core dependencies (shell, config, etc.) - _, err := pipelines.WithPipeline(injector, cmd.Context(), "basePipeline") - if err != nil { - return fmt.Errorf("failed to initialize dependencies: %w", err) - } - - // Now check if directory is trusted using the initialized shell - shellInstance := injector.Resolve("shell") - if shellInstance != nil { - if s, ok := shellInstance.(shell.Shell); ok { - if err := s.CheckTrustedDirectory(); err != nil { - return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") - } - } - } - - // Directory is trusted, proceed with normal pipeline execution // First, run the env pipeline in quiet mode to set up environment variables envPipeline, err := pipelines.WithPipeline(injector, cmd.Context(), "envPipeline") if err != nil { diff --git a/cmd/up_test.go b/cmd/up_test.go index 725032513..5782d1566 100644 --- a/cmd/up_test.go +++ b/cmd/up_test.go @@ -477,28 +477,4 @@ func TestUpCmd(t *testing.T) { } }) - t.Run("FailsWhenDirectoryNotTrusted", func(t *testing.T) { - // Given a temporary directory with mocked dependencies - mocks := setupUpTest(t) - - // And shell CheckTrustedDirectory returns an error - mocks.Shell.CheckTrustedDirectoryFunc = func() error { - return fmt.Errorf("directory not trusted") - } - - // When executing the up command - cmd := createTestUpCmd() - ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) - cmd.SetArgs([]string{}) - cmd.SetContext(ctx) - err := cmd.Execute() - - // Then an error should occur about untrusted directory - if err == nil { - t.Error("Expected error when directory is not trusted, got nil") - } - if !strings.Contains(err.Error(), "not in a trusted directory") { - t.Errorf("Expected error about untrusted directory, got: %v", err) - } - }) } diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go index 03f73ba75..6fcf07ed9 100644 --- a/pkg/shell/shell.go +++ b/pkg/shell/shell.go @@ -637,6 +637,45 @@ func (s *DefaultShell) ResetSessionToken() { s.sessionToken = "" } +// ============================================================================= +// Standalone Functions +// ============================================================================= + +// CheckTrustedDirectory verifies if the current directory is in the trusted file list. +// This is a standalone function that can be called without shell instance initialization. +func CheckTrustedDirectory() error { + currentDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("Error getting current directory: %w", err) + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("Error getting user home directory: %w", err) + } + + trustedDirPath := path.Join(homeDir, ".config", "windsor") + trustedFilePath := path.Join(trustedDirPath, ".trusted") + + data, err := os.ReadFile(trustedFilePath) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("Trusted file does not exist") + } + return fmt.Errorf("Error reading trusted file: %w", err) + } + + trustedDirs := strings.Split(strings.TrimSpace(string(data)), "\n") + for _, trustedDir := range trustedDirs { + trimmedDir := strings.TrimSpace(trustedDir) + if trimmedDir != "" && strings.HasPrefix(currentDir, trimmedDir) { + return nil + } + } + + return fmt.Errorf("Current directory not in the trusted list") +} + // ============================================================================= // Private Methods // ============================================================================= diff --git a/pkg/shell/shell_test.go b/pkg/shell/shell_test.go index e07c6640d..e59b24385 100644 --- a/pkg/shell/shell_test.go +++ b/pkg/shell/shell_test.go @@ -3247,3 +3247,104 @@ func TestScrubbingWriter(t *testing.T) { } }) } + +// ============================================================================= +// Standalone Function Tests +// ============================================================================= + +func TestCheckTrustedDirectoryStandalone(t *testing.T) { + t.Run("Success", func(t *testing.T) { + // Given a directory and trusted file + tmpDir := t.TempDir() + testDir := filepath.Join(tmpDir, "project") + if err := os.MkdirAll(testDir, 0755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + + // Create trusted file + trustedDir := filepath.Join(tmpDir, ".config", "windsor") + if err := os.MkdirAll(trustedDir, 0755); err != nil { + t.Fatalf("Failed to create trusted directory: %v", err) + } + + trustedFile := filepath.Join(trustedDir, ".trusted") + // Use EvalSymlinks to handle macOS /private prefix + realTestDir, _ := filepath.EvalSymlinks(testDir) + trustedContent := realTestDir + "\n" + if err := os.WriteFile(trustedFile, []byte(trustedContent), 0644); err != nil { + t.Fatalf("Failed to create trusted file: %v", err) + } + + // Change to test directory + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + defer os.Chdir(originalDir) + + if err := os.Chdir(testDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + // Mock home directory + originalHome := os.Getenv("HOME") + defer os.Setenv("HOME", originalHome) + os.Setenv("HOME", tmpDir) + + // When checking trusted directory + err = CheckTrustedDirectory() + + // Then it should succeed + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + }) + + t.Run("NotTrusted", func(t *testing.T) { + // Given a directory and trusted file that doesn't include it + tmpDir := t.TempDir() + testDir := filepath.Join(tmpDir, "project") + if err := os.MkdirAll(testDir, 0755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + + // Create trusted file with different directory + trustedDir := filepath.Join(tmpDir, ".config", "windsor") + if err := os.MkdirAll(trustedDir, 0755); err != nil { + t.Fatalf("Failed to create trusted directory: %v", err) + } + + trustedFile := filepath.Join(trustedDir, ".trusted") + trustedContent := "/other/directory\n" + if err := os.WriteFile(trustedFile, []byte(trustedContent), 0644); err != nil { + t.Fatalf("Failed to create trusted file: %v", err) + } + + // Change to test directory + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + defer os.Chdir(originalDir) + + if err := os.Chdir(testDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + // Mock home directory + originalHome := os.Getenv("HOME") + defer os.Setenv("HOME", originalHome) + os.Setenv("HOME", tmpDir) + + // When checking trusted directory + err = CheckTrustedDirectory() + + // Then it should fail + if err == nil { + t.Error("Expected error, got nil") + } + if !strings.Contains(err.Error(), "Current directory not in the trusted list") { + t.Errorf("Expected error about directory not trusted, got: %v", err) + } + }) +} From 02142b8f3e674c600b0ae6005ff02beba4002f25 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Tue, 15 Jul 2025 21:18:56 -0400 Subject: [PATCH 2/6] Establish mock trusted directory --- cmd/env_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/cmd/env_test.go b/cmd/env_test.go index 08d899c9f..b8f4cf549 100644 --- a/cmd/env_test.go +++ b/cmd/env_test.go @@ -2,6 +2,8 @@ package cmd import ( "bytes" + "os" + "path/filepath" "testing" ) @@ -14,9 +16,55 @@ func TestEnvCmd(t *testing.T) { return stdout, stderr } + setupTrustedDirectory := func(t *testing.T) func() { + t.Helper() + + // Set up a temporary directory structure with trusted file + tmpDir := t.TempDir() + testDir := filepath.Join(tmpDir, "project") + if err := os.MkdirAll(testDir, 0755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + + // Create trusted file + trustedDir := filepath.Join(tmpDir, ".config", "windsor") + if err := os.MkdirAll(trustedDir, 0755); err != nil { + t.Fatalf("Failed to create trusted directory: %v", err) + } + + trustedFile := filepath.Join(trustedDir, ".trusted") + realTestDir, _ := filepath.EvalSymlinks(testDir) + trustedContent := realTestDir + "\n" + if err := os.WriteFile(trustedFile, []byte(trustedContent), 0644); err != nil { + t.Fatalf("Failed to create trusted file: %v", err) + } + + // Change to test directory + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + + if err := os.Chdir(testDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + // Mock home directory + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + + // Return cleanup function + return func() { + os.Chdir(originalDir) + os.Setenv("HOME", originalHome) + } + } + t.Run("Success", func(t *testing.T) { - // Given proper output capture + // Given proper output capture and trusted directory _, stderr := setup(t) + cleanup := setupTrustedDirectory(t) + defer cleanup() rootCmd.SetArgs([]string{"env"}) @@ -35,8 +83,10 @@ func TestEnvCmd(t *testing.T) { }) t.Run("SuccessWithDecrypt", func(t *testing.T) { - // Given proper output capture + // Given proper output capture and trusted directory _, stderr := setup(t) + cleanup := setupTrustedDirectory(t) + defer cleanup() rootCmd.SetArgs([]string{"env", "--decrypt"}) @@ -75,8 +125,10 @@ func TestEnvCmd(t *testing.T) { }) t.Run("SuccessWithVerbose", func(t *testing.T) { - // Given proper output capture + // Given proper output capture and trusted directory _, stderr := setup(t) + cleanup := setupTrustedDirectory(t) + defer cleanup() rootCmd.SetArgs([]string{"env", "--verbose"}) From ff09ce3ee6bfa83bc029c2f7aa6e8bd27cff3f42 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Tue, 15 Jul 2025 21:34:40 -0400 Subject: [PATCH 3/6] Fix windows tests --- pkg/shell/shell.go | 4 ++-- pkg/shell/shell_test.go | 31 +++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go index 6fcf07ed9..6ba34f957 100644 --- a/pkg/shell/shell.go +++ b/pkg/shell/shell.go @@ -654,8 +654,8 @@ func CheckTrustedDirectory() error { return fmt.Errorf("Error getting user home directory: %w", err) } - trustedDirPath := path.Join(homeDir, ".config", "windsor") - trustedFilePath := path.Join(trustedDirPath, ".trusted") + trustedDirPath := filepath.Join(homeDir, ".config", "windsor") + trustedFilePath := filepath.Join(trustedDirPath, ".trusted") data, err := os.ReadFile(trustedFilePath) if err != nil { diff --git a/pkg/shell/shell_test.go b/pkg/shell/shell_test.go index e59b24385..3b77e96c0 100644 --- a/pkg/shell/shell_test.go +++ b/pkg/shell/shell_test.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" "text/template" @@ -3286,10 +3287,17 @@ func TestCheckTrustedDirectoryStandalone(t *testing.T) { t.Fatalf("Failed to change directory: %v", err) } - // Mock home directory - originalHome := os.Getenv("HOME") - defer os.Setenv("HOME", originalHome) - os.Setenv("HOME", tmpDir) + // Mock home directory for cross-platform compatibility + var originalHome string + if runtime.GOOS == "windows" { + originalHome = os.Getenv("USERPROFILE") + defer os.Setenv("USERPROFILE", originalHome) + os.Setenv("USERPROFILE", tmpDir) + } else { + originalHome = os.Getenv("HOME") + defer os.Setenv("HOME", originalHome) + os.Setenv("HOME", tmpDir) + } // When checking trusted directory err = CheckTrustedDirectory() @@ -3331,10 +3339,17 @@ func TestCheckTrustedDirectoryStandalone(t *testing.T) { t.Fatalf("Failed to change directory: %v", err) } - // Mock home directory - originalHome := os.Getenv("HOME") - defer os.Setenv("HOME", originalHome) - os.Setenv("HOME", tmpDir) + // Mock home directory for cross-platform compatibility + var originalHome string + if runtime.GOOS == "windows" { + originalHome = os.Getenv("USERPROFILE") + defer os.Setenv("USERPROFILE", originalHome) + os.Setenv("USERPROFILE", tmpDir) + } else { + originalHome = os.Getenv("HOME") + defer os.Setenv("HOME", originalHome) + os.Setenv("HOME", tmpDir) + } // When checking trusted directory err = CheckTrustedDirectory() From 7d5c5ed72d65843eaa2de0a8a6c446dba842f94d Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Tue, 15 Jul 2025 21:56:51 -0400 Subject: [PATCH 4/6] Attempt to fix test failing due to trust --- cmd/root_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cmd/root_test.go b/cmd/root_test.go index f6dc1e123..5f812530d 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" @@ -362,10 +363,17 @@ func TestCheckTrust(t *testing.T) { t.Fatalf("Failed to change directory: %v", err) } - // Mock home directory - originalHome := os.Getenv("HOME") - defer os.Setenv("HOME", originalHome) - os.Setenv("HOME", tmpDir) + // Mock home directory for cross-platform compatibility + var originalHome string + if runtime.GOOS == "windows" { + originalHome = os.Getenv("USERPROFILE") + defer os.Setenv("USERPROFILE", originalHome) + os.Setenv("USERPROFILE", tmpDir) + } else { + originalHome = os.Getenv("HOME") + defer os.Setenv("HOME", originalHome) + os.Setenv("HOME", tmpDir) + } // When checking trust err = checkTrust(cmd, []string{}) From 715c60bd39368e1937ebc7beabdfd816e71c349b Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Tue, 15 Jul 2025 23:25:25 -0400 Subject: [PATCH 5/6] Rewrite tests with shims --- cmd/env_test.go | 70 +++++++++++------------------------------------- cmd/root.go | 34 ++++++++++++++++++++--- cmd/root_test.go | 6 ++++- cmd/shims.go | 2 ++ 4 files changed, 53 insertions(+), 59 deletions(-) diff --git a/cmd/env_test.go b/cmd/env_test.go index b8f4cf549..d42a1c768 100644 --- a/cmd/env_test.go +++ b/cmd/env_test.go @@ -2,8 +2,6 @@ package cmd import ( "bytes" - "os" - "path/filepath" "testing" ) @@ -16,55 +14,13 @@ func TestEnvCmd(t *testing.T) { return stdout, stderr } - setupTrustedDirectory := func(t *testing.T) func() { - t.Helper() - - // Set up a temporary directory structure with trusted file - tmpDir := t.TempDir() - testDir := filepath.Join(tmpDir, "project") - if err := os.MkdirAll(testDir, 0755); err != nil { - t.Fatalf("Failed to create test directory: %v", err) - } - - // Create trusted file - trustedDir := filepath.Join(tmpDir, ".config", "windsor") - if err := os.MkdirAll(trustedDir, 0755); err != nil { - t.Fatalf("Failed to create trusted directory: %v", err) - } - - trustedFile := filepath.Join(trustedDir, ".trusted") - realTestDir, _ := filepath.EvalSymlinks(testDir) - trustedContent := realTestDir + "\n" - if err := os.WriteFile(trustedFile, []byte(trustedContent), 0644); err != nil { - t.Fatalf("Failed to create trusted file: %v", err) - } - - // Change to test directory - originalDir, err := os.Getwd() - if err != nil { - t.Fatalf("Failed to get current directory: %v", err) - } - - if err := os.Chdir(testDir); err != nil { - t.Fatalf("Failed to change directory: %v", err) - } - - // Mock home directory - originalHome := os.Getenv("HOME") - os.Setenv("HOME", tmpDir) - - // Return cleanup function - return func() { - os.Chdir(originalDir) - os.Setenv("HOME", originalHome) - } - } - t.Run("Success", func(t *testing.T) { - // Given proper output capture and trusted directory + // Given proper output capture and mock setup _, stderr := setup(t) - cleanup := setupTrustedDirectory(t) - defer cleanup() + + // Set up mocks with trusted directory + mocks := setupMocks(t) + _ = mocks rootCmd.SetArgs([]string{"env"}) @@ -83,10 +39,12 @@ func TestEnvCmd(t *testing.T) { }) t.Run("SuccessWithDecrypt", func(t *testing.T) { - // Given proper output capture and trusted directory + // Given proper output capture and mock setup _, stderr := setup(t) - cleanup := setupTrustedDirectory(t) - defer cleanup() + + // Set up mocks with trusted directory + mocks := setupMocks(t) + _ = mocks rootCmd.SetArgs([]string{"env", "--decrypt"}) @@ -125,10 +83,12 @@ func TestEnvCmd(t *testing.T) { }) t.Run("SuccessWithVerbose", func(t *testing.T) { - // Given proper output capture and trusted directory + // Given proper output capture and mock setup _, stderr := setup(t) - cleanup := setupTrustedDirectory(t) - defer cleanup() + + // Set up mocks with trusted directory + mocks := setupMocks(t) + _ = mocks rootCmd.SetArgs([]string{"env", "--verbose"}) diff --git a/cmd/root.go b/cmd/root.go index 31874a796..4043851a4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,10 +3,12 @@ package cmd import ( "context" "fmt" + "os" + "path/filepath" + "strings" "github.com/spf13/cobra" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/shell" ) // verbose is a flag for verbose output @@ -68,9 +70,35 @@ func checkTrust(cmd *cobra.Command, args []string) error { } } - if err := shell.CheckTrustedDirectory(); err != nil { + // Use shims to allow mocking in tests + currentDir, err := shims.Getwd() + if err != nil { + return fmt.Errorf("Error getting current directory: %w", err) + } + + homeDir, err := shims.UserHomeDir() + if err != nil { + return fmt.Errorf("Error getting user home directory: %w", err) + } + + trustedDirPath := filepath.Join(homeDir, ".config", "windsor") + trustedFilePath := filepath.Join(trustedDirPath, ".trusted") + + data, err := shims.ReadFile(trustedFilePath) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") + } return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") } - return nil + trustedDirs := strings.Split(strings.TrimSpace(string(data)), "\n") + for _, trustedDir := range trustedDirs { + trimmedDir := strings.TrimSpace(trustedDir) + if trimmedDir != "" && strings.HasPrefix(currentDir, trimmedDir) { + return nil + } + } + + return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") } diff --git a/cmd/root_test.go b/cmd/root_test.go index 5f812530d..aba58cbdd 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -66,9 +66,13 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks { UserHomeDir: func() (string, error) { return t.TempDir(), nil }, Stat: func(string) (os.FileInfo, error) { return nil, nil }, RemoveAll: func(string) error { return nil }, - Getwd: func() (string, error) { return t.TempDir(), nil }, + Getwd: func() (string, error) { return "/test/project", nil }, Command: func(string, ...string) *exec.Cmd { return exec.Command("echo") }, Setenv: func(string, string) error { return nil }, + ReadFile: func(filename string) ([]byte, error) { + // Mock trusted file content that includes the current directory + return []byte("/test/project\n"), nil + }, } // Override with provided shims if any diff --git a/cmd/shims.go b/cmd/shims.go index 270948f86..2b4288985 100644 --- a/cmd/shims.go +++ b/cmd/shims.go @@ -25,6 +25,7 @@ type Shims struct { Setenv func(string, string) error Command func(string, ...string) *exec.Cmd Getenv func(string) string + ReadFile func(string) ([]byte, error) } // ============================================================================= @@ -42,6 +43,7 @@ func NewShims() *Shims { Setenv: os.Setenv, Command: exec.Command, Getenv: os.Getenv, + ReadFile: os.ReadFile, } } From 93437f8fe838d333d4a1b3ce3f18074154be1340 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Wed, 16 Jul 2025 00:00:54 -0400 Subject: [PATCH 6/6] Removed dead code --- pkg/shell/shell.go | 39 -------------- pkg/shell/shell_test.go | 116 ---------------------------------------- 2 files changed, 155 deletions(-) diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go index 6ba34f957..03f73ba75 100644 --- a/pkg/shell/shell.go +++ b/pkg/shell/shell.go @@ -637,45 +637,6 @@ func (s *DefaultShell) ResetSessionToken() { s.sessionToken = "" } -// ============================================================================= -// Standalone Functions -// ============================================================================= - -// CheckTrustedDirectory verifies if the current directory is in the trusted file list. -// This is a standalone function that can be called without shell instance initialization. -func CheckTrustedDirectory() error { - currentDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("Error getting current directory: %w", err) - } - - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("Error getting user home directory: %w", err) - } - - trustedDirPath := filepath.Join(homeDir, ".config", "windsor") - trustedFilePath := filepath.Join(trustedDirPath, ".trusted") - - data, err := os.ReadFile(trustedFilePath) - if err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("Trusted file does not exist") - } - return fmt.Errorf("Error reading trusted file: %w", err) - } - - trustedDirs := strings.Split(strings.TrimSpace(string(data)), "\n") - for _, trustedDir := range trustedDirs { - trimmedDir := strings.TrimSpace(trustedDir) - if trimmedDir != "" && strings.HasPrefix(currentDir, trimmedDir) { - return nil - } - } - - return fmt.Errorf("Current directory not in the trusted list") -} - // ============================================================================= // Private Methods // ============================================================================= diff --git a/pkg/shell/shell_test.go b/pkg/shell/shell_test.go index 3b77e96c0..e07c6640d 100644 --- a/pkg/shell/shell_test.go +++ b/pkg/shell/shell_test.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "strings" "testing" "text/template" @@ -3248,118 +3247,3 @@ func TestScrubbingWriter(t *testing.T) { } }) } - -// ============================================================================= -// Standalone Function Tests -// ============================================================================= - -func TestCheckTrustedDirectoryStandalone(t *testing.T) { - t.Run("Success", func(t *testing.T) { - // Given a directory and trusted file - tmpDir := t.TempDir() - testDir := filepath.Join(tmpDir, "project") - if err := os.MkdirAll(testDir, 0755); err != nil { - t.Fatalf("Failed to create test directory: %v", err) - } - - // Create trusted file - trustedDir := filepath.Join(tmpDir, ".config", "windsor") - if err := os.MkdirAll(trustedDir, 0755); err != nil { - t.Fatalf("Failed to create trusted directory: %v", err) - } - - trustedFile := filepath.Join(trustedDir, ".trusted") - // Use EvalSymlinks to handle macOS /private prefix - realTestDir, _ := filepath.EvalSymlinks(testDir) - trustedContent := realTestDir + "\n" - if err := os.WriteFile(trustedFile, []byte(trustedContent), 0644); err != nil { - t.Fatalf("Failed to create trusted file: %v", err) - } - - // Change to test directory - originalDir, err := os.Getwd() - if err != nil { - t.Fatalf("Failed to get current directory: %v", err) - } - defer os.Chdir(originalDir) - - if err := os.Chdir(testDir); err != nil { - t.Fatalf("Failed to change directory: %v", err) - } - - // Mock home directory for cross-platform compatibility - var originalHome string - if runtime.GOOS == "windows" { - originalHome = os.Getenv("USERPROFILE") - defer os.Setenv("USERPROFILE", originalHome) - os.Setenv("USERPROFILE", tmpDir) - } else { - originalHome = os.Getenv("HOME") - defer os.Setenv("HOME", originalHome) - os.Setenv("HOME", tmpDir) - } - - // When checking trusted directory - err = CheckTrustedDirectory() - - // Then it should succeed - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - }) - - t.Run("NotTrusted", func(t *testing.T) { - // Given a directory and trusted file that doesn't include it - tmpDir := t.TempDir() - testDir := filepath.Join(tmpDir, "project") - if err := os.MkdirAll(testDir, 0755); err != nil { - t.Fatalf("Failed to create test directory: %v", err) - } - - // Create trusted file with different directory - trustedDir := filepath.Join(tmpDir, ".config", "windsor") - if err := os.MkdirAll(trustedDir, 0755); err != nil { - t.Fatalf("Failed to create trusted directory: %v", err) - } - - trustedFile := filepath.Join(trustedDir, ".trusted") - trustedContent := "/other/directory\n" - if err := os.WriteFile(trustedFile, []byte(trustedContent), 0644); err != nil { - t.Fatalf("Failed to create trusted file: %v", err) - } - - // Change to test directory - originalDir, err := os.Getwd() - if err != nil { - t.Fatalf("Failed to get current directory: %v", err) - } - defer os.Chdir(originalDir) - - if err := os.Chdir(testDir); err != nil { - t.Fatalf("Failed to change directory: %v", err) - } - - // Mock home directory for cross-platform compatibility - var originalHome string - if runtime.GOOS == "windows" { - originalHome = os.Getenv("USERPROFILE") - defer os.Setenv("USERPROFILE", originalHome) - os.Setenv("USERPROFILE", tmpDir) - } else { - originalHome = os.Getenv("HOME") - defer os.Setenv("HOME", originalHome) - os.Setenv("HOME", tmpDir) - } - - // When checking trusted directory - err = CheckTrustedDirectory() - - // Then it should fail - if err == nil { - t.Error("Expected error, got nil") - } - if !strings.Contains(err.Error(), "Current directory not in the trusted list") { - t.Errorf("Expected error about directory not trusted, got: %v", err) - } - }) -}