//! The speaker `deliver` is given goes on the turn it sends to `loopd`, and nowhere else: not into //! the turn's text (M5c spec, section 11). 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; use bxcore::{ErrorCode, SessionId, Turn}; use fake_loop::{done, error, serve_loop}; use gatewayd::deliver::{Notice, Poster, deliver}; use gatewayd::mm::MmError; use gatewayd::sessions::{Batch, Thread}; use tmp::TempDir; const DM: &str = "d0000000000000000000000000"; const ROOT: &str = "r0000000000000000000000000"; const KYLE: &str = "k0000000000000000000000000"; const WAIT: Duration = Duration::from_secs(5); #[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(()) } } /// One `deliver` of "one\n\ntwo" with `speaker`, against a fake `loopd` whose n-th turn gets /// `script(n)`; the turns it received and what was posted. fn deliver_with( tag: &str, speaker: Option<&str>, script: fn(usize) -> Vec, ) -> (Vec, Vec) { let dir = TempDir::new(tag); let socket = dir.path().join("loop.sock"); let turns = serve_loop(&socket, move |n, _| script(n)); let batch = Batch { session: SessionId::new(&format!("mm-{ROOT}")).unwrap(), thread: Thread { channel: DM.to_string(), root: ROOT.to_string(), }, resume: true, text: "one\n\ntwo".to_string(), senders: vec![KYLE.to_string(), KYLE.to_string()], }; let never = |_: u64| false; let notice = Notice { posted: &never, wait: Duration::ZERO, }; let poster = Record::default(); let log = Mutex::new(Vec::new()); deliver( &poster, &socket, &batch, speaker, &|line| log.lock().unwrap().push(line.to_string()), ¬ice, ); assert!(log.lock().unwrap().is_empty(), "{:?}", log.lock().unwrap()); // The fake reports each turn before it answers, so every turn is in by now. let mut got = vec![turns.recv_timeout(WAIT).unwrap()]; while let Ok(turn) = turns.try_recv() { got.push(turn); } (got, poster.0.into_inner().unwrap()) } fn answer(_: usize) -> Vec { vec![done("fine")] } #[test] fn the_speaker_goes_on_the_turn_and_not_into_its_text() { let (turns, posts) = deliver_with("speaker-some", Some("first.last"), answer); assert_eq!(turns.len(), 1); let turn = &turns[0]; assert_eq!(turn.speaker.as_deref(), Some("first.last")); assert_eq!( (turn.content.as_str(), turn.resume), ("one\n\ntwo", true), "the text is the batch's, unchanged" ); assert_eq!(posts, ["fine"]); } #[test] fn without_a_speaker_the_turn_has_none() { let (turns, posts) = deliver_with("speaker-none", None, answer); assert_eq!(turns.len(), 1); assert_eq!(turns[0].speaker, None, "never the user id"); assert_eq!(turns[0].content, "one\n\ntwo"); assert_eq!(posts, ["fine"]); } #[test] fn the_turn_sent_again_for_an_unknown_session_keeps_the_speaker() { let unknown_first = |n: usize| { if n == 0 { vec![error(ErrorCode::NoSuchSession, "no such session")] } else { vec![done("hello")] } }; let (turns, posts) = deliver_with("speaker-again", Some("kyle"), unknown_first); let seen: Vec<(bool, Option<&str>)> = turns .iter() .map(|t| (t.resume, t.speaker.as_deref())) .collect(); assert_eq!(seen, [(true, Some("kyle")), (false, Some("kyle"))]); assert_eq!(posts, ["hello"]); }