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
69pub 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(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}