Files
boxmaker/crates/brokerd/tests/ledger_post.rs
T
kyleandClaude Opus 5.5 2b3ae080b4 proto becomes bxcore, the one shared crate (M5b task 00)
Done the way the owner asked and the spike mapped: the crate and the
manifests renamed, then what the build reported fixed. Layers: 138
"proto unresolved" errors across 169 files; cargo fmt reordering
imports; six fixture paths built as strings in brokerd's and bxctl's
tests, which the compiler cannot see, and comments naming
crates/proto/; the crate-dependency check and its self-test. One more
the spike did not reach: a `\nproto =` key inside a printf format in
the self-test, which a word-boundary replacement skips because `n` and
`p` are both word characters.

make gate: gate: ok, 944 tests; no new entry in /tmp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:48:38 -07:00

119 lines
3.2 KiB
Rust

//! An answer from Mattermost records who answered and in which post, approved or refused; the
//! table says whether an approval is still pending (M4b spec, section 5). Do not edit.
#[path = "support/build.rs"]
mod build;
#[path = "support/rig.rs"]
mod rig;
#[path = "support/sink.rs"]
mod sink;
#[path = "support/tmp.rs"]
mod tmp;
use brokerd::approvals::Table;
use brokerd::ledger::{Answer, Decided, Grants, Ledger};
use brokerd::policy::Ask;
use build::{grant, now, read, set};
use bxcore::{ApprovalAnswer, AuditEvent, CallId, DataClass, Mode, PendingApproval, SessionId};
use rig::Rig;
const KYLE: &str = "k0000000000000000000000000";
const POST: &str = "c0000000000000000000000001";
fn asking() -> Grants {
Ok(set(vec![grant("n", "read_file", Mode::Ask).paths(&["/n"])]))
}
fn pending(ledger: &Ledger) -> Ask {
match ledger.decide(read("/n/deep/a"), &asking(), now()) {
Decided::Ask { ask, seq: 0, .. } => ask,
other => panic!("{other:?}"),
}
}
/// The one `Approval` record's (answer, by, post, reason).
fn recorded(
rig: &Rig,
) -> (
ApprovalAnswer,
Option<String>,
Option<String>,
Option<String>,
) {
let events = rig.events();
match &events[1] {
AuditEvent::Approval {
answer,
by,
post,
reason,
..
} => (*answer, by.clone(), post.clone(), reason.clone()),
other => panic!("{other:?}"),
}
}
#[test]
fn an_approval_from_mattermost_names_the_user_and_the_post() {
let rig = Rig::new("post-approve");
let ledger = rig.ledger();
let answer = Answer::Approved {
by: Some(KYLE.to_string()),
post: Some(POST.to_string()),
};
ledger.answer(pending(&ledger), 0, answer, &asking(), now());
assert_eq!(
recorded(&rig),
(
ApprovalAnswer::Approved,
Some(KYLE.to_string()),
Some(POST.to_string()),
None
)
);
}
#[test]
fn a_refusal_from_mattermost_names_them_and_keeps_the_reason() {
let rig = Rig::new("post-refuse");
let ledger = rig.ledger();
let answer = Answer::Refused {
by: Some(KYLE.to_string()),
post: Some(POST.to_string()),
reason: Some("not on a Friday".to_string()),
};
ledger.answer(pending(&ledger), 0, answer, &asking(), now());
assert_eq!(
recorded(&rig),
(
ApprovalAnswer::Refused,
Some(KYLE.to_string()),
Some(POST.to_string()),
Some("not on a Friday".to_string())
)
);
}
#[test]
fn the_table_says_what_is_pending() {
let rig = Rig::new("post-table");
let ledger = rig.ledger();
let table = Table::new();
assert!(!table.contains(0));
let info = PendingApproval {
approval: 0,
session: SessionId::new("s1").unwrap(),
call: CallId(1),
tool: "read_file".to_string(),
arguments: "{}".to_string(),
grant: "n".to_string(),
taint: DataClass::Private,
created: now(),
expires: now(),
};
let _verdict = table.insert(info, pending(&ledger));
assert!(table.contains(0) && !table.contains(1));
assert!(table.take(0).is_some());
assert!(!table.contains(0));
}