use super::*;
use crate::binstall_metadata;
use rstest::rstest;
use std::fs;
use std::io::Read;
struct PackagingFixture {
temp: tempfile::TempDir,
binary_path: std::path::PathBuf,
}
fn packaging_fixture(target: &str, content: &[u8]) -> PackagingFixture {
let temp = tempfile::tempdir().expect("temp dir");
let bin_name = binary_filename(&TargetTriple::try_from(target).expect("valid target"));
let binary_path = temp.path().join(bin_name);
fs::write(&binary_path, content).expect("write fake binary");
PackagingFixture { temp, binary_path }
}
fn params_from_fixture(
fixture: &PackagingFixture,
version: &str,
target: &str,
) -> InstallerPackageParams {
InstallerPackageParams {
version: Version::new(version),
target: TargetTriple::try_from(target).expect("valid target"),
binary_path: fixture.binary_path.clone(),
output_dir: fixture.temp.path().to_path_buf(),
}
}
fn read_tgz_entry_paths(archive_path: &std::path::Path) -> Vec<String> {
let file = fs::File::open(archive_path).expect("open archive");
let gz = flate2::read::GzDecoder::new(file);
let mut tar_archive = tar::Archive::new(gz);
tar_archive
.entries()
.expect("entries")
.map(|e| {
let entry = e.expect("valid tar entry");
entry
.path()
.expect("valid entry path")
.to_string_lossy()
.into_owned()
})
.collect()
}
fn read_zip_entry_names(archive_path: &std::path::Path) -> Vec<String> {
let file = fs::File::open(archive_path).expect("open archive");
let zip_archive = zip::ZipArchive::new(file).expect("open zip");
(0..zip_archive.len())
.map(|i| {
zip_archive
.name_for_index(i)
.expect("valid zip entry name")
.to_owned()
})
.collect()
}
#[rstest]
#[case::linux_x86("x86_64-unknown-linux-gnu")]
#[case::linux_arm("aarch64-unknown-linux-gnu")]
#[case::macos_x86("x86_64-apple-darwin")]
#[case::macos_arm("aarch64-apple-darwin")]
fn archive_filename_tgz_for_non_windows(#[case] target: &str) {
let v = Version::new("0.2.1");
let t = TargetTriple::try_from(target).expect("valid target");
let name = archive_filename(&v, &t);
assert!(name.ends_with(".tgz"), "expected .tgz suffix, got {name}");
assert!(name.contains(target), "expected target in name, got {name}");
assert!(
name.contains("v0.2.1"),
"expected version in name, got {name}"
);
}
#[test]
fn archive_filename_zip_for_windows() {
let v = Version::new("0.2.1");
let t = TargetTriple::try_from("x86_64-pc-windows-msvc").expect("valid target");
let name = archive_filename(&v, &t);
assert_eq!(name, "whitaker-installer-x86_64-pc-windows-msvc-v0.2.1.zip");
}
#[rstest]
#[case::linux(
"x86_64-unknown-linux-gnu",
"whitaker-installer-x86_64-unknown-linux-gnu-v0.2.1"
)]
#[case::macos(
"aarch64-apple-darwin",
"whitaker-installer-aarch64-apple-darwin-v0.2.1"
)]
#[case::windows(
"x86_64-pc-windows-msvc",
"whitaker-installer-x86_64-pc-windows-msvc-v0.2.1"
)]
fn inner_dir_name_matches_expected(#[case] target: &str, #[case] expected: &str) {
assert_eq!(
inner_dir_name(
&Version::new("0.2.1"),
&TargetTriple::try_from(target).expect("valid target")
),
expected
);
}
#[rstest]
#[case::linux("x86_64-unknown-linux-gnu")]
#[case::macos("aarch64-apple-darwin")]
fn binary_filename_unix(#[case] target: &str) {
assert_eq!(
binary_filename(&TargetTriple::try_from(target).expect("valid target")),
"whitaker-installer"
);
}
#[test]
fn binary_filename_windows() {
assert_eq!(
binary_filename(&TargetTriple::try_from("x86_64-pc-windows-msvc").expect("valid target")),
"whitaker-installer.exe"
);
}
#[rstest]
#[case::linux_x86("x86_64-unknown-linux-gnu")]
#[case::linux_arm("aarch64-unknown-linux-gnu")]
#[case::macos_x86("x86_64-apple-darwin")]
#[case::macos_arm("aarch64-apple-darwin")]
fn archive_format_tgz_for_non_windows(#[case] target: &str) {
assert_eq!(
archive_format(&TargetTriple::try_from(target).expect("valid target")),
ArchiveFormat::Tgz
);
}
#[test]
fn archive_format_zip_for_windows() {
assert_eq!(
archive_format(&TargetTriple::try_from("x86_64-pc-windows-msvc").expect("valid target")),
ArchiveFormat::Zip
);
}
#[rstest]
#[case::linux(
"x86_64-unknown-linux-gnu",
b"fake-binary-content" as &[u8],
"whitaker-installer-x86_64-unknown-linux-gnu-v0.2.1.tgz",
"whitaker-installer-x86_64-unknown-linux-gnu-v0.2.1/whitaker-installer",
)]
#[case::windows(
"x86_64-pc-windows-msvc",
b"fake-exe-content" as &[u8],
"whitaker-installer-x86_64-pc-windows-msvc-v0.2.1.zip",
"whitaker-installer-x86_64-pc-windows-msvc-v0.2.1/whitaker-installer.exe",
)]
fn package_installer_creates_archive(
#[case] target: &str,
#[case] content: &[u8],
#[case] expected_name: &str,
#[case] expected_entry: &str,
) {
let fixture = packaging_fixture(target, content);
let params = params_from_fixture(&fixture, "0.2.1", target);
let output = package_installer(params).expect("packaging should succeed");
assert!(output.archive_path.exists(), "archive should exist");
assert_eq!(output.archive_name, expected_name);
let entries = if expected_name.ends_with(".tgz") {
read_tgz_entry_paths(&output.archive_path)
} else {
read_zip_entry_names(&output.archive_path)
};
assert_eq!(entries.len(), 1, "expected 1 entry, got {entries:?}");
assert_eq!(entries[0], expected_entry);
}
#[test]
fn package_installer_rejects_missing_binary() {
let temp = tempfile::tempdir().expect("temp dir");
let missing = temp.path().join("does-not-exist");
let params = InstallerPackageParams {
version: Version::new("0.2.1"),
target: TargetTriple::try_from("x86_64-unknown-linux-gnu").expect("valid target"),
binary_path: missing.clone(),
output_dir: temp.path().to_path_buf(),
};
let err = package_installer(params).expect_err("should fail");
assert!(
matches!(err, InstallerPackagingError::BinaryNotFound(ref p) if *p == missing),
"expected BinaryNotFound, got {err:?}"
);
}
#[cfg(unix)]
#[test]
fn package_installer_returns_io_error_for_unwritable_output() {
let temp = tempfile::tempdir().expect("temp dir");
let binary = temp.path().join("whitaker-installer");
fs::write(&binary, b"content").expect("write");
let unwritable = binary.join("impossible");
let params = InstallerPackageParams {
version: Version::new("0.2.1"),
target: TargetTriple::try_from("x86_64-unknown-linux-gnu").expect("valid target"),
binary_path: binary,
output_dir: unwritable,
};
let err = package_installer(params).expect_err("should fail on unwritable output dir");
assert!(
matches!(err, InstallerPackagingError::Io(_)),
"expected Io error, got {err:?}"
);
}
#[rstest]
#[case::linux("x86_64-unknown-linux-gnu", "0.2.1")]
#[case::windows("x86_64-pc-windows-msvc", "0.2.1")]
#[case::macos_arm("aarch64-apple-darwin", "1.0.0")]
fn archive_name_matches_binstall_template(#[case] target: &str, #[case] version: &str) {
let v = Version::new(version);
let t = TargetTriple::try_from(target).expect("valid target");
let name = archive_filename(&v, &t);
let url = binstall_metadata::expand_pkg_url(version, target);
assert!(
url.ends_with(&name),
"expected binstall URL to end with archive filename\n URL: {url}\n name: {name}"
);
}
#[test]
fn tgz_archive_preserves_binary_content() {
let content = b"binary-payload-12345";
let fixture = packaging_fixture("aarch64-unknown-linux-gnu", content);
let params = params_from_fixture(&fixture, "0.2.1", "aarch64-unknown-linux-gnu");
let output = package_installer(params).expect("packaging");
let file = fs::File::open(&output.archive_path).expect("open");
let gz = flate2::read::GzDecoder::new(file);
let mut tar_archive = tar::Archive::new(gz);
let mut entry = tar_archive
.entries()
.expect("entries")
.next()
.expect("one entry")
.expect("valid entry");
let mut extracted = Vec::new();
entry.read_to_end(&mut extracted).expect("read");
assert_eq!(extracted.as_slice(), content);
}