ge_man/
ui.rs

1use std::io::Write;
2
3use anyhow::{anyhow, bail, Context};
4use ge_man_lib::archive;
5use ge_man_lib::config::{LutrisConfig, SteamConfig};
6use ge_man_lib::download::response::DownloadedAssets;
7use ge_man_lib::download::{DownloadRequest, GeDownload};
8use ge_man_lib::error::{GithubError, LutrisConfigError, SteamConfigError};
9use ge_man_lib::tag::TagKind;
10use itertools::Itertools;
11
12use crate::args::{
13    AddArgs, ApplyArgs, CheckArgs, CopyUserSettingsArgs, ForgetArgs, ListArgs, MigrationArgs, RemoveArgs,
14};
15use crate::data::{ManagedVersion, ManagedVersions};
16use crate::filesystem::FilesystemManager;
17use crate::path::{xdg_data_home, AppConfigPaths, PathConfiguration};
18use crate::progress::{DownloadProgressTracker, ExtractionProgressTracker};
19use crate::version::{Version, Versioned};
20
21const PROTON_APPLY_HINT: &str = "Successfully modified Steam config: If Steam is currently running, \
22any external change by GE-Man will not take effect and the new version can not be selected in the Steam settings!
23 
24 To select the latest version you have two options:
25 \t1. Restart Steam to select the new version in Steam (which then requires a second restart for Steam to register the change).
26 \t2. Close Steam and run the apply command for your desired version. On the next start Steam will use the applied version.";
27
28trait AppConfig {
29    fn version_dir_name(&self) -> String;
30    fn kind(&self) -> String;
31}
32
33impl AppConfig for SteamConfig {
34    fn version_dir_name(&self) -> String {
35        self.proton_version()
36    }
37
38    fn kind(&self) -> String {
39        String::from("Steam")
40    }
41}
42
43impl AppConfig for LutrisConfig {
44    fn version_dir_name(&self) -> String {
45        self.wine_version()
46    }
47
48    fn kind(&self) -> String {
49        String::from("Lutris")
50    }
51}
52
53trait AppConfigError {
54    fn kind(&self) -> String;
55}
56
57impl AppConfigError for SteamConfigError {
58    fn kind(&self) -> String {
59        String::from("Steam")
60    }
61}
62
63impl AppConfigError for LutrisConfigError {
64    fn kind(&self) -> String {
65        String::from("Lutris")
66    }
67}
68
69/// Handles user interaction and user feedback. This struct basically ties everything together to provide the
70/// functionality of each terminal command.
71pub struct TerminalWriter<'a> {
72    ge_downloader: &'a dyn GeDownload,
73    fs_mng: &'a dyn FilesystemManager,
74    path_cfg: &'a dyn PathConfiguration,
75}
76
77impl<'a> TerminalWriter<'a> {
78    pub fn new(
79        ge_downloader: &'a dyn GeDownload,
80        fs_mng: &'a dyn FilesystemManager,
81        path_cfg: &'a dyn PathConfiguration,
82    ) -> Self {
83        TerminalWriter {
84            ge_downloader,
85            fs_mng,
86            path_cfg,
87        }
88    }
89
90    fn create_list_line(
91        &self,
92        version: ManagedVersion,
93        wine_version_dir_name: Option<&String>,
94        proton_version_dir_name: Option<&String>,
95    ) -> String {
96        if let Some(dir) = wine_version_dir_name {
97            if dir.eq(version.directory_name()) {
98                return format!("{} - In use by Lutris", version.tag());
99            }
100        }
101
102        if let Some(dir) = proton_version_dir_name {
103            if dir.eq(version.directory_name()) {
104                return format!("{} - In use by Steam", version.tag());
105            }
106        }
107
108        version.tag().str().clone()
109    }
110
111    fn read_managed_versions(&self) -> anyhow::Result<ManagedVersions> {
112        let path = self.path_cfg.managed_versions_config(xdg_data_home());
113        ManagedVersions::from_file(&path)
114            .context(format!("Could not read managed_versions.json from {}", path.display()))
115    }
116
117    fn write_managed_versions(&self, managed_versions: ManagedVersions) -> anyhow::Result<()> {
118        let path = self.path_cfg.managed_versions_config(xdg_data_home());
119        managed_versions.write_to_file(&path)
120    }
121
122    pub fn list(&self, stdout: &mut impl Write, args: ListArgs, config_paths: AppConfigPaths) -> anyhow::Result<()> {
123        let wine_dir_name = match LutrisConfig::create_copy(&config_paths.lutris) {
124            Ok(config) => Some(config.wine_version()),
125            Err(_) => None,
126        };
127
128        let proton_dir_name = match SteamConfig::create_copy(&config_paths.steam) {
129            Ok(config) => Some(config.proton_version()),
130            Err(_) => None,
131        };
132
133        let mut managed_versions: Vec<ManagedVersion> = if args.newest {
134            self.read_managed_versions()?.latest_versions()
135        } else {
136            self.read_managed_versions()?.versions()
137        };
138
139        if let Some(kind) = args.kind {
140            managed_versions.retain(|v| v.kind().eq(&kind));
141        }
142
143        if !managed_versions.is_empty() {
144            // Allow clone of version.kind() due to lifetime not living long enough.
145            #[allow(clippy::clone_on_copy)]
146            let grouped_versions = managed_versions
147                .into_iter()
148                .sorted_unstable_by(|a, b| a.kind().cmp(b.kind()))
149                .group_by(|version| version.kind().clone());
150
151            for (kind, group) in &grouped_versions {
152                writeln!(stdout, "{}:", kind.compatibility_tool_name()).unwrap();
153
154                group
155                    .sorted_unstable_by(|a, b| a.tag().cmp(b.tag()).reverse())
156                    .for_each(|version| {
157                        let line = self.create_list_line(version, wine_dir_name.as_ref(), proton_dir_name.as_ref());
158                        writeln!(stdout, "* {}", line).unwrap();
159                    });
160
161                writeln!(stdout).unwrap();
162            }
163        } else {
164            writeln!(stdout, "No versions installed").unwrap();
165        }
166        Ok(())
167    }
168
169    pub fn add(&self, stdout: &mut impl Write, args: AddArgs) -> anyhow::Result<()> {
170        let tag = args.tag_arg.value();
171        let kind = args.tag_arg.kind;
172        let mut managed_versions = self.read_managed_versions()?;
173
174        let version = if tag.is_some() {
175            Version::new(tag.cloned(), kind)
176        } else {
177            match self.ge_downloader.fetch_release(tag.cloned(), kind) {
178                Ok(release) => Version::new(release.tag_name, kind),
179                Err(err) => {
180                    return Err(anyhow!(err).context(r#"Could not get latest tag for tagless "add" operation."#))
181                }
182            }
183        };
184
185        if managed_versions.find_version(&version).is_some() {
186            writeln!(stdout, "Version {} is already managed", version)?;
187            return Ok(());
188        }
189
190        let download_tracker = Box::new(DownloadProgressTracker::default());
191        let request = DownloadRequest::new(
192            Some(version.tag().to_string()),
193            *version.kind(),
194            download_tracker,
195            args.skip_checksum,
196        );
197
198        let assets = match self.ge_downloader.download_release_assets(request) {
199            Ok(assets) => assets,
200            Err(err) => {
201                let res = if let GithubError::ReleaseHasNoAssets { tag, kind } = err {
202                    let err = GithubError::ReleaseHasNoAssets { tag: tag.clone(), kind };
203
204                    anyhow!(err).context(format!(
205                        "The given release has no assets for {} {}. It might be possible that the \
206                        release assets have been removed due to fixes in a newer version.",
207                        tag, kind
208                    ))
209                } else {
210                    anyhow!(err).context("Could not fetch release assets from Github")
211                };
212
213                bail!(res);
214            }
215        };
216
217        let DownloadedAssets {
218            compressed_archive: compressed_tar,
219            checksum,
220            ..
221        } = assets;
222
223        if args.skip_checksum {
224            writeln!(stdout, "Skipping checksum comparison").unwrap();
225        } else {
226            write!(stdout, "Performing checksum comparison").unwrap();
227            let checksum = checksum.unwrap();
228
229            let result = archive::checksums_match(&compressed_tar.compressed_content, checksum.checksum.as_bytes());
230
231            if !result {
232                bail!("Checksum comparison failed: Checksum generated from downloaded archive does not match downloaded expected checksum");
233            } else {
234                writeln!(stdout, ": Checksums match").unwrap();
235            }
236        }
237
238        let extraction_tracker = ExtractionProgressTracker::new(compressed_tar.compressed_content.len() as u64);
239        let compressed_tar_reader = extraction_tracker
240            .inner()
241            .wrap_read(std::io::Cursor::new(compressed_tar.compressed_content));
242
243        let version = self
244            .fs_mng
245            .setup_version(version, Box::new(compressed_tar_reader))
246            .context("Could not add version")?;
247        extraction_tracker.finish();
248
249        let version = managed_versions.add(version)?;
250        self.write_managed_versions(managed_versions)?;
251
252        writeln!(stdout, "Successfully added version").unwrap();
253        if args.apply {
254            self.do_apply_to_app_config(stdout, &version)?;
255        }
256
257        Ok(())
258    }
259
260    pub fn remove(
261        &self,
262        stdout: &mut impl Write,
263        args: RemoveArgs,
264        config_paths: AppConfigPaths,
265    ) -> anyhow::Result<()> {
266        let version = args.tag_arg.version();
267        let mut managed_versions = self.read_managed_versions()?;
268
269        let version = match managed_versions.find_version(&version) {
270            Some(v) => v,
271            None => bail!("Given version is not managed"),
272        };
273
274        match &version.kind() {
275            TagKind::Proton => {
276                let path = &config_paths.steam;
277                let config = SteamConfig::create_copy(path)
278                    .map_err(|err| anyhow!(err))
279                    .context(format!("Failed to read Steam config: {}", path.display()))?;
280
281                if self.check_if_version_in_use_by_config(&version, &config) {
282                    bail!("Proton version is in use by Steam. Select a different version to make removal possible.");
283                }
284            }
285            TagKind::Wine { .. } => {
286                let path = &config_paths.lutris;
287                let config = LutrisConfig::create_copy(path);
288                match config {
289                    Ok(config) => {
290                        if self.check_if_version_in_use_by_config(&version, &config) {
291                            bail!(
292                                "Wine version is in use by Lutris. Select a different version to make removal \
293                            possible."
294                            );
295                        }
296                    }
297                    Err(err) => {
298                        if let LutrisConfigError::IoError { source } = &err {
299                            if source.raw_os_error().unwrap() != 2 {
300                                bail!(err);
301                            }
302                        }
303                    }
304                }
305            }
306        }
307
308        self.fs_mng.remove_version(&version).unwrap();
309        managed_versions.remove(&version).unwrap();
310
311        self.write_managed_versions(managed_versions)?;
312        writeln!(stdout, "Successfully removed version {}.", version).unwrap();
313        Ok(())
314    }
315
316    fn check_if_version_in_use_by_config<T>(&self, version: &ManagedVersion, app_config: &T) -> bool
317    where
318        T: AppConfig,
319    {
320        app_config.version_dir_name().eq(version.directory_name())
321    }
322
323    pub fn check(&self, stdout: &mut impl Write, stderr: &mut impl Write, args: CheckArgs) {
324        match args.kind {
325            Some(kind) => match self.ge_downloader.fetch_release(None, kind) {
326                Ok(release) => {
327                    writeln!(
328                        stdout,
329                        "The latest version of {} is \"{}\"",
330                        kind.compatibility_tool_name(),
331                        release.tag_name
332                    )
333                    .unwrap();
334                }
335                Err(err) => {
336                    writeln!(stderr, "Could not fetch latest release from Github: {}", err).unwrap();
337                }
338            },
339            None => {
340                let proton = self.ge_downloader.fetch_release(None, TagKind::Proton);
341                let wine = self.ge_downloader.fetch_release(None, TagKind::wine());
342                let lol = self.ge_downloader.fetch_release(None, TagKind::lol());
343
344                writeln!(stdout, "These are the latest releases.").unwrap();
345                writeln!(stdout).unwrap();
346                match proton {
347                    Ok(release) => writeln!(stdout, "Proton GE: {}", release.tag_name).unwrap(),
348                    Err(err) => writeln!(
349                        stderr,
350                        "Proton GE: Could not fetch release information from GitHub: {}",
351                        err
352                    )
353                    .unwrap(),
354                }
355
356                match wine {
357                    Ok(release) => writeln!(stdout, "Wine GE: {}", release.tag_name).unwrap(),
358                    Err(err) => writeln!(
359                        stderr,
360                        "Wine GE: Could not fetch release information from GitHub: {}",
361                        err
362                    )
363                    .unwrap(),
364                }
365
366                match lol {
367                    Ok(release) => writeln!(stdout, "Wine GE - LoL: {}", release.tag_name).unwrap(),
368                    Err(err) => writeln!(
369                        stderr,
370                        "Wine GE - LoL: Could not fetch release information from GitHub: {}",
371                        err
372                    )
373                    .unwrap(),
374                }
375            }
376        }
377    }
378
379    pub fn migrate(&self, stdout: &mut impl Write, args: MigrationArgs) -> anyhow::Result<()> {
380        let version = args.tag_arg.version();
381        let mut managed_versions = self.read_managed_versions()?;
382
383        if managed_versions.find_version(&version).is_some() {
384            bail!("Given version to migrate already exists as a managed version");
385        }
386
387        let source_path = &args.source_path;
388        let version = self
389            .fs_mng
390            .migrate_folder(version, source_path)
391            .context("Could not migrate directory")?;
392        let version = managed_versions.add(version)?;
393
394        self.write_managed_versions(managed_versions)?;
395        writeln!(stdout, "Successfully migrated directory as {}", version).unwrap();
396        Ok(())
397    }
398
399    fn do_apply_to_app_config(&self, stdout: &mut impl Write, version: &ManagedVersion) -> anyhow::Result<()> {
400        let (modify_msg, success_msg) = match version.kind() {
401            TagKind::Proton => (
402                format!("Modifying Steam configuration to use {}", version),
403                PROTON_APPLY_HINT,
404            ),
405            TagKind::Wine { .. } => (
406                format!("Modifying Lutris configuration to use {}", version),
407                "Successfully modified Lutris config: Lutris should be restarted for the new settings to take effect.",
408            ),
409        };
410
411        writeln!(stdout, "{}", modify_msg).unwrap();
412
413        self.fs_mng
414            .apply_to_app_config(version)
415            .context("Could not modify app config")?;
416
417        writeln!(stdout, "{}", success_msg).unwrap();
418
419        Ok(())
420    }
421
422    pub fn apply_to_app_config(&self, stdout: &mut impl Write, args: ApplyArgs) -> anyhow::Result<()> {
423        let managed_versions = self.read_managed_versions()?;
424
425        let version = if args.tag_arg.tag.is_some() {
426            let version = args.tag_arg.version();
427            match managed_versions.find_version(&version) {
428                Some(v) => v,
429                None => bail!("Given version is not managed"),
430            }
431        } else {
432            let kind = args.tag_arg.kind;
433            if let Some(version) = managed_versions.find_latest_by_kind(&kind) {
434                version
435            } else {
436                bail!("No managed versions exist");
437            }
438        };
439
440        self.do_apply_to_app_config(stdout, &version)
441    }
442
443    pub fn copy_user_settings(&self, stdout: &mut impl Write, args: CopyUserSettingsArgs) -> anyhow::Result<()> {
444        let managed_versions = self.read_managed_versions()?;
445        let src_version = Version::new(args.src_tag, TagKind::Proton);
446        let dst_version = Version::new(args.dst_tag, TagKind::Proton);
447
448        let src_version = match managed_versions.find_version(&src_version) {
449            Some(v) => v,
450            None => bail!("Given source Proton version does not exist"),
451        };
452        let dst_version = match managed_versions.find_version(&dst_version) {
453            Some(v) => v,
454            None => bail!("Given destination Proton version does not exist"),
455        };
456
457        self.fs_mng.copy_user_settings(&src_version, &dst_version)?;
458
459        writeln!(
460            stdout,
461            "Copied user_settings.py from {} to {}",
462            src_version, dst_version
463        )
464        .unwrap();
465        Ok(())
466    }
467
468    pub fn forget(&self, stdout: &mut impl Write, args: ForgetArgs) -> anyhow::Result<()> {
469        let version = args.tag_arg.version();
470        let mut managed_versions = self.read_managed_versions()?;
471        if managed_versions.remove(&version).is_none() {
472            bail!("Failed to forget version: Version is not managed");
473        }
474
475        self.write_managed_versions(managed_versions)?;
476        writeln!(stdout, "{} is now not managed by GE Helper", version).unwrap();
477        Ok(())
478    }
479}
480
481#[cfg(test)]
482mod tests {
483    use std::path::{Path, PathBuf};
484    use std::{fs, io};
485
486    use anyhow::bail;
487    use assert_fs::TempDir;
488    use ge_man_lib::download::response::{DownloadedArchive, DownloadedChecksum, GeRelease};
489    use ge_man_lib::tag::Tag;
490    use mockall::mock;
491
492    use crate::args::TagArg;
493    use crate::filesystem::MockFilesystemManager;
494    use crate::path::MockPathConfiguration;
495
496    use super::*;
497
498    mock! {
499        Downloader {}
500        impl GeDownload for Downloader {
501            fn fetch_release(&self, tag: Option<String>, kind: TagKind) -> Result<GeRelease, GithubError>;
502            fn download_release_assets(&self, request: DownloadRequest) -> Result<DownloadedAssets, GithubError>;
503        }
504    }
505
506    struct AssertLines {
507        latest_line: String,
508        pub lines: Vec<String>,
509    }
510
511    impl AssertLines {
512        pub fn new() -> Self {
513            AssertLines {
514                latest_line: String::new(),
515                lines: Vec::new(),
516            }
517        }
518
519        pub fn assert_line(&self, line: usize, expected: &str) {
520            let line = self.lines.get(line).unwrap();
521            assert!(line.contains("\n"));
522            assert_eq!(line.trim(), expected);
523        }
524
525        pub fn assert_empty(&self) {
526            assert!(self.lines.is_empty())
527        }
528    }
529
530    impl io::Write for AssertLines {
531        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
532            let len = buf.len();
533            let str = String::from_utf8_lossy(buf);
534            self.latest_line.push_str(&str);
535
536            if self.latest_line.contains("\n") {
537                self.lines.push(self.latest_line.clone());
538                self.latest_line = String::new();
539            }
540
541            Ok(len)
542        }
543
544        fn flush(&mut self) -> io::Result<()> {
545            Ok(())
546        }
547    }
548
549    fn proton_6_20_1() -> ManagedVersion {
550        ManagedVersion::from(Version::proton("6.20-GE-1"))
551    }
552
553    fn setup_managed_versions(json_path: &Path, versions: Vec<ManagedVersion>) {
554        fs::create_dir_all(json_path.parent().unwrap()).unwrap();
555        let managed_versions = ManagedVersions::new(versions);
556        managed_versions.write_to_file(json_path).unwrap();
557    }
558
559    #[test]
560    fn forget_should_print_success_message() {
561        let args = ForgetArgs::new(TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton));
562        let fs_mng = MockFilesystemManager::new();
563        let ge_downloader = MockDownloader::new();
564
565        let tmp_dir = TempDir::new().unwrap();
566        let json_path = tmp_dir.join("ge_man/managed_versions.json");
567        setup_managed_versions(&json_path, vec![proton_6_20_1()]);
568
569        let mut path_cfg = MockPathConfiguration::new();
570        path_cfg
571            .expect_managed_versions_config()
572            .times(2)
573            .returning(move |_| json_path.clone());
574
575        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
576
577        let mut stdout = AssertLines::new();
578        writer.forget(&mut stdout, args).unwrap();
579
580        stdout.assert_line(0, "6.20-GE-1 (Proton) is now not managed by GE Helper");
581    }
582
583    #[test]
584    fn forget_should_print_error_message() {
585        let args = ForgetArgs::new(TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton));
586        let fs_mng = MockFilesystemManager::new();
587        let ge_downloader = MockDownloader::new();
588
589        let tmp_dir = TempDir::new().unwrap();
590        let json_path = tmp_dir.join("ge_man/managed_versions.json");
591        setup_managed_versions(&json_path, vec![]);
592
593        let mut path_cfg = MockPathConfiguration::new();
594        path_cfg
595            .expect_managed_versions_config()
596            .once()
597            .returning(move |_| json_path.clone());
598
599        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
600
601        let mut stdout = AssertLines::new();
602        let result = writer.forget(&mut stdout, args);
603        assert!(result.is_err());
604
605        let err = result.unwrap_err();
606        assert_eq!(err.to_string(), "Failed to forget version: Version is not managed");
607        stdout.assert_empty();
608    }
609
610    #[test]
611    fn list_newest_output() {
612        let args = ListArgs::new(None, true);
613        let fs_mng = MockFilesystemManager::new();
614        let ge_downloader = MockDownloader::new();
615
616        let tmp_dir = TempDir::new().unwrap();
617        let json_path = tmp_dir.join("ge_man/managed_versions.json");
618        setup_managed_versions(
619            &json_path,
620            vec![
621                ManagedVersion::new("6.20-GE-1", TagKind::Proton, ""),
622                ManagedVersion::new("6.20-GE-1", TagKind::wine(), ""),
623                ManagedVersion::new("6.16-GE-3-LoL", TagKind::lol(), ""),
624            ],
625        );
626
627        let mut path_cfg = MockPathConfiguration::new();
628        path_cfg
629            .expect_managed_versions_config()
630            .once()
631            .returning(move |_| json_path.clone());
632
633        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
634
635        let config_paths = AppConfigPaths::new("test_resources/assets/config.vdf", "test_resources/assets/wine.yml");
636        let mut stdout = AssertLines::new();
637        writer.list(&mut stdout, args, config_paths).unwrap();
638
639        stdout.assert_line(0, "Proton GE:");
640        stdout.assert_line(1, "* 6.20-GE-1");
641        stdout.assert_line(2, "");
642        stdout.assert_line(3, "Wine GE:");
643        stdout.assert_line(4, "* 6.20-GE-1");
644        stdout.assert_line(5, "");
645        stdout.assert_line(6, "Wine GE (LoL):");
646        stdout.assert_line(7, "* 6.16-GE-3-LoL");
647        stdout.assert_line(8, "");
648    }
649
650    #[test]
651    fn list_newest_output_with_in_use_version() {
652        let args = ListArgs::new(None, true);
653        let fs_mng = MockFilesystemManager::new();
654        let ge_downloader = MockDownloader::new();
655
656        let tmp_dir = TempDir::new().unwrap();
657        let json_path = tmp_dir.join("ge_man/managed_versions.json");
658        setup_managed_versions(
659            &json_path,
660            vec![
661                ManagedVersion::new("6.21-GE-2", TagKind::Proton, "Proton-6.21-GE-2"),
662                ManagedVersion::new("6.21-GE-1", TagKind::wine(), "lutris-ge-6.21-1-x86_64"),
663                ManagedVersion::new("6.16-GE-3-LoL", TagKind::lol(), ""),
664            ],
665        );
666
667        let mut path_cfg = MockPathConfiguration::new();
668        path_cfg
669            .expect_managed_versions_config()
670            .once()
671            .returning(move |_| json_path.clone());
672
673        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
674
675        let config_paths = AppConfigPaths::new("test_resources/assets/config.vdf", "test_resources/assets/wine.yml");
676        let mut stdout = AssertLines::new();
677        writer.list(&mut stdout, args, config_paths).unwrap();
678
679        stdout.assert_line(0, "Proton GE:");
680        stdout.assert_line(1, "* 6.21-GE-2 - In use by Steam");
681        stdout.assert_line(2, "");
682        stdout.assert_line(3, "Wine GE:");
683        stdout.assert_line(4, "* 6.21-GE-1 - In use by Lutris");
684        stdout.assert_line(5, "");
685        stdout.assert_line(6, "Wine GE (LoL):");
686        stdout.assert_line(7, "* 6.16-GE-3-LoL");
687        stdout.assert_line(8, "");
688    }
689
690    #[test]
691    fn list_all() {
692        let args = ListArgs::new(None, false);
693        let fs_mng = MockFilesystemManager::new();
694        let ge_downloader = MockDownloader::new();
695
696        let tmp_dir = TempDir::new().unwrap();
697        let json_path = tmp_dir.join("ge_man/managed_versions.json");
698        setup_managed_versions(
699            &json_path,
700            vec![
701                ManagedVersion::new("6.20-GE-2", TagKind::Proton, ""),
702                ManagedVersion::new("6.20-GE-1", TagKind::Proton, ""),
703                ManagedVersion::new("6.19-GE-1", TagKind::Proton, ""),
704                ManagedVersion::new("6.20-GE-2", TagKind::wine(), ""),
705                ManagedVersion::new("6.20-GE-1", TagKind::wine(), ""),
706                ManagedVersion::new("6.19-GE-1", TagKind::wine(), ""),
707                ManagedVersion::new("6.16-GE-3-LoL", TagKind::lol(), ""),
708                ManagedVersion::new("6.16-2-GE-LoL", TagKind::lol(), ""),
709                ManagedVersion::new("6.16-1-GE-LoL", TagKind::lol(), ""),
710            ],
711        );
712
713        let mut path_cfg = MockPathConfiguration::new();
714        path_cfg
715            .expect_managed_versions_config()
716            .once()
717            .returning(move |_| json_path.clone());
718
719        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
720
721        let mut stdout = AssertLines::new();
722        let config_paths = AppConfigPaths::new("test_resources/assets/config.vdf", "test_resources/assets/wine.yml");
723        writer.list(&mut stdout, args, config_paths).unwrap();
724
725        stdout.assert_line(0, "Proton GE:");
726        stdout.assert_line(1, "* 6.20-GE-2");
727        stdout.assert_line(2, "* 6.20-GE-1");
728        stdout.assert_line(3, "* 6.19-GE-1");
729        stdout.assert_line(4, "");
730        stdout.assert_line(5, "Wine GE:");
731        stdout.assert_line(6, "* 6.20-GE-2");
732        stdout.assert_line(7, "* 6.20-GE-1");
733        stdout.assert_line(8, "* 6.19-GE-1");
734        stdout.assert_line(9, "");
735        stdout.assert_line(10, "Wine GE (LoL):");
736        stdout.assert_line(11, "* 6.16-GE-3-LoL");
737        stdout.assert_line(12, "* 6.16-2-GE-LoL");
738        stdout.assert_line(13, "* 6.16-1-GE-LoL");
739        stdout.assert_line(14, "");
740    }
741
742    #[test]
743    fn list_all_with_in_use_version() {
744        let args = ListArgs::new(None, false);
745        let fs_mng = MockFilesystemManager::new();
746        let ge_downloader = MockDownloader::new();
747
748        let tmp_dir = TempDir::new().unwrap();
749        let json_path = tmp_dir.join("ge_man/managed_versions.json");
750        setup_managed_versions(
751            &json_path,
752            vec![
753                ManagedVersion::new("6.21-GE-2", TagKind::Proton, "Proton-6.21-GE-2"),
754                ManagedVersion::new("6.20-GE-1", TagKind::Proton, ""),
755                ManagedVersion::new("6.19-GE-1", TagKind::Proton, ""),
756                ManagedVersion::new("6.21-GE-1", TagKind::wine(), "lutris-ge-6.21-1-x86_64"),
757                ManagedVersion::new("6.20-GE-1", TagKind::wine(), ""),
758                ManagedVersion::new("6.19-GE-1", TagKind::wine(), ""),
759                ManagedVersion::new("6.16-GE-3-LoL", TagKind::lol(), ""),
760                ManagedVersion::new("6.16-2-GE-LoL", TagKind::lol(), ""),
761                ManagedVersion::new("6.16-1-GE-LoL", TagKind::lol(), ""),
762            ],
763        );
764
765        let mut path_cfg = MockPathConfiguration::new();
766        path_cfg
767            .expect_managed_versions_config()
768            .once()
769            .returning(move |_| json_path.clone());
770
771        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
772
773        let mut stdout = AssertLines::new();
774        let config_paths = AppConfigPaths::new("test_resources/assets/config.vdf", "test_resources/assets/wine.yml");
775        writer.list(&mut stdout, args, config_paths).unwrap();
776
777        stdout.assert_line(0, "Proton GE:");
778        stdout.assert_line(1, "* 6.21-GE-2 - In use by Steam");
779        stdout.assert_line(2, "* 6.20-GE-1");
780        stdout.assert_line(3, "* 6.19-GE-1");
781        stdout.assert_line(4, "");
782        stdout.assert_line(5, "Wine GE:");
783        stdout.assert_line(6, "* 6.21-GE-1 - In use by Lutris");
784        stdout.assert_line(7, "* 6.20-GE-1");
785        stdout.assert_line(8, "* 6.19-GE-1");
786        stdout.assert_line(9, "");
787        stdout.assert_line(10, "Wine GE (LoL):");
788        stdout.assert_line(11, "* 6.16-GE-3-LoL");
789        stdout.assert_line(12, "* 6.16-2-GE-LoL");
790        stdout.assert_line(13, "* 6.16-1-GE-LoL");
791        stdout.assert_line(14, "");
792    }
793
794    #[test]
795    fn add_successful_output() {
796        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
797        let args = AddArgs::new(tag_arg, true, false);
798
799        let mut ge_downloader = MockDownloader::new();
800        ge_downloader.expect_download_release_assets().once().returning(|_| {
801            Ok(DownloadedAssets {
802                tag: "".to_string(),
803                compressed_archive: DownloadedArchive {
804                    compressed_content: vec![],
805                    file_name: "".to_string(),
806                },
807                checksum: None,
808            })
809        });
810
811        let mut fs_mng = MockFilesystemManager::new();
812        fs_mng
813            .expect_setup_version()
814            .once()
815            .returning(|_, _| Ok(ManagedVersion::new("6.20-GE-1", TagKind::Proton, "")));
816
817        let tmp_dir = TempDir::new().unwrap();
818        let json_path = tmp_dir.join("ge_man/managed_versions.json");
819        setup_managed_versions(&json_path, vec![]);
820
821        let mut path_cfg = MockPathConfiguration::new();
822        path_cfg
823            .expect_managed_versions_config()
824            .times(2)
825            .returning(move |_| json_path.clone());
826
827        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
828
829        let mut stdout = AssertLines::new();
830        writer.add(&mut stdout, args).unwrap();
831
832        stdout.assert_line(0, "Skipping checksum comparison");
833        stdout.assert_line(1, "Successfully added version");
834    }
835
836    #[test]
837    fn add_with_checksum_comparison_successful_output() {
838        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
839        let args = AddArgs::new(tag_arg, false, false);
840
841        let mut ge_downloader = MockDownloader::new();
842        ge_downloader.expect_download_release_assets().once().returning(|_| {
843            let tar = fs::read("test_resources/assets/Proton-6.20-GE-1.tar.gz").unwrap();
844            let checksum = fs::read_to_string("test_resources/assets/Proton-6.20-GE-1.sha512sum").unwrap();
845
846            Ok(DownloadedAssets {
847                tag: "6.20-GE-1".to_string(),
848                compressed_archive: DownloadedArchive {
849                    compressed_content: tar,
850                    file_name: "Proton-6.20-GE-1.tar.gz".to_string(),
851                },
852                checksum: Some(DownloadedChecksum {
853                    checksum,
854                    file_name: "Proton-6.20-GE-1.sha512sum".to_string(),
855                }),
856            })
857        });
858
859        let mut fs_mng = MockFilesystemManager::new();
860        fs_mng
861            .expect_setup_version()
862            .once()
863            .returning(|_, _| Ok(ManagedVersion::new("6.20-GE-1", TagKind::Proton, "")));
864
865        let tmp_dir = TempDir::new().unwrap();
866        let json_path = tmp_dir.join("ge_man/managed_versions.json");
867        setup_managed_versions(&json_path, vec![]);
868
869        let mut path_cfg = MockPathConfiguration::new();
870        path_cfg
871            .expect_managed_versions_config()
872            .times(2)
873            .returning(move |_| json_path.clone());
874
875        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
876
877        let mut stdout = AssertLines::new();
878        writer.add(&mut stdout, args).unwrap();
879
880        stdout.assert_line(0, "Performing checksum comparison: Checksums match");
881        stdout.assert_line(1, "Successfully added version");
882    }
883
884    #[test]
885    fn add_specific_version_which_is_already_managed_again_expect_message_about_already_being_managed() {
886        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
887        let args = AddArgs::new(tag_arg, false, false);
888
889        let ge_downloader = MockDownloader::new();
890        let mut fs_mng = MockFilesystemManager::new();
891        fs_mng.expect_setup_version().never();
892
893        let tmp_dir = TempDir::new().unwrap();
894        let json_path = tmp_dir.join("ge_man/managed_versions.json");
895        setup_managed_versions(&json_path, vec![ManagedVersion::new("6.20-GE-1", TagKind::Proton, "")]);
896
897        let mut path_cfg = MockPathConfiguration::new();
898        path_cfg
899            .expect_managed_versions_config()
900            .once()
901            .returning(move |_| json_path.clone());
902
903        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
904
905        let mut stdout = AssertLines::new();
906        let result = writer.add(&mut stdout, args);
907        assert!(result.is_ok());
908        stdout.assert_line(0, "Version 6.20-GE-1 (Proton) is already managed");
909    }
910
911    #[test]
912    fn add_latest_version_which_is_already_managed_again_expect_message_about_already_being_managed() {
913        let tag_arg = TagArg::new(None, TagKind::Proton);
914        let args = AddArgs::new(tag_arg, false, false);
915
916        let mut fs_mng = MockFilesystemManager::new();
917        fs_mng.expect_setup_version().never();
918
919        let tmp_dir = TempDir::new().unwrap();
920        let json_path = tmp_dir.join("ge_man/managed_versions.json");
921        setup_managed_versions(&json_path, vec![ManagedVersion::new("6.20-GE-1", TagKind::Proton, "")]);
922
923        let mut path_cfg = MockPathConfiguration::new();
924        path_cfg
925            .expect_managed_versions_config()
926            .once()
927            .returning(move |_| json_path.clone());
928
929        let mut ge_downloader = MockDownloader::new();
930        ge_downloader
931            .expect_fetch_release()
932            .once()
933            .returning(move |_, _| Ok(GeRelease::new(String::from("6.20-GE-1"), Vec::new())));
934
935        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
936
937        let mut stdout = AssertLines::new();
938        let result = writer.add(&mut stdout, args);
939        assert!(result.is_ok());
940        stdout.assert_line(0, "Version 6.20-GE-1 (Proton) is already managed");
941    }
942
943    #[test]
944    fn add_with_apply_should_modify_app_config() {
945        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
946        let args = AddArgs::new(tag_arg, false, true);
947
948        let mut ge_downloader = MockDownloader::new();
949        ge_downloader.expect_download_release_assets().once().returning(|_| {
950            let tar = fs::read("test_resources/assets/Proton-6.20-GE-1.tar.gz").unwrap();
951            let checksum = fs::read_to_string("test_resources/assets/Proton-6.20-GE-1.sha512sum").unwrap();
952
953            Ok(DownloadedAssets {
954                tag: "6.20-GE-1".to_string(),
955                compressed_archive: DownloadedArchive {
956                    compressed_content: tar,
957                    file_name: "Proton-6.20-GE-1.tar.gz".to_string(),
958                },
959                checksum: Some(DownloadedChecksum {
960                    checksum,
961                    file_name: "Proton-6.20-GE-1.sha512sum".to_string(),
962                }),
963            })
964        });
965
966        let mut fs_mng = MockFilesystemManager::new();
967        fs_mng
968            .expect_setup_version()
969            .once()
970            .returning(|_, _| Ok(ManagedVersion::new("6.20-GE-1", TagKind::Proton, "")));
971        fs_mng.expect_apply_to_app_config().once().returning(|_| Ok(()));
972
973        let tmp_dir = TempDir::new().unwrap();
974        let json_path = tmp_dir.join("ge_man/managed_versions.json");
975        setup_managed_versions(&json_path, vec![]);
976
977        let mut path_cfg = MockPathConfiguration::new();
978        path_cfg
979            .expect_managed_versions_config()
980            .times(2)
981            .returning(move |_| json_path.clone());
982
983        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
984
985        let mut stdout = AssertLines::new();
986        writer.add(&mut stdout, args).unwrap();
987
988        stdout.assert_line(0, "Performing checksum comparison: Checksums match");
989        stdout.assert_line(1, "Successfully added version");
990        stdout.assert_line(2, "Modifying Steam configuration to use 6.20-GE-1 (Proton)");
991        stdout.assert_line(3, PROTON_APPLY_HINT);
992    }
993
994    #[test]
995    fn remove_not_managed_version() {
996        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
997        let args = RemoveArgs::new(tag_arg);
998        let ge_downloader = MockDownloader::new();
999
1000        let mut fs_mng = MockFilesystemManager::new();
1001        fs_mng.expect_remove_version().never();
1002
1003        let tmp_dir = TempDir::new().unwrap();
1004        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1005        setup_managed_versions(&json_path, vec![]);
1006
1007        let mut path_cfg = MockPathConfiguration::new();
1008        path_cfg
1009            .expect_managed_versions_config()
1010            .once()
1011            .returning(move |_| json_path.clone());
1012
1013        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1014
1015        let mut stdout = AssertLines::new();
1016        let config_paths = AppConfigPaths::new("invalid-path", "invalid-path");
1017        let result = writer.remove(&mut stdout, args, config_paths);
1018        assert!(result.is_err());
1019
1020        let err = result.unwrap_err();
1021        assert_eq!(err.to_string(), "Given version is not managed");
1022        stdout.assert_empty();
1023    }
1024
1025    #[test]
1026    fn remove_existing_version() {
1027        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
1028        let args = RemoveArgs::new(tag_arg);
1029        let ge_downloader = MockDownloader::new();
1030
1031        let mut fs_mng = MockFilesystemManager::new();
1032        fs_mng.expect_remove_version().once().returning(|_| Ok(()));
1033
1034        let tmp_dir = TempDir::new().unwrap();
1035        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1036        setup_managed_versions(
1037            &json_path,
1038            vec![ManagedVersion::new(Tag::from("6.20-GE-1"), TagKind::Proton, "")],
1039        );
1040
1041        let mut path_cfg = MockPathConfiguration::new();
1042        path_cfg
1043            .expect_managed_versions_config()
1044            .times(2)
1045            .returning(move |_| json_path.clone());
1046
1047        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1048        let mut stdout = AssertLines::new();
1049
1050        let config_path = PathBuf::from("test_resources/assets/config.vdf");
1051        let config_paths = AppConfigPaths::new(config_path, PathBuf::from("ignored"));
1052        writer.remove(&mut stdout, args, config_paths).unwrap();
1053
1054        stdout.assert_line(0, "Successfully removed version 6.20-GE-1 (Proton).")
1055    }
1056
1057    #[test]
1058    fn remove_version_used_by_app_config() {
1059        let tag_arg = TagArg::new(Some(Tag::from("6.21-GE-2")), TagKind::Proton);
1060        let args = RemoveArgs::new(tag_arg);
1061        let ge_downloader = MockDownloader::new();
1062
1063        let mut fs_mng = MockFilesystemManager::new();
1064        fs_mng.expect_remove_version().never();
1065
1066        let tmp_dir = TempDir::new().unwrap();
1067        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1068        setup_managed_versions(
1069            &json_path,
1070            vec![ManagedVersion::new(
1071                Tag::from("6.21-GE-2"),
1072                TagKind::Proton,
1073                "Proton-6.21-GE-2",
1074            )],
1075        );
1076
1077        let mut path_cfg = MockPathConfiguration::new();
1078        path_cfg
1079            .expect_managed_versions_config()
1080            .once()
1081            .returning(move |_| json_path.clone());
1082
1083        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1084        let mut stdout = AssertLines::new();
1085
1086        let config_path = PathBuf::from("test_resources/assets/config.vdf");
1087        let config_paths = AppConfigPaths::new(config_path, PathBuf::from("ignored"));
1088        let result = writer.remove(&mut stdout, args, config_paths);
1089        assert!(result.is_err());
1090
1091        let err = result.unwrap_err();
1092        assert_eq!(
1093            err.to_string(),
1094            "Proton version is in use by Steam. Select a different version to make removal possible."
1095        );
1096        stdout.assert_empty();
1097    }
1098
1099    #[test]
1100    fn check_with_successful_requests() {
1101        let args = CheckArgs::new(None);
1102
1103        let mut ge_downloader = MockDownloader::new();
1104        ge_downloader
1105            .expect_fetch_release()
1106            .once()
1107            .withf(|tag, kind| tag.is_none() && kind.eq(&TagKind::Proton))
1108            .returning(|_, _| Ok(GeRelease::new(String::from("6.20-GE-1"), vec![])));
1109        ge_downloader
1110            .expect_fetch_release()
1111            .once()
1112            .withf(|tag, kind| tag.is_none() && kind.eq(&TagKind::wine()))
1113            .returning(|_, _| Ok(GeRelease::new(String::from("6.20-GE-1"), vec![])));
1114        ge_downloader
1115            .expect_fetch_release()
1116            .once()
1117            .withf(|tag, kind| tag.is_none() && kind.eq(&TagKind::lol()))
1118            .returning(|_, _| Ok(GeRelease::new(String::from("6.16-GE-3-LoL"), vec![])));
1119
1120        let path_cfg = MockPathConfiguration::new();
1121        let fs_mng = MockFilesystemManager::new();
1122
1123        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1124
1125        let mut stdout = AssertLines::new();
1126        let mut stderr = AssertLines::new();
1127        writer.check(&mut stdout, &mut stderr, args);
1128
1129        stdout.assert_line(0, "These are the latest releases.");
1130        stdout.assert_line(1, "");
1131        stdout.assert_line(2, "Proton GE: 6.20-GE-1");
1132        stdout.assert_line(3, "Wine GE: 6.20-GE-1");
1133        stdout.assert_line(4, "Wine GE - LoL: 6.16-GE-3-LoL");
1134    }
1135
1136    #[test]
1137    fn check_with_only_errors() {
1138        let args = CheckArgs::new(None);
1139
1140        let mut ge_downloader = MockDownloader::new();
1141        ge_downloader
1142            .expect_fetch_release()
1143            .once()
1144            .withf(|tag, kind| tag.is_none() && kind.eq(&TagKind::Proton))
1145            .returning(|_, _| Err(GithubError::NoTags));
1146        ge_downloader
1147            .expect_fetch_release()
1148            .once()
1149            .withf(|tag, kind| tag.is_none() && kind.eq(&TagKind::wine()))
1150            .returning(|_, _| Err(GithubError::NoTags));
1151        ge_downloader
1152            .expect_fetch_release()
1153            .once()
1154            .withf(|tag, kind| tag.is_none() && kind.eq(&TagKind::lol()))
1155            .returning(|_, _| Err(GithubError::NoTags));
1156
1157        let path_cfg = MockPathConfiguration::new();
1158        let fs_mng = MockFilesystemManager::new();
1159
1160        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1161
1162        let mut stdout = AssertLines::new();
1163        let mut stderr = AssertLines::new();
1164        writer.check(&mut stdout, &mut stderr, args);
1165
1166        stdout.assert_line(0, "These are the latest releases.");
1167        stderr.assert_line(
1168            0,
1169            "Proton GE: Could not fetch release information from GitHub: No tags could be found",
1170        );
1171        stderr.assert_line(
1172            1,
1173            "Wine GE: Could not fetch release information from GitHub: No tags could be found",
1174        );
1175        stderr.assert_line(
1176            2,
1177            "Wine GE - LoL: Could not fetch release information from GitHub: No tags could be found",
1178        );
1179    }
1180
1181    #[test]
1182    fn migrate_already_managed_version() {
1183        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
1184        let args = MigrationArgs::new(tag_arg, "invalid-path");
1185
1186        let ge_downloader = MockDownloader::new();
1187        let fs_mng = MockFilesystemManager::new();
1188
1189        let tmp_dir = TempDir::new().unwrap();
1190        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1191        setup_managed_versions(
1192            &json_path,
1193            vec![ManagedVersion::new(
1194                Tag::from("6.20-GE-1"),
1195                TagKind::Proton,
1196                "Proton-6.20-GE-1",
1197            )],
1198        );
1199
1200        let mut path_cfg = MockPathConfiguration::new();
1201        path_cfg
1202            .expect_managed_versions_config()
1203            .once()
1204            .returning(move |_| json_path.clone());
1205
1206        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1207
1208        let mut stdout = AssertLines::new();
1209        let result = writer.migrate(&mut stdout, args);
1210        assert!(result.is_err());
1211
1212        let err = result.unwrap_err();
1213        assert_eq!(
1214            err.to_string(),
1215            "Given version to migrate already exists as a managed version"
1216        );
1217        stdout.assert_empty();
1218    }
1219
1220    #[test]
1221    fn migrate_not_present_version() {
1222        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
1223        let args = MigrationArgs::new(tag_arg, "migration-source");
1224
1225        let ge_downloader = MockDownloader::new();
1226
1227        let mut fs_mng = MockFilesystemManager::new();
1228        fs_mng
1229            .expect_migrate_folder()
1230            .once()
1231            .returning(|_, _| Ok(ManagedVersion::new("6.20-GE-1", TagKind::Proton, "Proton-6.20-GE-1")));
1232
1233        let tmp_dir = TempDir::new().unwrap();
1234        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1235        setup_managed_versions(&json_path, vec![]);
1236
1237        let mut path_cfg = MockPathConfiguration::new();
1238        path_cfg
1239            .expect_managed_versions_config()
1240            .times(2)
1241            .returning(move |_| json_path.clone());
1242
1243        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1244
1245        let mut stdout = AssertLines::new();
1246        writer.migrate(&mut stdout, args).unwrap();
1247        stdout.assert_line(0, "Successfully migrated directory as 6.20-GE-1 (Proton)");
1248    }
1249
1250    #[test]
1251    fn migrate_fails_due_to_filesystem_error() {
1252        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
1253        let args = MigrationArgs::new(tag_arg, "migration-source");
1254
1255        let ge_downloader = MockDownloader::new();
1256
1257        let mut fs_mng = MockFilesystemManager::new();
1258        fs_mng
1259            .expect_migrate_folder()
1260            .once()
1261            .returning(|_, _| bail!("Mocked error"));
1262
1263        let tmp_dir = TempDir::new().unwrap();
1264        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1265        setup_managed_versions(&json_path, vec![]);
1266
1267        let mut path_cfg = MockPathConfiguration::new();
1268        path_cfg
1269            .expect_managed_versions_config()
1270            .once()
1271            .returning(move |_| json_path.clone());
1272
1273        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1274
1275        let mut stdout = AssertLines::new();
1276        let result = writer.migrate(&mut stdout, args);
1277        assert!(result.is_err());
1278
1279        let err = result.unwrap_err();
1280        assert_eq!(err.to_string(), "Could not migrate directory");
1281        stdout.assert_empty();
1282    }
1283
1284    #[test]
1285    fn apply_to_app_config_for_non_existent_version() {
1286        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
1287        let args = ApplyArgs::new(tag_arg);
1288
1289        let ge_downloader = MockDownloader::new();
1290        let fs_mng = MockFilesystemManager::new();
1291
1292        let tmp_dir = TempDir::new().unwrap();
1293        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1294        setup_managed_versions(&json_path, vec![]);
1295
1296        let mut path_cfg = MockPathConfiguration::new();
1297        path_cfg
1298            .expect_managed_versions_config()
1299            .once()
1300            .returning(move |_| json_path.clone());
1301
1302        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1303
1304        let mut stdout = AssertLines::new();
1305        let result = writer.apply_to_app_config(&mut stdout, args);
1306        assert!(result.is_err());
1307
1308        let err = result.unwrap_err();
1309        assert_eq!(err.to_string(), "Given version is not managed");
1310        stdout.assert_empty();
1311    }
1312
1313    #[test]
1314    fn apply_to_app_config_for_latest_version() {
1315        let tag_arg = TagArg::new(None, TagKind::Proton);
1316        let args = ApplyArgs::new(tag_arg);
1317
1318        let ge_downloader = MockDownloader::new();
1319        let mut fs_mng = MockFilesystemManager::new();
1320        fs_mng.expect_apply_to_app_config().once().returning(|_| Ok(()));
1321
1322        let tmp_dir = TempDir::new().unwrap();
1323        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1324        setup_managed_versions(
1325            &json_path,
1326            vec![ManagedVersion::new("6.20-GE-1", TagKind::Proton, "Proton-6.20-GE-1")],
1327        );
1328
1329        let mut path_cfg = MockPathConfiguration::new();
1330        path_cfg
1331            .expect_managed_versions_config()
1332            .once()
1333            .returning(move |_| json_path.clone());
1334
1335        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1336
1337        let mut stdout = AssertLines::new();
1338        writer.apply_to_app_config(&mut stdout, args).unwrap();
1339
1340        stdout.assert_line(0, "Modifying Steam configuration to use 6.20-GE-1 (Proton)");
1341        stdout.assert_line(1, PROTON_APPLY_HINT);
1342    }
1343
1344    #[test]
1345    fn apply_to_app_config_for_existent_version() {
1346        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
1347        let args = ApplyArgs::new(tag_arg);
1348
1349        let ge_downloader = MockDownloader::new();
1350        let mut fs_mng = MockFilesystemManager::new();
1351        fs_mng.expect_apply_to_app_config().once().returning(|_| Ok(()));
1352
1353        let tmp_dir = TempDir::new().unwrap();
1354        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1355        setup_managed_versions(
1356            &json_path,
1357            vec![ManagedVersion::new("6.20-GE-1", TagKind::Proton, "Proton-6.20-GE-1")],
1358        );
1359
1360        let mut path_cfg = MockPathConfiguration::new();
1361        path_cfg
1362            .expect_managed_versions_config()
1363            .once()
1364            .returning(move |_| json_path.clone());
1365
1366        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1367
1368        let mut stdout = AssertLines::new();
1369        writer.apply_to_app_config(&mut stdout, args).unwrap();
1370
1371        stdout.assert_line(0, "Modifying Steam configuration to use 6.20-GE-1 (Proton)");
1372        stdout.assert_line(1, PROTON_APPLY_HINT);
1373    }
1374
1375    #[test]
1376    fn apply_to_app_config_fails_with_an_error() {
1377        let tag_arg = TagArg::new(Some(Tag::from("6.20-GE-1")), TagKind::Proton);
1378        let args = ApplyArgs::new(tag_arg);
1379
1380        let ge_downloader = MockDownloader::new();
1381        let mut fs_mng = MockFilesystemManager::new();
1382        fs_mng
1383            .expect_apply_to_app_config()
1384            .once()
1385            .returning(|_| bail!("Mocked error"));
1386
1387        let tmp_dir = TempDir::new().unwrap();
1388        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1389        setup_managed_versions(
1390            &json_path,
1391            vec![ManagedVersion::new("6.20-GE-1", TagKind::Proton, "Proton-6.20-GE-1")],
1392        );
1393
1394        let mut path_cfg = MockPathConfiguration::new();
1395        path_cfg
1396            .expect_managed_versions_config()
1397            .once()
1398            .returning(move |_| json_path.clone());
1399
1400        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1401
1402        let mut stdout = AssertLines::new();
1403        let result = writer.apply_to_app_config(&mut stdout, args);
1404        assert!(result.is_err());
1405
1406        stdout.assert_line(0, "Modifying Steam configuration to use 6.20-GE-1 (Proton)");
1407    }
1408
1409    #[test]
1410    fn copy_user_settings_where_source_tag_does_not_exist() {
1411        let args = CopyUserSettingsArgs::new("6.20-GE-1", "6.21-GE-1");
1412        let ge_downloader = MockDownloader::new();
1413        let fs_mng = MockFilesystemManager::new();
1414
1415        let tmp_dir = TempDir::new().unwrap();
1416        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1417        setup_managed_versions(&json_path, vec![ManagedVersion::new("6.21-GE-1", TagKind::Proton, "")]);
1418
1419        let mut path_cfg = MockPathConfiguration::new();
1420        path_cfg
1421            .expect_managed_versions_config()
1422            .once()
1423            .returning(move |_| json_path.clone());
1424
1425        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1426
1427        let mut stdout = AssertLines::new();
1428        let result = writer.copy_user_settings(&mut stdout, args);
1429        assert!(result.is_err());
1430
1431        let err = result.unwrap_err();
1432        assert_eq!(err.to_string(), "Given source Proton version does not exist");
1433        stdout.assert_empty();
1434    }
1435
1436    #[test]
1437    fn copy_user_settings_where_destination_tag_does_not_exist() {
1438        let args = CopyUserSettingsArgs::new("6.20-GE-1", "6.21-GE-1");
1439        let ge_downloader = MockDownloader::new();
1440        let fs_mng = MockFilesystemManager::new();
1441
1442        let tmp_dir = TempDir::new().unwrap();
1443        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1444        setup_managed_versions(&json_path, vec![ManagedVersion::new("6.20-GE-1", TagKind::Proton, "")]);
1445
1446        let mut path_cfg = MockPathConfiguration::new();
1447        path_cfg
1448            .expect_managed_versions_config()
1449            .once()
1450            .returning(move |_| json_path.clone());
1451
1452        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1453
1454        let mut stdout = AssertLines::new();
1455        let result = writer.copy_user_settings(&mut stdout, args);
1456        assert!(result.is_err());
1457
1458        let err = result.unwrap_err();
1459        assert_eq!(err.to_string(), "Given destination Proton version does not exist");
1460        stdout.assert_empty();
1461    }
1462
1463    #[test]
1464    fn copy_user_settings_for_present_versions() {
1465        let args = CopyUserSettingsArgs::new("6.20-GE-1", "6.21-GE-1");
1466        let ge_downloader = MockDownloader::new();
1467
1468        let mut fs_mng = MockFilesystemManager::new();
1469        fs_mng.expect_copy_user_settings().once().returning(|_, _| Ok(()));
1470
1471        let tmp_dir = TempDir::new().unwrap();
1472        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1473        setup_managed_versions(
1474            &json_path,
1475            vec![
1476                ManagedVersion::new("6.20-GE-1", TagKind::Proton, ""),
1477                ManagedVersion::new("6.21-GE-1", TagKind::Proton, ""),
1478            ],
1479        );
1480
1481        let mut path_cfg = MockPathConfiguration::new();
1482        path_cfg
1483            .expect_managed_versions_config()
1484            .once()
1485            .returning(move |_| json_path.clone());
1486
1487        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1488
1489        let mut stdout = AssertLines::new();
1490        writer.copy_user_settings(&mut stdout, args).unwrap();
1491
1492        stdout.assert_line(
1493            0,
1494            "Copied user_settings.py from 6.20-GE-1 (Proton) to 6.21-GE-1 (Proton)",
1495        );
1496    }
1497
1498    #[test]
1499    fn copy_user_settings_fails_on_filesystem_operation() {
1500        let args = CopyUserSettingsArgs::new("6.20-GE-1", "6.21-GE-1");
1501        let ge_downloader = MockDownloader::new();
1502
1503        let mut fs_mng = MockFilesystemManager::new();
1504        fs_mng
1505            .expect_copy_user_settings()
1506            .once()
1507            .returning(|_, _| bail!("Mocked error"));
1508
1509        let tmp_dir = TempDir::new().unwrap();
1510        let json_path = tmp_dir.join("ge_man/managed_versions.json");
1511        setup_managed_versions(
1512            &json_path,
1513            vec![
1514                ManagedVersion::new("6.20-GE-1", TagKind::Proton, ""),
1515                ManagedVersion::new("6.21-GE-1", TagKind::Proton, ""),
1516            ],
1517        );
1518
1519        let mut path_cfg = MockPathConfiguration::new();
1520        path_cfg
1521            .expect_managed_versions_config()
1522            .once()
1523            .returning(move |_| json_path.clone());
1524
1525        let writer = TerminalWriter::new(&ge_downloader, &fs_mng, &path_cfg);
1526
1527        let mut stdout = AssertLines::new();
1528        let result = writer.copy_user_settings(&mut stdout, args);
1529        assert!(result.is_err());
1530
1531        let err = result.unwrap_err();
1532        assert_eq!(err.to_string(), "Mocked error");
1533        stdout.assert_empty();
1534    }
1535}