Implement remaining TestClient service commands (#94)
Some checks failed
Native code generation / deterministic (push) Successful in 18m19s
Imaging and meshing gate / native (push) Failing after 4m28s
Native Rust workspace compile / compile (push) Failing after 12m58s

This commit is contained in:
2026-08-11 13:10:27 +00:00
parent 374d7958f1
commit 21f85a1b58
13 changed files with 3300 additions and 24 deletions

View File

@@ -0,0 +1,215 @@
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
const CLIENT: &str = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
const AVATAR: &str = "11111111-2222-3333-4444-555555555555";
const BOT: &str = "22222222-3333-4444-5555-666666666666";
const GROUP: &str = "33333333-4444-5555-6666-777777777777";
const MEMBER: &str = "44444444-5555-6666-7777-888888888888";
const ROLE: &str = "55555555-6666-7777-8888-999999999999";
const CLASSIFIED: &str = "66666666-7777-8888-9999-aaaaaaaaaaaa";
const LAND: &str = "77777777-8888-9999-aaaa-bbbbbbbbbbbb";
const SIM: &str = "88888888-9999-aaaa-bbbb-cccccccccccc";
const OBJECT: &str = "99999999-aaaa-bbbb-cccc-dddddddddddd";
const DANCE1: &str = "b68a3d7c-de9e-fc87-eec8-543d787e5b0d";
static TEMP_ID: AtomicU64 = AtomicU64::new(0);
struct TestDir(PathBuf);
impl TestDir {
fn new() -> Self {
let id = TEMP_ID.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"metacrate-test-client-services-{}-{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("session.tsv");
fs::write(&path, contents).expect("write 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 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")
}
#[test]
#[allow(clippy::too_many_lines)]
fn full_offline_session_exercises_every_remaining_non_voice_command() {
let directory = TestDir::new();
let script = directory.write(&format!(
"!client\t{CLIENT}\tAlice\tBot\n\
!service-avatar\t{CLIENT}\t{AVATAR}\tActive Resident\tBuilders\t128\t129\t25\t10\ttrue\n\
!service-avatar\t{CLIENT}\t{BOT}\tQuiet Bot\tNone\t130\t131\t25\t11\tfalse\n\
!service-friend\t{CLIENT}\t{AVATAR}\tActive Resident\ttrue\t4294967298000\t128\t129\t25\n\
!service-group\t{CLIENT}\t{GROUP}\tBuilders Guild\n\
!service-group-result\t{CLIENT}\t{GROUP}\tBuilders Guild\t42\n\
!service-group-member\t{CLIENT}\t{GROUP}\t{MEMBER}\n\
!service-group-role\t{CLIENT}\t{GROUP}\t{ROLE}\tOfficer\tArchitect\n\
!service-person\t{CLIENT}\t{AVATAR}\tActive\tResident\ttrue\n\
!service-classified\t{CLIENT}\t{CLASSIFIED}\tBuilder Services\t25\n\
!service-event\t{CLIENT}\t7\tBuilding Class\tToday\n\
!service-event-info\t{CLIENT}\t7\tBuilding Class\tTest Region\t1000\t2000\t25\n\
!service-land\t{CLIENT}\t{LAND}\tBuilder Parcel\t1024\t500\n\
!service-place\t{CLIENT}\tBuilder Plaza\tTest Region\t128\t128\t25\n\
!service-sim\t{CLIENT}\tTest Region\t{SIM}\t4294967298000\t0.98\n\
!service-utilization\t{CLIENT}\ttrue\n\
!service-utilization-row\t{CLIENT}\tpacket\tAgentUpdate\t2\t3\t2048\t4096\n\
!service-utilization-row\t{CLIENT}\tcapability\tEventQueueGet\t1\t4\t512\t8192\n\
who\n\
bots\n\
friends\n\
mapfriend {AVATAR}\n\
key2name {AVATAR}\n\
key2name {GROUP}\n\
searchclassifieds Builder\n\
searchevents Building\n\
searchgroups Builders\n\
searchland mainland 1000 512\n\
searchpeople Active\n\
searchplaces Builder\n\
showevent 7\n\
groups\n\
groupmembers Builders Guild\n\
grouproles Builders Guild\n\
play list\n\
play show\n\
dilation\n\
netstats\n\
regioninfo\n\
stats\n\
uptime\n\
cloneprofile {AVATAR} --confirm\n\
sendgeneric test-method alpha beta --confirm\n\
play DANCE1 --confirm\n\
touch {OBJECT} --confirm\n\
activategroup Builders Guild --confirm\n\
invitegroup {AVATAR} {GROUP} {ROLE} --confirm\n\
joingroup Builders Guild --confirm\n\
leavegroup Builders Guild --confirm\n\
quit\n"
));
let output = run(&[
"--allow-live-mutations",
"--allow-spending",
"--fake-script",
path_text(&script),
]);
let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
let stderr = String::from_utf8(output.stderr).expect("UTF-8 stderr");
assert!(
output.status.success(),
"stdout:\n{stdout}\nstderr:\n{stderr}"
);
assert!(stderr.is_empty(), "{stderr}");
for expected in [
"Active Resident (Group: Builders",
"Quiet Bot (Group: None",
"Is Probably a bot",
"has 1 friends:",
"Found Friend 11111111-2222-3333-4444-555555555555",
"Avatar: Active Resident",
"Group: Builders Guild",
"returned 1 classified ads",
"matched 1 Events",
"matched 1 Groups",
"1 results",
"matched 1 People",
"returned 1 results",
"<redacted-url> Region/232/208/0",
"got 1 groups",
"MemberCount 1",
"RoleCount 1",
"AFRAID",
DANCE1,
"Dilation is 0.98",
"Capabilities Totals",
"Packet Totals",
"UUID: 88888888-9999-aaaa-bbbb-cccccccccccc",
"Packets in the queue:",
"I am Alice Bot, Up Since:",
"Synchronized our profile",
"Sent generic message with method test-method",
"Touched object",
"Active group is now Builders Guild",
"invited",
"Joined the group Builders Guild",
"has left the group Builders Guild",
"CALL profile-clone",
"CALL generic-message",
"CALL animation-start",
"CALL object-touch",
"CALL group-activate",
"CALL group-invite",
"CALL group-join",
"CALL group-leave",
] {
assert!(stdout.contains(expected), "missing {expected:?}:\n{stdout}");
}
}
#[test]
fn social_mutations_require_startup_and_per_command_confirmation() {
let directory = TestDir::new();
let script = directory.write(&format!(
"!client\t{CLIENT}\tAlice\tBot\nplay {DANCE1} --confirm\nquit\n"
));
let output = run(&["--fake-script", path_text(&script)]);
let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
assert!(output.status.success());
assert!(stdout.contains("Live mutations are disabled"), "{stdout}");
assert!(!stdout.contains("CALL animation-start"), "{stdout}");
let script = directory.write(&format!(
"!client\t{CLIENT}\tAlice\tBot\ntouch {OBJECT}\nquit\n"
));
let output = run(&[
"--allow-live-mutations",
"--fake-script",
path_text(&script),
]);
let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
assert!(output.status.success());
assert!(stdout.contains("append --confirm"), "{stdout}");
assert!(!stdout.contains("CALL object-touch"), "{stdout}");
let script = directory.write(&format!(
"!client\t{CLIENT}\tAlice\tBot\njoingroup uuid {GROUP} --confirm\nquit\n"
));
let output = run(&[
"--allow-live-mutations",
"--fake-script",
path_text(&script),
]);
let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
assert!(output.status.success());
assert!(stdout.contains("restart with --allow-spending"), "{stdout}");
assert!(!stdout.contains("CALL group-join"), "{stdout}");
}
#[test]
fn command_inventory_leaves_only_explicit_voice_adapters() {
assert_eq!(
libremetaverse_programs::test_client::pending_test_client_commands(),
["ParcelVoiceInfo", "VoiceAcountCommand"]
);
}