1use crate::platform::detection::PackageManager;
13use std::fmt;
14use std::process::Command;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum PackageOperation {
19 Install,
20 Remove,
21 Purge,
22 Update,
23 Upgrade,
24 Search,
25 Info,
26 IsInstalled,
27 ListInstalled,
28}
29
30impl fmt::Display for PackageOperation {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 PackageOperation::Install => write!(f, "install"),
34 PackageOperation::Remove => write!(f, "remove"),
35 PackageOperation::Purge => write!(f, "purge"),
36 PackageOperation::Update => write!(f, "update"),
37 PackageOperation::Upgrade => write!(f, "upgrade"),
38 PackageOperation::Search => write!(f, "search"),
39 PackageOperation::Info => write!(f, "info"),
40 PackageOperation::IsInstalled => write!(f, "is_installed"),
41 PackageOperation::ListInstalled => write!(f, "list_installed"),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct DryRunResult {
49 pub program: String,
51 pub args: Vec<String>,
53 pub description: String,
55}
56
57impl DryRunResult {
58 pub fn to_command_string(&self) -> String {
60 if self.args.is_empty() {
61 self.program.clone()
62 } else {
63 format!("{} {}", self.program, self.args.join(" "))
64 }
65 }
66}
67
68impl fmt::Display for DryRunResult {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "[DRY-RUN] {}", self.to_command_string())
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct PackageManagerFacade {
77 pkg_manager: PackageManager,
79 dry_run: bool,
81 translate_packages: bool,
83}
84
85impl PackageManagerFacade {
86 pub fn new(pkg_manager: PackageManager) -> Self {
88 Self {
89 pkg_manager,
90 dry_run: false,
91 translate_packages: true,
92 }
93 }
94
95 pub fn with_dry_run(mut self, dry_run: bool) -> Self {
97 self.dry_run = dry_run;
98 self
99 }
100
101 pub fn with_translate_packages(mut self, translate: bool) -> Self {
103 self.translate_packages = translate;
104 self
105 }
106
107 pub fn package_manager(&self) -> PackageManager {
109 self.pkg_manager
110 }
111
112 pub fn is_dry_run(&self) -> bool {
114 self.dry_run
115 }
116
117 pub fn build_update_indices_cmd(&self) -> (String, Vec<String>) {
125 match self.pkg_manager {
126 PackageManager::Apt => (
127 "apt".to_string(),
128 vec!["update".to_string(), "-qq".to_string()],
129 ),
130 PackageManager::Pacman => (
131 "pacman".to_string(),
132 vec!["-Sy".to_string(), "--noconfirm".to_string()],
133 ),
134 PackageManager::Dnf => ("dnf".to_string(), vec!["makecache".to_string()]),
135 PackageManager::Yum => ("yum".to_string(), vec!["makecache".to_string()]),
136 PackageManager::Zypper => ("zypper".to_string(), vec!["refresh".to_string()]),
137 PackageManager::Unknown => (
138 "echo".to_string(),
139 vec!["unknown package manager".to_string()],
140 ),
141 }
142 }
143
144 pub fn build_upgrade_cmd(&self) -> (String, Vec<String>) {
146 match self.pkg_manager {
147 PackageManager::Apt => (
148 "apt".to_string(),
149 vec!["upgrade".to_string(), "-y".to_string(), "-qq".to_string()],
150 ),
151 PackageManager::Pacman => (
152 "pacman".to_string(),
153 vec!["-Su".to_string(), "--noconfirm".to_string()],
154 ),
155 PackageManager::Dnf => (
156 "dnf".to_string(),
157 vec![
158 "upgrade".to_string(),
159 "-y".to_string(),
160 "--refresh".to_string(),
161 ],
162 ),
163 PackageManager::Yum => (
164 "yum".to_string(),
165 vec!["upgrade".to_string(), "-y".to_string()],
166 ),
167 PackageManager::Zypper => (
168 "zypper".to_string(),
169 vec!["update".to_string(), "-y".to_string()],
170 ),
171 PackageManager::Unknown => (
172 "echo".to_string(),
173 vec!["unknown package manager".to_string()],
174 ),
175 }
176 }
177
178 pub fn build_install_cmd(&self, packages: &[&str]) -> (String, Vec<String>) {
182 let native = self.translate_package_names(packages);
183 match self.pkg_manager {
184 PackageManager::Apt => (
185 "apt".to_string(),
186 vec!["install".to_string(), "-y".to_string(), "-qq".to_string()]
187 .into_iter()
188 .chain(native)
189 .collect(),
190 ),
191 PackageManager::Pacman => (
192 "pacman".to_string(),
193 vec![
194 "-S".to_string(),
195 "--noconfirm".to_string(),
196 "--needed".to_string(),
197 ]
198 .into_iter()
199 .chain(native)
200 .collect(),
201 ),
202 PackageManager::Dnf => (
203 "dnf".to_string(),
204 vec!["install".to_string(), "-y".to_string()]
205 .into_iter()
206 .chain(native)
207 .collect(),
208 ),
209 PackageManager::Yum => (
210 "yum".to_string(),
211 vec!["install".to_string(), "-y".to_string()]
212 .into_iter()
213 .chain(native)
214 .collect(),
215 ),
216 PackageManager::Zypper => (
217 "zypper".to_string(),
218 vec!["install".to_string(), "-y".to_string()]
219 .into_iter()
220 .chain(native)
221 .collect(),
222 ),
223 PackageManager::Unknown => (
224 "echo".to_string(),
225 vec!["unknown package manager".to_string()],
226 ),
227 }
228 }
229
230 pub fn build_remove_cmd(&self, packages: &[&str]) -> (String, Vec<String>) {
232 let native = self.translate_package_names(packages);
233 match self.pkg_manager {
234 PackageManager::Apt => (
235 "apt".to_string(),
236 vec!["remove".to_string(), "-y".to_string(), "-qq".to_string()]
237 .into_iter()
238 .chain(native)
239 .collect(),
240 ),
241 PackageManager::Pacman => (
242 "pacman".to_string(),
243 vec!["-R".to_string(), "--noconfirm".to_string()]
244 .into_iter()
245 .chain(native)
246 .collect(),
247 ),
248 PackageManager::Dnf => (
249 "dnf".to_string(),
250 vec!["remove".to_string(), "-y".to_string()]
251 .into_iter()
252 .chain(native)
253 .collect(),
254 ),
255 PackageManager::Yum => (
256 "yum".to_string(),
257 vec!["remove".to_string(), "-y".to_string()]
258 .into_iter()
259 .chain(native)
260 .collect(),
261 ),
262 PackageManager::Zypper => (
263 "zypper".to_string(),
264 vec!["remove".to_string(), "-y".to_string()]
265 .into_iter()
266 .chain(native)
267 .collect(),
268 ),
269 PackageManager::Unknown => (
270 "echo".to_string(),
271 vec!["unknown package manager".to_string()],
272 ),
273 }
274 }
275
276 pub fn build_purge_cmd(&self, packages: &[&str]) -> (String, Vec<String>) {
278 let native = self.translate_package_names(packages);
279 match self.pkg_manager {
280 PackageManager::Apt => (
281 "apt".to_string(),
282 vec!["purge".to_string(), "-y".to_string(), "-qq".to_string()]
283 .into_iter()
284 .chain(native)
285 .collect(),
286 ),
287 PackageManager::Pacman => (
288 "pacman".to_string(),
289 vec!["-Rn".to_string(), "--noconfirm".to_string()]
290 .into_iter()
291 .chain(native)
292 .collect(),
293 ),
294 PackageManager::Dnf => (
296 "dnf".to_string(),
297 vec!["remove".to_string(), "-y".to_string()]
298 .into_iter()
299 .chain(native)
300 .collect(),
301 ),
302 PackageManager::Yum => (
303 "yum".to_string(),
304 vec!["remove".to_string(), "-y".to_string()]
305 .into_iter()
306 .chain(native)
307 .collect(),
308 ),
309 PackageManager::Zypper => (
310 "zypper".to_string(),
311 vec!["remove".to_string(), "-y".to_string()]
312 .into_iter()
313 .chain(native)
314 .collect(),
315 ),
316 PackageManager::Unknown => (
317 "echo".to_string(),
318 vec!["unknown package manager".to_string()],
319 ),
320 }
321 }
322
323 pub fn build_is_installed_cmd(&self, package: &str) -> (String, Vec<String>) {
328 let native = self.translate_single(package);
329 match self.pkg_manager {
330 PackageManager::Apt => ("dpkg".to_string(), vec!["-l".to_string(), native]),
331 PackageManager::Pacman => ("pacman".to_string(), vec!["-Qi".to_string(), native]),
332 PackageManager::Dnf | PackageManager::Yum | PackageManager::Zypper => {
333 ("rpm".to_string(), vec!["-q".to_string(), native])
334 }
335 PackageManager::Unknown => (
336 "echo".to_string(),
337 vec!["unknown package manager".to_string()],
338 ),
339 }
340 }
341
342 pub fn build_search_cmd(&self, query: &str) -> (String, Vec<String>) {
344 match self.pkg_manager {
345 PackageManager::Apt => (
346 "apt".to_string(),
347 vec!["search".to_string(), query.to_string()],
348 ),
349 PackageManager::Pacman => (
350 "pacman".to_string(),
351 vec!["-Ss".to_string(), query.to_string()],
352 ),
353 PackageManager::Dnf => (
354 "dnf".to_string(),
355 vec!["search".to_string(), query.to_string()],
356 ),
357 PackageManager::Yum => (
358 "yum".to_string(),
359 vec!["search".to_string(), query.to_string()],
360 ),
361 PackageManager::Zypper => (
362 "zypper".to_string(),
363 vec!["search".to_string(), query.to_string()],
364 ),
365 PackageManager::Unknown => (
366 "echo".to_string(),
367 vec!["unknown package manager".to_string()],
368 ),
369 }
370 }
371
372 pub fn build_info_cmd(&self, package: &str) -> (String, Vec<String>) {
374 let native = self.translate_single(package);
375 match self.pkg_manager {
376 PackageManager::Apt => ("apt".to_string(), vec!["show".to_string(), native]),
377 PackageManager::Pacman => ("pacman".to_string(), vec!["-Si".to_string(), native]),
378 PackageManager::Dnf => ("dnf".to_string(), vec!["info".to_string(), native]),
379 PackageManager::Yum => ("yum".to_string(), vec!["info".to_string(), native]),
380 PackageManager::Zypper => ("zypper".to_string(), vec!["info".to_string(), native]),
381 PackageManager::Unknown => (
382 "echo".to_string(),
383 vec!["unknown package manager".to_string()],
384 ),
385 }
386 }
387
388 pub fn update_indices(&self) -> Result<Option<DryRunResult>, anyhow::Error> {
396 let (program, args) = self.build_update_indices_cmd();
397 self.execute_with_sudo(&program, &args, "update package indices")
398 }
399
400 pub fn install(&self, packages: &[&str]) -> Result<Option<DryRunResult>, anyhow::Error> {
404 if packages.is_empty() {
405 anyhow::bail!("No packages specified for install");
406 }
407 let (program, args) = self.build_install_cmd(packages);
408 let desc = format!("install packages: {}", packages.join(", "));
409 self.execute_with_sudo(&program, &args, &desc)
410 }
411
412 pub fn remove(&self, packages: &[&str]) -> Result<Option<DryRunResult>, anyhow::Error> {
414 if packages.is_empty() {
415 anyhow::bail!("No packages specified for remove");
416 }
417 let (program, args) = self.build_remove_cmd(packages);
418 let desc = format!("remove packages: {}", packages.join(", "));
419 self.execute_with_sudo(&program, &args, &desc)
420 }
421
422 pub fn purge(&self, packages: &[&str]) -> Result<Option<DryRunResult>, anyhow::Error> {
424 if packages.is_empty() {
425 anyhow::bail!("No packages specified for purge");
426 }
427 let (program, args) = self.build_purge_cmd(packages);
428 let desc = format!("purge packages: {}", packages.join(", "));
429 self.execute_with_sudo(&program, &args, &desc)
430 }
431
432 pub fn is_installed(&self, package: &str) -> Result<bool, anyhow::Error> {
437 let (program, args) = self.build_is_installed_cmd(package);
438
439 if self.dry_run {
440 eprintln!("[DRY-RUN] {} {}", program, args.join(" "));
441 return Ok(false);
442 }
443
444 let status = Command::new(&program).args(&args).output()?;
445 Ok(status.status.success())
446 }
447
448 pub fn search(&self, query: &str) -> Result<Option<String>, anyhow::Error> {
450 let (program, args) = self.build_search_cmd(query);
451
452 if self.dry_run {
453 eprintln!("[DRY-RUN] {} {}", program, args.join(" "));
454 return Ok(None);
455 }
456
457 let output = Command::new(&program).args(&args).output()?;
458 Ok(Some(String::from_utf8_lossy(&output.stdout).to_string()))
459 }
460
461 pub fn info(&self, package: &str) -> Result<Option<String>, anyhow::Error> {
463 let (program, args) = self.build_info_cmd(package);
464
465 if self.dry_run {
466 eprintln!("[DRY-RUN] {} {}", program, args.join(" "));
467 return Ok(None);
468 }
469
470 let output = Command::new(&program).args(&args).output()?;
471 Ok(Some(String::from_utf8_lossy(&output.stdout).to_string()))
472 }
473
474 fn translate_package_names(&self, packages: &[&str]) -> Vec<String> {
480 if self.translate_packages {
481 super::package_mappings::map_package_names(packages, self.pkg_manager)
482 } else {
483 packages.iter().map(|s| s.to_string()).collect()
484 }
485 }
486
487 fn translate_single(&self, package: &str) -> String {
489 if self.translate_packages {
490 super::package_mappings::map_package_name(package, self.pkg_manager)
491 } else {
492 package.to_string()
493 }
494 }
495
496 fn execute_with_sudo(
498 &self,
499 program: &str,
500 args: &[String],
501 description: &str,
502 ) -> Result<Option<DryRunResult>, anyhow::Error> {
503 if self.dry_run {
504 let result = DryRunResult {
505 program: format!("sudo {}", program),
506 args: args.to_vec(),
507 description: description.to_string(),
508 };
509 eprintln!("{}", result);
510 return Ok(Some(result));
511 }
512
513 let status = Command::new("sudo")
514 .arg(program)
515 .args(args)
516 .env("DEBIAN_FRONTEND", "noninteractive")
517 .status()?;
518
519 if !status.success() {
520 anyhow::bail!("Failed to {}: exit code {:?}", description, status.code());
521 }
522
523 Ok(None)
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
538 fn test_update_indices_apt() {
539 let pm = PackageManagerFacade::new(PackageManager::Apt);
540 let (prog, args) = pm.build_update_indices_cmd();
541 assert_eq!(prog, "apt");
542 assert!(args.contains(&"update".to_string()));
543 }
544
545 #[test]
546 fn test_update_indices_pacman() {
547 let pm = PackageManagerFacade::new(PackageManager::Pacman);
548 let (prog, args) = pm.build_update_indices_cmd();
549 assert_eq!(prog, "pacman");
550 assert!(args.contains(&"-Sy".to_string()));
551 assert!(args.contains(&"--noconfirm".to_string()));
552 }
553
554 #[test]
555 fn test_update_indices_dnf() {
556 let pm = PackageManagerFacade::new(PackageManager::Dnf);
557 let (prog, args) = pm.build_update_indices_cmd();
558 assert_eq!(prog, "dnf");
559 assert!(args.contains(&"makecache".to_string()));
560 }
561
562 #[test]
563 fn test_update_indices_yum() {
564 let pm = PackageManagerFacade::new(PackageManager::Yum);
565 let (prog, args) = pm.build_update_indices_cmd();
566 assert_eq!(prog, "yum");
567 assert!(args.contains(&"makecache".to_string()));
568 }
569
570 #[test]
571 fn test_update_indices_zypper() {
572 let pm = PackageManagerFacade::new(PackageManager::Zypper);
573 let (prog, args) = pm.build_update_indices_cmd();
574 assert_eq!(prog, "zypper");
575 assert!(args.contains(&"refresh".to_string()));
576 }
577
578 #[test]
581 fn test_install_apt() {
582 let pm = PackageManagerFacade::new(PackageManager::Apt);
583 let (prog, args) = pm.build_install_cmd(&["build-essential", "cmake"]);
584 assert_eq!(prog, "apt");
585 assert!(args.contains(&"install".to_string()));
586 assert!(args.contains(&"-y".to_string()));
587 assert!(args.contains(&"build-essential".to_string()));
588 assert!(args.contains(&"cmake".to_string()));
589 }
590
591 #[test]
592 fn test_install_pacman_with_translation() {
593 let pm = PackageManagerFacade::new(PackageManager::Pacman);
594 let (prog, args) = pm.build_install_cmd(&["build-essential"]);
595 assert_eq!(prog, "pacman");
596 assert!(args.contains(&"-S".to_string()));
597 assert!(args.contains(&"--noconfirm".to_string()));
598 assert!(args.contains(&"--needed".to_string()));
599 assert!(args.contains(&"base-devel".to_string()));
601 }
602
603 #[test]
604 fn test_install_dnf() {
605 let pm = PackageManagerFacade::new(PackageManager::Dnf);
606 let (prog, args) = pm.build_install_cmd(&["cmake"]);
607 assert_eq!(prog, "dnf");
608 assert!(args.contains(&"install".to_string()));
609 assert!(args.contains(&"-y".to_string()));
610 assert!(args.contains(&"cmake".to_string()));
611 }
612
613 #[test]
614 fn test_install_yum() {
615 let pm = PackageManagerFacade::new(PackageManager::Yum);
616 let (prog, args) = pm.build_install_cmd(&["git"]);
617 assert_eq!(prog, "yum");
618 assert!(args.contains(&"install".to_string()));
619 assert!(args.contains(&"-y".to_string()));
620 }
621
622 #[test]
623 fn test_install_zypper() {
624 let pm = PackageManagerFacade::new(PackageManager::Zypper);
625 let (prog, args) = pm.build_install_cmd(&["git"]);
626 assert_eq!(prog, "zypper");
627 assert!(args.contains(&"install".to_string()));
628 assert!(args.contains(&"-y".to_string()));
629 }
630
631 #[test]
634 fn test_remove_apt() {
635 let pm = PackageManagerFacade::new(PackageManager::Apt);
636 let (prog, args) = pm.build_remove_cmd(&["nano"]);
637 assert_eq!(prog, "apt");
638 assert!(args.contains(&"remove".to_string()));
639 assert!(args.contains(&"-y".to_string()));
640 assert!(args.contains(&"nano".to_string()));
641 }
642
643 #[test]
644 fn test_remove_pacman() {
645 let pm = PackageManagerFacade::new(PackageManager::Pacman);
646 let (prog, args) = pm.build_remove_cmd(&["nano"]);
647 assert_eq!(prog, "pacman");
648 assert!(args.contains(&"-R".to_string()));
649 assert!(args.contains(&"--noconfirm".to_string()));
650 }
651
652 #[test]
653 fn test_remove_dnf() {
654 let pm = PackageManagerFacade::new(PackageManager::Dnf);
655 let (prog, args) = pm.build_remove_cmd(&["nano"]);
656 assert_eq!(prog, "dnf");
657 assert!(args.contains(&"remove".to_string()));
658 }
659
660 #[test]
663 fn test_purge_apt() {
664 let pm = PackageManagerFacade::new(PackageManager::Apt);
665 let (prog, args) = pm.build_purge_cmd(&["nginx"]);
666 assert_eq!(prog, "apt");
667 assert!(args.contains(&"purge".to_string()));
668 }
669
670 #[test]
671 fn test_purge_pacman() {
672 let pm = PackageManagerFacade::new(PackageManager::Pacman);
673 let (prog, args) = pm.build_purge_cmd(&["nginx"]);
674 assert_eq!(prog, "pacman");
675 assert!(args.contains(&"-Rn".to_string()));
676 }
677
678 #[test]
679 fn test_purge_dnf_uses_remove() {
680 let pm = PackageManagerFacade::new(PackageManager::Dnf);
681 let (prog, args) = pm.build_purge_cmd(&["nginx"]);
682 assert_eq!(prog, "dnf");
683 assert!(args.contains(&"remove".to_string()));
684 }
685
686 #[test]
689 fn test_is_installed_apt() {
690 let pm = PackageManagerFacade::new(PackageManager::Apt);
691 let (prog, args) = pm.build_is_installed_cmd("curl");
692 assert_eq!(prog, "dpkg");
693 assert!(args.contains(&"-l".to_string()));
694 assert!(args.contains(&"curl".to_string()));
695 }
696
697 #[test]
698 fn test_is_installed_pacman() {
699 let pm = PackageManagerFacade::new(PackageManager::Pacman);
700 let (prog, args) = pm.build_is_installed_cmd("curl");
701 assert_eq!(prog, "pacman");
702 assert!(args.contains(&"-Qi".to_string()));
703 assert!(args.contains(&"curl".to_string()));
704 }
705
706 #[test]
707 fn test_is_installed_dnf() {
708 let pm = PackageManagerFacade::new(PackageManager::Dnf);
709 let (prog, args) = pm.build_is_installed_cmd("curl");
710 assert_eq!(prog, "rpm");
711 assert!(args.contains(&"-q".to_string()));
712 }
713
714 #[test]
715 fn test_is_installed_yum() {
716 let pm = PackageManagerFacade::new(PackageManager::Yum);
717 let (prog, _args) = pm.build_is_installed_cmd("curl");
718 assert_eq!(prog, "rpm");
719 }
720
721 #[test]
722 fn test_is_installed_zypper() {
723 let pm = PackageManagerFacade::new(PackageManager::Zypper);
724 let (prog, _args) = pm.build_is_installed_cmd("curl");
725 assert_eq!(prog, "rpm");
726 }
727
728 #[test]
731 fn test_search_apt() {
732 let pm = PackageManagerFacade::new(PackageManager::Apt);
733 let (prog, args) = pm.build_search_cmd("python");
734 assert_eq!(prog, "apt");
735 assert!(args.contains(&"search".to_string()));
736 assert!(args.contains(&"python".to_string()));
737 }
738
739 #[test]
740 fn test_search_pacman() {
741 let pm = PackageManagerFacade::new(PackageManager::Pacman);
742 let (prog, args) = pm.build_search_cmd("python");
743 assert_eq!(prog, "pacman");
744 assert!(args.contains(&"-Ss".to_string()));
745 }
746
747 #[test]
750 fn test_info_apt() {
751 let pm = PackageManagerFacade::new(PackageManager::Apt);
752 let (prog, args) = pm.build_info_cmd("python3");
753 assert_eq!(prog, "apt");
754 assert!(args.contains(&"show".to_string()));
755 }
756
757 #[test]
758 fn test_info_pacman() {
759 let pm = PackageManagerFacade::new(PackageManager::Pacman);
760 let (prog, args) = pm.build_info_cmd("python3");
761 assert_eq!(prog, "pacman");
762 assert!(args.contains(&"-Si".to_string()));
763 }
764
765 #[test]
766 fn test_info_dnf() {
767 let pm = PackageManagerFacade::new(PackageManager::Dnf);
768 let (prog, args) = pm.build_info_cmd("python3");
769 assert_eq!(prog, "dnf");
770 assert!(args.contains(&"info".to_string()));
771 }
772
773 #[test]
776 fn test_upgrade_apt() {
777 let pm = PackageManagerFacade::new(PackageManager::Apt);
778 let (prog, args) = pm.build_upgrade_cmd();
779 assert_eq!(prog, "apt");
780 assert!(args.contains(&"upgrade".to_string()));
781 assert!(args.contains(&"-y".to_string()));
782 }
783
784 #[test]
785 fn test_upgrade_pacman() {
786 let pm = PackageManagerFacade::new(PackageManager::Pacman);
787 let (prog, args) = pm.build_upgrade_cmd();
788 assert_eq!(prog, "pacman");
789 assert!(args.contains(&"-Su".to_string()));
790 }
791
792 #[test]
795 fn test_install_translates_package_names() {
796 let pm = PackageManagerFacade::new(PackageManager::Pacman);
797 let (_, args) = pm.build_install_cmd(&["build-essential", "python3-dev"]);
798 assert!(args.contains(&"base-devel".to_string()));
800 assert!(args.contains(&"python".to_string()));
801 }
802
803 #[test]
804 fn test_install_no_translation() {
805 let pm = PackageManagerFacade::new(PackageManager::Pacman).with_translate_packages(false);
806 let (_, args) = pm.build_install_cmd(&["build-essential"]);
807 assert!(args.contains(&"build-essential".to_string()));
809 assert!(!args.contains(&"base-devel".to_string()));
810 }
811
812 #[test]
815 fn test_unknown_pm_install() {
816 let pm = PackageManagerFacade::new(PackageManager::Unknown);
817 let (prog, args) = pm.build_install_cmd(&["git"]);
818 assert_eq!(prog, "echo");
819 assert!(args.contains(&"unknown package manager".to_string()));
820 }
821
822 #[test]
827 fn test_dry_run_install_returns_result_without_executing() {
828 let pm = PackageManagerFacade::new(PackageManager::Apt).with_dry_run(true);
829 let result = pm.install(&["git", "cmake"]).unwrap();
830 assert!(result.is_some());
831 let dry = result.unwrap();
832 assert!(dry.to_command_string().contains("sudo apt"));
833 assert!(dry.to_command_string().contains("install"));
834 assert!(dry.description.contains("git"));
835 assert!(dry.description.contains("cmake"));
836 }
837
838 #[test]
839 fn test_dry_run_update_indices() {
840 let pm = PackageManagerFacade::new(PackageManager::Apt).with_dry_run(true);
841 let result = pm.update_indices().unwrap();
842 assert!(result.is_some());
843 let dry = result.unwrap();
844 assert!(dry.to_command_string().contains("sudo apt update"));
845 }
846
847 #[test]
848 fn test_dry_run_remove() {
849 let pm = PackageManagerFacade::new(PackageManager::Pacman).with_dry_run(true);
850 let result = pm.remove(&["nano"]).unwrap();
851 assert!(result.is_some());
852 let dry = result.unwrap();
853 assert!(dry.to_command_string().contains("sudo pacman"));
854 assert!(dry.to_command_string().contains("-R"));
855 }
856
857 #[test]
858 fn test_dry_run_is_installed_returns_false() {
859 let pm = PackageManagerFacade::new(PackageManager::Apt).with_dry_run(true);
860 let result = pm.is_installed("bash").unwrap();
862 assert!(!result);
863 }
864
865 #[test]
866 fn test_dry_run_search_returns_none() {
867 let pm = PackageManagerFacade::new(PackageManager::Apt).with_dry_run(true);
868 let result = pm.search("python").unwrap();
869 assert!(result.is_none());
870 }
871
872 #[test]
873 fn test_dry_run_info_returns_none() {
874 let pm = PackageManagerFacade::new(PackageManager::Apt).with_dry_run(true);
875 let result = pm.info("python3").unwrap();
876 assert!(result.is_none());
877 }
878
879 #[test]
880 fn test_dry_run_display_format() {
881 let pm = PackageManagerFacade::new(PackageManager::Apt).with_dry_run(true);
882 let result = pm.install(&["git"]).unwrap().unwrap();
883 let display = format!("{}", result);
884 assert!(display.starts_with("[DRY-RUN]"));
885 assert!(display.contains("sudo apt"));
886 }
887
888 #[test]
889 fn test_dry_run_command_string_format() {
890 let dry = DryRunResult {
891 program: "sudo apt".to_string(),
892 args: vec!["install".to_string(), "-y".to_string(), "git".to_string()],
893 description: "install git".to_string(),
894 };
895 assert_eq!(dry.to_command_string(), "sudo apt install -y git");
896 }
897
898 #[test]
901 fn test_install_empty_packages_errors() {
902 let pm = PackageManagerFacade::new(PackageManager::Apt);
903 let result = pm.install(&[]);
904 assert!(result.is_err());
905 }
906
907 #[test]
908 fn test_remove_empty_packages_errors() {
909 let pm = PackageManagerFacade::new(PackageManager::Apt);
910 let result = pm.remove(&[]);
911 assert!(result.is_err());
912 }
913
914 #[test]
915 fn test_purge_empty_packages_errors() {
916 let pm = PackageManagerFacade::new(PackageManager::Apt);
917 let result = pm.purge(&[]);
918 assert!(result.is_err());
919 }
920
921 #[test]
924 fn test_package_operation_display() {
925 assert_eq!(format!("{}", PackageOperation::Install), "install");
926 assert_eq!(format!("{}", PackageOperation::Remove), "remove");
927 assert_eq!(format!("{}", PackageOperation::Purge), "purge");
928 assert_eq!(format!("{}", PackageOperation::Update), "update");
929 assert_eq!(format!("{}", PackageOperation::Upgrade), "upgrade");
930 assert_eq!(format!("{}", PackageOperation::Search), "search");
931 assert_eq!(format!("{}", PackageOperation::Info), "info");
932 assert_eq!(format!("{}", PackageOperation::IsInstalled), "is_installed");
933 assert_eq!(
934 format!("{}", PackageOperation::ListInstalled),
935 "list_installed"
936 );
937 }
938
939 #[test]
942 fn test_accessors() {
943 let pm = PackageManagerFacade::new(PackageManager::Dnf).with_dry_run(true);
944 assert_eq!(pm.package_manager(), PackageManager::Dnf);
945 assert!(pm.is_dry_run());
946 }
947
948 #[test]
949 fn test_dry_run_default_false() {
950 let pm = PackageManagerFacade::new(PackageManager::Apt);
951 assert!(!pm.is_dry_run());
952 }
953}