use std::path::{Path, PathBuf};
use crate::assets::{build_assets_report, build_dependency_report};
use tempfile::TempDir;
fn write_file(dir: &Path, rel: &str, content: &[u8]) -> PathBuf {
let full = dir.join(rel);
if let Some(parent) = full.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(&full, content).unwrap();
PathBuf::from(rel)
}
#[test]
fn assets_lockfiles_are_not_assets() {
let tmp = TempDir::new().unwrap();
let files = vec![
write_file(tmp.path(), "Cargo.lock", b"[[package]]"),
write_file(tmp.path(), "package-lock.json", b"{}"),
write_file(tmp.path(), "yarn.lock", b"# yarn"),
write_file(tmp.path(), "go.sum", b""),
write_file(tmp.path(), "Gemfile.lock", b""),
write_file(tmp.path(), "pnpm-lock.yaml", b""),
];
let report = build_assets_report(tmp.path(), &files).unwrap();
assert_eq!(report.total_files, 0);
}
#[test]
fn dependency_npm_malformed_json_returns_zero() {
let tmp = TempDir::new().unwrap();
let rel = write_file(tmp.path(), "package-lock.json", b"NOT VALID JSON {{{");
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].kind, "npm");
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn dependency_npm_empty_packages_object() {
let tmp = TempDir::new().unwrap();
let rel = write_file(tmp.path(), "package-lock.json", br#"{"packages": {}}"#);
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn dependency_npm_only_root_key_returns_zero() {
let tmp = TempDir::new().unwrap();
let rel = write_file(
tmp.path(),
"package-lock.json",
br#"{"packages": {"": {}}}"#,
);
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn dependency_cargo_lock_single_package() {
let tmp = TempDir::new().unwrap();
let content = "[[package]]\nname = \"only\"\nversion = \"0.1.0\"\n";
let rel = write_file(tmp.path(), "Cargo.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 1);
assert_eq!(report.total, 1);
}
#[test]
fn dependency_cargo_lock_many_packages() {
let tmp = TempDir::new().unwrap();
let mut content = String::new();
for i in 0..50 {
content.push_str(&format!(
"[[package]]\nname = \"pkg-{i}\"\nversion = \"1.0.0\"\n\n"
));
}
let rel = write_file(tmp.path(), "Cargo.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 50);
}
#[test]
fn dependency_yarn_lock_empty() {
let tmp = TempDir::new().unwrap();
let content = "# yarn lockfile v1\n\n";
let rel = write_file(tmp.path(), "yarn.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn dependency_yarn_lock_comment_only() {
let tmp = TempDir::new().unwrap();
let content = "# yarn lockfile v1\n# comment line\n# another comment\n";
let rel = write_file(tmp.path(), "yarn.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn dependency_go_sum_all_go_mod_lines_skipped() {
let tmp = TempDir::new().unwrap();
let content = "github.com/a/b v1.0.0/go.mod h1:abc=\ngithub.com/c/d v2.0.0/go.mod h1:def=\n";
let rel = write_file(tmp.path(), "go.sum", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn dependency_go_sum_same_module_different_versions() {
let tmp = TempDir::new().unwrap();
let content = "github.com/pkg v1.0.0 h1:a=\ngithub.com/pkg v2.0.0 h1:b=\ngithub.com/pkg v1.0.0/go.mod h1:c=\n";
let rel = write_file(tmp.path(), "go.sum", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 2);
}
#[test]
fn dependency_go_sum_empty_lines_ignored() {
let tmp = TempDir::new().unwrap();
let content = "\n\ngithub.com/x v1.0.0 h1:z=\n\n\n";
let rel = write_file(tmp.path(), "go.sum", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 1);
}
#[test]
fn dependency_gemfile_lock_empty_specs() {
let tmp = TempDir::new().unwrap();
let content = "GEM\n remote: https://rubygems.org/\n specs:\n\nPLATFORMS\n ruby\n";
let rel = write_file(tmp.path(), "Gemfile.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn dependency_gemfile_lock_nested_deps_not_double_counted() {
let tmp = TempDir::new().unwrap();
let content = "GEM\n remote: https://rubygems.org/\n specs:\n rails (7.0.0)\n actionpack (= 7.0.0)\n activesupport (= 7.0.0)\n rack (2.2.0)\n\nPLATFORMS\n ruby\n";
let rel = write_file(tmp.path(), "Gemfile.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert!(report.lockfiles[0].dependencies >= 2);
}
#[test]
fn dependency_pnpm_lock_empty_packages() {
let tmp = TempDir::new().unwrap();
let content = "lockfileVersion: 5.4\n\npackages:\n";
let rel = write_file(tmp.path(), "pnpm-lock.yaml", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}
#[test]
fn assets_report_empty_has_zero_totals() {
let tmp = TempDir::new().unwrap();
let report = build_assets_report(tmp.path(), &[]).unwrap();
assert_eq!(report.total_files, 0);
assert_eq!(report.total_bytes, 0);
assert!(report.categories.is_empty());
assert!(report.top_files.is_empty());
}
#[test]
fn dependency_report_empty_has_zero_total() {
let tmp = TempDir::new().unwrap();
let report = build_dependency_report(tmp.path(), &[]).unwrap();
assert_eq!(report.total, 0);
assert!(report.lockfiles.is_empty());
}
#[test]
fn assets_categories_sorted_by_bytes_desc_then_name() {
let tmp = TempDir::new().unwrap();
let files = vec![
write_file(tmp.path(), "a.png", &[0u8; 100]), write_file(tmp.path(), "b.mp4", &[0u8; 100]), write_file(tmp.path(), "c.mp3", &[0u8; 200]), write_file(tmp.path(), "d.zip", &[0u8; 200]), write_file(tmp.path(), "e.exe", &[0u8; 50]), write_file(tmp.path(), "f.ttf", &[0u8; 50]), ];
let report = build_assets_report(tmp.path(), &files).unwrap();
assert_eq!(report.categories.len(), 6);
assert_eq!(report.categories[0].category, "archive");
assert_eq!(report.categories[1].category, "audio");
assert_eq!(report.categories[2].category, "image");
assert_eq!(report.categories[3].category, "video");
assert_eq!(report.categories[4].category, "binary");
assert_eq!(report.categories[5].category, "font");
}
#[test]
fn assets_top_files_sorted_by_bytes_desc_then_path() {
let tmp = TempDir::new().unwrap();
let files = vec![
write_file(tmp.path(), "b.png", &[0u8; 100]),
write_file(tmp.path(), "a.png", &[0u8; 100]),
write_file(tmp.path(), "c.png", &[0u8; 200]),
];
let report = build_assets_report(tmp.path(), &files).unwrap();
assert_eq!(report.top_files[0].path, "c.png");
assert_eq!(report.top_files[1].path, "a.png");
assert_eq!(report.top_files[2].path, "b.png");
}
#[test]
fn assets_report_json_keys_present() {
let tmp = TempDir::new().unwrap();
let files = vec![
write_file(tmp.path(), "logo.svg", &[0u8; 512]),
write_file(tmp.path(), "app.exe", &[0u8; 1024]),
];
let report = build_assets_report(tmp.path(), &files).unwrap();
let json = serde_json::to_value(&report).unwrap();
assert!(json.get("total_files").is_some());
assert!(json.get("total_bytes").is_some());
assert!(json.get("categories").is_some());
assert!(json.get("top_files").is_some());
}
#[test]
fn dependency_report_json_keys_present() {
let tmp = TempDir::new().unwrap();
let content = "[[package]]\nname = \"serde\"\nversion = \"1.0\"\n";
let rel = write_file(tmp.path(), "Cargo.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
let json = serde_json::to_value(&report).unwrap();
assert!(json.get("total").is_some());
assert!(json.get("lockfiles").is_some());
let lockfile = &json["lockfiles"][0];
assert!(lockfile.get("path").is_some());
assert!(lockfile.get("kind").is_some());
assert!(lockfile.get("dependencies").is_some());
}
#[test]
fn assets_report_deterministic_output() {
let tmp = TempDir::new().unwrap();
let files = vec![
write_file(tmp.path(), "z.png", &[0u8; 50]),
write_file(tmp.path(), "a.mp4", &[0u8; 100]),
write_file(tmp.path(), "m.zip", &[0u8; 75]),
];
let r1 = build_assets_report(tmp.path(), &files).unwrap();
let r2 = build_assets_report(tmp.path(), &files).unwrap();
let j1 = serde_json::to_string(&r1).unwrap();
let j2 = serde_json::to_string(&r2).unwrap();
assert_eq!(j1, j2);
}
#[test]
fn dependency_report_deterministic_output() {
let tmp = TempDir::new().unwrap();
let cargo = "[[package]]\nname = \"a\"\n\n[[package]]\nname = \"b\"\n";
let rel = write_file(tmp.path(), "Cargo.lock", cargo.as_bytes());
let r1 = build_dependency_report(tmp.path(), std::slice::from_ref(&rel)).unwrap();
let r2 = build_dependency_report(tmp.path(), std::slice::from_ref(&rel)).unwrap();
let j1 = serde_json::to_string(&r1).unwrap();
let j2 = serde_json::to_string(&r2).unwrap();
assert_eq!(j1, j2);
}
#[test]
fn assets_total_bytes_equals_sum_of_category_bytes() {
let tmp = TempDir::new().unwrap();
let files = vec![
write_file(tmp.path(), "a.png", &[0u8; 100]),
write_file(tmp.path(), "b.mp3", &[0u8; 200]),
write_file(tmp.path(), "c.zip", &[0u8; 300]),
];
let report = build_assets_report(tmp.path(), &files).unwrap();
let sum: u64 = report.categories.iter().map(|c| c.bytes).sum();
assert_eq!(report.total_bytes, sum);
}
#[test]
fn assets_total_files_equals_sum_of_category_files() {
let tmp = TempDir::new().unwrap();
let files = vec![
write_file(tmp.path(), "a.png", &[0u8; 10]),
write_file(tmp.path(), "b.jpg", &[0u8; 20]),
write_file(tmp.path(), "c.mp4", &[0u8; 30]),
];
let report = build_assets_report(tmp.path(), &files).unwrap();
let sum: usize = report.categories.iter().map(|c| c.files).sum();
assert_eq!(report.total_files, sum);
}
#[test]
fn dependency_lockfile_path_uses_forward_slashes() {
let tmp = TempDir::new().unwrap();
let content = "[[package]]\nname = \"x\"\n";
let rel = write_file(tmp.path(), "subdir/Cargo.lock", content.as_bytes());
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].path, "subdir/Cargo.lock");
assert!(!report.lockfiles[0].path.contains('\\'));
}
#[test]
fn dependency_total_equals_sum_of_lockfile_deps() {
let tmp = TempDir::new().unwrap();
let cargo = "[[package]]\nname = \"a\"\n\n[[package]]\nname = \"b\"\n";
let yarn = "# yarn\npkg-a@^1:\n version \"1.0.0\"\n";
let f1 = write_file(tmp.path(), "Cargo.lock", cargo.as_bytes());
let f2 = write_file(tmp.path(), "yarn.lock", yarn.as_bytes());
let report = build_dependency_report(tmp.path(), &[f1, f2]).unwrap();
let sum: usize = report.lockfiles.iter().map(|l| l.dependencies).sum();
assert_eq!(report.total, sum);
}
#[test]
fn dependency_missing_lockfile_gives_zero_count() {
let tmp = TempDir::new().unwrap();
let rel = PathBuf::from("Cargo.lock");
let report = build_dependency_report(tmp.path(), &[rel]).unwrap();
assert_eq!(report.lockfiles[0].dependencies, 0);
}