//! Which commands answer an approval, in the order of the M4b spec, section 7; everything else is //! answered with its reason and changes nothing. Do not edit. use std::sync::mpsc::{self, Receiver}; use gatewayd::approvals::{Judged, NOT_APPROVER, Posted, Registry, USAGE}; use gatewayd::sessions::ApprovalCommand::{self, Approve, Deny, Malformed}; use proto::ApprovalReply; const KYLE: &str = "k0000000000000000000000000"; const HERMES: &str = "h0000000000000000000000000"; const DM: &str = "d0000000000000000000000000"; const ROOT: &str = "r0000000000000000000000000"; const ELSEWHERE: &str = "e0000000000000000000000000"; const CMD: &str = "c0000000000000000000000001"; fn approvers() -> Vec { vec![KYLE.to_string()] } /// A registry with approval 42 posted in ROOT, shown at 100. fn posted(answerable: bool) -> (Registry, Receiver) { let registry = Registry::new(); let (tx, rx) = mpsc::channel(); registry.add(Posted { approval: 42, session: format!("mm-{ROOT}"), channel: DM.to_string(), root: ROOT.to_string(), shown_at: 100, answerable, answer: tx, }); (registry, rx) } fn judge(r: &Registry, command: &ApprovalCommand, user: &str, root: &str, at: i64) -> Judged { r.judge(command, user, root, CMD, at, &approvers()) } #[test] fn an_approver_in_the_thread_after_the_post_answers_it() { let (r, rx) = posted(true); assert_eq!(judge(&r, &Approve(42), KYLE, ROOT, 101), Judged::Sent); assert_eq!( rx.try_recv().unwrap(), ApprovalReply { approval: 42, approved: true, by: KYLE.to_string(), post: CMD.to_string(), reason: None } ); assert!(!r.holds(42), "answered once"); assert_eq!( judge(&r, &Approve(42), KYLE, ROOT, 102), Judged::Reply("approval 42 is not pending here".to_string()) ); assert!(rx.try_recv().is_err(), "a second answer goes nowhere"); } #[test] fn a_refusal_carries_its_reason() { let (r, rx) = posted(true); assert_eq!( judge( &r, &Deny(42, Some("not on a Friday".to_string())), KYLE, ROOT, 101 ), Judged::Sent ); let got = rx.try_recv().unwrap(); assert_eq!( (got.approved, got.reason.as_deref()), (false, Some("not on a Friday")) ); let (r, rx) = posted(true); assert_eq!(judge(&r, &Deny(42, None), KYLE, ROOT, 101), Judged::Sent); assert_eq!(rx.try_recv().unwrap().reason, None); } #[test] fn everything_else_is_refused_with_its_reason_in_order() { let cases: &[(&ApprovalCommand, &str, &str, i64, &str)] = &[ (&Approve(42), HERMES, ROOT, 101, NOT_APPROVER), (&Malformed, HERMES, ROOT, 101, NOT_APPROVER), (&Malformed, KYLE, ROOT, 101, USAGE), ( &Approve(7), KYLE, ROOT, 101, "approval 7 is not pending here", ), ( &Approve(42), KYLE, ELSEWHERE, 101, "approval 42 is answered in its own thread", ), ( &Approve(42), KYLE, ROOT, 100, "approval 42 was answered before it was shown in full", ), ( &Approve(42), KYLE, ROOT, 50, "approval 42 was answered before it was shown in full", ), ( &Approve(42), KYLE, ELSEWHERE, 50, "approval 42 is answered in its own thread", ), ]; for (command, user, root, at, want) in cases { let (r, rx) = posted(true); assert_eq!( judge(&r, command, user, root, *at), Judged::Reply(want.to_string()), "{command:?} {user} {root} {at}" ); assert!(r.holds(42), "still pending after {command:?}"); assert!(rx.try_recv().is_err(), "nothing sent for {command:?}"); } } #[test] fn too_long_to_show_is_answered_only_in_bxctl() { let (r, rx) = posted(false); assert_eq!( judge(&r, &Approve(42), KYLE, ROOT, 101), Judged::Reply("approval 42 can only be answered with bxctl approvals".to_string()) ); assert!(r.holds(42) && rx.try_recv().is_err()); } #[test] fn an_answer_whose_connection_is_gone_is_not_pending() { let (r, rx) = posted(true); drop(rx); assert_eq!( judge(&r, &Approve(42), KYLE, ROOT, 101), Judged::Reply("approval 42 is not pending here".to_string()) ); } #[test] fn the_waiting_sessions_and_removal() { let (r, _rx) = posted(true); assert_eq!(r.waiting_sessions(), [format!("mm-{ROOT}")]); assert_eq!(r.remove(42).map(|p| p.approval), Some(42)); assert!(r.waiting_sessions().is_empty() && !r.holds(42) && r.remove(42).is_none()); }