nordisk/heimdall
crates/heimdall-run/tests/indir.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! Reading the input dir: exactly one regular file, capped, no symlinks.
//! Heimdall is a defensive sandbox; all tests work in temporary directories.
use heimdall_run::{read_single_input, InputError};
fn dir() -> tempfile::TempDir {
tempfile::tempdir().unwrap()
}
#[test]
fn single_regular_file_is_read_with_name_and_bytes() {
let d = dir();
std::fs::write(d.path().join("bundle.znippy"), b"abc").unwrap();
let f = read_single_input(d.path(), 10).unwrap();
assert_eq!(f.name, "bundle.znippy");
assert_eq!(f.bytes, b"abc");
}
#[test]
fn empty_input_file_is_read_as_empty_bytes() {
let d = dir();
std::fs::write(d.path().join("e"), b"").unwrap();
assert!(read_single_input(d.path(), 10).unwrap().bytes.is_empty());
}
#[test]
fn missing_input_dir_is_refused() {
let d = dir();
assert!(matches!(
read_single_input(&d.path().join("nope"), 10),
Err(InputError::Missing(_))
));
}
#[test]
fn input_path_that_is_a_file_is_refused() {
let d = dir();
let f = d.path().join("f");
std::fs::write(&f, b"x").unwrap();
assert!(matches!(
read_single_input(&f, 10),
Err(InputError::NotDir(_))
));
}
#[test]
fn symlinked_input_dir_is_refused() {
let d = dir();
let real = d.path().join("real");
std::fs::create_dir(&real).unwrap();
std::fs::write(real.join("f"), b"x").unwrap();
let link = d.path().join("link");
std::os::unix::fs::symlink(&real, &link).unwrap();
assert!(matches!(
read_single_input(&link, 10),
Err(InputError::Symlink(_))
));
}
#[test]
fn empty_input_dir_is_refused() {
let d = dir();
assert_eq!(read_single_input(d.path(), 10), Err(InputError::Empty));
}
#[test]
fn two_input_files_are_refused_in_milestone_1() {
let d = dir();
std::fs::write(d.path().join("a"), b"1").unwrap();
std::fs::write(d.path().join("b"), b"2").unwrap();
assert_eq!(
read_single_input(d.path(), 10),
Err(InputError::MoreThanOne)
);
}
#[test]
fn symlink_as_the_input_file_is_refused_and_its_target_never_read() {
let d = dir();
let secret = tempfile::NamedTempFile::new().unwrap();
std::fs::write(secret.path(), b"secret").unwrap();
let inp = d.path().join("in");
std::fs::create_dir(&inp).unwrap();
std::os::unix::fs::symlink(secret.path(), inp.join("f")).unwrap();
assert_eq!(
read_single_input(&inp, 100),
Err(InputError::NotRegularFile("f".into()))
);
}
#[test]
fn subdirectory_as_the_input_entry_is_refused() {
let d = dir();
std::fs::create_dir(d.path().join("sub")).unwrap();
assert_eq!(
read_single_input(d.path(), 10),
Err(InputError::NotRegularFile("sub".into()))
);
}
#[test]
fn fifo_as_the_input_entry_is_refused_without_blocking() {
let d = dir();
let p = d.path().join("fifo");
let c = std::ffi::CString::new(p.as_os_str().as_encoded_bytes()).unwrap();
// SAFETY: c is a valid NUL-terminated path.
assert_eq!(unsafe { libc::mkfifo(c.as_ptr(), 0o600) }, 0);
assert_eq!(
read_single_input(d.path(), 10),
Err(InputError::NotRegularFile("fifo".into()))
);
}
#[test]
fn input_above_size_cap_is_refused() {
let d = dir();
std::fs::write(d.path().join("big"), vec![0u8; 11]).unwrap();
assert_eq!(
read_single_input(d.path(), 10),
Err(InputError::TooLarge(10))
);
}
#[test]
fn input_exactly_at_size_cap_is_accepted() {
let d = dir();
std::fs::write(d.path().join("ok"), vec![7u8; 10]).unwrap();
assert_eq!(read_single_input(d.path(), 10).unwrap().bytes.len(), 10);
}
#[test]
fn reading_input_never_changes_the_input_dir() {
let d = dir();
std::fs::write(d.path().join("f"), b"same").unwrap();
let before = std::fs::metadata(d.path().join("f"))
.unwrap()
.modified()
.unwrap();
read_single_input(d.path(), 10).unwrap();
assert_eq!(std::fs::read(d.path().join("f")).unwrap(), b"same");
assert_eq!(
std::fs::metadata(d.path().join("f"))
.unwrap()
.modified()
.unwrap(),
before
);
}