use poolster_core::{GeneratedFile, GeneratedTree};
#[test]
fn generated_files_cannot_escape_the_output_directory() {
assert!(GeneratedFile::new("../outside.ts", "").is_err());
assert!(GeneratedFile::new("/absolute.ts", "").is_err());
assert!(GeneratedTree::default().get("missing.ts").is_none());
}
fn tree(entries: &[(&str, &str)]) -> GeneratedTree {
let mut tree = GeneratedTree::default();
for (path, contents) in entries {
tree.insert(GeneratedFile::new(path, *contents).unwrap())
.unwrap();
}
tree
}
fn snapshot(root: &std::path::Path) -> std::collections::BTreeMap<std::path::PathBuf, Vec<u8>> {
fn visit(
root: &std::path::Path,
directory: &std::path::Path,
result: &mut std::collections::BTreeMap<std::path::PathBuf, Vec<u8>>,
) {
for entry in std::fs::read_dir(directory).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
visit(root, &path, result);
} else {
result.insert(
path.strip_prefix(root).unwrap().into(),
std::fs::read(path).unwrap(),
);
}
}
}
let mut result = std::collections::BTreeMap::new();
visit(root, root, &mut result);
result
}
#[test]
fn edited_stale_file_blocks_all_package_writes_and_can_be_resolved() {
let root = tempfile::tempdir().unwrap();
let mut original = tree(&[
("typescript/client.ts", "client v1"),
("typescript/obsolete.ts", "old operation"),
("go/client.go", "client v1"),
]);
original
.insert_custom(GeneratedFile::new("typescript/custom.ts", "starter").unwrap())
.unwrap();
original.write_to(root.path()).unwrap();
std::fs::write(
root.path().join("typescript/custom.ts"),
"customer middleware",
)
.unwrap();
std::fs::write(
root.path().join("typescript/obsolete.ts"),
"customer edited operation",
)
.unwrap();
let next = tree(&[
("typescript/client.ts", "client v2"),
("typescript/new.ts", "new operation"),
("go/client.go", "client v2"),
]);
let before = snapshot(root.path());
assert!(next.write_to(root.path()).is_err());
assert_eq!(
snapshot(root.path()),
before,
"preflight failure must preserve every package and ownership manifest"
);
std::fs::write(root.path().join("typescript/obsolete.ts"), "old operation").unwrap();
next.write_to(root.path()).unwrap();
assert!(!root.path().join("typescript/obsolete.ts").exists());
assert_eq!(
std::fs::read_to_string(root.path().join("go/client.go")).unwrap(),
"client v2"
);
assert_eq!(
std::fs::read_to_string(root.path().join("typescript/custom.ts")).unwrap(),
"customer middleware"
);
assert!(next.check(root.path()).unwrap().is_empty());
}
#[test]
fn unowned_late_collision_blocks_earlier_updates_additions_and_deletions() {
let root = tempfile::tempdir().unwrap();
tree(&[("a/update.ts", "v1"), ("a/obsolete.ts", "remove later")])
.write_to(root.path())
.unwrap();
std::fs::create_dir(root.path().join("z")).unwrap();
std::fs::write(root.path().join("z/customer.ts"), "handwritten").unwrap();
let next = tree(&[
("a/update.ts", "v2"),
("a/new.ts", "new"),
("z/customer.ts", "collision"),
]);
let before = snapshot(root.path());
assert!(next.check(root.path()).is_err());
assert!(next.write_to(root.path()).is_err());
assert_eq!(snapshot(root.path()), before);
}
#[test]
fn drift_check_is_read_only_and_missing_owned_files_are_repaired() {
let root = tempfile::tempdir().unwrap();
let generated = tree(&[
("typescript/client.ts", "typed client"),
("go/client.go", "native client"),
]);
generated.write_to(root.path()).unwrap();
std::fs::remove_file(root.path().join("typescript/client.ts")).unwrap();
std::fs::write(root.path().join("go/client.go"), "customer edit").unwrap();
let before = snapshot(root.path());
let drift = generated.check(root.path()).unwrap();
assert_eq!(
drift.added,
[std::path::PathBuf::from("typescript/client.ts")]
);
assert_eq!(drift.modified, [std::path::PathBuf::from("go/client.go")]);
assert_eq!(snapshot(root.path()), before);
assert!(generated.write_to(root.path()).is_err());
assert_eq!(snapshot(root.path()), before);
std::fs::write(root.path().join("go/client.go"), "native client").unwrap();
generated.write_to(root.path()).unwrap();
assert_eq!(
std::fs::read_to_string(root.path().join("typescript/client.ts")).unwrap(),
"typed client"
);
assert!(generated.check(root.path()).unwrap().is_empty());
}