Skip to main content

sal_os/
package.rs

1use std::process::Command;
2
3/// A structure to hold command execution results
4#[derive(Debug, Clone)]
5pub struct CommandResult {
6    pub stdout: String,
7    pub stderr: String,
8    pub success: bool,
9    pub code: i32,
10}
11
12/// Error type for package management operations
13#[derive(Debug)]
14pub enum PackageError {
15    /// Command failed with error message
16    CommandFailed(String),
17    /// Command execution failed with IO error
18    CommandExecutionFailed(std::io::Error),
19    /// Unsupported platform
20    UnsupportedPlatform(String),
21    /// Other error
22    Other(String),
23}
24
25impl std::fmt::Display for PackageError {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            PackageError::CommandFailed(msg) => write!(f, "Command failed: {}", msg),
29            PackageError::CommandExecutionFailed(e) => write!(f, "Command execution failed: {}", e),
30            PackageError::UnsupportedPlatform(msg) => write!(f, "Unsupported platform: {}", msg),
31            PackageError::Other(msg) => write!(f, "Error: {}", msg),
32        }
33    }
34}
35
36impl std::error::Error for PackageError {}
37
38/// Platform enum for detecting the current operating system
39#[derive(Debug, Clone, Copy, PartialEq)]
40pub enum Platform {
41    /// Ubuntu Linux
42    Ubuntu,
43    /// macOS
44    MacOS,
45    /// Unknown platform
46    Unknown,
47}
48
49impl Platform {
50    /// Detect the current platform
51    pub fn detect() -> Self {
52        // Check for macOS
53        if std::path::Path::new("/usr/bin/sw_vers").exists() {
54            return Platform::MacOS;
55        }
56
57        // Check for Ubuntu
58        if std::path::Path::new("/etc/lsb-release").exists() {
59            // Read the file to confirm it's Ubuntu
60            if let Ok(content) = std::fs::read_to_string("/etc/lsb-release") {
61                if content.contains("Ubuntu") {
62                    return Platform::Ubuntu;
63                }
64            }
65        }
66
67        Platform::Unknown
68    }
69}
70
71// Thread-local storage for debug flag
72thread_local! {
73    static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
74}
75
76/// Set the debug flag for the current thread
77pub fn set_thread_local_debug(debug: bool) {
78    DEBUG.with(|cell| {
79        *cell.borrow_mut() = debug;
80    });
81}
82
83/// Get the debug flag for the current thread
84pub fn thread_local_debug() -> bool {
85    DEBUG.with(|cell| *cell.borrow())
86}
87
88/// Execute a package management command and return the result
89pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> {
90    // Save the current debug flag
91    let previous_debug = thread_local_debug();
92
93    // Set the thread-local debug flag
94    set_thread_local_debug(debug);
95
96    if debug {
97        println!("Executing command: {}", args.join(" "));
98    }
99
100    let output = Command::new(args[0]).args(&args[1..]).output();
101
102    // Restore the previous debug flag
103    set_thread_local_debug(previous_debug);
104
105    match output {
106        Ok(output) => {
107            let stdout = String::from_utf8_lossy(&output.stdout).to_string();
108            let stderr = String::from_utf8_lossy(&output.stderr).to_string();
109
110            let result = CommandResult {
111                stdout,
112                stderr,
113                success: output.status.success(),
114                code: output.status.code().unwrap_or(-1),
115            };
116
117            // Always output stdout/stderr when debug is true
118            if debug {
119                if !result.stdout.is_empty() {
120                    println!("Command stdout: {}", result.stdout);
121                }
122
123                if !result.stderr.is_empty() {
124                    println!("Command stderr: {}", result.stderr);
125                }
126
127                if result.success {
128                    println!("Command succeeded with code {}", result.code);
129                } else {
130                    println!("Command failed with code {}", result.code);
131                }
132            }
133
134            if result.success {
135                Ok(result)
136            } else {
137                // If command failed and debug is false, output stderr
138                if !debug {
139                    println!(
140                        "Command failed with code {}: {}",
141                        result.code,
142                        result.stderr.trim()
143                    );
144                }
145                Err(PackageError::CommandFailed(format!(
146                    "Command failed with code {}: {}",
147                    result.code,
148                    result.stderr.trim()
149                )))
150            }
151        }
152        Err(e) => {
153            // Always output error information
154            println!("Command execution failed: {}", e);
155            Err(PackageError::CommandExecutionFailed(e))
156        }
157    }
158}
159
160/// Trait for package managers
161pub trait PackageManager {
162    /// Install a package
163    fn install(&self, package: &str) -> Result<CommandResult, PackageError>;
164
165    /// Remove a package
166    fn remove(&self, package: &str) -> Result<CommandResult, PackageError>;
167
168    /// Update package lists
169    fn update(&self) -> Result<CommandResult, PackageError>;
170
171    /// Upgrade installed packages
172    fn upgrade(&self) -> Result<CommandResult, PackageError>;
173
174    /// List installed packages
175    fn list_installed(&self) -> Result<Vec<String>, PackageError>;
176
177    /// Search for packages
178    fn search(&self, query: &str) -> Result<Vec<String>, PackageError>;
179
180    /// Check if a package is installed
181    fn is_installed(&self, package: &str) -> Result<bool, PackageError>;
182}
183
184/// APT package manager for Ubuntu
185pub struct AptPackageManager {
186    debug: bool,
187}
188
189impl AptPackageManager {
190    /// Create a new APT package manager
191    pub fn new(debug: bool) -> Self {
192        Self { debug }
193    }
194}
195
196impl PackageManager for AptPackageManager {
197    fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
198        // Use -y to make it non-interactive and --quiet to reduce output
199        execute_package_command(
200            &["apt-get", "install", "-y", "--quiet", package],
201            self.debug,
202        )
203    }
204
205    fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
206        // Use -y to make it non-interactive and --quiet to reduce output
207        execute_package_command(&["apt-get", "remove", "-y", "--quiet", package], self.debug)
208    }
209
210    fn update(&self) -> Result<CommandResult, PackageError> {
211        // Use -y to make it non-interactive and --quiet to reduce output
212        execute_package_command(&["apt-get", "update", "-y", "--quiet"], self.debug)
213    }
214
215    fn upgrade(&self) -> Result<CommandResult, PackageError> {
216        // Use -y to make it non-interactive and --quiet to reduce output
217        execute_package_command(&["apt-get", "upgrade", "-y", "--quiet"], self.debug)
218    }
219
220    fn list_installed(&self) -> Result<Vec<String>, PackageError> {
221        let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?;
222        let packages = result
223            .stdout
224            .lines()
225            .filter_map(|line| {
226                let parts: Vec<&str> = line.split_whitespace().collect();
227                if parts.len() >= 2 && parts[1] == "install" {
228                    Some(parts[0].to_string())
229                } else {
230                    None
231                }
232            })
233            .collect();
234        Ok(packages)
235    }
236
237    fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
238        let result = execute_package_command(&["apt-cache", "search", query], self.debug)?;
239        let packages = result
240            .stdout
241            .lines()
242            .map(|line| {
243                let parts: Vec<&str> = line.split_whitespace().collect();
244                if !parts.is_empty() {
245                    parts[0].to_string()
246                } else {
247                    String::new()
248                }
249            })
250            .filter(|s| !s.is_empty())
251            .collect();
252        Ok(packages)
253    }
254
255    fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
256        let result = execute_package_command(&["dpkg", "-s", package], self.debug);
257        match result {
258            Ok(cmd_result) => Ok(cmd_result.success),
259            Err(_) => Ok(false),
260        }
261    }
262}
263
264/// Homebrew package manager for macOS
265pub struct BrewPackageManager {
266    debug: bool,
267}
268
269impl BrewPackageManager {
270    /// Create a new Homebrew package manager
271    pub fn new(debug: bool) -> Self {
272        Self { debug }
273    }
274}
275
276impl PackageManager for BrewPackageManager {
277    fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
278        // Use --quiet to reduce output
279        execute_package_command(&["brew", "install", "--quiet", package], self.debug)
280    }
281
282    fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
283        // Use --quiet to reduce output
284        execute_package_command(&["brew", "uninstall", "--quiet", package], self.debug)
285    }
286
287    fn update(&self) -> Result<CommandResult, PackageError> {
288        // Use --quiet to reduce output
289        execute_package_command(&["brew", "update", "--quiet"], self.debug)
290    }
291
292    fn upgrade(&self) -> Result<CommandResult, PackageError> {
293        // Use --quiet to reduce output
294        execute_package_command(&["brew", "upgrade", "--quiet"], self.debug)
295    }
296
297    fn list_installed(&self) -> Result<Vec<String>, PackageError> {
298        let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?;
299        let packages = result
300            .stdout
301            .lines()
302            .map(|line| line.trim().to_string())
303            .filter(|s| !s.is_empty())
304            .collect();
305        Ok(packages)
306    }
307
308    fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
309        let result = execute_package_command(&["brew", "search", query], self.debug)?;
310        let packages = result
311            .stdout
312            .lines()
313            .map(|line| line.trim().to_string())
314            .filter(|s| !s.is_empty())
315            .collect();
316        Ok(packages)
317    }
318
319    fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
320        let result = execute_package_command(&["brew", "list", package], self.debug);
321        match result {
322            Ok(cmd_result) => Ok(cmd_result.success),
323            Err(_) => Ok(false),
324        }
325    }
326}
327
328/// PackHero factory for package management
329pub struct PackHero {
330    platform: Platform,
331    debug: bool,
332}
333
334impl PackHero {
335    /// Create a new PackHero instance
336    pub fn new() -> Self {
337        let platform = Platform::detect();
338        Self {
339            platform,
340            debug: false,
341        }
342    }
343
344    /// Set the debug mode
345    pub fn set_debug(&mut self, debug: bool) -> &mut Self {
346        self.debug = debug;
347        self
348    }
349
350    /// Get the debug mode
351    pub fn debug(&self) -> bool {
352        self.debug
353    }
354
355    /// Get the detected platform
356    pub fn platform(&self) -> Platform {
357        self.platform
358    }
359
360    /// Get a package manager for the current platform
361    fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> {
362        match self.platform {
363            Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))),
364            Platform::MacOS => Ok(Box::new(BrewPackageManager::new(self.debug))),
365            Platform::Unknown => Err(PackageError::UnsupportedPlatform(
366                "Unsupported platform".to_string(),
367            )),
368        }
369    }
370
371    /// Install a package
372    pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
373        let pm = self.get_package_manager()?;
374        pm.install(package)
375    }
376
377    /// Remove a package
378    pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
379        let pm = self.get_package_manager()?;
380        pm.remove(package)
381    }
382
383    /// Update package lists
384    pub fn update(&self) -> Result<CommandResult, PackageError> {
385        let pm = self.get_package_manager()?;
386        pm.update()
387    }
388
389    /// Upgrade installed packages
390    pub fn upgrade(&self) -> Result<CommandResult, PackageError> {
391        let pm = self.get_package_manager()?;
392        pm.upgrade()
393    }
394
395    /// List installed packages
396    pub fn list_installed(&self) -> Result<Vec<String>, PackageError> {
397        let pm = self.get_package_manager()?;
398        pm.list_installed()
399    }
400
401    /// Search for packages
402    pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
403        let pm = self.get_package_manager()?;
404        pm.search(query)
405    }
406
407    /// Check if a package is installed
408    pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
409        let pm = self.get_package_manager()?;
410        pm.is_installed(package)
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    // Import the std::process::Command directly for some test-specific commands
417    use super::*;
418    use std::process::Command as StdCommand;
419    use std::sync::{Arc, Mutex};
420
421    #[test]
422    fn test_platform_detection() {
423        // Test that platform detection returns a valid platform
424        let platform = Platform::detect();
425        println!("Detected platform: {:?}", platform);
426
427        // Verify that we get one of the expected platform values
428        match platform {
429            Platform::Ubuntu | Platform::MacOS | Platform::Unknown => {
430                // All valid platforms
431            }
432        }
433
434        // Test that detection is consistent (calling it twice should return the same result)
435        let platform2 = Platform::detect();
436        assert_eq!(platform, platform2);
437
438        // Test that the platform detection logic makes sense for the current environment
439        match platform {
440            Platform::MacOS => {
441                // If detected as macOS, sw_vers should exist
442                assert!(std::path::Path::new("/usr/bin/sw_vers").exists());
443            }
444            Platform::Ubuntu => {
445                // If detected as Ubuntu, lsb-release should exist and contain "Ubuntu"
446                assert!(std::path::Path::new("/etc/lsb-release").exists());
447                if let Ok(content) = std::fs::read_to_string("/etc/lsb-release") {
448                    assert!(content.contains("Ubuntu"));
449                }
450            }
451            Platform::Unknown => {
452                // If unknown, neither macOS nor Ubuntu indicators should be present
453                // (or Ubuntu file exists but doesn't contain "Ubuntu")
454                if std::path::Path::new("/usr/bin/sw_vers").exists() {
455                    // This shouldn't happen - if sw_vers exists, it should be detected as macOS
456                    panic!("sw_vers exists but platform detected as Unknown");
457                }
458            }
459        }
460    }
461
462    #[test]
463    fn test_debug_flag() {
464        // Test setting and getting the debug flag
465        set_thread_local_debug(true);
466        assert_eq!(thread_local_debug(), true);
467
468        set_thread_local_debug(false);
469        assert_eq!(thread_local_debug(), false);
470    }
471
472    #[test]
473    fn test_package_error_display() {
474        // Test the Display implementation for PackageError
475        let err1 = PackageError::CommandFailed("command failed".to_string());
476        assert_eq!(err1.to_string(), "Command failed: command failed");
477
478        let err2 = PackageError::UnsupportedPlatform("test platform".to_string());
479        assert_eq!(err2.to_string(), "Unsupported platform: test platform");
480
481        let err3 = PackageError::Other("other error".to_string());
482        assert_eq!(err3.to_string(), "Error: other error");
483
484        // We can't easily test CommandExecutionFailed because std::io::Error doesn't implement PartialEq
485    }
486
487    // Mock package manager for testing
488    struct MockPackageManager {
489        // debug field is kept for consistency with real package managers
490        #[allow(dead_code)]
491        debug: bool,
492        install_called: Arc<Mutex<bool>>,
493        remove_called: Arc<Mutex<bool>>,
494        update_called: Arc<Mutex<bool>>,
495        upgrade_called: Arc<Mutex<bool>>,
496        list_installed_called: Arc<Mutex<bool>>,
497        search_called: Arc<Mutex<bool>>,
498        is_installed_called: Arc<Mutex<bool>>,
499        // Control what the mock returns
500        should_succeed: bool,
501    }
502
503    impl MockPackageManager {
504        fn new(debug: bool, should_succeed: bool) -> Self {
505            Self {
506                debug,
507                install_called: Arc::new(Mutex::new(false)),
508                remove_called: Arc::new(Mutex::new(false)),
509                update_called: Arc::new(Mutex::new(false)),
510                upgrade_called: Arc::new(Mutex::new(false)),
511                list_installed_called: Arc::new(Mutex::new(false)),
512                search_called: Arc::new(Mutex::new(false)),
513                is_installed_called: Arc::new(Mutex::new(false)),
514                should_succeed,
515            }
516        }
517    }
518
519    impl PackageManager for MockPackageManager {
520        fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
521            *self.install_called.lock().unwrap() = true;
522            if self.should_succeed {
523                Ok(CommandResult {
524                    stdout: format!("Installed package {}", package),
525                    stderr: String::new(),
526                    success: true,
527                    code: 0,
528                })
529            } else {
530                Err(PackageError::CommandFailed(
531                    "Mock install failed".to_string(),
532                ))
533            }
534        }
535
536        fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
537            *self.remove_called.lock().unwrap() = true;
538            if self.should_succeed {
539                Ok(CommandResult {
540                    stdout: format!("Removed package {}", package),
541                    stderr: String::new(),
542                    success: true,
543                    code: 0,
544                })
545            } else {
546                Err(PackageError::CommandFailed(
547                    "Mock remove failed".to_string(),
548                ))
549            }
550        }
551
552        fn update(&self) -> Result<CommandResult, PackageError> {
553            *self.update_called.lock().unwrap() = true;
554            if self.should_succeed {
555                Ok(CommandResult {
556                    stdout: "Updated package lists".to_string(),
557                    stderr: String::new(),
558                    success: true,
559                    code: 0,
560                })
561            } else {
562                Err(PackageError::CommandFailed(
563                    "Mock update failed".to_string(),
564                ))
565            }
566        }
567
568        fn upgrade(&self) -> Result<CommandResult, PackageError> {
569            *self.upgrade_called.lock().unwrap() = true;
570            if self.should_succeed {
571                Ok(CommandResult {
572                    stdout: "Upgraded packages".to_string(),
573                    stderr: String::new(),
574                    success: true,
575                    code: 0,
576                })
577            } else {
578                Err(PackageError::CommandFailed(
579                    "Mock upgrade failed".to_string(),
580                ))
581            }
582        }
583
584        fn list_installed(&self) -> Result<Vec<String>, PackageError> {
585            *self.list_installed_called.lock().unwrap() = true;
586            if self.should_succeed {
587                Ok(vec!["package1".to_string(), "package2".to_string()])
588            } else {
589                Err(PackageError::CommandFailed(
590                    "Mock list_installed failed".to_string(),
591                ))
592            }
593        }
594
595        fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
596            *self.search_called.lock().unwrap() = true;
597            if self.should_succeed {
598                Ok(vec![
599                    format!("result1-{}", query),
600                    format!("result2-{}", query),
601                ])
602            } else {
603                Err(PackageError::CommandFailed(
604                    "Mock search failed".to_string(),
605                ))
606            }
607        }
608
609        fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
610            *self.is_installed_called.lock().unwrap() = true;
611            if self.should_succeed {
612                Ok(package == "installed-package")
613            } else {
614                Err(PackageError::CommandFailed(
615                    "Mock is_installed failed".to_string(),
616                ))
617            }
618        }
619    }
620
621    // Custom PackHero for testing with a mock package manager
622    struct TestPackHero {
623        platform: Platform,
624        #[allow(dead_code)]
625        debug: bool,
626        mock_manager: MockPackageManager,
627    }
628
629    impl TestPackHero {
630        fn new(platform: Platform, debug: bool, should_succeed: bool) -> Self {
631            Self {
632                platform,
633                debug,
634                mock_manager: MockPackageManager::new(debug, should_succeed),
635            }
636        }
637
638        fn get_package_manager(&self) -> Result<&dyn PackageManager, PackageError> {
639            match self.platform {
640                Platform::Ubuntu | Platform::MacOS => Ok(&self.mock_manager),
641                Platform::Unknown => Err(PackageError::UnsupportedPlatform(
642                    "Unsupported platform".to_string(),
643                )),
644            }
645        }
646
647        fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
648            let pm = self.get_package_manager()?;
649            pm.install(package)
650        }
651
652        fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
653            let pm = self.get_package_manager()?;
654            pm.remove(package)
655        }
656
657        fn update(&self) -> Result<CommandResult, PackageError> {
658            let pm = self.get_package_manager()?;
659            pm.update()
660        }
661
662        fn upgrade(&self) -> Result<CommandResult, PackageError> {
663            let pm = self.get_package_manager()?;
664            pm.upgrade()
665        }
666
667        fn list_installed(&self) -> Result<Vec<String>, PackageError> {
668            let pm = self.get_package_manager()?;
669            pm.list_installed()
670        }
671
672        fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
673            let pm = self.get_package_manager()?;
674            pm.search(query)
675        }
676
677        fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
678            let pm = self.get_package_manager()?;
679            pm.is_installed(package)
680        }
681    }
682
683    #[test]
684    fn test_packhero_with_mock_success() {
685        // Test PackHero with a mock package manager that succeeds
686        let hero = TestPackHero::new(Platform::Ubuntu, false, true);
687
688        // Test install
689        let result = hero.install("test-package");
690        assert!(result.is_ok());
691        assert!(*hero.mock_manager.install_called.lock().unwrap());
692
693        // Test remove
694        let result = hero.remove("test-package");
695        assert!(result.is_ok());
696        assert!(*hero.mock_manager.remove_called.lock().unwrap());
697
698        // Test update
699        let result = hero.update();
700        assert!(result.is_ok());
701        assert!(*hero.mock_manager.update_called.lock().unwrap());
702
703        // Test upgrade
704        let result = hero.upgrade();
705        assert!(result.is_ok());
706        assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
707
708        // Test list_installed
709        let result = hero.list_installed();
710        assert!(result.is_ok());
711        assert_eq!(
712            result.unwrap(),
713            vec!["package1".to_string(), "package2".to_string()]
714        );
715        assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
716
717        // Test search
718        let result = hero.search("query");
719        assert!(result.is_ok());
720        assert_eq!(
721            result.unwrap(),
722            vec!["result1-query".to_string(), "result2-query".to_string()]
723        );
724        assert!(*hero.mock_manager.search_called.lock().unwrap());
725
726        // Test is_installed
727        let result = hero.is_installed("installed-package");
728        assert!(result.is_ok());
729        assert!(result.unwrap());
730        assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
731
732        let result = hero.is_installed("not-installed-package");
733        assert!(result.is_ok());
734        assert!(!result.unwrap());
735    }
736
737    #[test]
738    fn test_packhero_with_mock_failure() {
739        // Test PackHero with a mock package manager that fails
740        let hero = TestPackHero::new(Platform::Ubuntu, false, false);
741
742        // Test install
743        let result = hero.install("test-package");
744        assert!(result.is_err());
745        assert!(*hero.mock_manager.install_called.lock().unwrap());
746
747        // Test remove
748        let result = hero.remove("test-package");
749        assert!(result.is_err());
750        assert!(*hero.mock_manager.remove_called.lock().unwrap());
751
752        // Test update
753        let result = hero.update();
754        assert!(result.is_err());
755        assert!(*hero.mock_manager.update_called.lock().unwrap());
756
757        // Test upgrade
758        let result = hero.upgrade();
759        assert!(result.is_err());
760        assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
761
762        // Test list_installed
763        let result = hero.list_installed();
764        assert!(result.is_err());
765        assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
766
767        // Test search
768        let result = hero.search("query");
769        assert!(result.is_err());
770        assert!(*hero.mock_manager.search_called.lock().unwrap());
771
772        // Test is_installed
773        let result = hero.is_installed("installed-package");
774        assert!(result.is_err());
775        assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
776    }
777
778    #[test]
779    fn test_packhero_unsupported_platform() {
780        // Test PackHero with an unsupported platform
781        let hero = TestPackHero::new(Platform::Unknown, false, true);
782
783        // All operations should fail with UnsupportedPlatform error
784        let result = hero.install("test-package");
785        assert!(result.is_err());
786        match result {
787            Err(PackageError::UnsupportedPlatform(_)) => (),
788            _ => panic!("Expected UnsupportedPlatform error"),
789        }
790
791        let result = hero.remove("test-package");
792        assert!(result.is_err());
793        match result {
794            Err(PackageError::UnsupportedPlatform(_)) => (),
795            _ => panic!("Expected UnsupportedPlatform error"),
796        }
797
798        let result = hero.update();
799        assert!(result.is_err());
800        match result {
801            Err(PackageError::UnsupportedPlatform(_)) => (),
802            _ => panic!("Expected UnsupportedPlatform error"),
803        }
804    }
805
806    // Real-world tests that actually install and remove packages on Ubuntu
807    // These tests will only run on Ubuntu and will be skipped on other platforms
808    #[test]
809    fn test_real_package_operations_on_ubuntu() {
810        // Check if we're on Ubuntu
811        let platform = Platform::detect();
812        if platform != Platform::Ubuntu {
813            println!(
814                "Skipping real package operations test on non-Ubuntu platform: {:?}",
815                platform
816            );
817            return;
818        }
819
820        println!("Running real package operations test on Ubuntu");
821
822        // Create a PackHero instance with debug enabled
823        let mut hero = PackHero::new();
824        hero.set_debug(true);
825
826        // Test package to install/remove
827        let test_package = "wget";
828
829        // First, check if the package is already installed
830        let is_installed_before = match hero.is_installed(test_package) {
831            Ok(result) => result,
832            Err(e) => {
833                println!("Error checking if package is installed: {}", e);
834                return;
835            }
836        };
837
838        println!(
839            "Package {} is installed before test: {}",
840            test_package, is_installed_before
841        );
842
843        // If the package is already installed, we'll remove it first
844        if is_installed_before {
845            println!("Removing existing package {} before test", test_package);
846            match hero.remove(test_package) {
847                Ok(_) => println!("Successfully removed package {}", test_package),
848                Err(e) => {
849                    println!("Error removing package {}: {}", test_package, e);
850                    return;
851                }
852            }
853
854            // Verify it was removed
855            match hero.is_installed(test_package) {
856                Ok(is_installed) => {
857                    if is_installed {
858                        println!("Failed to remove package {}", test_package);
859                        return;
860                    } else {
861                        println!("Verified package {} was removed", test_package);
862                    }
863                }
864                Err(e) => {
865                    println!(
866                        "Error checking if package is installed after removal: {}",
867                        e
868                    );
869                    return;
870                }
871            }
872        }
873
874        // Now install the package
875        println!("Installing package {}", test_package);
876        match hero.install(test_package) {
877            Ok(_) => println!("Successfully installed package {}", test_package),
878            Err(e) => {
879                println!("Error installing package {}: {}", test_package, e);
880                return;
881            }
882        }
883
884        // Verify it was installed
885        match hero.is_installed(test_package) {
886            Ok(is_installed) => {
887                if !is_installed {
888                    println!("Failed to install package {}", test_package);
889                    return;
890                } else {
891                    println!("Verified package {} was installed", test_package);
892                }
893            }
894            Err(e) => {
895                println!(
896                    "Error checking if package is installed after installation: {}",
897                    e
898                );
899                return;
900            }
901        }
902
903        // Test the search functionality
904        println!("Searching for packages with 'wget'");
905        match hero.search("wget") {
906            Ok(results) => {
907                println!("Search results: {:?}", results);
908                assert!(
909                    results.iter().any(|r| r.contains("wget")),
910                    "Search results should contain wget"
911                );
912            }
913            Err(e) => {
914                println!("Error searching for packages: {}", e);
915                return;
916            }
917        }
918
919        // Test listing installed packages
920        println!("Listing installed packages");
921        match hero.list_installed() {
922            Ok(packages) => {
923                println!("Found {} installed packages", packages.len());
924                // Check if our test package is in the list
925                assert!(
926                    packages.iter().any(|p| p == test_package),
927                    "Installed packages list should contain {}",
928                    test_package
929                );
930            }
931            Err(e) => {
932                println!("Error listing installed packages: {}", e);
933                return;
934            }
935        }
936
937        // Now remove the package if it wasn't installed before
938        if !is_installed_before {
939            println!("Removing package {} after test", test_package);
940            match hero.remove(test_package) {
941                Ok(_) => println!("Successfully removed package {}", test_package),
942                Err(e) => {
943                    println!("Error removing package {}: {}", test_package, e);
944                    return;
945                }
946            }
947
948            // Verify it was removed
949            match hero.is_installed(test_package) {
950                Ok(is_installed) => {
951                    if is_installed {
952                        println!("Failed to remove package {}", test_package);
953                        return;
954                    } else {
955                        println!("Verified package {} was removed", test_package);
956                    }
957                }
958                Err(e) => {
959                    println!(
960                        "Error checking if package is installed after removal: {}",
961                        e
962                    );
963                    return;
964                }
965            }
966        }
967
968        // Test update functionality
969        println!("Testing package list update");
970        match hero.update() {
971            Ok(_) => println!("Successfully updated package lists"),
972            Err(e) => {
973                println!("Error updating package lists: {}", e);
974                return;
975            }
976        }
977
978        println!("All real package operations tests passed on Ubuntu");
979    }
980
981    // Test to check if apt-get is available on the system
982    #[test]
983    fn test_apt_get_availability() {
984        // This test checks if apt-get is available on the system
985        let output = StdCommand::new("which")
986            .arg("apt-get")
987            .output()
988            .expect("Failed to execute which apt-get");
989
990        let success = output.status.success();
991        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
992
993        println!("apt-get available: {}", success);
994        if success {
995            println!("apt-get path: {}", stdout.trim());
996        }
997
998        // On Ubuntu, this should pass
999        if Platform::detect() == Platform::Ubuntu {
1000            assert!(success, "apt-get should be available on Ubuntu");
1001        }
1002    }
1003}