package installer import ( "context" "io" "net/http" "os" "path/filepath" "strings" "sync" "time" ) // MockCommandExecutor implements CommandExecutor for testing type MockCommandExecutor struct { mu sync.Mutex Commands []CommandCall ShellCommands []string RunCommandFunc func(name string, args ...string) error RunShellFunc func(script string) error ctx context.Context } // CommandCall records a single command invocation type CommandCall struct { Name string Args []string } // NewMockCommandExecutor creates a new mock command executor func NewMockCommandExecutor() *MockCommandExecutor { return &MockCommandExecutor{ ctx: context.Background(), } } // RunCommand records and optionally executes a command func (m *MockCommandExecutor) RunCommand(name string, args ...string) error { m.mu.Lock() m.Commands = append(m.Commands, CommandCall{Name: name, Args: args}) m.mu.Unlock() if m.RunCommandFunc != nil { return m.RunCommandFunc(name, args...) } return nil } // RunShellCommand records and optionally executes a shell command func (m *MockCommandExecutor) RunShellCommand(script string) error { m.mu.Lock() m.ShellCommands = append(m.ShellCommands, script) m.mu.Unlock() if m.RunShellFunc != nil { return m.RunShellFunc(script) } return nil } // RunCommands executes multiple shell commands func (m *MockCommandExecutor) RunCommands(commands []string) error { for _, cmd := range commands { if err := m.RunShellCommand(cmd); err != nil { return err } } return nil } // Context returns the command executor's context func (m *MockCommandExecutor) Context() context.Context { return m.ctx } // SetContext sets the context for the command executor func (m *MockCommandExecutor) SetContext(ctx context.Context) { m.ctx = ctx } // IsCancelled returns true if the context has been cancelled func (m *MockCommandExecutor) IsCancelled() bool { select { case <-m.ctx.Done(): return true default: return false } } // CheckCancelled returns an error if the context has been cancelled func (m *MockCommandExecutor) CheckCancelled() error { if m.IsCancelled() { return m.ctx.Err() } return nil } // GetCommands returns a copy of all recorded commands func (m *MockCommandExecutor) GetCommands() []CommandCall { m.mu.Lock() defer m.mu.Unlock() result := make([]CommandCall, len(m.Commands)) copy(result, m.Commands) return result } // GetShellCommands returns a copy of all recorded shell commands func (m *MockCommandExecutor) GetShellCommands() []string { m.mu.Lock() defer m.mu.Unlock() result := make([]string, len(m.ShellCommands)) copy(result, m.ShellCommands) return result } // Reset clears all recorded commands func (m *MockCommandExecutor) Reset() { m.mu.Lock() m.Commands = nil m.ShellCommands = nil m.mu.Unlock() } // MockFileSystem implements FileSystem for testing type MockFileSystem struct { mu sync.Mutex Files map[string][]byte Dirs map[string]bool CopiedFiles []CopyCall RenamedFiles []RenameCall // Function overrides for custom behavior ExistsFunc func(path string) bool ReadFileFunc func(path string) ([]byte, error) WriteFileFunc func(path string, data []byte, perm os.FileMode) error MkdirAllFunc func(path string, perm os.FileMode) error RemoveFunc func(path string) error StatFunc func(path string) (os.FileInfo, error) } // CopyCall records a file copy operation type CopyCall struct { Src string Dst string } // RenameCall records a file rename operation type RenameCall struct { OldPath string NewPath string } // NewMockFileSystem creates a new mock file system func NewMockFileSystem() *MockFileSystem { return &MockFileSystem{ Files: make(map[string][]byte), Dirs: make(map[string]bool), } } // Exists checks if a path exists func (m *MockFileSystem) Exists(path string) bool { if m.ExistsFunc != nil { return m.ExistsFunc(path) } m.mu.Lock() defer m.mu.Unlock() _, hasFile := m.Files[path] _, hasDir := m.Dirs[path] return hasFile || hasDir } // IsDir checks if a path is a directory func (m *MockFileSystem) IsDir(path string) bool { m.mu.Lock() defer m.mu.Unlock() return m.Dirs[path] } // MkdirAll creates a directory tree func (m *MockFileSystem) MkdirAll(path string, perm os.FileMode) error { if m.MkdirAllFunc != nil { return m.MkdirAllFunc(path, perm) } m.mu.Lock() m.Dirs[path] = true m.mu.Unlock() return nil } // MkdirTemp creates a temporary directory func (m *MockFileSystem) MkdirTemp(dir, prefix string) (string, error) { return "/tmp/mocktemp_" + prefix, nil } // ReadFile reads file contents func (m *MockFileSystem) ReadFile(path string) ([]byte, error) { if m.ReadFileFunc != nil { return m.ReadFileFunc(path) } m.mu.Lock() defer m.mu.Unlock() data, ok := m.Files[path] if !ok { return nil, os.ErrNotExist } return data, nil } // WriteFile writes data to a file func (m *MockFileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { if m.WriteFileFunc != nil { return m.WriteFileFunc(path, data, perm) } m.mu.Lock() m.Files[path] = data m.mu.Unlock() return nil } // Remove removes a file or directory func (m *MockFileSystem) Remove(path string) error { if m.RemoveFunc != nil { return m.RemoveFunc(path) } m.mu.Lock() delete(m.Files, path) delete(m.Dirs, path) m.mu.Unlock() return nil } // RemoveAll removes a path and all children func (m *MockFileSystem) RemoveAll(path string) error { m.mu.Lock() for p := range m.Files { if len(p) >= len(path) && p[:len(path)] == path { delete(m.Files, p) } } for p := range m.Dirs { if len(p) >= len(path) && p[:len(path)] == path { delete(m.Dirs, p) } } m.mu.Unlock() return nil } // Rename renames a file or directory func (m *MockFileSystem) Rename(oldPath, newPath string) error { m.mu.Lock() m.RenamedFiles = append(m.RenamedFiles, RenameCall{OldPath: oldPath, NewPath: newPath}) if data, ok := m.Files[oldPath]; ok { m.Files[newPath] = data delete(m.Files, oldPath) } m.mu.Unlock() return nil } // CopyFile copies a file func (m *MockFileSystem) CopyFile(src, dst string, perm os.FileMode) error { m.mu.Lock() m.CopiedFiles = append(m.CopiedFiles, CopyCall{Src: src, Dst: dst}) if data, ok := m.Files[src]; ok { m.Files[dst] = data } m.mu.Unlock() return nil } // CopyDir copies a directory func (m *MockFileSystem) CopyDir(src, dst string) error { m.mu.Lock() for path, data := range m.Files { if len(path) >= len(src) && path[:len(src)] == src { newPath := dst + path[len(src):] m.Files[newPath] = data } } m.mu.Unlock() return nil } // Stat returns file info for a path func (m *MockFileSystem) Stat(path string) (os.FileInfo, error) { if m.StatFunc != nil { return m.StatFunc(path) } m.mu.Lock() defer m.mu.Unlock() if _, ok := m.Files[path]; ok { return mockFileInfo{name: filepath.Base(path), isDir: false}, nil } if ok := m.Dirs[path]; ok { return mockFileInfo{name: filepath.Base(path), isDir: true}, nil } return nil, os.ErrNotExist } // Walk walks a directory tree func (m *MockFileSystem) Walk(root string, walkFn filepath.WalkFunc) error { m.mu.Lock() defer m.mu.Unlock() for path := range m.Files { if len(path) >= len(root) && path[:len(root)] == root { walkFn(path, mockFileInfo{name: filepath.Base(path)}, nil) } } return nil } // Glob returns matches for a pattern func (m *MockFileSystem) Glob(pattern string) ([]string, error) { m.mu.Lock() defer m.mu.Unlock() var matches []string for path := range m.Files { matched, _ := filepath.Match(pattern, path) if matched { matches = append(matches, path) } } return matches, nil } // mockFileInfo implements os.FileInfo for testing type mockFileInfo struct { name string isDir bool } func (m mockFileInfo) Name() string { return m.name } func (m mockFileInfo) Size() int64 { return 0 } func (m mockFileInfo) Mode() os.FileMode { return 0644 } func (m mockFileInfo) ModTime() time.Time { return time.Time{} } func (m mockFileInfo) IsDir() bool { return m.isDir } func (m mockFileInfo) Sys() interface{} { return nil } // MockHTTPClient implements HTTPClient for testing type MockHTTPClient struct { mu sync.Mutex Requests []*http.Request DoFunc func(req *http.Request) (*http.Response, error) } // NewMockHTTPClient creates a new mock HTTP client func NewMockHTTPClient() *MockHTTPClient { return &MockHTTPClient{} } // Do records and optionally executes an HTTP request func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { m.mu.Lock() m.Requests = append(m.Requests, req) m.mu.Unlock() if m.DoFunc != nil { return m.DoFunc(req) } // Default: return 200 OK with empty body return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("")), }, nil } // GetRequests returns a copy of all recorded requests func (m *MockHTTPClient) GetRequests() []*http.Request { m.mu.Lock() defer m.mu.Unlock() result := make([]*http.Request, len(m.Requests)) copy(result, m.Requests) return result } // Reset clears all recorded requests func (m *MockHTTPClient) Reset() { m.mu.Lock() m.Requests = nil m.mu.Unlock() } // MockDownloader implements Downloader for testing type MockDownloader struct { Downloads []DownloadCall Func func(ctx context.Context, url, dest string, progress func(int64, int64)) error } // DownloadCall records a download operation type DownloadCall struct { URL string Dest string } // NewMockDownloader creates a new mock downloader func NewMockDownloader() *MockDownloader { return &MockDownloader{} } // DownloadFile records and optionally performs a download func (m *MockDownloader) DownloadFile(ctx context.Context, url, dest string, progress func(int64, int64)) error { m.Downloads = append(m.Downloads, DownloadCall{URL: url, Dest: dest}) if m.Func != nil { return m.Func(ctx, url, dest, progress) } return nil } // MockOutputWriter implements OutputWriter for testing type MockOutputWriter struct { mu sync.Mutex Outputs []string Errors []error } // NewMockOutputWriter creates a new mock output writer func NewMockOutputWriter() *MockOutputWriter { return &MockOutputWriter{} } // WriteOutput records an output message func (m *MockOutputWriter) WriteOutput(msg string) { m.mu.Lock() m.Outputs = append(m.Outputs, msg) m.mu.Unlock() } // WriteError records an error func (m *MockOutputWriter) WriteError(err error) { m.mu.Lock() m.Errors = append(m.Errors, err) m.mu.Unlock() } // GetOutputs returns a copy of all recorded outputs func (m *MockOutputWriter) GetOutputs() []string { m.mu.Lock() defer m.mu.Unlock() result := make([]string, len(m.Outputs)) copy(result, m.Outputs) return result } // GetErrors returns a copy of all recorded errors func (m *MockOutputWriter) GetErrors() []error { m.mu.Lock() defer m.mu.Unlock() result := make([]error, len(m.Errors)) copy(result, m.Errors) return result } // Reset clears all recorded outputs and errors func (m *MockOutputWriter) Reset() { m.mu.Lock() m.Outputs = nil m.Errors = nil m.mu.Unlock() } // MockArchiveExtractor implements ArchiveExtractor for testing type MockArchiveExtractor struct { Extractions []ExtractionCall Func func(ctx context.Context, src, dest string) error SupportedFormatsList []string } // ExtractionCall records an extraction operation type ExtractionCall struct { Src string Dest string } // NewMockArchiveExtractor creates a new mock archive extractor func NewMockArchiveExtractor() *MockArchiveExtractor { return &MockArchiveExtractor{ SupportedFormatsList: []string{".zip", ".tar.gz", ".tar.xz", ".7z"}, } } // Extract records and optionally performs an extraction func (m *MockArchiveExtractor) Extract(ctx context.Context, src, dest string) error { m.Extractions = append(m.Extractions, ExtractionCall{Src: src, Dest: dest}) if m.Func != nil { return m.Func(ctx, src, dest) } return nil } // SupportedFormats returns the list of supported archive formats func (m *MockArchiveExtractor) SupportedFormats() []string { return m.SupportedFormatsList }