Files
MetaCrate/programs/tests/test_client_cli.rs
Chili Palmer ea10b7672c
Some checks failed
Native code generation / deterministic (push) Successful in 18m25s
Imaging and meshing gate / native (push) Failing after 4m23s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 12m37s
Skia feature / linux (push) Has been cancelled
Implement TestClient inventory and appearance commands (#92)
2026-08-11 11:14:50 +00:00

276 lines
9.6 KiB
Rust

use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
const ALICE: &str = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
const BOB: &str = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff";
const MASTER: &str = "11111111-2222-3333-4444-555555555555";
const TARGET: &str = "22222222-3333-4444-5555-666666666666";
const GROUP: &str = "33333333-4444-5555-6666-777777777777";
const GROUP_MEMBER: &str = "44444444-5555-6666-7777-888888888888";
const STRANGER: &str = "55555555-6666-7777-8888-999999999999";
const SESSION: &str = "66666666-7777-8888-9999-aaaaaaaaaaaa";
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-test-client-{name}-{}-{id}",
std::process::id()
));
fs::create_dir(&path).expect("create test directory");
Self(path)
}
fn write(&self, name: &str, contents: &str) -> PathBuf {
let path = self.0.join(name);
fs::write(&path, contents).expect("write fixture");
path
}
fn path(&self, name: &str) -> PathBuf {
self.0.join(name)
}
}
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 test path")
}
fn run(args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_test-client"))
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("run test-client")
}
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, expected: i32) {
assert_eq!(
output.status.code(),
Some(expected),
"stdout:\n{}\nstderr:\n{}",
stdout(output),
stderr(output)
);
}
#[test]
fn help_documents_upstream_framework_and_owned_commands() {
let output = run(&["--help"]);
assert!(output.status.success());
let text = stdout(&output);
for marker in [
"--first",
"--last",
"--pass",
"--file",
"--loginuri",
"--startpos",
"--master",
"--masterkey",
"--groupcommands",
"--gettextures",
"--scriptfile",
"--nogui",
"--fake-script",
"--allow-live-mutations",
"--allow-spending",
"echomaster",
"imgroup",
"logpacket",
"waitforlogin",
] {
assert!(text.contains(marker), "help omitted {marker}:\n{text}");
}
let output = run(&["--first", "Only"]);
assert_exit(&output, EXIT_USAGE);
}
#[test]
fn scripted_terminal_exercises_registry_communication_system_and_remote_auth() {
let directory = TestDir::new("commands");
let packet_log = directory.path("packets.log");
let script = directory.write(
"terminal.tsv",
&format!(
"!client\t{ALICE}\tAlice\tBot\tMaster Resident\t{MASTER}\ttrue\n\
!client\t{BOB}\tBob\tBot\tMaster Resident\t{MASTER}\ttrue\n\
!person\tTarget Resident\t{TARGET}\n\
!group-member\t{GROUP_MEMBER}\n\
help say\n\
say 7 \"hello world\"\n\
whisper quiet\n\
shout loud\n\
@ Alice Bot\n\
im Target Resident private hello\n\
imgroup {GROUP} group hello\n\
echomaster\n\
showeffects on\n\
logpacket 2 \"{}\"\n\
!chat\t{ALICE}\t{MASTER}\tMaster Resident\techo this\n\
!effect\t{ALICE}\tViewerEffect [LookAt] source={MASTER}\n\
!packet\t{ALICE}\tChatFromSimulator\tFake Region\t120\n\
!packet\t{ALICE}\tImprovedInstantMessage\tFake Region\t240\n\
!im\t{ALICE}\t{MASTER}\tMaster Resident\tagent\tfalse\tsay remote works\t{SESSION}\n\
!im\t{ALICE}\t{GROUP_MEMBER}\tGroup Member\tagent\ttrue\twhisper group works\t{SESSION}\n\
!im\t{ALICE}\t{STRANGER}\tStranger Resident\tagent\tfalse\tlogin Victim Resident supersecret\t{SESSION}\n\
setmaster Target Resident\n\
setmasterkey {MASTER}\n\
!im\t{ALICE}\t{MASTER}\tMaster Resident\tteleport\tfalse\tlure\t{SESSION}\n\
sleep 0\n\
debug info\n\
md5 secret\n\
logout\n\
@\n\
say after logout\n\
quit\n",
path_text(&packet_log)
),
);
let output = run(&["--fake-script", path_text(&script)]);
assert!(output.status.success(), "{}", stderr(&output));
assert!(output.stderr.is_empty(), "{}", stderr(&output));
let text = stdout(&output);
for expected in [
"Say something. Usage: say [optional-channel] message",
"[Alice Bot] CALL chat channel=7 type=Normal hello world",
"[Bob Bot] CALL chat channel=7 type=Normal hello world",
"[Alice Bot] CALL instant-message 22222222-3333-4444-5555-666666666666 private hello",
"[Alice Bot] CALL group-chat-join 33333333-4444-5555-6666-777777777777",
"[Alice Bot] CALL group-instant-message 33333333-4444-5555-6666-777777777777 group hello",
"[Alice Bot] CALL chat channel=0 type=Normal echo this",
"ViewerEffect [LookAt] source=11111111-2222-3333-4444-555555555555",
"[Alice Bot] CALL chat channel=0 type=Normal remote works",
"[Alice Bot] CALL chat channel=0 type=Whisper group works",
"<sensitive command redacted>",
"Master set to Target Resident",
"CALL teleport-lure-respond",
"CALL agent-pause",
"CALL agent-resume",
"Logging is set to Info",
"$1$5ebe2294ecd0e0f08eab7690d2a6ee69",
"[Bob Bot] CALL chat channel=0 type=Normal after logout",
"active-tasks=0 shutdown=true",
] {
assert!(text.contains(expected), "missing {expected}:\n{text}");
}
assert!(!text.contains("supersecret"), "credential leaked:\n{text}");
assert_eq!(text.matches("CALL logout-dispose").count(), 2, "{text}");
let packet_text = fs::read_to_string(packet_log).expect("read packet log");
assert_eq!(packet_text.lines().count(), 2, "{packet_text}");
assert!(packet_text.contains("ChatFromSimulator"));
assert!(packet_text.contains("ImprovedInstantMessage"));
}
#[test]
fn login_replaces_duplicate_client_and_waitforlogin_is_deterministic() {
let directory = TestDir::new("login");
let script = directory.write(
"terminal.tsv",
"login Carol Bot credential-one\n\
waitforlogin\n\
login Carol Bot credential-two\n\
say only replacement\n\
quit\n",
);
let output = run(&["--fake-script", path_text(&script)]);
assert!(output.status.success(), "{}", stderr(&output));
let text = stdout(&output);
assert!(text.contains("currently tracking 1 bots"), "{text}");
assert_eq!(text.matches("Logged in Carol Bot").count(), 2, "{text}");
assert_eq!(
text.matches("CALL chat channel=0 type=Normal only replacement")
.count(),
1,
"{text}"
);
assert!(!text.contains("credential-one"), "password leaked:\n{text}");
assert!(!text.contains("credential-two"), "password leaked:\n{text}");
}
#[test]
fn native_alias_manifest_and_parser_are_bounded_and_deterministic() {
let directory = TestDir::new("load");
let manifest = directory.write(
"commands.tsv",
"greet\tSend a greeting through channel 9\tsay 9 hello {args}\n",
);
let script = directory.write(
"terminal.tsv",
&format!(
"!client\t{ALICE}\tAlice\tBot\n\
load \"{}\"\n\
help greet\n\
greet \"quoted world\"\n\
say 'unterminated\n\
quit\n",
path_text(&manifest)
),
);
let output = run(&["--fake-script", path_text(&script)]);
assert!(output.status.success(), "{}", stderr(&output));
let text = stdout(&output);
assert!(text.contains("Loaded 1 native command alias"), "{text}");
assert!(text.contains("Send a greeting through channel 9"), "{text}");
assert!(
text.contains("CALL chat channel=9 type=Normal hello quoted world"),
"{text}"
);
assert!(
text.contains("Command parse error: unterminated quoted token"),
"{text}"
);
}
#[test]
fn malformed_inputs_and_cancellation_fail_or_shutdown_cleanly() {
let directory = TestDir::new("invalid");
let invalid = directory.write("terminal.tsv", "!unknown\tvalue\n");
let output = run(&["--fake-script", path_text(&invalid)]);
assert_exit(&output, EXIT_INPUT);
assert!(stderr(&output).contains("line 1"));
let cancelled = directory.write(
"cancelled.tsv",
&format!(
"!client\t{ALICE}\tAlice\tBot\n\
!cancel\n\
sleep 60\n\
!shutdown\n"
),
);
let output = run(&["--fake-script", path_text(&cancelled)]);
assert!(output.status.success(), "{}", stderr(&output));
let text = stdout(&output);
assert!(text.contains("Pause cancelled; agent resumed"), "{text}");
assert!(text.contains("CALL agent-pause"), "{text}");
assert!(text.contains("CALL agent-resume"), "{text}");
assert!(text.contains("connected=0"), "{text}");
assert!(text.contains("active-tasks=0 shutdown=true"), "{text}");
}