brokerd serves a third socket, [sockets] state (default <home>/run/memory-broker/state.sock), bound by the same listen (dir 0700, stale socket removed, socket 0600), one thread per connection, one question per connection. The answer is StateStore::read: the stored taint and flag, the store's default (private, trusted) for a session with no file. A damaged state is an internal error with the store's text, never an answer; other kinds are forbidden; malformed frames get read_request's error. A new tests/serve.rs waits for an answer before reading the startup notices (a race that failed about one run in ten under load, before this task too). Checks: every before block found once at ref 04 (20 edits); the new file in the task is byte-identical to the reference; the given tests fail before as the task says (config and state_socket do not compile; serve_state 5 of 5 fail); gate ok with 1171 tests on the reference and on a replay from the task text in a fresh ref-04 worktree with its own build dir (byte-identical to the reference; removed); /tmp unchanged around the gate; 25 mutations, 24 caught, the one missed (state bound last) changes no tested behaviour; the socket tests 25 runs under load, 0 failures; no task-text line over 100 characters outside tables and code. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
139 lines
4.9 KiB
Rust
139 lines
4.9 KiB
Rust
//! Tests for `brokerd`'s configuration. Do not edit: these define the required behaviour.
|
|
|
|
use brokerd::config::{Approvals, Config, ConfigError, Sockets};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
fn fixture(name: &str) -> PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests/fixtures/config")
|
|
.join(name)
|
|
}
|
|
|
|
/// What `home` must default to in this process. The test does not set the variable: changing the
|
|
/// environment of a running test binary would race with the other tests.
|
|
fn default_home() -> PathBuf {
|
|
std::env::var_os("BOXMAKER_HOME")
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(|| PathBuf::from("/var/lib/boxmaker"))
|
|
}
|
|
|
|
#[test]
|
|
fn an_empty_file_gets_every_default() {
|
|
let c = Config::load(&fixture("empty.toml")).unwrap();
|
|
assert_eq!(c.paths.home, default_home());
|
|
assert_eq!(c.paths.grants, PathBuf::from("/etc/boxmaker/grants"));
|
|
assert_eq!(c.sockets, Sockets::default());
|
|
assert_eq!(c.approvals, Approvals { ttl_ms: 900_000 });
|
|
assert_eq!(Approvals::default(), Approvals { ttl_ms: 900_000 });
|
|
assert_eq!(c, Config::parse("").unwrap());
|
|
assert_eq!(c, Config::default());
|
|
}
|
|
|
|
#[test]
|
|
fn sockets_and_directories_default_to_places_under_home() {
|
|
let c = Config::load(&fixture("home_only.toml")).unwrap();
|
|
assert_eq!(c.paths.home, PathBuf::from("/srv/boxmaker"));
|
|
assert_eq!(
|
|
c.broker_socket(),
|
|
PathBuf::from("/srv/boxmaker/run/loop-broker/broker.sock")
|
|
);
|
|
assert_eq!(
|
|
c.admin_socket(),
|
|
PathBuf::from("/srv/boxmaker/run/owner-broker/admin.sock")
|
|
);
|
|
assert_eq!(
|
|
c.state_socket(),
|
|
PathBuf::from("/srv/boxmaker/run/memory-broker/state.sock")
|
|
);
|
|
assert_eq!(c.audit_dir(), PathBuf::from("/srv/boxmaker/audit"));
|
|
assert_eq!(
|
|
c.state_dir(),
|
|
PathBuf::from("/srv/boxmaker/broker/sessions")
|
|
);
|
|
// The grants are not under home: the owner writes them, brokerd only reads them.
|
|
assert_eq!(c.paths.grants, PathBuf::from("/etc/boxmaker/grants"));
|
|
}
|
|
|
|
#[test]
|
|
fn every_key_can_be_set() {
|
|
let c = Config::load(&fixture("full.toml")).unwrap();
|
|
assert_eq!(c.paths.home, PathBuf::from("/srv/boxmaker"));
|
|
assert_eq!(c.paths.grants, PathBuf::from("/srv/boxmaker-grants"));
|
|
assert_eq!(c.broker_socket(), PathBuf::from("/run/bx/broker.sock"));
|
|
assert_eq!(c.admin_socket(), PathBuf::from("/run/bx/admin.sock"));
|
|
assert_eq!(c.state_socket(), PathBuf::from("/run/bx/state.sock"));
|
|
assert_eq!(c.approvals.ttl_ms, 60_000);
|
|
// The two directories always follow home.
|
|
assert_eq!(c.audit_dir(), PathBuf::from("/srv/boxmaker/audit"));
|
|
assert_eq!(
|
|
c.state_dir(),
|
|
PathBuf::from("/srv/boxmaker/broker/sessions")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn one_socket_set_leaves_the_other_at_its_default() {
|
|
let c =
|
|
Config::parse("[paths]\nhome = \"/h\"\n[sockets]\nadmin = \"/x/admin.sock\"\n").unwrap();
|
|
assert_eq!(c.admin_socket(), PathBuf::from("/x/admin.sock"));
|
|
assert_eq!(
|
|
c.broker_socket(),
|
|
PathBuf::from("/h/run/loop-broker/broker.sock")
|
|
);
|
|
assert_eq!(
|
|
c.state_socket(),
|
|
PathBuf::from("/h/run/memory-broker/state.sock")
|
|
);
|
|
let c =
|
|
Config::parse("[paths]\nhome = \"/h\"\n[sockets]\nstate = \"/x/state.sock\"\n").unwrap();
|
|
assert_eq!(c.state_socket(), PathBuf::from("/x/state.sock"));
|
|
assert_eq!(
|
|
c.broker_socket(),
|
|
PathBuf::from("/h/run/loop-broker/broker.sock")
|
|
);
|
|
assert_eq!(
|
|
c.admin_socket(),
|
|
PathBuf::from("/h/run/owner-broker/admin.sock")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_keys_and_tables_are_errors() {
|
|
for name in ["unknown_key.toml", "unknown_table.toml", "wrong_type.toml"] {
|
|
match Config::load(&fixture(name)) {
|
|
Err(ConfigError::Parse(path, _)) => assert_eq!(path, fixture(name)),
|
|
other => panic!("{name}: expected a parse error, got {other:?}"),
|
|
}
|
|
}
|
|
// In every table, not only the one the fixture shows.
|
|
for text in [
|
|
"[paths]\nhome = \"/h\"\nhouse = \"/h\"\n",
|
|
"[sockets]\nbroker = \"/b.sock\"\nloop = \"/l.sock\"\n",
|
|
"[approvals]\nttl_ms = 1\nttl_s = 1\n",
|
|
"top = 1\n",
|
|
"[approvals]\nttl_ms = -5\n",
|
|
] {
|
|
assert!(Config::parse(text).is_err(), "accepted: {text}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_missing_file_is_a_read_error_that_names_the_file() {
|
|
let path = fixture("does-not-exist.toml");
|
|
match Config::load(&path) {
|
|
Err(ConfigError::Read(p, _)) => assert_eq!(p, path),
|
|
other => panic!("expected a read error, got {other:?}"),
|
|
}
|
|
let text = Config::load(&path).unwrap_err().to_string();
|
|
assert!(text.contains("does-not-exist.toml"), "{text}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_parse_error_names_the_file_and_the_key() {
|
|
let text = Config::load(&fixture("unknown_key.toml"))
|
|
.unwrap_err()
|
|
.to_string();
|
|
assert!(text.contains("unknown_key.toml"), "{text}");
|
|
assert!(text.contains("ttl"), "{text}");
|
|
}
|