mod helpers;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use helpers::{current_username, run_whyno};
use tempfile::TempDir;
#[test]
fn readable_file_owner_check_exits_zero() {
let dir = TempDir::new().expect("create tempdir");
let file = dir.path().join("readable.txt");
fs::write(&file, "hello").expect("write file");
fs::set_permissions(&file, fs::Permissions::from_mode(0o644)).expect("chmod");
let user = current_username();
let path_str = file.to_str().expect("path to str");
let output = run_whyno(&[&user, "read", path_str]);
assert_eq!(
output.status.code(),
Some(0),
"expected exit 0 for readable file, got stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("[PASS]"),
"expected [PASS] in output, got: {stdout}"
);
}
#[test]
fn json_mode_produces_valid_json() {
let dir = TempDir::new().expect("create tempdir");
let file = dir.path().join("json_test.txt");
fs::write(&file, "content").expect("write file");
fs::set_permissions(&file, fs::Permissions::from_mode(0o644)).expect("chmod");
let user = current_username();
let path_str = file.to_str().expect("path to str");
let output = run_whyno(&[&user, "read", path_str, "--json"]);
assert_eq!(
output.status.code(),
Some(0),
"expected exit 0 for JSON mode, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("stdout should be valid JSON");
assert_eq!(parsed["version"], 1, "JSON schema version should be 1");
assert_eq!(
parsed["result"], "allowed",
"result should be 'allowed' for readable file"
);
assert!(parsed["layers"].is_array(), "layers should be an array");
}
#[test]
fn json_mode_denied_has_fixes() {
let dir = TempDir::new().expect("create tempdir");
let file = dir.path().join("denied.txt");
fs::write(&file, "secret").expect("write file");
fs::set_permissions(&file, fs::Permissions::from_mode(0o000)).expect("chmod");
let user = current_username();
let path_str = file.to_str().expect("path to str");
let output = run_whyno(&[&user, "read", path_str, "--json"]);
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("stdout should be valid JSON");
if parsed["result"] == "denied" {
assert_eq!(output.status.code(), Some(1), "denied should exit 1");
assert!(
parsed["fixes"].is_array(),
"denied result should include fixes array"
);
}
}
#[test]
fn op_chmod_produces_metadata_layer_in_output() {
let user = current_username();
let output = run_whyno(&[&user, "chmod", "/etc/passwd", "--json"]);
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("stdout should be valid JSON");
let layers = parsed["layers"].as_array().expect("layers should be array");
let has_metadata = layers
.iter()
.any(|l| l["name"].as_str() == Some("metadata"));
assert!(
has_metadata,
"expected metadata layer in output, got: {stdout}"
);
}
#[test]
fn op_setxattr_with_xattr_key_produces_metadata_layer() {
let dir = TempDir::new().expect("create tempdir");
let file = dir.path().join("xattr_test.txt");
fs::write(&file, "data").expect("write file");
fs::set_permissions(&file, fs::Permissions::from_mode(0o644)).expect("chmod");
let user = current_username();
let path_str = file.to_str().expect("path to str");
let output = run_whyno(&[&user, "setxattr", "--xattr-key", "user.test", path_str, "--json"]);
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("stdout should be valid JSON");
let layers = parsed["layers"].as_array().expect("layers should be array");
let has_metadata = layers
.iter()
.any(|l| l["name"].as_str() == Some("metadata"));
assert!(
has_metadata,
"expected metadata layer in output, got: {stdout}"
);
}
#[test]
fn op_setxattr_without_xattr_key_errors() {
let dir = TempDir::new().expect("create tempdir");
let file = dir.path().join("xattr_nokey.txt");
fs::write(&file, "data").expect("write file");
let user = current_username();
let path_str = file.to_str().expect("path to str");
let output = run_whyno(&[&user, "setxattr", path_str]);
assert_ne!(
output.status.code(),
Some(0),
"setxattr without --xattr-key should fail"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("xattr-key"),
"stderr should mention xattr-key, got: {stderr}"
);
}
#[test]
fn explain_mode_has_sections() {
let dir = TempDir::new().expect("create tempdir");
let file = dir.path().join("explain_test.txt");
fs::write(&file, "content").expect("write file");
fs::set_permissions(&file, fs::Permissions::from_mode(0o644)).expect("chmod");
let user = current_username();
let path_str = file.to_str().expect("path to str");
let output = run_whyno(&[&user, "read", path_str, "--explain"]);
assert_eq!(
output.status.code(),
Some(0),
"expected exit 0 for explain mode"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("=== Subject ==="),
"explain mode should have Subject section"
);
assert!(
stdout.contains("=== Layer Results ==="),
"explain mode should have Layer Results section"
);
assert!(
stdout.contains("=== Fix Plan ==="),
"explain mode should have Fix Plan section"
);
}