From 11cbf52ea71f4a4ff5dc01b90dfd90bd8b85b894 Mon Sep 17 00:00:00 2001 From: euisuh <55032583+euisuh@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:03:59 +0900 Subject: [PATCH] test(settings): respect effective filesystem permissions Probe effective write restrictions so privileged container runs do not assert errors the OS cannot provide, while retaining unprivileged coverage. Closes #46 Co-Authored-By: OpenAI Codex --- crates/settings/tests/common/mod.rs | 18 ++++++++++++++++++ crates/settings/tests/paths.rs | 4 +++- crates/settings/tests/recovery.rs | 9 ++++++--- crates/settings/tests/store.rs | 8 ++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/settings/tests/common/mod.rs b/crates/settings/tests/common/mod.rs index a9017e46..2ae6ec0c 100644 --- a/crates/settings/tests/common/mod.rs +++ b/crates/settings/tests/common/mod.rs @@ -94,3 +94,21 @@ pub fn write_config(dir: &Path, text: &str) { pub fn read_config(dir: &Path) -> String { std::fs::read_to_string(config_path(dir)).unwrap() } + +#[cfg(unix)] +pub fn restrict_directory_writes(dir: &Path) -> bool { + use std::os::unix::fs::PermissionsExt; + + let permissions = std::fs::metadata(dir).unwrap().permissions(); + std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o500)).unwrap(); + match tempfile::NamedTempFile::new_in(dir) { + Ok(probe) => { + drop(probe); + std::fs::set_permissions(dir, permissions).unwrap(); + eprintln!("skipping permission test: environment permits writes into a 0500 directory"); + false + } + Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => true, + Err(error) => panic!("unexpected directory write-probe failure: {error}"), + } +} diff --git a/crates/settings/tests/paths.rs b/crates/settings/tests/paths.rs index e0bcde66..9da68836 100644 --- a/crates/settings/tests/paths.rs +++ b/crates/settings/tests/paths.rs @@ -134,7 +134,9 @@ fn unwritable_folder_is_reported_at_open_not_first_save() { let tmp = tempfile::tempdir().unwrap(); let dir = tmp.path().join("ro"); std::fs::create_dir(&dir).unwrap(); - std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o500)).unwrap(); + if !common::restrict_directory_writes(&dir) { + return; + } let (mut store, report) = open_in(&dir); std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)).unwrap(); assert!(store.is_read_only()); diff --git a/crates/settings/tests/recovery.rs b/crates/settings/tests/recovery.rs index 4af90bb4..97967732 100644 --- a/crates/settings/tests/recovery.rs +++ b/crates/settings/tests/recovery.rs @@ -77,9 +77,10 @@ fn damaged_file_that_cannot_be_moved_is_left_alone() { use std::os::unix::fs::PermissionsExt; let tmp = tempfile::tempdir().unwrap(); write_config(tmp.path(), "not = [toml"); - // The folder passes the write probe only while it is writable, so open first, then lock it. let dir = tmp.path(); - std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o500)).unwrap(); + if !common::restrict_directory_writes(dir) { + return; + } let (mut store, report) = open_in(dir); std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700)).unwrap(); assert!(store.is_read_only(), "{report:?}"); @@ -152,7 +153,9 @@ fn failed_migration_in_an_unwritable_folder_shows_defaults() { let tmp = tempfile::tempdir().unwrap(); // Version 0 has no migration step, so upgrading fails. write_config(tmp.path(), "schema_version = 0\n[general]\nlanguage = \"de\"\n"); - std::fs::set_permissions(tmp.path(), std::fs::Permissions::from_mode(0o500)).unwrap(); + if !common::restrict_directory_writes(tmp.path()) { + return; + } let (store, report) = open_in(tmp.path()); std::fs::set_permissions(tmp.path(), std::fs::Permissions::from_mode(0o700)).unwrap(); assert!(matches!(report.read_only, Some(ReadOnlyReason::DirNotWritable(_))), "{report:?}"); diff --git a/crates/settings/tests/store.rs b/crates/settings/tests/store.rs index 385e9556..bc55d97c 100644 --- a/crates/settings/tests/store.rs +++ b/crates/settings/tests/store.rs @@ -204,6 +204,14 @@ fn read_only_file_makes_the_save_fail_visibly() { let (mut store, _) = open_in(tmp.path()); store.flush().unwrap(); std::fs::set_permissions(config_path(tmp.path()), std::fs::Permissions::from_mode(0o400)).unwrap(); + match std::fs::File::options().write(true).open(config_path(tmp.path())) { + Ok(_) => { + eprintln!("skipping permission test: environment permits writes into a 0400 file"); + return; + } + Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => {} + Err(error) => panic!("unexpected file write-probe failure: {error}"), + } store.update(|p| p.general.language = "fr".into()); let r = store.tick(Instant::now() + Duration::from_secs(1)); assert!(matches!(r.status, SaveStatus::Failed(craft_settings::SettingsError::Io(_))), "{r:?}");