//! Tests for `bxctl`'s admin client and the commands built on it, against a fake `brokerd`. //! Do not edit. mod support; use bxctl::admin::{ AdminError, cmd_approvals, cmd_approve, cmd_grants_check, cmd_refuse, list, reason_name, request, write_block, }; use proto::{ Approve, DecisionRecord, DenyReason, Empty, Envelope, ErrorCode, GrantProblem, GrantsReport, Message, PROTOCOL_VERSION, Refuse, }; use std::process::Command; use support::{brokerd_with, fake_brokerd, fake_brokerd_frames, pending, ts, wire_error}; const BACKSLASH: char = '\\'; /// The escape for one code point, built and never spelled out (see `tests/escape.rs`). fn esc(code: u32) -> String { format!("{BACKSLASH}u{code:04x}") } fn text(out: Vec) -> String { String::from_utf8(out).unwrap() } const ALLOWED: DecisionRecord = DecisionRecord::Allowed {}; // ---- the client ---- #[test] fn request_sends_one_final_frame_with_id_1_and_returns_the_answer() { let fake = fake_brokerd_frames(|request| { assert_eq!(request.v, PROTOCOL_VERSION); assert_eq!(request.id, 1); assert!(request.r#final); vec![Envelope { v: PROTOCOL_VERSION, id: 1, r#final: true, msg: Message::Ok(Empty {}), }] }); let answer = request(&fake.socket, Message::CheckGrants(Empty {})).unwrap(); assert_eq!(answer, Message::Ok(Empty {})); assert_eq!(fake.requests(), vec![Message::CheckGrants(Empty {})]); } #[test] fn an_error_frame_is_refused_with_its_code_and_detail() { let fake = fake_brokerd(|_| wire_error(ErrorCode::Forbidden, "not on this socket")); match request(&fake.socket, Message::Approvals(Empty {})) { Err(AdminError::Refused(w)) => { assert_eq!(w.code, ErrorCode::Forbidden); assert_eq!(w.detail, "not on this socket"); } other => panic!("{other:?}"), } let e = request(&fake.socket, Message::Approvals(Empty {})).unwrap_err(); assert_eq!(e.to_string(), "forbidden: not on this socket"); } #[test] fn an_answer_that_is_not_final_or_has_another_id_is_a_protocol_error() { let not_final = fake_brokerd_frames(|request| { vec![Envelope { v: PROTOCOL_VERSION, id: request.id, r#final: false, msg: Message::Ok(Empty {}), }] }); assert!(matches!( request(¬_final.socket, Message::Approvals(Empty {})), Err(AdminError::Protocol(_)) )); let other_id = fake_brokerd_frames(|request| { vec![Envelope { v: PROTOCOL_VERSION, id: request.id + 1, r#final: true, msg: Message::Ok(Empty {}), }] }); assert!(matches!( request(&other_id.socket, Message::Approvals(Empty {})), Err(AdminError::Protocol(_)) )); } #[test] fn a_connection_closed_without_an_answer_is_a_frame_error() { let fake = fake_brokerd_frames(|_| Vec::new()); assert!(matches!( request(&fake.socket, Message::Approvals(Empty {})), Err(AdminError::Frame(_)) )); } #[test] fn no_brokerd_is_a_connect_error_that_names_the_socket() { let missing = support::temp_socket("nobody-listens.sock"); let e = request(&missing, Message::Approvals(Empty {})).unwrap_err(); assert!(matches!(e, AdminError::Connect(_, _)), "{e:?}"); let message = e.to_string(); assert!(message.starts_with("cannot reach brokerd at "), "{message}"); assert!(message.contains(missing.to_str().unwrap()), "{message}"); } #[test] fn list_returns_the_items_and_rejects_any_other_kind() { let items = vec![pending(41, "shell", "{}"), pending(44, "read_file", "{}")]; let fake = brokerd_with(items.clone(), ALLOWED); assert_eq!(list(&fake.socket).unwrap(), items); assert_eq!(fake.requests(), vec![Message::Approvals(Empty {})]); let wrong = fake_brokerd(|_| Message::Ok(Empty {})); assert!(matches!(list(&wrong.socket), Err(AdminError::Protocol(_)))); } #[test] fn reason_names_are_the_wire_names() { for reason in [ DenyReason::NoGrant, DenyReason::GrantExpired, DenyReason::TaintTooHigh, DenyReason::DeniedByGrant, DenyReason::ApprovalRefused, DenyReason::ApprovalExpired, DenyReason::GrantsInvalid, DenyReason::AuditUnavailable, DenyReason::InvalidArguments, DenyReason::StateUnreadable, ] { let wire = serde_json::to_string(&reason).unwrap(); assert_eq!(format!("\"{}\"", reason_name(reason)), wire); } } // ---- the block ---- fn block(item: &proto::PendingApproval, now: &str) -> String { let mut out = Vec::new(); write_block(&mut out, item, ts(now)).unwrap(); text(out) } #[test] fn the_block_is_two_lines_in_this_exact_form() { let item = pending( 41, "shell", r#"{"command":"rm -rf /home/kyle/scratch/build","cwd":"/home/kyle/scratch"}"#, ); assert_eq!( block(&item, "2026-09-18T12:02:00.000Z"), concat!( "41 2 min ago expires in 13 min session chat-1758… grant shell-scratch taint private\n", " shell {\"command\":\"rm -rf /home/kyle/scratch/build\",\"cwd\":\"/home/kyle/scratch\"}\n", ) ); } #[test] fn times_are_whole_seconds_minutes_or_hours_rounded_down() { let item = pending(41, "shell", "{}"); let first = |now: &str| block(&item, now).lines().next().unwrap().to_string(); // created 12:00:00, expires 12:15:00 assert!(first("2026-09-18T12:00:00.000Z").starts_with("41 0 s ago expires in 15 min ")); assert!(first("2026-09-18T12:00:59.999Z").starts_with("41 59 s ago expires in 14 min ")); assert!(first("2026-09-18T12:01:00.000Z").starts_with("41 1 min ago expires in 14 min ")); assert!(first("2026-09-18T12:14:30.000Z").starts_with("41 14 min ago expires in 30 s ")); // At or after `expires` there is nothing left to wait for. assert!(first("2026-09-18T12:15:00.000Z").starts_with("41 15 min ago expired ")); assert!(first("2026-09-18T14:05:00.000Z").starts_with("41 2 h ago expired ")); // A clock that is behind the broker's must not underflow. assert!(first("2026-09-18T11:59:00.000Z").starts_with("41 0 s ago expires in 16 min ")); let mut long = pending(41, "shell", "{}"); long.expires = ts("2026-09-18T15:30:00.000Z"); assert!(block(&long, "2026-09-18T12:00:00.000Z").starts_with("41 0 s ago expires in 3 h ")); } #[test] fn a_session_id_longer_than_ten_characters_is_cut_to_nine_and_an_ellipsis() { let mut item = pending(41, "shell", "{}"); for (id, shown) in [ ("s1", "session s1 "), ("abcdefghij", "session abcdefghij "), ("abcdefghijk", "session abcdefghi… "), ] { item.session = proto::SessionId::new(id).unwrap(); let got = block(&item, "2026-09-18T12:00:00.000Z"); assert!(got.contains(shown), "{id}: {got}"); } } #[test] fn taint_is_the_wire_name() { let mut item = pending(41, "shell", "{}"); item.taint = proto::DataClass::Secret; assert!(block(&item, "2026-09-18T12:00:00.000Z").contains(" taint secret\n")); } /// Whatever `brokerd` sends is printed as data: tool, grant and arguments all go through /// `escape_json_text`. #[test] fn nothing_in_the_block_reaches_the_terminal_raw() { let rlo = char::from_u32(0x202e).unwrap(); let zwsp = char::from_u32(0x200b).unwrap(); let isolate = char::from_u32(0x2066).unwrap(); let arguments = format!("{{\"path\":\"/home/kyle/notes/{rlo}dm.terces{zwsp}{isolate}\x1b[8m\x1b[2J\"}}"); let mut item = pending(41, "read\x1b[1mfile", &arguments); item.grant = "notes\nread".to_string(); let got = block(&item, "2026-09-18T12:00:00.000Z"); for c in ['\x1b', rlo, zwsp, isolate] { assert!(!got.contains(c), "U+{:04X} in {got:?}", u32::from(c)); } assert_eq!(got.matches('\n').count(), 2, "still two lines: {got:?}"); assert!( got.contains(&format!( "/home/kyle/notes/{}dm.terces{}{}{}[8m{}[2J", esc(0x202e), esc(0x200b), esc(0x2066), esc(0x1b), esc(0x1b) )), "{got:?}" ); assert!( got.contains(&format!(" read{}[1mfile ", esc(0x1b))), "{got:?}" ); assert!( got.contains(&format!("grant notes{}read ", esc(0x0a))), "{got:?}" ); } // ---- the commands ---- #[test] fn approvals_prints_one_block_per_item_in_the_order_given() { let fake = brokerd_with( vec![ pending(41, "shell", r#"{"command":"ls"}"#), pending(44, "read_file", r#"{"path":"/home/kyle/notes/a.md"}"#), ], ALLOWED, ); let mut out = Vec::new(); let ok = cmd_approvals(&fake.socket, ts("2026-09-18T12:02:00.000Z"), &mut out).unwrap(); assert!(ok); let got = text(out); let lines: Vec<&str> = got.lines().collect(); assert_eq!(lines.len(), 4, "{got}"); assert!(lines[0].starts_with("41 2 min ago "), "{got}"); assert_eq!(lines[1], r#" shell {"command":"ls"}"#); assert!(lines[2].starts_with("44 2 min ago "), "{got}"); assert_eq!( lines[3], r#" read_file {"path":"/home/kyle/notes/a.md"}"# ); } #[test] fn approvals_with_nothing_pending_says_so() { let fake = brokerd_with(Vec::new(), ALLOWED); let mut out = Vec::new(); assert!(cmd_approvals(&fake.socket, ts("2026-09-18T12:00:00.000Z"), &mut out).unwrap()); assert_eq!(text(out), "no pending approvals\n"); } #[test] fn approve_reports_the_re_decision() { // `ask` and `allowed` both let the call run; only a denial stops it. for (outcome, line, ok) in [ (DecisionRecord::Allowed {}, "approved 41: runs\n", true), (DecisionRecord::Ask {}, "approved 41: runs\n", true), ( DecisionRecord::Denied { reason: DenyReason::NoGrant, }, "approved 41: denied (no_grant)\n", false, ), ( DecisionRecord::Denied { reason: DenyReason::TaintTooHigh, }, "approved 41: denied (taint_too_high)\n", false, ), ( DecisionRecord::Denied { reason: DenyReason::AuditUnavailable, }, "approved 41: denied (audit_unavailable)\n", false, ), ] { let fake = brokerd_with(vec![pending(41, "shell", "{}")], outcome); let mut out = Vec::new(); assert_eq!(cmd_approve(&fake.socket, 41, &mut out).unwrap(), ok); assert_eq!(text(out), line); assert_eq!( fake.requests(), vec![Message::Approve(Approve { approval: 41 })] ); } } #[test] fn refuse_sends_the_reason_when_there_is_one() { for reason in [None, Some("not on a Friday")] { let fake = brokerd_with(vec![pending(41, "shell", "{}")], ALLOWED); let mut out = Vec::new(); assert!(cmd_refuse(&fake.socket, 41, reason, &mut out).unwrap()); assert_eq!(text(out), "refused 41\n"); assert_eq!( fake.requests(), vec![Message::Refuse(Refuse { approval: 41, reason: reason.map(str::to_string) })] ); } } #[test] fn an_unknown_or_answered_id_is_reported_the_same_way_by_both() { let fake = brokerd_with(vec![pending(41, "shell", "{}")], ALLOWED); let want = "99: no such approval (already answered or expired)\n"; let mut out = Vec::new(); assert!(!cmd_approve(&fake.socket, 99, &mut out).unwrap()); assert_eq!(text(out), want); let mut out = Vec::new(); assert!(!cmd_refuse(&fake.socket, 99, None, &mut out).unwrap()); assert_eq!(text(out), want); } /// Every exit of every command: any other error frame is an error, and so is an answer of the /// wrong kind. Nothing is printed for them. #[test] fn other_errors_and_wrong_kinds_are_errors_for_every_command() { let refused = fake_brokerd(|_| wire_error(ErrorCode::Internal, "boom")); let wrong = fake_brokerd(|_| { Message::GrantsReport(GrantsReport { problems: Vec::new(), }) }); let wrong_for_grants = fake_brokerd(|_| Message::Ok(Empty {})); let now = ts("2026-09-18T12:00:00.000Z"); let mut out = Vec::new(); assert!(matches!( cmd_approvals(&refused.socket, now, &mut out), Err(AdminError::Refused(_)) )); assert!(matches!( cmd_approve(&refused.socket, 41, &mut out), Err(AdminError::Refused(_)) )); assert!(matches!( cmd_refuse(&refused.socket, 41, None, &mut out), Err(AdminError::Refused(_)) )); assert!(matches!( cmd_grants_check(&refused.socket, &mut out), Err(AdminError::Refused(_)) )); assert!(matches!( cmd_approvals(&wrong.socket, now, &mut out), Err(AdminError::Protocol(_)) )); assert!(matches!( cmd_approve(&wrong.socket, 41, &mut out), Err(AdminError::Protocol(_)) )); assert!(matches!( cmd_refuse(&wrong.socket, 41, None, &mut out), Err(AdminError::Protocol(_)) )); assert!(matches!( cmd_grants_check(&wrong_for_grants.socket, &mut out), Err(AdminError::Protocol(_)) )); assert_eq!(text(out), "", "an error prints nothing on the output"); } #[test] fn grants_check_prints_ok_or_every_problem() { let fine = fake_brokerd(|_| { Message::GrantsReport(GrantsReport { problems: Vec::new(), }) }); let mut out = Vec::new(); assert!(cmd_grants_check(&fine.socket, &mut out).unwrap()); assert_eq!(text(out), "grants: ok\n"); assert_eq!(fine.requests(), vec![Message::CheckGrants(Empty {})]); let broken = fake_brokerd(|_| { Message::GrantsReport(GrantsReport { problems: vec![ GrantProblem { file: "notes-read.toml".to_string(), line: Some(3), problem: "unknown field `mod`".to_string(), }, GrantProblem { file: "Bad Name.toml".to_string(), line: None, problem: "the file name is not a valid grant id".to_string(), }, GrantProblem { file: "x.toml".to_string(), line: Some(1), problem: "two\nlines".to_string(), }, ], }) }); let mut out = Vec::new(); assert!(!cmd_grants_check(&broken.socket, &mut out).unwrap()); assert_eq!( text(out), format!( "notes-read.toml:3: unknown field `mod`\n\ Bad Name.toml: the file name is not a valid grant id\n\ x.toml:1: two{}lines\n", esc(0x0a) ), "every problem, one line each; file and problem go through escape_json_text" ); } // ---- the binary ---- fn bxctl(args: &[&str], socket: &std::path::Path) -> std::process::Output { Command::new(env!("CARGO_BIN_EXE_bxctl")) .args(args) .arg("--admin-socket") .arg(socket) .output() .unwrap() } #[test] fn the_binary_prints_on_stdout_and_sets_the_exit_status() { let fake = brokerd_with( vec![pending(41, "shell", r#"{"command":"ls"}"#)], DecisionRecord::Denied { reason: DenyReason::DeniedByGrant, }, ); let output = bxctl(&["approvals"], &fake.socket); assert_eq!(output.status.code(), Some(0)); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.ends_with(" shell {\"command\":\"ls\"}\n"), "{stdout}" ); let output = bxctl(&["approve", "41"], &fake.socket); assert_eq!(output.status.code(), Some(1)); assert_eq!( String::from_utf8_lossy(&output.stdout), "approved 41: denied (denied_by_grant)\n" ); let output = bxctl(&["refuse", "41", "--reason", "no"], &fake.socket); assert_eq!(output.status.code(), Some(0)); assert_eq!(String::from_utf8_lossy(&output.stdout), "refused 41\n"); let output = bxctl(&["refuse", "7"], &fake.socket); assert_eq!(output.status.code(), Some(1)); assert_eq!( String::from_utf8_lossy(&output.stdout), "7: no such approval (already answered or expired)\n" ); } #[test] fn the_binary_reports_an_error_on_stderr_with_status_1() { let fake = fake_brokerd(|_| wire_error(ErrorCode::Internal, "boom")); let output = bxctl(&["grants", "check"], &fake.socket); assert_eq!(output.status.code(), Some(1)); assert_eq!(String::from_utf8_lossy(&output.stdout), ""); assert_eq!( String::from_utf8_lossy(&output.stderr), "bxctl: internal: boom\n" ); }