1use std::fmt::Display;
7use std::io::Read;
8
9use lazy_static::lazy_static;
10use reqwest::blocking::Response;
11
12use crate::download::github::{GithubDownload, GithubDownloader};
13use crate::download::response::{
14 CompatibilityToolTag, DownloadedArchive, DownloadedAssets, DownloadedChecksum, GeAsset, GeRelease,
15};
16use crate::error::GithubError;
17use crate::tag::{Tag, TagKind, WineTagKind};
18
19mod github;
20
21pub mod response;
22
23#[cfg(test)]
24mod mime {
25 pub const APPLICATION_GZIP: &str = "application/gzip";
26 pub const APPLICATION_OCTET_STREAM: &str = "application/octet-stream";
27 pub const BINARY_OCTET_STREAM: &str = "binary/octet-stream";
28}
29
30const GITHUB_API_URL: &str = "https://api.github.com";
31const PROTON_GE_RELEASE_LATEST_URL: &str = "repos/GloriousEggroll/proton-ge-custom/releases/latest";
32const PROTON_GE_RELEASE_TAGS_URL: &str = "repos/GloriousEggroll/proton-ge-custom/releases/tags";
33const WINE_GE_RELEASE_TAGS_URL: &str = "repos/GloriousEggroll/wine-ge-custom/releases/tags";
34const WINE_GE_TAGS_URL: &str = "repos/GloriousEggroll/wine-ge-custom/tags";
35
36lazy_static! {
37 static ref GITHUB_PROTON_GE_LATEST_URL: String = format!("{}/{}", GITHUB_API_URL, PROTON_GE_RELEASE_LATEST_URL);
38 static ref GITHUB_PROTON_GE_TAG_URL: String = format!("{}/{}", GITHUB_API_URL, PROTON_GE_RELEASE_TAGS_URL);
39 static ref GITHUB_WINE_GE_RELEASE_TAG_URL: String = format!("{}/{}", GITHUB_API_URL, WINE_GE_RELEASE_TAGS_URL);
40 static ref GITHUB_WINE_GE_TAGS_URL: String = format!("{}/{}", GITHUB_API_URL, WINE_GE_TAGS_URL);
41}
42
43pub trait ReadProgressWrapper {
49 fn init(self: Box<Self>, len: u64, asset: &GeAsset) -> Box<dyn ReadProgressWrapper>;
66 fn wrap(&self, reader: Box<dyn Read>) -> Box<dyn Read>;
68 fn finish(&self, release: &GeAsset);
70}
71
72pub struct DownloadRequest {
77 pub tag: Option<String>,
79 pub kind: TagKind,
81 pub progress_wrapper: Box<dyn ReadProgressWrapper>,
83 pub download_checksum: bool,
85}
86
87impl DownloadRequest {
88 pub fn new(
89 tag: Option<String>,
90 kind: TagKind,
91 progress_wrapper: Box<dyn ReadProgressWrapper>,
92 download_checksum: bool,
93 ) -> Self {
94 DownloadRequest {
95 tag,
96 kind,
97 progress_wrapper,
98 download_checksum,
99 }
100 }
101}
102
103pub trait GeDownload {
107 fn fetch_release(&self, tag: Option<String>, kind: TagKind) -> Result<GeRelease, GithubError>;
108 fn download_release_assets(&self, request: DownloadRequest) -> Result<DownloadedAssets, GithubError>;
109}
110
111pub struct GeDownloader {
113 github_downloader: Box<dyn GithubDownload>,
114}
115
116impl GeDownloader {
117 pub fn new(github_downloader: Box<dyn GithubDownload>) -> Self {
118 GeDownloader { github_downloader }
119 }
120
121 fn create_url<S: AsRef<str>>(&self, tag: Option<S>, kind: &TagKind) -> Result<String, GithubError>
122 where
123 S: AsRef<str> + Display,
124 {
125 let tag = tag.as_ref();
126 match kind {
127 TagKind::Proton => {
128 if let Some(t) = tag {
129 Ok(format!("{}/{}", *GITHUB_PROTON_GE_TAG_URL, t))
130 } else {
131 Ok(String::from(&*GITHUB_PROTON_GE_LATEST_URL))
132 }
133 }
134 TagKind::Wine { kind: wine_kind } => {
135 if let Some(t) = tag {
136 Ok(format!("{}/{}", *GITHUB_WINE_GE_RELEASE_TAG_URL, t))
137 } else {
138 self.find_latest_wine_ge_release_tag(wine_kind)
139 .map(|t| format!("{}/{}", *GITHUB_WINE_GE_RELEASE_TAG_URL, t))
140 }
141 }
142 }
143 }
144
145 fn create_wine_ge_tags_url(&self, page: u8) -> String {
146 format!("{}?page={}", *GITHUB_WINE_GE_TAGS_URL, page)
147 }
148
149 fn find_latest_wine_ge_release_tag(&self, kind: &WineTagKind) -> Result<Tag, GithubError> {
150 let mut page = 1;
151 loop {
152 let mut tag_names: Vec<String> = self
153 .fetch_wine_ge_tags(page)?
154 .json::<Vec<CompatibilityToolTag>>()?
155 .into_iter()
156 .map(Into::into)
157 .collect();
158
159 if tag_names.is_empty() {
160 return Err(GithubError::NoTags);
161 }
162
163 if let WineTagKind::LolWineGe = kind {
164 tag_names.retain(|t| t.contains("LoL"));
165 } else {
166 tag_names.retain(|t| !t.contains("LoL"));
167 }
168
169 let latest_tag = tag_names.into_iter().map(|t| Tag::from(t)).max_by(Tag::cmp);
170 if let Some(t) = latest_tag {
171 return Ok(t);
172 }
173 page += 1
174 }
175 }
176
177 fn fetch_wine_ge_tags(&self, page: u8) -> Result<Response, GithubError> {
178 let url = self.create_wine_ge_tags_url(page);
179 self.github_downloader.download_from_url(&url)
180 }
181
182 fn download_archive(
183 &self,
184 progress_wrapper: Box<dyn ReadProgressWrapper>,
185 asset: &GeAsset,
186 ) -> Result<DownloadedArchive, GithubError> {
187 let response = self.github_downloader.download_from_url(&asset.browser_download_url)?;
188
189 let tar_size: u64 = response.content_length().unwrap();
190 let mut compressed_archive: Vec<u8> = Vec::with_capacity(tar_size as usize);
191
192 let progress_wrapper = progress_wrapper.init(tar_size, asset);
193 progress_wrapper
194 .wrap(Box::new(response))
195 .read_to_end(&mut compressed_archive)
196 .unwrap();
197 progress_wrapper.finish(asset);
198
199 Ok(DownloadedArchive::new(compressed_archive, String::from(&asset.name)))
200 }
201
202 fn download_checksum(&self, asset: &GeAsset) -> Result<DownloadedChecksum, GithubError> {
203 let mut response = self.github_downloader.download_from_url(&asset.browser_download_url)?;
204
205 let file_size = response.content_length().unwrap();
206 let mut checksum_str = String::with_capacity(file_size as usize);
207 response.read_to_string(&mut checksum_str).unwrap();
208
209 Ok(DownloadedChecksum::new(checksum_str, String::from(&asset.name)))
210 }
211}
212
213impl GeDownload for GeDownloader {
214 fn fetch_release(&self, tag: Option<String>, kind: TagKind) -> Result<GeRelease, GithubError> {
235 let tag = tag.as_ref();
236 let url = self.create_url(tag, &kind)?;
237 self.github_downloader.download_from_url(&url).and_then(|response| {
238 response
239 .json::<GeRelease>()
240 .map_err(|err| GithubError::ReqwestError { source: err })
241 })
242 }
243
244 fn download_release_assets(&self, request: DownloadRequest) -> Result<DownloadedAssets, GithubError> {
255 let DownloadRequest {
256 tag,
257 kind,
258 progress_wrapper,
259 download_checksum: skip_checksum,
260 } = request;
261
262 let release = self.fetch_release(tag, kind)?;
263 if release.assets.is_empty() {
264 return Err(GithubError::ReleaseHasNoAssets {
265 tag: release.tag_name,
266 kind,
267 });
268 }
269
270 let downloaded_checksum = match skip_checksum {
271 false => Some(self.download_checksum(release.checksum_asset())?),
272 true => None,
273 };
274
275 let downloaded_archive = self.download_archive(progress_wrapper, release.tar_asset())?;
276
277 Ok(DownloadedAssets::new(
278 release.tag_name,
279 downloaded_archive,
280 downloaded_checksum,
281 ))
282 }
283}
284
285impl Default for GeDownloader {
286 fn default() -> Self {
287 let github_downloader = Box::new(GithubDownloader::new());
288 GeDownloader::new(github_downloader)
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use crate::download::mime::{APPLICATION_GZIP, APPLICATION_OCTET_STREAM};
295 use httpmock::Method::GET;
296 use httpmock::MockServer;
297 use mockall::mock;
298 use reqwest::blocking::Response;
299
300 use super::*;
301
302 lazy_static! {
303 static ref RELEASES: &'static str = "test_resources/responses/releases";
304 static ref ASSETS: &'static str = "test_resources/assets";
305 static ref TAGS: &'static str = "test_resources/responses/tags";
306 pub static ref PROTON_GE: String = format!("{}/proton-ge-release.json", *RELEASES);
307 pub static ref WINE_GE: String = format!("{}/wine-ge-release.json", *RELEASES);
308 pub static ref WINE_GE_LOL: String = format!("{}/wine-ge-lol-release.json", *RELEASES);
309 pub static ref NO_TAGS: String = format!("{}/empty.json", *TAGS);
310 pub static ref WINE_GE_TAGS: String = format!("{}/wine_ge.json", *TAGS);
311 pub static ref WINE_GE_LOL_TAGS: String = format!("{}/wine_ge_lol.json", *TAGS);
312 pub static ref TEST_TAR_GZ: String = format!("{}/{}", *ASSETS, "test.tar.gz");
313 pub static ref TEST_SHA512SUM: String = format!("{}/{}", *ASSETS, "test-gz.sha512sum");
314 }
315
316 mock! {
317 ProgressWrapper {}
318 impl ReadProgressWrapper for ProgressWrapper {
319 fn init(self: Box<Self>, len: u64, asset: &GeAsset) -> Box<dyn ReadProgressWrapper>;
320 fn wrap(&self, reader: Box<dyn Read>) -> Box<dyn Read>;
321 fn finish(&self, release: &GeAsset);
322 }
323 }
324
325 pub fn download_url(server: Option<&str>, tag: &str, kind: &TagKind, file_name: &str) -> String {
326 let url = download_url_without_server(tag, kind, file_name);
327
328 if let Some(host) = server {
329 format!("{}{}", host, url)
330 } else {
331 format!("SERVER{}", url)
332 }
333 }
334
335 pub fn download_url_without_server(tag: &str, kind: &TagKind, file_name: &str) -> String {
336 let repo = match kind {
337 TagKind::Proton => "proton",
338 TagKind::Wine { .. } => "wine",
339 };
340 format!(
341 "/GloriousEggroll/{}-ge-custom/releases/download/{}/{}",
342 repo, tag, file_name
343 )
344 }
345
346 pub fn mock_url(kind: &TagKind, server: &str) -> String {
347 let file_path = match kind {
348 TagKind::Proton => &*PROTON_GE,
349 TagKind::Wine { kind: wine_kind } => match wine_kind {
350 WineTagKind::WineGe => &*WINE_GE,
351 WineTagKind::LolWineGe => &*WINE_GE_LOL,
352 },
353 };
354 std::fs::read_to_string(file_path).unwrap().replace("SERVER", server)
355 }
356
357 struct FetchSpecifiedReleaseTestData {
358 given_tag: Option<String>,
359 expected_tag: String,
360 kind: TagKind,
361 github_resource: String,
362 body_content_file: String,
363 gzip_name: String,
364 gzip_download_url: String,
365 checksum_name: String,
366 checksum_download_url: String,
367 }
368
369 impl FetchSpecifiedReleaseTestData {
370 pub fn new(
371 tag: Option<&str>,
372 expected_tag: &str,
373 kind: TagKind,
374 gzip_name: &str,
375 checksum_name: &str,
376 github_resource: &str,
377 body_content_file: &str,
378 ) -> Self {
379 let github_resource = if tag.is_some() {
380 format!("{}/{}", github_resource, tag.unwrap())
381 } else {
382 String::from(github_resource)
383 };
384 let tag = tag.map(String::from);
385
386 let gzip_download_url = download_url(None, expected_tag, &kind, gzip_name);
387 let checksum_download_url = download_url(None, expected_tag, &kind, checksum_name);
388
389 let expected_tag = String::from(expected_tag);
390 let body_content_file = String::from(body_content_file);
391 let gzip_name = String::from(gzip_name);
392 let checksum_name = String::from(checksum_name);
393 FetchSpecifiedReleaseTestData {
394 given_tag: tag,
395 expected_tag,
396 kind,
397 github_resource,
398 body_content_file,
399 gzip_name,
400 gzip_download_url,
401 checksum_name,
402 checksum_download_url,
403 }
404 }
405 }
406
407 struct FetchLatestReleaseTestData {
408 tags_body_from_file: String,
409 github_tags_resource: String,
410 expected_specific_release: FetchSpecifiedReleaseTestData,
411 }
412
413 impl FetchLatestReleaseTestData {
414 pub fn new<S: Into<String>>(
415 tags_body_from_file: S,
416 github_tags_resource: S,
417 mut expected_specific_release: FetchSpecifiedReleaseTestData,
418 ) -> Self {
419 let tags_body_from_file = tags_body_from_file.into();
420 let github_tags_resource = github_tags_resource.into();
421 expected_specific_release.github_resource = format!(
422 "{}/{}",
423 expected_specific_release.github_resource, expected_specific_release.expected_tag
424 );
425
426 FetchLatestReleaseTestData {
427 tags_body_from_file,
428 github_tags_resource,
429 expected_specific_release,
430 }
431 }
432 }
433
434 struct FetchReleaseContentTestData {
435 expected_tag: String,
436 kind: TagKind,
437 compressed_tar_file_name: String,
438 checksum_file_name: String,
439 release_url: String,
440 }
441
442 impl FetchReleaseContentTestData {
443 pub fn new<S: Into<String>>(
444 tag: S,
445 kind: &TagKind,
446 compressed_tar_file_name: S,
447 checksum_file_name: S,
448 release_url: S,
449 ) -> Self {
450 let expected_tag = tag.into();
451 let kind = kind.clone();
452 let compressed_tar_file_name = compressed_tar_file_name.into();
453 let checksum_file_name = checksum_file_name.into();
454 let release_url = release_url.into();
455
456 FetchReleaseContentTestData {
457 expected_tag,
458 kind,
459 compressed_tar_file_name,
460 checksum_file_name,
461 release_url,
462 }
463 }
464 }
465
466 struct MockGithubDownloader {
467 host: String,
468 }
469
470 impl MockGithubDownloader {
471 pub fn new<S: Into<String>>(host: S) -> Self {
472 MockGithubDownloader { host: host.into() }
473 }
474 }
475
476 impl GithubDownload for MockGithubDownloader {
477 fn download_from_url(&self, url: &str) -> Result<Response, GithubError> {
478 let find_index = match url.find("repos") {
479 Some(i) => i,
480 None => url.find("G").unwrap(),
481 };
482
483 let target = url.split_at(find_index).1;
484 let mocked_url = format!("{}/{}", self.host, target);
485
486 match reqwest::blocking::get(&mocked_url) {
487 Ok(resp) => Ok(resp),
488 Err(err) => panic!("Get request failed during integration test: {:?}", err),
489 }
490 }
491 }
492
493 fn fetch_release_test(test_data: FetchSpecifiedReleaseTestData) {
494 let server = MockServer::start();
495
496 let mock = server.mock(|when, then| {
497 when.method(GET).path(format!("/{}", &test_data.github_resource));
498 then.status(200)
499 .header("Content-Type", "application/json")
500 .body_from_file(&test_data.body_content_file);
501 });
502
503 let github_downloader = Box::new(MockGithubDownloader::new(String::from(server.base_url())));
504 let tool_downloader = GeDownloader::new(github_downloader);
505
506 let release = tool_downloader
507 .fetch_release(test_data.given_tag, test_data.kind)
508 .unwrap();
509 let gzip = release.tar_asset();
510 let checksum = release.checksum_asset();
511 assert_eq!(release.tag_name, test_data.expected_tag);
512 assert_eq!(gzip.name, test_data.gzip_name);
513 assert_eq!(gzip.browser_download_url, test_data.gzip_download_url);
514 assert_eq!(gzip.content_type, APPLICATION_GZIP);
515 assert_eq!(checksum.name, test_data.checksum_name);
516 assert_eq!(checksum.browser_download_url, test_data.checksum_download_url);
517 assert_eq!(checksum.content_type, APPLICATION_OCTET_STREAM);
518
519 mock.assert();
520 }
521
522 fn fetch_latest_wine_or_lol_release_test(test_data: FetchLatestReleaseTestData) {
523 let server = MockServer::start();
524 let FetchLatestReleaseTestData {
525 expected_specific_release: test_data,
526 tags_body_from_file,
527 github_tags_resource,
528 } = test_data;
529
530 let tags_mock = server.mock(|when, then| {
531 when.method(GET)
532 .path(format!("/{}", github_tags_resource))
533 .query_param("page", "1");
534 then.status(200)
535 .header("Content-Type", "application/json")
536 .body_from_file(tags_body_from_file);
537 });
538
539 let release_mock = server.mock(|when, then| {
540 when.method(GET).path(format!("/{}", &test_data.github_resource));
541 then.status(200)
542 .header("Content-Type", "application/json")
543 .body_from_file(&test_data.body_content_file);
544 });
545
546 let github_downloader = Box::new(MockGithubDownloader::new(String::from(server.base_url())));
547 let tool_downloader = GeDownloader::new(github_downloader);
548
549 let release = tool_downloader
550 .fetch_release(test_data.given_tag, test_data.kind)
551 .unwrap();
552 let gzip = release.tar_asset();
553 let checksum = release.checksum_asset();
554
555 release_mock.assert();
556 tags_mock.assert();
557
558 assert_eq!(release.tag_name, test_data.expected_tag);
559 assert_eq!(gzip.name, test_data.gzip_name);
560 assert_eq!(gzip.browser_download_url, test_data.gzip_download_url);
561 assert_eq!(gzip.content_type, APPLICATION_GZIP);
562 assert_eq!(checksum.name, test_data.checksum_name);
563 assert_eq!(checksum.browser_download_url, test_data.checksum_download_url);
564 assert_eq!(checksum.content_type, APPLICATION_OCTET_STREAM);
565 }
566
567 #[test]
568 fn fetch_proton_ge_release() {
569 let expected_tag = "6.20-GE-1";
570 let given_tag = Some(expected_tag);
571 let gzip = "Proton-6.20-GE-1.tar.gz";
572 let checksum = "Proton-6.20-GE-1.sha512sum";
573 fetch_release_test(FetchSpecifiedReleaseTestData::new(
574 given_tag,
575 expected_tag,
576 TagKind::Proton,
577 gzip,
578 checksum,
579 PROTON_GE_RELEASE_TAGS_URL,
580 &*PROTON_GE,
581 ));
582 }
583
584 #[test]
585 fn fetch_wine_ge_release() {
586 let expected_tag = "6.20-GE-1";
587 let given_tag = Some(expected_tag);
588 let gzip = "wine-lutris-ge-6.20-1-x86_64.tar.gz";
589 let checksum = "wine-lutris-ge-6.20-1-x86_64.sha512sum";
590 fetch_release_test(FetchSpecifiedReleaseTestData::new(
591 given_tag,
592 expected_tag,
593 TagKind::wine(),
594 gzip,
595 checksum,
596 WINE_GE_RELEASE_TAGS_URL,
597 &*WINE_GE,
598 ));
599 }
600
601 #[test]
602 fn fetch_wine_lol_ge_release() {
603 let expected_tag = "6.16-GE-3-LoL";
604 let given_tag = Some(expected_tag);
605 let gzip = "wine-lutris-ge-6.16-3-lol-x86_64.tar.gz";
606 let checksum = "wine-lutris-ge-6.16-3-lol-x86_64.sha512sum";
607 fetch_release_test(FetchSpecifiedReleaseTestData::new(
608 given_tag,
609 expected_tag,
610 TagKind::lol(),
611 gzip,
612 checksum,
613 WINE_GE_RELEASE_TAGS_URL,
614 &*WINE_GE_LOL,
615 ));
616 }
617
618 #[test]
619 fn fetch_latest_proton_release() {
620 let expected_tag = "6.20-GE-1";
621 let given_tag = None;
622 let gzip = "Proton-6.20-GE-1.tar.gz";
623 let checksum = "Proton-6.20-GE-1.sha512sum";
624 fetch_release_test(FetchSpecifiedReleaseTestData::new(
625 given_tag,
626 expected_tag,
627 TagKind::Proton,
628 gzip,
629 checksum,
630 PROTON_GE_RELEASE_LATEST_URL,
631 &*PROTON_GE,
632 ));
633 }
634
635 #[test]
636 fn fetch_latest_wine_ge_release() {
637 let expected_tag = "6.20-GE-1";
638 let given_tag = None;
639 let gzip = "wine-lutris-ge-6.20-1-x86_64.tar.gz";
640 let checksum = "wine-lutris-ge-6.20-1-x86_64.sha512sum";
641 let expected_data = FetchSpecifiedReleaseTestData::new(
642 given_tag,
643 expected_tag,
644 TagKind::wine(),
645 gzip,
646 checksum,
647 WINE_GE_RELEASE_TAGS_URL,
648 &*WINE_GE,
649 );
650
651 fetch_latest_wine_or_lol_release_test(FetchLatestReleaseTestData::new(
652 &*WINE_GE_TAGS,
653 &WINE_GE_TAGS_URL.to_owned(),
654 expected_data,
655 ));
656 }
657
658 #[test]
659 fn fetch_latest_wine_ge_lol_release() {
660 let expected_tag = "6.16-GE-3-LoL";
661 let given_tag = None;
662 let gzip = "wine-lutris-ge-6.16-3-lol-x86_64.tar.gz";
663 let checksum = "wine-lutris-ge-6.16-3-lol-x86_64.sha512sum";
664 let expected_data = FetchSpecifiedReleaseTestData::new(
665 given_tag,
666 expected_tag,
667 TagKind::lol(),
668 gzip,
669 checksum,
670 WINE_GE_RELEASE_TAGS_URL,
671 &*WINE_GE_LOL,
672 );
673 fetch_latest_wine_or_lol_release_test(FetchLatestReleaseTestData::new(
674 &*WINE_GE_LOL_TAGS,
675 &WINE_GE_TAGS_URL.to_owned(),
676 expected_data,
677 ));
678 }
679
680 #[test]
681 fn fetch_latest_wine_ge_lol_version_from_second_tags_page() {
682 let tag = "6.16-GE-3-LoL";
683 let kind = TagKind::lol();
684 let gzip_file_name = "wine-lutris-ge-6.16-3-lol-x86_64.tar.gz";
685 let checksum_file_name = "wine-lutris-ge-6.16-3-lol-x86_64.sha512sum";
686
687 let server = MockServer::start();
688
689 let first_page_tags = server.mock(|when, then| {
690 when.method(GET)
691 .path(format!("/{}", WINE_GE_TAGS_URL))
692 .query_param("page", "1");
693 then.status(200)
694 .header("Content-Type", "application/json")
695 .body_from_file(&*WINE_GE_TAGS);
696 });
697
698 let second_page_tags = server.mock(|when, then| {
699 when.method(GET)
700 .path(format!("/{}", WINE_GE_TAGS_URL))
701 .query_param("page", "2");
702 then.status(200)
703 .header("Content-Type", "application/json")
704 .body_from_file(&*WINE_GE_LOL_TAGS);
705 });
706
707 let release_mock = server.mock(|when, then| {
708 when.method(GET).path(format!("/{}/{}", WINE_GE_RELEASE_TAGS_URL, tag));
709 then.status(200)
710 .header("Content-Type", "application/json")
711 .body(mock_url(&kind, &server.base_url()));
712 });
713
714 let github_downloader = Box::new(MockGithubDownloader::new(String::from(server.base_url())));
715 let tool_downloader = GeDownloader::new(github_downloader);
716
717 let release = tool_downloader.fetch_release(None, TagKind::lol()).unwrap();
718 let gzip = release.tar_asset();
719 let checksum = release.checksum_asset();
720
721 first_page_tags.assert();
722 second_page_tags.assert();
723 release_mock.assert();
724
725 assert_eq!(release.tag_name, tag);
726 assert_eq!(gzip.name, gzip_file_name);
727 assert_eq!(
728 gzip.browser_download_url,
729 download_url(Some(&server.base_url()), tag, &kind, gzip_file_name)
730 );
731 assert_eq!(gzip.content_type, APPLICATION_GZIP);
732 assert_eq!(checksum.name, checksum_file_name);
733 assert_eq!(
734 checksum.browser_download_url,
735 download_url(Some(&server.base_url()), tag, &kind, checksum_file_name)
736 );
737 assert_eq!(checksum.content_type, APPLICATION_OCTET_STREAM);
738 }
739
740 #[test]
741 fn fetch_latest_version_but_could_not_find_latest_tag() {
742 let server = MockServer::start();
743
744 let first_page_tags = server.mock(|when, then| {
745 when.method(GET)
746 .path(format!("/{}", WINE_GE_TAGS_URL))
747 .query_param("page", "1");
748 then.status(200)
749 .header("Content-Type", "application/json")
750 .body_from_file(&*WINE_GE_TAGS);
751 });
752
753 let second_page_tags = server.mock(|when, then| {
754 when.method(GET)
755 .path(format!("/{}", WINE_GE_TAGS_URL))
756 .query_param("page", "2");
757 then.status(200)
758 .header("Content-Type", "application/json")
759 .body_from_file(&*NO_TAGS);
760 });
761
762 let github_downloader = Box::new(MockGithubDownloader::new(String::from(server.base_url())));
763 let tool_downloader = GeDownloader::new(github_downloader);
764
765 let release = tool_downloader.fetch_release(None, TagKind::lol());
766 assert!(release.is_err());
767 let err = release.err().unwrap();
768 assert!(
769 matches!(err, GithubError::NoTags),
770 "Result contains unexpected error: {:?}",
771 err
772 );
773
774 first_page_tags.assert();
775 second_page_tags.assert();
776 }
777
778 fn fetch_release_content_test(test_data: FetchReleaseContentTestData) {
779 let FetchReleaseContentTestData {
780 expected_tag,
781 kind,
782 compressed_tar_file_name: gzip_file_name,
783 checksum_file_name,
784 release_url,
785 } = test_data;
786
787 let server = MockServer::start();
788
789 let release_mock = server.mock(|when, then| {
790 when.method(GET).path(format!("/{}/{}", release_url, expected_tag));
791 then.status(200)
792 .header("Content-Type", "application/json")
793 .body(mock_url(&kind, &server.base_url()));
794 });
795
796 let gzip_asset = server.mock(|when, then| {
797 when.method(GET)
798 .path(download_url_without_server(&expected_tag, &kind, &gzip_file_name));
799 then.status(200)
800 .header("Content-Type", "application/gzip")
801 .body_from_file(&*TEST_TAR_GZ);
802 });
803
804 let checksum_asset = server.mock(|when, then| {
805 when.method(GET)
806 .path(download_url_without_server(&expected_tag, &kind, &checksum_file_name));
807 then.status(200)
808 .header("Content-Type", "application/octet-stream")
809 .body_from_file(&*TEST_SHA512SUM);
810 });
811
812 let mut progress_wrapper = MockProgressWrapper::new();
815 progress_wrapper.expect_finish().never();
816 progress_wrapper.expect_wrap().never();
817 progress_wrapper.expect_init().once().returning(|_, _| {
818 let mut initialized_prog_wrapper = MockProgressWrapper::new();
819 initialized_prog_wrapper.expect_init().never();
820 initialized_prog_wrapper.expect_wrap().once().returning(|reader| reader);
821 initialized_prog_wrapper.expect_finish().once().returning(|_| ());
822
823 Box::new(initialized_prog_wrapper)
824 });
825
826 let github_downloader = Box::new(MockGithubDownloader::new(String::from(server.base_url())));
827 let tool_downloader = GeDownloader::new(github_downloader);
828
829 let request = DownloadRequest::new(Some(expected_tag), kind, Box::new(progress_wrapper), false);
830 let fetched_assets = tool_downloader.download_release_assets(request).unwrap();
831
832 release_mock.assert();
833 gzip_asset.assert();
834 checksum_asset.assert();
835
836 let expected_gzip_content = std::fs::read(&*TEST_TAR_GZ).unwrap();
837 let expected_checksum_content = std::fs::read_to_string(&*TEST_SHA512SUM).unwrap();
838
839 let downloaded_tar = fetched_assets.compressed_archive;
840 let downloaded_checksum = fetched_assets.checksum.unwrap();
841
842 assert_eq!(downloaded_tar.compressed_content, expected_gzip_content);
843 assert_eq!(downloaded_tar.file_name, gzip_file_name);
844 assert_eq!(downloaded_checksum.checksum, expected_checksum_content);
845 assert_eq!(downloaded_checksum.file_name, checksum_file_name);
846 }
847
848 #[test]
849 fn fetch_release_content_should_download_data_for_wine_ge() {
850 let expected_tag = "6.16-GE-3-LoL";
851 let kind = TagKind::lol();
852 let gzip_file_name = "wine-lutris-ge-6.16-3-lol-x86_64.tar.gz";
853 let checksum_file_name = "wine-lutris-ge-6.16-3-lol-x86_64.sha512sum";
854
855 let data = FetchReleaseContentTestData::new(
856 expected_tag,
857 &kind,
858 gzip_file_name,
859 checksum_file_name,
860 WINE_GE_RELEASE_TAGS_URL,
861 );
862 fetch_release_content_test(data);
863 }
864
865 #[test]
866 fn fetch_release_content_should_download_data_for_proton_ge() {
867 let expected_tag = "6.20-GE-1";
868 let kind = TagKind::Proton;
869 let gzip_file_name = "Proton-6.20-GE-1.tar.gz";
870 let checksum_file_name = "Proton-6.20-GE-1.sha512sum";
871
872 let data = FetchReleaseContentTestData::new(
873 expected_tag,
874 &kind,
875 gzip_file_name,
876 checksum_file_name,
877 PROTON_GE_RELEASE_TAGS_URL,
878 );
879 fetch_release_content_test(data);
880 }
881}