use anyhow::Result;
use shiplog_bundle::*;
use shiplog_bundle::{write_bundle_manifest, write_zip};
use shiplog_ids::RunId;
use shiplog_schema::bundle::{BundleManifest, BundleProfile};
use std::fs;
use std::io::Read;
use std::path::Path;
fn make_test_dir(dir: &Path) {
fs::write(dir.join(FILE_PACKET_MD), "# Packet\ncontent").unwrap();
fs::write(dir.join(FILE_LEDGER_EVENTS_JSONL), r#"{"id":"evt1"}"#).unwrap();
fs::write(
dir.join(FILE_COVERAGE_MANIFEST_JSON),
r#"{"total":10,"covered":8}"#,
)
.unwrap();
let mgr = dir.join(DIR_PROFILES).join(PROFILE_MANAGER);
fs::create_dir_all(&mgr).unwrap();
fs::write(mgr.join(FILE_PACKET_MD), "# Manager Packet").unwrap();
let pub_dir = dir.join(DIR_PROFILES).join(PROFILE_PUBLIC);
fs::create_dir_all(&pub_dir).unwrap();
fs::write(pub_dir.join(FILE_PACKET_MD), "# Public Packet").unwrap();
}
#[test]
fn manifest_with_spaces_in_parent_path() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("my output dir");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let run_id = RunId("spaces-test".into());
let manifest = write_bundle_manifest(&dir, &run_id, &BundleProfile::Internal)?;
assert!(!manifest.files.is_empty());
for f in &manifest.files {
assert!(
!f.path.contains('\\'),
"manifest path should not contain backslash: {}",
f.path
);
}
Ok(())
}
#[test]
fn manifest_with_unicode_in_parent_path() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("出力ディレクトリ");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let run_id = RunId("unicode-test".into());
let manifest = write_bundle_manifest(&dir, &run_id, &BundleProfile::Internal)?;
assert!(!manifest.files.is_empty());
let json = fs::read_to_string(dir.join(FILE_BUNDLE_MANIFEST_JSON))?;
let parsed: BundleManifest = serde_json::from_str(&json)?;
assert_eq!(parsed.files.len(), manifest.files.len());
Ok(())
}
#[test]
fn manifest_with_special_chars_in_parent_path() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("project (v2.0-beta)");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let run_id = RunId("special-chars-test".into());
let manifest = write_bundle_manifest(&dir, &run_id, &BundleProfile::Internal)?;
assert!(!manifest.files.is_empty());
for f in &manifest.files {
assert!(
!f.path.contains("project (v2.0-beta)"),
"relative path should not contain parent dir name: {}",
f.path
);
}
Ok(())
}
#[test]
fn zip_with_spaces_in_paths() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("zip source dir");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let zip_path = root.path().join("output archive.zip");
write_zip(&dir, &zip_path, &BundleProfile::Internal)?;
let file = fs::File::open(&zip_path)?;
let archive = zip::ZipArchive::new(file)?;
assert!(!archive.is_empty(), "zip should not be empty");
for i in 0..archive.len() {
let name = archive.name_for_index(i).unwrap().to_string();
assert!(
!name.contains('\\'),
"zip entry should not contain backslash: {}",
name
);
}
Ok(())
}
#[test]
fn zip_with_unicode_in_paths() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("données_de_sortie");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let zip_path = root.path().join("archive_données.zip");
write_zip(&dir, &zip_path, &BundleProfile::Internal)?;
let file = fs::File::open(&zip_path)?;
let archive = zip::ZipArchive::new(file)?;
assert!(!archive.is_empty());
Ok(())
}
#[test]
fn zip_round_trip_with_spaces_preserves_content() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("round trip dir");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let zip_path = root.path().join("round trip.zip");
write_zip(&dir, &zip_path, &BundleProfile::Internal)?;
let file = fs::File::open(&zip_path)?;
let mut archive = zip::ZipArchive::new(file)?;
let mut entry = archive.by_name(FILE_PACKET_MD)?;
let mut content = String::new();
entry.read_to_string(&mut content)?;
assert_eq!(content, "# Packet\ncontent");
Ok(())
}
#[test]
fn manager_zip_with_spaces_in_path() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("manager zip dir");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let zip_path = root.path().join("manager output.zip");
write_zip(&dir, &zip_path, &BundleProfile::Manager)?;
let file = fs::File::open(&zip_path)?;
let mut archive = zip::ZipArchive::new(file)?;
assert_eq!(archive.len(), 2, "manager zip should have 2 files");
let manager_path = format!("{DIR_PROFILES}/{PROFILE_MANAGER}/{FILE_PACKET_MD}");
let mut entry = archive.by_name(&manager_path)?;
let mut content = String::new();
entry.read_to_string(&mut content)?;
assert_eq!(content, "# Manager Packet");
Ok(())
}
#[test]
fn manifest_paths_use_forward_slashes_for_profile_entries() -> Result<()> {
let dir = tempfile::tempdir()?;
make_test_dir(dir.path());
let run_id = RunId("fwd-slash-test".into());
let manifest = write_bundle_manifest(dir.path(), &run_id, &BundleProfile::Internal)?;
for f in &manifest.files {
assert!(
!f.path.contains('\\'),
"manifest path contains backslash: {}",
f.path
);
if f.path.contains(DIR_PROFILES) {
assert!(
f.path.starts_with(&format!("{DIR_PROFILES}/")),
"profile path not forward-slash prefixed: {}",
f.path
);
}
}
Ok(())
}
#[test]
fn checksums_are_correct_with_unicode_parent_path() -> Result<()> {
let root = tempfile::tempdir()?;
let dir = root.path().join("チェックサム");
fs::create_dir_all(&dir)?;
make_test_dir(&dir);
let run_id = RunId("checksum-unicode".into());
let manifest = write_bundle_manifest(&dir, &run_id, &BundleProfile::Internal)?;
for f in &manifest.files {
let full_path = dir.join(&f.path);
let bytes = fs::read(&full_path)?;
use sha2::{Digest, Sha256};
let digest = hex::encode(Sha256::digest(&bytes));
assert_eq!(
digest, f.sha256,
"SHA-256 mismatch for {} in unicode path",
f.path
);
}
Ok(())
}