package installer import ( "context" "fmt" "net/http" "os" "strings" "testing" ) func TestMockCommandExecutor_RecordsCommands(t *testing.T) { mock := NewMockCommandExecutor() err := mock.RunCommand("flatpak", "install", "-y", "io.mpv.Mpv") if err != nil { t.Fatalf("unexpected error: %v", err) } commands := mock.GetCommands() if len(commands) != 1 { t.Fatalf("expected 1 command, got %d", len(commands)) } if commands[0].Name != "flatpak" { t.Errorf("expected command name 'flatpak', got %q", commands[0].Name) } expectedArgs := []string{"install", "-y", "io.mpv.Mpv"} if len(commands[0].Args) != len(expectedArgs) { t.Errorf("expected %d args, got %d", len(expectedArgs), len(commands[0].Args)) } for i, arg := range expectedArgs { if commands[0].Args[i] != arg { t.Errorf("expected arg[%d] = %q, got %q", i, arg, commands[0].Args[i]) } } } func TestMockCommandExecutor_CustomBehavior(t *testing.T) { mock := NewMockCommandExecutor() mock.RunCommandFunc = func(name string, args ...string) error { return fmt.Errorf("simulated failure") } err := mock.RunCommand("apt", "install", "mpv") if err == nil { t.Fatal("expected error, got nil") } if err.Error() != "simulated failure" { t.Errorf("expected 'simulated failure', got %q", err.Error()) } } func TestMockCommandExecutor_Cancellation(t *testing.T) { mock := NewMockCommandExecutor() ctx, cancel := context.WithCancel(context.Background()) mock.SetContext(ctx) if mock.IsCancelled() { t.Fatal("should not be cancelled initially") } cancel() if !mock.IsCancelled() { t.Fatal("should be cancelled after cancel()") } err := mock.CheckCancelled() if err == nil { t.Fatal("CheckCancelled should return error when cancelled") } } func TestMockCommandExecutor_RunCommands(t *testing.T) { mock := NewMockCommandExecutor() commands := []string{ "echo 'hello'", "echo 'world'", } err := mock.RunCommands(commands) if err != nil { t.Fatalf("unexpected error: %v", err) } shellCommands := mock.GetShellCommands() if len(shellCommands) != 2 { t.Fatalf("expected 2 shell commands, got %d", len(shellCommands)) } } func TestMockCommandExecutor_RunCommands_WithError(t *testing.T) { mock := NewMockCommandExecutor() mock.RunShellFunc = func(script string) error { if strings.Contains(script, "fail") { return fmt.Errorf("command failed") } return nil } commands := []string{ "echo 'hello'", "fail", "echo 'world'", } err := mock.RunCommands(commands) if err == nil { t.Fatal("expected error, got nil") } // Should have stopped after the failing command shellCommands := mock.GetShellCommands() if len(shellCommands) != 2 { t.Errorf("expected 2 shell commands (stopped at failure), got %d", len(shellCommands)) } } func TestMockCommandExecutor_Reset(t *testing.T) { mock := NewMockCommandExecutor() mock.RunCommand("test", "arg1") mock.RunShellCommand("echo test") mock.Reset() commands := mock.GetCommands() if len(commands) != 0 { t.Errorf("expected 0 commands after reset, got %d", len(commands)) } shellCommands := mock.GetShellCommands() if len(shellCommands) != 0 { t.Errorf("expected 0 shell commands after reset, got %d", len(shellCommands)) } } func TestMockFileSystem_RecordsOperations(t *testing.T) { mock := NewMockFileSystem() // Test WriteFile err := mock.WriteFile("/test/file.txt", []byte("hello"), 0644) if err != nil { t.Fatalf("unexpected error: %v", err) } // Test Exists if !mock.Exists("/test/file.txt") { t.Error("file should exist") } // Test ReadFile data, err := mock.ReadFile("/test/file.txt") if err != nil { t.Fatalf("unexpected error: %v", err) } if string(data) != "hello" { t.Errorf("expected 'hello', got %q", string(data)) } } func TestMockFileSystem_Directories(t *testing.T) { mock := NewMockFileSystem() // Test MkdirAll err := mock.MkdirAll("/test/nested/dir", 0755) if err != nil { t.Fatalf("unexpected error: %v", err) } // Test IsDir if !mock.IsDir("/test/nested/dir") { t.Error("path should be a directory") } // Test Exists for directory if !mock.Exists("/test/nested/dir") { t.Error("directory should exist") } } func TestMockFileSystem_Remove(t *testing.T) { mock := NewMockFileSystem() mock.WriteFile("/test/file.txt", []byte("content"), 0644) mock.MkdirAll("/test/dir", 0755) // Remove file err := mock.Remove("/test/file.txt") if err != nil { t.Fatalf("unexpected error: %v", err) } if mock.Exists("/test/file.txt") { t.Error("file should not exist after remove") } // Remove directory err = mock.Remove("/test/dir") if err != nil { t.Fatalf("unexpected error: %v", err) } if mock.Exists("/test/dir") { t.Error("directory should not exist after remove") } } func TestMockFileSystem_RemoveAll(t *testing.T) { mock := NewMockFileSystem() mock.WriteFile("/test/file1.txt", []byte("content1"), 0644) mock.WriteFile("/test/file2.txt", []byte("content2"), 0644) mock.WriteFile("/test/nested/file3.txt", []byte("content3"), 0644) err := mock.RemoveAll("/test") if err != nil { t.Fatalf("unexpected error: %v", err) } if mock.Exists("/test/file1.txt") { t.Error("file1 should not exist after RemoveAll") } if mock.Exists("/test/file2.txt") { t.Error("file2 should not exist after RemoveAll") } if mock.Exists("/test/nested/file3.txt") { t.Error("nested file should not exist after RemoveAll") } } func TestMockFileSystem_Rename(t *testing.T) { mock := NewMockFileSystem() mock.WriteFile("/test/old.txt", []byte("content"), 0644) err := mock.Rename("/test/old.txt", "/test/new.txt") if err != nil { t.Fatalf("unexpected error: %v", err) } if mock.Exists("/test/old.txt") { t.Error("old file should not exist after rename") } if !mock.Exists("/test/new.txt") { t.Error("new file should exist after rename") } data, _ := mock.ReadFile("/test/new.txt") if string(data) != "content" { t.Errorf("expected 'content', got %q", string(data)) } } func TestMockFileSystem_CopyFile(t *testing.T) { mock := NewMockFileSystem() mock.WriteFile("/test/src.txt", []byte("source content"), 0644) err := mock.CopyFile("/test/src.txt", "/test/dst.txt", 0644) if err != nil { t.Fatalf("unexpected error: %v", err) } data, _ := mock.ReadFile("/test/dst.txt") if string(data) != "source content" { t.Errorf("expected 'source content', got %q", string(data)) } if len(mock.CopiedFiles) != 1 { t.Errorf("expected 1 copy record, got %d", len(mock.CopiedFiles)) } } func TestMockFileSystem_Stat(t *testing.T) { mock := NewMockFileSystem() mock.WriteFile("/test/file.txt", []byte("content"), 0644) mock.MkdirAll("/test/dir", 0755) // Stat file info, err := mock.Stat("/test/file.txt") if err != nil { t.Fatalf("unexpected error: %v", err) } if info.IsDir() { t.Error("file should not be reported as directory") } if info.Name() != "file.txt" { t.Errorf("expected name 'file.txt', got %q", info.Name()) } // Stat directory info, err = mock.Stat("/test/dir") if err != nil { t.Fatalf("unexpected error: %v", err) } if !info.IsDir() { t.Error("directory should be reported as directory") } // Stat non-existent file _, err = mock.Stat("/test/nonexistent") if !os.IsNotExist(err) { t.Errorf("expected os.ErrNotExist, got %v", err) } } func TestMockFileSystem_CustomBehavior(t *testing.T) { mock := NewMockFileSystem() // Override Exists behavior mock.ExistsFunc = func(path string) bool { return path == "/allowed" } if !mock.Exists("/allowed") { t.Error("should return true for allowed path") } if mock.Exists("/notallowed") { t.Error("should return false for non-allowed path") } // Override ReadFile behavior mock.ReadFileFunc = func(path string) ([]byte, error) { if path == "/error" { return nil, fmt.Errorf("read error") } return []byte("custom"), nil } data, err := mock.ReadFile("/normal") if err != nil { t.Fatalf("unexpected error: %v", err) } if string(data) != "custom" { t.Errorf("expected 'custom', got %q", string(data)) } _, err = mock.ReadFile("/error") if err == nil { t.Error("expected error for /error path") } } func TestMockHTTPClient_RecordsRequests(t *testing.T) { mock := NewMockHTTPClient() req, _ := http.NewRequest("GET", "http://example.com/file.zip", nil) resp, err := mock.Do(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("expected 200, got %d", resp.StatusCode) } requests := mock.GetRequests() if len(requests) != 1 { t.Errorf("expected 1 request, got %d", len(requests)) } if requests[0].URL.String() != "http://example.com/file.zip" { t.Errorf("unexpected URL: %s", requests[0].URL.String()) } } func TestMockHTTPClient_CustomBehavior(t *testing.T) { mock := NewMockHTTPClient() mock.DoFunc = func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusNotFound, Body: http.NoBody, }, nil } req, _ := http.NewRequest("GET", "http://example.com/notfound", nil) resp, err := mock.Do(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != http.StatusNotFound { t.Errorf("expected 404, got %d", resp.StatusCode) } } func TestMockHTTPClient_Reset(t *testing.T) { mock := NewMockHTTPClient() req, _ := http.NewRequest("GET", "http://example.com/", nil) mock.Do(req) mock.Do(req) mock.Reset() requests := mock.GetRequests() if len(requests) != 0 { t.Errorf("expected 0 requests after reset, got %d", len(requests)) } } func TestMockDownloader_RecordsDownloads(t *testing.T) { mock := NewMockDownloader() err := mock.DownloadFile(context.Background(), "http://example.com/file.zip", "/tmp/file.zip", nil) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(mock.Downloads) != 1 { t.Fatalf("expected 1 download, got %d", len(mock.Downloads)) } if mock.Downloads[0].URL != "http://example.com/file.zip" { t.Errorf("unexpected URL: %s", mock.Downloads[0].URL) } if mock.Downloads[0].Dest != "/tmp/file.zip" { t.Errorf("unexpected destination: %s", mock.Downloads[0].Dest) } } func TestMockDownloader_CustomBehavior(t *testing.T) { mock := NewMockDownloader() mock.Func = func(ctx context.Context, url, dest string, progress func(int64, int64)) error { return fmt.Errorf("network error") } err := mock.DownloadFile(context.Background(), "http://example.com/file.zip", "/tmp/file.zip", nil) if err == nil { t.Fatal("expected error, got nil") } if err.Error() != "network error" { t.Errorf("expected 'network error', got %q", err.Error()) } } func TestMockOutputWriter_RecordsOutput(t *testing.T) { mock := NewMockOutputWriter() mock.WriteOutput("Installing MPV...") mock.WriteOutput("Download complete") outputs := mock.GetOutputs() if len(outputs) != 2 { t.Fatalf("expected 2 outputs, got %d", len(outputs)) } if outputs[0] != "Installing MPV..." { t.Errorf("unexpected output[0]: %q", outputs[0]) } if outputs[1] != "Download complete" { t.Errorf("unexpected output[1]: %q", outputs[1]) } } func TestMockOutputWriter_RecordsErrors(t *testing.T) { mock := NewMockOutputWriter() mock.WriteError(fmt.Errorf("error 1")) mock.WriteError(fmt.Errorf("error 2")) errors := mock.GetErrors() if len(errors) != 2 { t.Fatalf("expected 2 errors, got %d", len(errors)) } } func TestMockOutputWriter_Reset(t *testing.T) { mock := NewMockOutputWriter() mock.WriteOutput("test") mock.WriteError(fmt.Errorf("test")) mock.Reset() if len(mock.GetOutputs()) != 0 { t.Error("outputs should be empty after reset") } if len(mock.GetErrors()) != 0 { t.Error("errors should be empty after reset") } } func TestMockArchiveExtractor_RecordsExtractions(t *testing.T) { mock := NewMockArchiveExtractor() err := mock.Extract(context.Background(), "/tmp/archive.zip", "/tmp/extracted") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(mock.Extractions) != 1 { t.Fatalf("expected 1 extraction, got %d", len(mock.Extractions)) } if mock.Extractions[0].Src != "/tmp/archive.zip" { t.Errorf("unexpected src: %s", mock.Extractions[0].Src) } if mock.Extractions[0].Dest != "/tmp/extracted" { t.Errorf("unexpected dest: %s", mock.Extractions[0].Dest) } } func TestMockArchiveExtractor_SupportedFormats(t *testing.T) { mock := NewMockArchiveExtractor() formats := mock.SupportedFormats() if len(formats) == 0 { t.Error("expected at least one supported format") } found := false for _, f := range formats { if f == ".zip" { found = true break } } if !found { t.Error("expected .zip to be in supported formats") } } func TestMockArchiveExtractor_CustomBehavior(t *testing.T) { mock := NewMockArchiveExtractor() mock.Func = func(ctx context.Context, src, dest string) error { return fmt.Errorf("extraction failed") } err := mock.Extract(context.Background(), "/tmp/archive.zip", "/tmp/extracted") if err == nil { t.Fatal("expected error, got nil") } if err.Error() != "extraction failed" { t.Errorf("expected 'extraction failed', got %q", err.Error()) } } // Test compile-time interface compliance func TestInterfaceCompliance(t *testing.T) { // These compile-time checks are in the source files, but we can // verify the interfaces are satisfied here as well var _ CommandExecutor = NewMockCommandExecutor() var _ FileSystem = NewMockFileSystem() var _ HTTPClient = NewMockHTTPClient() var _ Downloader = NewMockDownloader() var _ OutputWriter = NewMockOutputWriter() var _ ArchiveExtractor = NewMockArchiveExtractor() }