use std::fs; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; use std::sync::atomic::{AtomicU64, Ordering}; const MASTER: &str = "11111111-2222-3333-4444-555555555555"; const SESSION: &str = "22222222-3333-4444-5555-666666666666"; const OTHER: &str = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"; const SELF_ID: &str = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; const EXIT_USAGE: i32 = 2; const EXIT_INPUT: i32 = 3; static TEMP_ID: AtomicU64 = AtomicU64::new(0); struct TestDir(PathBuf); impl TestDir { fn new(name: &str) -> Self { let id = TEMP_ID.fetch_add(1, Ordering::Relaxed); let path = std::env::temp_dir().join(format!( "metacrate-irc-gateway-{name}-{}-{id}", std::process::id() )); fs::create_dir(&path).expect("create test directory"); Self(path) } fn write(&self, contents: &str) -> PathBuf { let path = self.0.join("bridge.tsv"); fs::write(&path, contents).expect("write bridge script"); path } } impl Drop for TestDir { fn drop(&mut self) { let _ = fs::remove_dir_all(&self.0); } } fn path_text(path: &Path) -> &str { path.to_str().expect("UTF-8 path") } fn run(args: &[&str]) -> Output { Command::new(env!("CARGO_BIN_EXE_irc-gateway")) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() .expect("run irc-gateway") } fn stdout(output: &Output) -> &str { std::str::from_utf8(&output.stdout).expect("UTF-8 stdout") } fn stderr(output: &Output) -> &str { std::str::from_utf8(&output.stderr).expect("UTF-8 stderr") } fn assert_exit(output: &Output, code: i32) { assert_eq!( output.status.code(), Some(code), "stdout:\n{}\nstderr:\n{}", stdout(output), stderr(output) ); } #[test] fn help_preserves_upstream_arguments_and_documents_controls() { let output = run(&["--help"]); assert!(output.status.success()); let help = stdout(&output); for marker in [ "[FIRSTNAME]", "[LASTNAME]", "[PASSWORD]", "[MASTER_UUID]", "[IRC_HOST]", "[IRC_PORT]", "[#CHANNEL]", "--fake-script", "--messages-per-second", "--burst", "irc-connect", "grid-chat", "teleport", ] { assert!(help.contains(marker), "help omitted {marker}:\n{help}"); } let output = run(&[]); assert_exit(&output, EXIT_USAGE); } #[test] fn scripted_bidirectional_bridge_filters_maps_and_accepts_only_master() { let directory = TestDir::new("bidirectional"); let script = directory.write(&format!( "irc-connect\n\ grid-login\tconnected to https://grid.invalid/cap/private\n\ grid-chat\t{OTHER}\tAlice \tnormal\tfull\thello IRC ☃\n\ grid-chat\t{OTHER}\tAlice \twhisper\tfull\tignored type\n\ grid-chat\t{OTHER}\tAlice \tnormal\tpartial\tignored range\n\ grid-chat\t{SELF_ID}\tGateway Resident\tnormal\tfull\tignored self\n\ irc\t#metacrate\tBob\thello grid ☃\n\ irc\t#other\tMallory\tignored channel\n\ irc\t#metacrate\tSLGateway\tignored self\n\ teleport\t{OTHER}\t{SESSION}\n\ teleport\t{MASTER}\t{SESSION}\n\ shutdown\n" )); let output = run(&["--fake-script", path_text(&script)]); assert!(output.status.success(), "{}", stderr(&output)); assert!(output.stderr.is_empty()); let text = stdout(&output); for expected in [ "CALL irc-join #metacrate", "CALL irc-privmsg #metacrate hello IRC ☃", "CALL grid-chat channel=0 type=Normal hello grid ☃", &format!("CALL teleport-lure-respond {MASTER} {SESSION} accept=true"), "active-transports=0 active-tasks=0 shutdown=true", ] { assert!(text.contains(expected), "missing {expected}:\n{text}"); } for rejected in [ "ignored type", "ignored range", "ignored self", "ignored channel", &format!("teleport-lure-respond {OTHER}"), "grid.invalid", "/cap/private", ] { assert!( !text.contains(rejected), "leaked/forwarded {rejected}:\n{text}" ); } assert!(text.contains("")); } #[test] fn reconnect_rate_limit_duplicate_and_loop_scenarios_are_deterministic() { let directory = TestDir::new("resilience"); let script = directory.write(&format!( "irc-connect\n\ grid-chat\t{OTHER}\tAlice\tnormal\tfull\tone\n\ grid-chat\t{OTHER}\tAlice\tnormal\tfull\ttwo\n\ grid-chat\t{OTHER}\tAlice\tnormal\tfull\ttwo\n\ irc\t#metacrate\tRelay\t one\n\ irc-disconnect\n\ grid-chat\t{OTHER}\tAlice\tnormal\tfull\twhile offline\n\ advance\t1000\n\ irc-connect\n\ advance\t1000\n\ shutdown\n" )); let output = run(&[ "--fake-script", path_text(&script), "--messages-per-second", "1", "--burst", "1", ]); assert!(output.status.success(), "{}", stderr(&output)); let text = stdout(&output); assert_eq!( text.matches("CALL irc-join #metacrate").count(), 2, "{text}" ); assert_eq!( text.matches(" two").count(), 2, "one log and one transport call expected:\n{text}" ); assert_eq!( text.matches(" one").count(), 2, "one log and one call expected:\n{text}" ); assert!( !text.contains("CALL grid-chat"), "loop was reflected:\n{text}" ); assert!( text.contains("CALL irc-privmsg #metacrate while offline"), "{text}" ); assert!(text.contains("queued=0"), "{text}"); } #[test] fn malformed_and_oversized_inputs_fail_without_network_access() { let directory = TestDir::new("invalid"); let invalid = directory.write("irc\tmissing fields\n"); let output = run(&["--fake-script", path_text(&invalid)]); assert_exit(&output, EXIT_INPUT); assert!(stderr(&output).contains("line 1")); let oversized = directory.write(&format!("grid-login\t{}\n", "x".repeat(4097))); let output = run(&["--fake-script", path_text(&oversized)]); assert_exit(&output, EXIT_INPUT); let output = run(&[ "First", "Last", "secret", "not-a-uuid", "irc.invalid", "6667", "#room", ]); assert_exit(&output, EXIT_USAGE); assert!(!stderr(&output).contains("secret")); }