Gunnar

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
    );
}