// Package testutil provides testing utilities for rdev-api. package testutil import ( "context" "sync" "github.com/orchard9/rdev/internal/executor" ) // MockExecutor is a mock implementation of the Executor for testing. type MockExecutor struct { mu sync.Mutex ExecCalls []ExecCall ExecResult executor.Result ExecOutputs []OutputLine PodExistsMap map[string]bool ConnectionError error } // ExecCall records the parameters of an Exec call. type ExecCall struct { Cmd *executor.Command } // OutputLine represents a line of output to send. type OutputLine struct { Stream string Line string } // Ensure MockExecutor implements CommandExecutor at compile time. var _ executor.CommandExecutor = (*MockExecutor)(nil) // NewMockExecutor creates a new mock executor. func NewMockExecutor() *MockExecutor { return &MockExecutor{ PodExistsMap: make(map[string]bool), } } // Exec mocks command execution. func (m *MockExecutor) Exec(ctx context.Context, cmd *executor.Command, handler executor.OutputHandler) executor.Result { m.mu.Lock() m.ExecCalls = append(m.ExecCalls, ExecCall{Cmd: cmd}) outputs := m.ExecOutputs result := m.ExecResult m.mu.Unlock() // Send mock outputs for _, o := range outputs { select { case <-ctx.Done(): return executor.Result{ExitCode: 130, Error: ctx.Err()} default: handler(o.Stream, o.Line) } } return result } // SetExecResult sets the result to return from Exec. func (m *MockExecutor) SetExecResult(result executor.Result) { m.mu.Lock() defer m.mu.Unlock() m.ExecResult = result } // SetExecOutputs sets the outputs to send during Exec. func (m *MockExecutor) SetExecOutputs(outputs []OutputLine) { m.mu.Lock() defer m.mu.Unlock() m.ExecOutputs = outputs } // PodExists mocks pod existence check. func (m *MockExecutor) PodExists(ctx context.Context, podName string) (bool, error) { m.mu.Lock() defer m.mu.Unlock() exists, ok := m.PodExistsMap[podName] if !ok { return false, nil } return exists, nil } // CheckConnection mocks cluster connection check. func (m *MockExecutor) CheckConnection(ctx context.Context) error { m.mu.Lock() defer m.mu.Unlock() return m.ConnectionError } // SetConnectionError sets the error to return from CheckConnection. func (m *MockExecutor) SetConnectionError(err error) { m.mu.Lock() defer m.mu.Unlock() m.ConnectionError = err } // SetPodExists sets whether a pod exists. func (m *MockExecutor) SetPodExists(podName string, exists bool) { m.mu.Lock() defer m.mu.Unlock() m.PodExistsMap[podName] = exists } // GetExecCalls returns all recorded Exec calls. func (m *MockExecutor) GetExecCalls() []ExecCall { m.mu.Lock() defer m.mu.Unlock() return append([]ExecCall{}, m.ExecCalls...) } // Reset clears all recorded calls and resets the mock. func (m *MockExecutor) Reset() { m.mu.Lock() defer m.mu.Unlock() m.ExecCalls = nil m.ExecOutputs = nil m.ExecResult = executor.Result{} m.PodExistsMap = make(map[string]bool) }