Files
wanderbooks/crates/ledger-store/tests/windows_root.rs
T
swaitsandClaude Fable 5.1 f2d1d4e892
check / check (push) Canceled after 0s
portable-macos / portable (macos-latest) (push) Canceled after 0s
portable-windows / portable (windows-latest) (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
fix(store): no root keeps the \\?\ prefix; the Windows root test compares canonical homes (P11c)
main's canonical default made Root::setup take std::fs::canonicalize(home),
which on Windows is \\?\C:\...  with the long name. root.rs now uses
root::canonical (canonicalize, then the prefix off) at every site, so no
root, record or message keeps the prefix; Unix is unchanged (the identity),
and macOS still agrees on /tmp and /private/tmp. windows_root.rs compares
against default_root(canonical(home)), so TEMP's 8.3 short name is not in
the expectation, and asserts the root has no prefix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-30 16:18:04 -06:00

129 lines
4.4 KiB
Rust

//! The data root on Windows (P11c, D-62): the same promises as Unix's
//! mode 0700, kept with an owner-only DACL. Runs on the Windows job only.
#![cfg(windows)]
use ledger_store::root::{Root, RootError, default_root};
use ledger_store::winsec;
use std::path::Path;
fn tmp(tag: &str) -> tempfile::TempDir {
tempfile::Builder::new()
.prefix(&format!("wb-winroot-{tag}-"))
.tempdir()
.unwrap()
}
#[test]
fn the_default_root_sits_under_local_app_data() {
let home = tmp("default");
assert_eq!(
default_root(home.path()),
home.path()
.join("AppData")
.join("Local")
.join("wanderbooks")
);
// This account's own home: the shell's LocalAppData, wherever it is.
let own = ledger_store::root::account_home().unwrap();
assert_eq!(
default_root(&own),
winsec::local_app_data().unwrap().join("wanderbooks")
);
}
#[test]
fn setup_makes_a_private_root_and_its_folders() {
let home = tmp("setup");
let (root, report) = Root::setup(home.path(), None, None).unwrap();
// The default root of the home as the system names it (its long name,
// no `\\?\` prefix; TEMP may hold an 8.3 short name).
let real_home = ledger_store::root::canonical(home.path()).unwrap();
assert_eq!(root.path(), default_root(&real_home));
assert!(!root.path().to_string_lossy().starts_with(r"\\?\"));
assert!(report.created && !report.tightened && !report.recorded);
assert!(winsec::is_private(root.path()).unwrap());
for sub in ["books", "run", "legacy"] {
assert!(winsec::is_private(&root.path().join(sub)).unwrap(), "{sub}");
}
// Again: nothing to do.
let (_, again) = Root::setup(home.path(), None, None).unwrap();
assert!(!again.created && !again.tightened && !again.recorded);
assert!(Root::find_in(home.path(), None).is_ok());
}
/// Let everyone into `path` and what it holds, as `icacls` does.
fn open_to_everyone(path: &Path) {
let out = std::process::Command::new("icacls")
.arg(path)
.args(["/grant", "*S-1-1-0:(OI)(CI)F"])
.output()
.unwrap();
assert!(out.status.success(), "{out:?}");
}
#[test]
fn a_root_others_can_open_is_refused_and_setup_makes_it_private_again() {
let home = tmp("loose");
let (root, _) = Root::setup(home.path(), None, None).unwrap();
open_to_everyone(root.path());
assert!(!winsec::is_private(root.path()).unwrap());
assert!(matches!(Root::open(root.path()), Err(RootError::Unsafe(_))));
let (_, report) = Root::setup(home.path(), None, None).unwrap();
assert!(report.tightened);
assert!(winsec::is_private(root.path()).unwrap());
assert!(Root::open(root.path()).is_ok());
}
#[test]
fn what_is_made_in_a_private_folder_is_private() {
let home = tmp("inherit");
let (root, _) = Root::setup(home.path(), None, None).unwrap();
let file = root.path().join("books").join("note.txt");
std::fs::write(&file, b"x").unwrap();
assert!(winsec::is_private(&file).unwrap());
let sub = root.path().join("books").join("inner");
std::fs::create_dir(&sub).unwrap();
assert!(winsec::is_private(&sub).unwrap());
}
#[test]
fn a_file_made_owner_only_is_private_and_a_loosened_one_is_not() {
let dir = tmp("file");
let file = dir.path().join("f");
std::fs::write(&file, b"x").unwrap();
winsec::set_owner_only(&file).unwrap();
assert!(winsec::is_private(&file).unwrap());
let out = std::process::Command::new("icacls")
.arg(&file)
.args(["/grant", "*S-1-1-0:R"])
.output()
.unwrap();
assert!(out.status.success(), "{out:?}");
assert!(!winsec::is_private(&file).unwrap());
}
#[test]
fn a_rename_never_goes_over_what_is_there() {
let dir = tmp("rename");
let (a, b, c) = (
dir.path().join("a"),
dir.path().join("b"),
dir.path().join("c"),
);
std::fs::write(&a, b"a").unwrap();
std::fs::write(&b, b"b").unwrap();
let err = winsec::rename_new(&a, &b).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::AlreadyExists, "{err:?}");
assert_eq!(std::fs::read(&b).unwrap(), b"b");
winsec::rename_new(&a, &c).unwrap();
assert_eq!(std::fs::read(&c).unwrap(), b"a");
assert!(!a.exists());
}
#[test]
fn this_process_runs_as_the_account_that_the_token_says() {
let me = winsec::current_user().unwrap();
assert_eq!(winsec::process_user(std::process::id()).unwrap(), me);
assert!(me.to_text().unwrap().starts_with("S-1-5-"));
}