use whyno_core::operation::Operation;
use whyno_core::state::acl::{AclEntry, AclPerms, AclTag};
use whyno_core::state::fsflags::FsFlags;
use whyno_core::test_helpers::StateBuilder;
fn render(state: &whyno_core::state::SystemState) -> String {
let mut buf = Vec::new();
super::write_path_walk(state, &mut buf).expect("render should succeed");
String::from_utf8(buf).expect("output should be valid UTF-8")
}
fn acl_entry(tag: AclTag, qualifier: Option<u32>, r: bool, w: bool, x: bool) -> AclEntry {
AclEntry {
tag,
qualifier,
perms: AclPerms {
read: r,
write: w,
execute: x,
},
}
}
#[test]
fn single_known_component() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component_file("/file.txt", 1000, 1000, 0o644)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn unknown_component_shows_unknown_markers() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component_unknown("/mystery")
.component_file("/mystery/file.txt", 0, 0, 0o644)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn inaccessible_component_shows_inaccessible_markers() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component_inaccessible("/secret")
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn all_three_probe_states_in_one_walk() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component_unknown("/vanished")
.component_inaccessible("/locked")
.component_file("/locked/file.txt", 0, 0, 0o644)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn acl_empty_entries_shows_none() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component_file_with_acl("/file.txt", 1000, 1000, 0o644, vec![])
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn acl_all_six_tag_types() {
let entries = vec![
acl_entry(AclTag::UserObj, None, true, true, true),
acl_entry(AclTag::User, Some(2000), true, false, false),
acl_entry(AclTag::GroupObj, None, true, false, true),
acl_entry(AclTag::Group, Some(50), false, false, true),
acl_entry(AclTag::Mask, None, true, false, true),
acl_entry(AclTag::Other, None, false, false, false),
];
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component_file_with_acl("/file.txt", 1000, 1000, 0o640, entries)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn flags_immutable_true_append_false() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Write)
.component("/", 0, 0, 0o755)
.component_file_with_flags(
"/locked.txt",
1000,
1000,
0o644,
FsFlags {
immutable: true,
append_only: false,
},
)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn flags_immutable_false_append_true() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Write)
.component("/", 0, 0, 0o755)
.component_file_with_flags(
"/append.txt",
1000,
1000,
0o644,
FsFlags {
immutable: false,
append_only: true,
},
)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn flags_both_true() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Write)
.component("/", 0, 0, 0o755)
.component_file_with_flags(
"/both.txt",
1000,
1000,
0o644,
FsFlags {
immutable: true,
append_only: true,
},
)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn mount_ro_option() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Write)
.component("/mnt", 0, 0, 0o755)
.component_file("/mnt/data.txt", 1000, 1000, 0o644)
.mount("/mnt", "ext4", "ro")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn mount_noexec_option() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Execute)
.component("/mnt", 0, 0, 0o755)
.component_file("/mnt/script.sh", 1000, 1000, 0o755)
.mount("/mnt", "tmpfs", "noexec")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn mount_nosuid_option() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Execute)
.component("/mnt", 0, 0, 0o755)
.component_file("/mnt/suid_bin", 0, 0, 0o4755)
.mount("/mnt", "ext4", "nosuid")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn component_with_no_mount_resolved() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component_file("/unmounted.txt", 1000, 1000, 0o644)
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}
#[test]
fn deep_path_four_components() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component("/a", 0, 0, 0o755)
.component("/a/b", 0, 0, 0o755)
.component_file("/a/b/file.txt", 1000, 1000, 0o644)
.mount("/", "ext4", "rw")
.build();
let output = render(&state);
insta::assert_snapshot!(output);
}