package exec_test import ( "context" "testing" "dappco.re/go/core" "dappco.re/go/core/process/exec" ) // mockLogger captures log calls for testing type mockLogger struct { debugCalls []logCall errorCalls []logCall } type logCall struct { msg string keyvals []any } func (m *mockLogger) Debug(msg string, keyvals ...any) { m.debugCalls = append(m.debugCalls, logCall{msg, keyvals}) } func (m *mockLogger) Error(msg string, keyvals ...any) { m.errorCalls = append(m.errorCalls, logCall{msg, keyvals}) } func TestCommand_Run_Good(t *testing.T) { logger := &mockLogger{} ctx := context.Background() err := exec.Command(ctx, "echo", "hello"). WithLogger(logger). Run() if err != nil { t.Fatalf("unexpected error: %v", err) } if len(logger.debugCalls) != 1 { t.Fatalf("expected 1 debug call, got %d", len(logger.debugCalls)) } if logger.debugCalls[0].msg != "executing command" { t.Errorf("expected msg 'executing command', got %q", logger.debugCalls[0].msg) } if len(logger.errorCalls) != 0 { t.Errorf("expected no error calls, got %d", len(logger.errorCalls)) } } func TestCommand_Run_Bad(t *testing.T) { logger := &mockLogger{} ctx := context.Background() err := exec.Command(ctx, "false"). WithLogger(logger). Run() if err == nil { t.Fatal("expected error") } if len(logger.debugCalls) != 1 { t.Fatalf("expected 1 debug call, got %d", len(logger.debugCalls)) } if len(logger.errorCalls) != 1 { t.Fatalf("expected 1 error call, got %d", len(logger.errorCalls)) } if logger.errorCalls[0].msg != "command failed" { t.Errorf("expected msg 'command failed', got %q", logger.errorCalls[0].msg) } } func TestCommand_Output_Good(t *testing.T) { logger := &mockLogger{} ctx := context.Background() out, err := exec.Command(ctx, "echo", "test"). WithLogger(logger). Output() if err != nil { t.Fatalf("unexpected error: %v", err) } if core.Trim(string(out)) != "test" { t.Errorf("expected 'test', got %q", string(out)) } if len(logger.debugCalls) != 1 { t.Errorf("expected 1 debug call, got %d", len(logger.debugCalls)) } } func TestCommand_CombinedOutput_Good(t *testing.T) { logger := &mockLogger{} ctx := context.Background() out, err := exec.Command(ctx, "echo", "combined"). WithLogger(logger). CombinedOutput() if err != nil { t.Fatalf("unexpected error: %v", err) } if core.Trim(string(out)) != "combined" { t.Errorf("expected 'combined', got %q", string(out)) } if len(logger.debugCalls) != 1 { t.Errorf("expected 1 debug call, got %d", len(logger.debugCalls)) } } func TestNopLogger_Methods_Good(t *testing.T) { // Verify NopLogger doesn't panic var nop exec.NopLogger nop.Debug("msg", "key", "val") nop.Error("msg", "key", "val") } func TestLogger_SetDefault_Good(t *testing.T) { original := exec.DefaultLogger() defer exec.SetDefaultLogger(original) logger := &mockLogger{} exec.SetDefaultLogger(logger) if exec.DefaultLogger() != logger { t.Error("default logger not set correctly") } // Test nil resets to NopLogger exec.SetDefaultLogger(nil) if _, ok := exec.DefaultLogger().(exec.NopLogger); !ok { t.Error("expected NopLogger when setting nil") } } func TestCommand_UsesDefaultLogger_Good(t *testing.T) { original := exec.DefaultLogger() defer exec.SetDefaultLogger(original) logger := &mockLogger{} exec.SetDefaultLogger(logger) ctx := context.Background() _ = exec.Command(ctx, "echo", "test").Run() if len(logger.debugCalls) != 1 { t.Errorf("expected default logger to receive 1 debug call, got %d", len(logger.debugCalls)) } } func TestCommand_WithDir_Good(t *testing.T) { ctx := context.Background() out, err := exec.Command(ctx, "pwd"). WithDir("/tmp"). WithLogger(&mockLogger{}). Output() if err != nil { t.Fatalf("unexpected error: %v", err) } trimmed := core.Trim(string(out)) if trimmed != "/tmp" && trimmed != "/private/tmp" { t.Errorf("expected /tmp or /private/tmp, got %q", trimmed) } } func TestCommand_WithEnv_Good(t *testing.T) { ctx := context.Background() out, err := exec.Command(ctx, "sh", "-c", "echo $TEST_EXEC_VAR"). WithEnv([]string{"TEST_EXEC_VAR=exec_val"}). WithLogger(&mockLogger{}). Output() if err != nil { t.Fatalf("unexpected error: %v", err) } if core.Trim(string(out)) != "exec_val" { t.Errorf("expected 'exec_val', got %q", string(out)) } } func TestCommand_WithStdinStdoutStderr_Good(t *testing.T) { ctx := context.Background() input := core.NewReader("piped input\n") stdout := core.NewBuilder() stderr := core.NewBuilder() err := exec.Command(ctx, "cat"). WithStdin(input). WithStdout(stdout). WithStderr(stderr). WithLogger(&mockLogger{}). Run() if err != nil { t.Fatalf("unexpected error: %v", err) } if core.Trim(stdout.String()) != "piped input" { t.Errorf("expected 'piped input', got %q", stdout.String()) } } func TestRunQuiet_Command_Good(t *testing.T) { ctx := context.Background() err := exec.RunQuiet(ctx, "echo", "quiet") if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestRunQuiet_Command_Bad(t *testing.T) { ctx := context.Background() err := exec.RunQuiet(ctx, "sh", "-c", "echo fail >&2; exit 1") if err == nil { t.Fatal("expected error") } } func TestCommand_Run_Ugly(t *testing.T) { t.Run("cancelled context terminates command", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() err := exec.Command(ctx, "sleep", "10"). WithLogger(&mockLogger{}). Run() if err == nil { t.Fatal("expected error from cancelled context") } }) } func TestCommand_Output_Bad(t *testing.T) { t.Run("non-zero exit returns error", func(t *testing.T) { ctx := context.Background() _, err := exec.Command(ctx, "sh", "-c", "exit 1"). WithLogger(&mockLogger{}). Output() if err == nil { t.Fatal("expected error") } }) } func TestCommand_Output_Ugly(t *testing.T) { t.Run("non-existent command returns error", func(t *testing.T) { ctx := context.Background() _, err := exec.Command(ctx, "nonexistent_command_xyz_abc"). WithLogger(&mockLogger{}). Output() if err == nil { t.Fatal("expected error for non-existent command") } }) } func TestCommand_CombinedOutput_Bad(t *testing.T) { t.Run("non-zero exit returns output and error", func(t *testing.T) { ctx := context.Background() out, err := exec.Command(ctx, "sh", "-c", "echo stderr >&2; exit 1"). WithLogger(&mockLogger{}). CombinedOutput() if err == nil { t.Fatal("expected error") } if string(out) == "" { t.Error("expected combined output even on failure") } }) } func TestCommand_CombinedOutput_Ugly(t *testing.T) { t.Run("command with no output returns empty bytes", func(t *testing.T) { ctx := context.Background() out, err := exec.Command(ctx, "true"). WithLogger(&mockLogger{}). CombinedOutput() if err != nil { t.Fatalf("unexpected error: %v", err) } if len(out) != 0 { t.Errorf("expected empty output, got %q", string(out)) } }) } func TestRunQuiet_Command_Ugly(t *testing.T) { t.Run("non-existent command returns error", func(t *testing.T) { ctx := context.Background() err := exec.RunQuiet(ctx, "nonexistent_command_xyz_abc") if err == nil { t.Fatal("expected error for non-existent command") } }) }