127 lines
3.7 KiB
Rust
127 lines
3.7 KiB
Rust
use oneclient_core::dev;
|
|
use oneclient_core::game::download_to_path;
|
|
use oneclient_events::{GroupedProgressSession, TaskCategory};
|
|
use polyio::testing::ScratchDir as Scratch;
|
|
use polyio::{Sha1Stream, sha1_bytes, sha1_file};
|
|
|
|
/// A Minecraft asset object. Objects are content-addressed and immutable, so
|
|
/// this URL/hash pair stays valid.
|
|
const ASSET_SHA1: &str = "af96f55a90eaf11b327f1b5f8834a051027dc506";
|
|
const ASSET_URL: &str =
|
|
"https://resources.download.minecraft.net/af/af96f55a90eaf11b327f1b5f8834a051027dc506";
|
|
const ASSET_SIZE: u64 = 2063;
|
|
|
|
#[test]
|
|
fn sha1_stream_matches_one_shot_hash() {
|
|
let data: Vec<u8> = (0..70_000u32).map(|i| (i % 251) as u8).collect();
|
|
|
|
let mut hasher = Sha1Stream::new();
|
|
for chunk in data.chunks(4096) {
|
|
hasher.update(chunk);
|
|
}
|
|
|
|
assert_eq!(hasher.finish(), sha1_bytes(&data));
|
|
}
|
|
|
|
#[test]
|
|
fn sha1_stream_of_empty_input_matches() {
|
|
assert_eq!(Sha1Stream::new().finish(), sha1_bytes(&[]));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn sha1_file_matches_sha1_bytes() {
|
|
let dir = Scratch::new("sha1-file");
|
|
let path = dir.join("payload.bin");
|
|
|
|
// Larger than the hashing buffer, so the multi-read path is covered too.
|
|
let data: Vec<u8> = (0..600_000u32).map(|i| (i % 253) as u8).collect();
|
|
std::fs::write(&path, &data).unwrap();
|
|
|
|
assert_eq!(sha1_file(&path).await.unwrap(), sha1_bytes(&data));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn sha1_file_handles_empty_file() {
|
|
let dir = Scratch::new("sha1-empty");
|
|
let path = dir.join("empty.bin");
|
|
std::fs::write(&path, []).unwrap();
|
|
|
|
assert_eq!(sha1_file(&path).await.unwrap(), sha1_bytes(&[]));
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires network"]
|
|
async fn download_to_path_accepts_matching_hash() {
|
|
let services = dev::ephemeral_services().await.unwrap();
|
|
let progress = GroupedProgressSession::start(&services.events, "test");
|
|
let dir = Scratch::new("download-ok");
|
|
let dest = dir.join("nested").join("icon.png");
|
|
|
|
download_to_path(
|
|
&services.requester,
|
|
&services.events,
|
|
&progress,
|
|
"icon",
|
|
TaskCategory::Assets,
|
|
ASSET_SIZE,
|
|
ASSET_URL,
|
|
&dest,
|
|
Some(ASSET_SHA1),
|
|
)
|
|
.await
|
|
.expect("download should verify");
|
|
|
|
assert_eq!(std::fs::metadata(&dest).unwrap().len(), ASSET_SIZE);
|
|
assert_eq!(sha1_file(&dest).await.unwrap(), ASSET_SHA1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires network"]
|
|
async fn download_to_path_rejects_mismatched_hash_and_removes_file() {
|
|
let services = dev::ephemeral_services().await.unwrap();
|
|
let progress = GroupedProgressSession::start(&services.events, "test");
|
|
let dir = Scratch::new("download-bad");
|
|
let dest = dir.join("icon.png");
|
|
|
|
let err = download_to_path(
|
|
&services.requester,
|
|
&services.events,
|
|
&progress,
|
|
"icon",
|
|
TaskCategory::Assets,
|
|
ASSET_SIZE,
|
|
ASSET_URL,
|
|
&dest,
|
|
Some("0000000000000000000000000000000000000000"),
|
|
)
|
|
.await
|
|
.expect_err("mismatched hash should fail");
|
|
|
|
assert!(err.to_string().contains("0000000000000000"), "{err}");
|
|
assert!(!dest.exists(), "corrupt download should be removed");
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires network"]
|
|
async fn download_to_path_without_expected_hash_still_writes() {
|
|
let services = dev::ephemeral_services().await.unwrap();
|
|
let progress = GroupedProgressSession::start(&services.events, "test");
|
|
let dir = Scratch::new("download-nohash");
|
|
let dest = dir.join("icon.png");
|
|
|
|
download_to_path(
|
|
&services.requester,
|
|
&services.events,
|
|
&progress,
|
|
"icon",
|
|
TaskCategory::Assets,
|
|
0,
|
|
ASSET_URL,
|
|
&dest,
|
|
None,
|
|
)
|
|
.await
|
|
.expect("download should succeed");
|
|
|
|
assert_eq!(sha1_file(&dest).await.unwrap(), ASSET_SHA1);
|
|
}
|