use std::collections::HashSet;
use callisto_graph::toposort_impl;
use callisto_manifests::atomic::atomic_write;
use callisto_model::{DepKind, PackageId};
use tempfile::tempdir;
#[test]
fn test_adversarial_path_traversal_rejection() {
let res = PackageId::parse("/etc/passwd");
assert!(res.is_err(), "Must reject leading slashes");
let res = PackageId::parse("../../secret");
assert!(
res.is_err(),
"Must reject path traversal .. in package identity"
);
}
#[test]
fn test_adversarial_atomic_write_over_existing_file() {
let dir = tempdir().unwrap();
let target = dir.path().join("Cargo.toml");
atomic_write(&target, "[package]\nname = \"foo\"\nversion = \"1.0.0\"\n").unwrap();
assert_eq!(
std::fs::read_to_string(&target).unwrap(),
"[package]\nname = \"foo\"\nversion = \"1.0.0\"\n"
);
atomic_write(&target, "[package]\nname = \"foo\"\nversion = \"1.0.1\"\n").unwrap();
assert_eq!(
std::fs::read_to_string(&target).unwrap(),
"[package]\nname = \"foo\"\nversion = \"1.0.1\"\n"
);
}
#[test]
fn test_adversarial_tarjan_scc_multi_node_cycle() {
let pkg_a = PackageId::parse("pkg-a").unwrap();
let pkg_b = PackageId::parse("pkg-b").unwrap();
let pkg_c = PackageId::parse("pkg-c").unwrap();
let subset: HashSet<_> = vec![pkg_a.clone(), pkg_b.clone(), pkg_c.clone()]
.into_iter()
.collect();
let all = vec![pkg_a.clone(), pkg_b.clone(), pkg_c.clone()];
let res = toposort_impl(&subset, &all, |id| {
if id == &pkg_a {
vec![(pkg_b.clone(), DepKind::Runtime)]
} else if id == &pkg_b {
vec![(pkg_c.clone(), DepKind::Runtime)]
} else {
vec![(pkg_a.clone(), DepKind::Runtime)]
}
});
assert!(res.is_err());
let err = res.unwrap_err();
let err_str = err.to_string();
assert!(
err_str.contains("cycle") || err_str.contains("circular"),
"Error must diagnose cycle: {err_str}"
);
}
#[test]
fn test_adversarial_package_json_tab_indentation_preservation() {
use callisto_manifests::{Manifest, OpenContext, PackageJson};
use callisto_model::{ManifestDecl, ManifestFormat, ManifestRole, WorkspaceKind};
let dir = tempdir().unwrap();
let path = dir.path().join("package.json");
let content = "{\n\t\"name\": \"tab-app\",\n\t\"version\": \"1.0.0\"\n}\n";
std::fs::write(&path, content).unwrap();
let decl = ManifestDecl::new(
"package.json",
ManifestRole::Canonical,
ManifestFormat::PackageJson,
)
.unwrap();
let ctx = OpenContext {
workspace_root: dir.path(),
cargo_workspace: None,
npm_workspace_kind: Some(WorkspaceKind::Pnpm),
};
let mut pj = PackageJson::open(&decl, &ctx).unwrap();
pj.write_version(
&callisto_model::Version::parse("1.1.0", callisto_model::VersionGrammar::SemVer).unwrap(),
)
.unwrap();
let updated = std::fs::read_to_string(&path).unwrap();
assert!(updated.contains("\"version\": \"1.1.0\""));
assert!(
updated.contains("\t\"version\""),
"Indentation tab must be preserved"
);
}