brokerd: answers carry the Mattermost post; Table::contains
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
@@ -73,6 +73,7 @@ fn answer_entry(
|
||||
fn approved() -> Answer {
|
||||
Answer::Approved {
|
||||
by: Some("bxctl".to_string()),
|
||||
post: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +167,7 @@ fn a_denied_verdict_reaches_the_requester_and_nothing_runs() {
|
||||
let (mut stream, approval, _) = start(&broker, r#"{"path":"/n/a"}"#);
|
||||
let refused = Answer::Refused {
|
||||
by: Some("bxctl".to_string()),
|
||||
post: None,
|
||||
reason: None,
|
||||
};
|
||||
answer_by_hand(&broker, approval, refused);
|
||||
|
||||
@@ -177,10 +177,14 @@ fn approved_and_refused_calls_run_only_after_an_approval() {
|
||||
continue;
|
||||
};
|
||||
let answer = if item.approval % 2 == 0 {
|
||||
Answer::Approved { by: None }
|
||||
Answer::Approved {
|
||||
by: None,
|
||||
post: None,
|
||||
}
|
||||
} else {
|
||||
Answer::Refused {
|
||||
by: None,
|
||||
post: None,
|
||||
reason: None,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ fn pending(ledger: &Ledger) -> Ask {
|
||||
fn bxctl() -> Answer {
|
||||
Answer::Approved {
|
||||
by: Some("bxctl".to_string()),
|
||||
post: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +207,7 @@ fn a_refusal_is_recorded_with_the_owners_reason() {
|
||||
let ask = pending(&ledger);
|
||||
let refused = Answer::Refused {
|
||||
by: Some("bxctl".to_string()),
|
||||
post: None,
|
||||
reason: Some("not now".to_string()),
|
||||
};
|
||||
let answered = ledger.answer(ask, 0, refused, &asking(), now());
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
//! 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 proto::{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));
|
||||
}
|
||||
Reference in New Issue
Block a user