//! The notice for an approval that `brokerd` did not bring: posted only when it has not arrived //! within the wait (M4b spec, section 7). Do not edit. #[path = "support/fake_loop.rs"] mod fake_loop; #[path = "support/tmp.rs"] mod tmp; use std::sync::Mutex; use std::time::{Duration, Instant}; use fake_loop::{done, event, serve_loop}; use gatewayd::deliver::{Notice, Poster, deliver}; use gatewayd::mm::MmError; use gatewayd::sessions::{Batch, Thread}; use proto::{SessionId, Timestamp, TurnEvent}; use tmp::TempDir; const DM: &str = "d0000000000000000000000000"; const ROOT: &str = "r0000000000000000000000000"; const NOTICE: &str = "waiting for approval 42: brokerd has not sent it here; answer it with bxctl approvals"; #[derive(Default)] struct Record(Mutex>); impl Poster for Record { fn post(&self, _: &str, _: &str, text: &str) -> Result<(), MmError> { self.0.lock().unwrap().push(text.to_string()); Ok(()) } } /// A turn whose loop says approval 42 is pending, then answers; what was posted, and how long it took. fn turn(tag: &str, posted: &dyn Fn(u64) -> bool, wait: Duration) -> (Vec, Duration) { let dir = TempDir::new(tag); let socket = dir.path().join("loop.sock"); let expires = Timestamp::parse("2026-09-24T08:20:00.000Z").unwrap(); let _turns = serve_loop(&socket, move |_, _| { vec![ event(TurnEvent::ApprovalPending { approval: 42, tool: "read_file".to_string(), expires, }), done("done"), ] }); let batch = Batch { session: SessionId::new(&format!("mm-{ROOT}")).unwrap(), thread: Thread { channel: DM.to_string(), root: ROOT.to_string(), }, resume: false, text: "go".to_string(), }; let poster = Record::default(); let started = Instant::now(); deliver(&poster, &socket, &batch, &|_| {}, &Notice { posted, wait }); let took = started.elapsed(); (poster.0.into_inner().unwrap(), took) } #[test] fn brought_already_no_notice_and_no_wait() { let (posts, took) = turn("notice-there", &|id| id == 42, Duration::from_secs(5)); assert_eq!(posts, ["done"]); assert!(took < Duration::from_secs(1), "{took:?}"); } #[test] fn brought_during_the_wait_no_notice() { let started = Instant::now(); let later = move |id: u64| id == 42 && started.elapsed() > Duration::from_millis(300); let (posts, took) = turn("notice-later", &later, Duration::from_secs(5)); assert_eq!(posts, ["done"]); assert!(took < Duration::from_secs(2), "{took:?}"); } #[test] fn never_brought_the_notice_after_the_wait() { let (posts, took) = turn("notice-never", &|_| false, Duration::from_millis(400)); assert_eq!(posts, [NOTICE, "done"]); assert!( took >= Duration::from_millis(400) && took < Duration::from_secs(3), "{took:?}" ); } #[test] fn another_approval_brought_does_not_count() { let (posts, _) = turn("notice-other", &|id| id == 7, Duration::from_millis(200)); assert_eq!(posts, [NOTICE, "done"]); }