Files
boxmaker/docs/plans/M3a/files/crates/brokerd/tests/state.rs
T
kyleandClaude Opus 5 e3f37da232 Hand over the M3a plan: 22 tasks, their files, and the check record
Task files, the files they copy in (byte-identical to the reference on
m3a-ref), each area's check record, and a README with the per-task
table of what each check exposed. The handoff note is done with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 23:45:43 -07:00

282 lines
9.1 KiB
Rust

//! Tests for the session state files. Do not edit.
#[path = "support/tmp.rs"]
mod tmp;
use brokerd::policy::{Label, SessionState};
use brokerd::state::{RUNBOOK, StateError, StateStore};
use proto::{DataClass, SessionId};
use std::os::unix::fs::PermissionsExt;
use tmp::TempDir;
fn id(text: &str) -> SessionId {
SessionId::new(text).unwrap()
}
fn label(class: DataClass, untrusted: bool) -> Label {
Label { class, untrusted }
}
fn state(taint: DataClass, untrusted: bool) -> SessionState {
SessionState { taint, untrusted }
}
/// The store's directory is two levels below the temporary one and does not exist yet, as on a
/// fresh install.
fn store(home: &TempDir) -> StateStore {
StateStore::new(&home.path().join("broker/sessions"))
}
#[test]
fn a_session_with_no_file_is_private_and_trusted() {
let home = TempDir::new("state");
let store = store(&home);
let fresh = store.read(&id("chat-1")).unwrap();
assert_eq!(fresh, state(DataClass::Private, false));
assert_eq!(fresh, SessionState::default());
// Reading creates nothing.
assert!(!home.path().join("broker").exists());
}
#[test]
fn the_first_result_creates_the_file_and_its_directory() {
let home = TempDir::new("state");
let store = store(&home);
let session = id("chat-1");
let next = store
.raise(
&session,
SessionState::default(),
label(DataClass::Private, false),
)
.unwrap();
assert_eq!(next, state(DataClass::Private, false));
let path = home.path().join("broker/sessions/chat-1.json");
assert_eq!(store.path(&session), path);
assert_eq!(
std::fs::read_to_string(&path).unwrap(),
"{\"taint\":\"private\",\"untrusted\":false}\n"
);
let mode = |p: &std::path::Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o777;
assert_eq!(mode(&path), 0o600);
assert_eq!(mode(&home.path().join("broker/sessions")), 0o700);
assert_eq!(mode(&home.path().join("broker")), 0o700);
// No temporary file is left behind.
assert!(!home.path().join("broker/sessions/chat-1.json.tmp").exists());
assert_eq!(store.read(&session).unwrap(), next);
}
#[test]
fn taint_and_the_untrusted_flag_only_go_up() {
let home = TempDir::new("state");
let store = store(&home);
let session = id("s");
let steps = [
(
label(DataClass::Public, false),
state(DataClass::Private, false),
),
(
label(DataClass::Private, true),
state(DataClass::Private, true),
),
(
label(DataClass::Secret, false),
state(DataClass::Secret, true),
),
(
label(DataClass::Public, false),
state(DataClass::Secret, true),
),
(
label(DataClass::Private, false),
state(DataClass::Secret, true),
),
];
let mut current = store.read(&session).unwrap();
for (result, want) in steps {
current = store.raise(&session, current, result).unwrap();
assert_eq!(current, want);
assert_eq!(store.read(&session).unwrap(), want, "what is on disk");
}
assert_eq!(
std::fs::read_to_string(store.path(&session)).unwrap(),
"{\"taint\":\"secret\",\"untrusted\":true}\n"
);
}
#[test]
fn sessions_do_not_share_state() {
let home = TempDir::new("state");
let store = store(&home);
store
.raise(
&id("a"),
SessionState::default(),
label(DataClass::Secret, true),
)
.unwrap();
assert_eq!(store.read(&id("b")).unwrap(), SessionState::default());
assert_eq!(
store.read(&id("a")).unwrap(),
state(DataClass::Secret, true)
);
}
/// A file that exists but does not hold a valid state is an error, never "no file".
#[test]
fn a_damaged_file_is_an_error_that_names_the_file_and_the_runbook() {
let home = TempDir::new("state");
let store = store(&home);
std::fs::create_dir_all(home.path().join("broker/sessions")).unwrap();
let session = id("hurt");
for text in [
"",
"{",
"null",
"[]",
"{\"taint\":\"secret\"}",
"{\"untrusted\":false}",
"{\"taint\":\"internal\",\"untrusted\":false}",
"{\"taint\":\"secret\",\"untrusted\":\"no\"}",
"{\"taint\":\"secret\",\"untrusted\":false,\"note\":1}",
"{\"taint\":\"secret\",\"untrusted\":false} trailing",
// A session is never below private, so this file was not written by brokerd.
"{\"taint\":\"public\",\"untrusted\":false}",
] {
std::fs::write(store.path(&session), text).unwrap();
let err = store.read(&session).expect_err(text);
assert!(
matches!(err, StateError::Unreadable(..)),
"{text:?}: {err:?}"
);
let shown = err.to_string();
assert!(shown.contains("hurt.json"), "{shown}");
assert!(shown.ends_with(RUNBOOK), "{shown}");
}
assert_eq!(RUNBOOK, "see docs/runbook.md#broker-state-damaged");
// A good file with or without its final newline reads fine.
for text in [
"{\"taint\":\"secret\",\"untrusted\":true}\n",
"{\"taint\":\"secret\",\"untrusted\":true}",
] {
std::fs::write(store.path(&session), text).unwrap();
assert_eq!(
store.read(&session).unwrap(),
state(DataClass::Secret, true)
);
}
// Not valid UTF-8, and a directory where the file should be.
std::fs::write(store.path(&session), b"\xff\xfe").unwrap();
assert!(matches!(
store.read(&session),
Err(StateError::Unreadable(..))
));
std::fs::remove_file(store.path(&session)).unwrap();
std::fs::create_dir(store.path(&session)).unwrap();
assert!(matches!(
store.read(&session),
Err(StateError::Unreadable(..))
));
}
#[test]
fn a_file_without_read_permission_is_an_error_not_a_fresh_session() {
if tmp::running_as_root("a_file_without_read_permission_is_an_error_not_a_fresh_session") {
return;
}
let home = TempDir::new("state");
let store = store(&home);
let session = id("locked");
store
.raise(
&session,
SessionState::default(),
label(DataClass::Secret, false),
)
.unwrap();
let path = store.path(&session);
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o000)).unwrap();
assert!(matches!(
store.read(&session),
Err(StateError::Unreadable(..))
));
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
}
#[test]
fn a_failed_write_is_an_error_and_leaves_the_old_state() {
if tmp::running_as_root("a_failed_write_is_an_error_and_leaves_the_old_state") {
return;
}
let home = TempDir::new("state");
let store = store(&home);
let session = id("s");
let before = store
.raise(
&session,
SessionState::default(),
label(DataClass::Private, true),
)
.unwrap();
let dir = home.path().join("broker/sessions");
std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o500)).unwrap();
let err = store
.raise(&session, before, label(DataClass::Secret, false))
.expect_err("the directory is read-only");
std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)).unwrap();
assert!(matches!(err, StateError::Write(..)), "{err:?}");
let shown = err.to_string();
assert!(shown.contains("s.json"), "{shown}");
assert!(shown.ends_with(RUNBOOK), "{shown}");
assert_eq!(store.read(&session).unwrap(), before);
}
/// A `.tmp` file beside the state is a write that did not finish. It is not the state, it does
/// not stop the next write, and the next write replaces it.
#[test]
fn a_leftover_tmp_file_is_neither_read_nor_in_the_way() {
let home = TempDir::new("state");
let store = store(&home);
let session = id("s");
let dir = home.path().join("broker/sessions");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("s.json.tmp"), "{\"taint\":\"secret\",\"untr").unwrap();
assert_eq!(store.read(&session).unwrap(), SessionState::default());
let next = store
.raise(
&session,
SessionState::default(),
label(DataClass::Secret, false),
)
.unwrap();
assert_eq!(store.read(&session).unwrap(), next);
assert!(!dir.join("s.json.tmp").exists());
}
#[test]
fn raise_trusts_the_state_it_is_given_not_the_file() {
// The caller read the state under the ledger lock a moment ago; `raise` does not read again.
let home = TempDir::new("state");
let store = store(&home);
let session = id("s");
let given = state(DataClass::Secret, true);
let next = store
.raise(&session, given, label(DataClass::Public, false))
.unwrap();
assert_eq!(next, given);
// Even a state below private is lifted to private on the way to disk.
let low = store
.raise(
&id("low"),
state(DataClass::Public, false),
label(DataClass::Public, false),
)
.unwrap();
assert_eq!(low, state(DataClass::Private, false));
}