Files
MetaCrate/tests/compat/tests/world_capability_semantics.rs
Chili Palmer c9a1170a27
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Complete first release candidate audit (#107)
2026-08-12 14:44:28 +00:00

485 lines
18 KiB
Rust

// Exact Rust translations of world capability tests owned by milestone issue #23.
// Source SHA-256: f83be0075683edda91f8e345661b2f753d81c134585f38ddd52b8b9b9e406089
// Source SHA-256: 79483c67d93295f09d281ec577f642d4967e281f88e531b2b43ee14c1444a440
// Source SHA-256: 1ef332ad434b9bdc468282eae99150393950db352a98f5f822517fa48021222f
// Source SHA-256: e49de5d4d588aa944bc27eeecd9921891b851eaecb76270abcec91f649579f7b
use libremetaverse::messages::linden::{EnvironmentData, SimConsoleResponseMessage};
use libremetaverse::{
GridClient, HttpCapsClient, RegionRestartDays, RegionRestartSchedule, Simulator,
};
use libremetaverse_compat_tests::block_on;
use libremetaverse_structured_data::{OSD, OSDParser};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::{HttpMessageHandler, HttpRequest, HttpResponse, Uri};
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
const CAP: &str = "http://test.invalid/capability";
const SEED: &str = "http://test.invalid/seed";
#[derive(Clone)]
struct Recording(Arc<Mutex<Vec<HttpRequest>>>);
impl Recording {
fn requests(&self) -> Vec<HttpRequest> {
self.0.lock().unwrap().clone()
}
}
fn response(status: u16, content_type: &str, body: &str) -> HttpResponse {
HttpResponse {
status_code: status,
headers: BTreeMap::new(),
content_type: Some(content_type.into()),
body: body.as_bytes().to_vec(),
}
}
fn client_with_cap(
cap_name: &str,
expose_capability: bool,
cap_response: HttpResponse,
) -> (GridClient, Recording) {
let requests = Arc::new(Mutex::new(Vec::new()));
let captured = Arc::clone(&requests);
let cap_reply = Mutex::new(Some(cap_response));
let seed = if expose_capability {
format!("<llsd><map><key>{cap_name}</key><uri>{CAP}</uri></map></llsd>")
} else {
"<llsd><map></map></llsd>".into()
};
let handler = HttpMessageHandler::new(move |request, _| {
let reply = if request.uri.0 == SEED {
response(200, "application/llsd+xml", &seed)
} else {
captured.lock().unwrap().push(request);
cap_reply
.lock()
.unwrap()
.take()
.unwrap_or_else(|| response(404, "application/octet-stream", ""))
};
async move { reply }
});
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(HttpCapsClient::new(handler.clone()).unwrap());
let mut simulator_client = GridClient::new().expect("simulator GridClient constructor");
simulator_client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
let simulator = Simulator::new(
simulator_client,
"127.0.0.1:13".parse().unwrap(),
0,
None,
None,
)
.unwrap();
simulator
.set_seed_caps(Some(Uri(SEED.into())), Some(true))
.unwrap();
let mut network = client.network();
network.set_current_sim(Some(simulator));
client.set_network(network);
(client, Recording(requests))
}
fn only_request(recording: &Recording) -> HttpRequest {
let requests = recording.requests();
assert_eq!(requests.len(), 1);
requests.into_iter().next().unwrap()
}
fn uuid() -> UUID {
UUID::random().expect("UUID Random")
}
// parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.GetParcelEnvironment_UsesParcelidQueryParameter::test 5c2f2b0fa2a86955975c621b2415a92ff3da283215e669956e14bf5ee2a4c1f4 translated
#[test]
fn get_parcel_environment_uses_parcelid_query_parameter() {
let (client, recording) = client_with_cap(
"ExtEnvironment",
true,
response(
200,
"application/llsd+xml",
"<llsd><map><key>success</key><boolean>true</boolean></map></llsd>",
),
);
block_on(client.environment().get_parcel_environment(42, None)).unwrap();
let request = only_request(&recording);
assert_eq!(request.method, "GET");
assert!(request.uri.0.contains("parcelid=42"));
assert!(!request.uri.0.contains("parcel_id"));
}
// parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.SetParcelEnvironment_SendsParcelidInQueryStringNotBody::test f633e2d78e4c6a0fff42917ccfe8de68e4f6133a11e7eef7f9b757c3d1622f0f translated
#[test]
fn set_parcel_environment_sends_parcelid_in_query_not_body() {
let (client, recording) = client_with_cap(
"ExtEnvironment",
true,
response(
200,
"application/llsd+xml",
"<llsd><map><key>success</key><boolean>true</boolean></map></llsd>",
),
);
let mut environment = EnvironmentData::new().expect("EnvironmentData constructor");
environment.set_day_length(7200);
block_on(
client
.environment()
.set_parcel_environment(7, environment, None),
)
.unwrap();
let request = only_request(&recording);
assert_eq!(request.method, "POST");
assert!(request.uri.0.contains("parcelid=7"));
let OSD::Map(body) = OSDParser::deserialize_with_bytes(request.body).unwrap() else {
panic!("ExtEnvironment POST body is not an LLSD map")
};
assert!(!body.contains_key("parcel_id"));
let Some(OSD::Map(environment)) = body.get("environment") else {
panic!("environment map missing")
};
assert_eq!(
environment.get("day_length").unwrap().as_integer().unwrap(),
7200
);
assert!(!environment.contains_key("sky_track"));
}
// parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.SetRegionEnvironment_OmitsQueryStringEntirely::test bade6e06765506ad7e500ac3e5c9f168605ce25fef86647819d285a68b39f109 translated
#[test]
fn set_region_environment_omits_query_string_entirely() {
let (client, recording) = client_with_cap(
"ExtEnvironment",
true,
response(
200,
"application/llsd+xml",
"<llsd><map><key>success</key><boolean>true</boolean></map></llsd>",
),
);
let environment = EnvironmentData::new().expect("EnvironmentData constructor");
block_on(
client
.environment()
.set_region_environment_with_environment_data_cancellation_token(environment, None),
)
.unwrap();
let request = only_request(&recording);
assert_eq!(request.method, "POST");
assert_eq!(request.uri.0, CAP);
}
// parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.ResetParcelEnvironment_SendsHttpDeleteNotPost::test 4360439192c137f5d7a2ceb83b8664f3917b4b261798050cb855d596f483f082 translated
#[test]
fn reset_parcel_environment_sends_delete_not_post() {
let (client, recording) = client_with_cap(
"ExtEnvironment",
true,
response(
200,
"application/llsd+xml",
"<llsd><map><key>success</key><boolean>true</boolean></map></llsd>",
),
);
assert!(block_on(client.environment().reset_parcel_environment(3, None)).unwrap());
let request = only_request(&recording);
assert_eq!(request.method, "DELETE");
assert!(request.uri.0.contains("parcelid=3"));
}
// parity-case: LibreMetaverse.Tests/QueryExperiencesOnParcelTests.cs::QueryExperiencesOnParcelTests.QueryExperiencesOnParcelAsync_HappyPath_ParsesAllowedMapAndBuildsExpectedQuery::test 2571d3a4100face21268d7ca2401cc8468e711c03c808704aacc7a50006125d5 translated
#[test]
fn query_experiences_on_parcel_happy_path() {
let first = uuid();
let second = uuid();
let first_text = first.to_string();
let second_text = second.to_string();
let body = format!(r#"{{"experiences":{{"{first_text}":true,"{second_text}":false}}}}"#);
let (mut client, recording) = client_with_cap(
"ExperienceQuery",
true,
response(200, "application/json", &body),
);
let result = block_on(client.self_().query_experiences_on_parcel(
42,
Box::new([first, second].into_iter()),
None,
))
.unwrap()
.expect("experience result");
assert_eq!(result.get(&first), Some(&true));
assert_eq!(result.get(&second), Some(&false));
let request = only_request(&recording);
assert!(request.uri.0.contains("parcelid=42"));
assert!(
request
.uri
.0
.contains(&format!("experiences={first_text},{second_text}"))
);
}
// parity-case: LibreMetaverse.Tests/QueryExperiencesOnParcelTests.cs::QueryExperiencesOnParcelTests.QueryExperiencesOnParcelAsync_NoExperienceIds_OmitsExperiencesParam::test ddbaa53aa8c63262c2241588b4281c42bbb66efdecac95e297deb0fb81797455 translated
#[test]
fn query_experiences_on_parcel_no_ids_omits_parameter() {
let (mut client, recording) = client_with_cap(
"ExperienceQuery",
true,
response(200, "application/json", r#"{"experiences":{}}"#),
);
let result = block_on(client.self_().query_experiences_on_parcel(
7,
Box::new(std::iter::empty()),
None,
))
.unwrap()
.expect("experience result");
assert!(result.is_empty());
let request = only_request(&recording);
assert!(request.uri.0.contains("parcelid=7"));
assert!(!request.uri.0.contains("experiences"));
}
// parity-case: LibreMetaverse.Tests/QueryExperiencesOnParcelTests.cs::QueryExperiencesOnParcelTests.QueryExperiencesOnParcelAsync_NoCapability_ReturnsNullWithoutRequest::test 5c6a6584fba8ddc66200367217f658d342fb2c26d6be9e49ccf3892bea420095 translated
#[test]
fn query_experiences_on_parcel_no_capability_returns_none_without_request() {
let (mut client, recording) = client_with_cap(
"ExperienceQuery",
false,
response(404, "application/json", ""),
);
let result = block_on(client.self_().query_experiences_on_parcel(
1,
Box::new([uuid()].into_iter()),
None,
))
.unwrap();
assert!(result.is_none());
assert!(recording.requests().is_empty());
}
// parity-case: LibreMetaverse.Tests/QueryExperiencesOnParcelTests.cs::QueryExperiencesOnParcelTests.QueryExperiencesOnParcelAsync_NonSuccessStatus_ReturnsNull::test 977fe55acd5aeb5735cc718f705a2742ae0dfab12dc1211c03a64cc6d1f3bbf5 translated
#[test]
fn query_experiences_on_parcel_non_success_returns_none() {
let (mut client, _) = client_with_cap(
"ExperienceQuery",
true,
response(500, "application/json", ""),
);
assert!(
block_on(client.self_().query_experiences_on_parcel(
1,
Box::new([uuid()].into_iter()),
None
))
.unwrap()
.is_none()
);
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.GetRegionRestartScheduleAsync_WeeklySchedule_ParsesDaysAndTime::test 0f64aab66c6a6d807b3d2bbc46b636a85900e4f76e35d5e3dd8cd0d8b295618b translated
#[test]
fn get_region_restart_schedule_weekly_parses_days_and_time() {
let (client, _) = client_with_cap(
"RegionSchedule",
true,
response(
200,
"application/json",
r#"{"restart":{"type":"W","days":"TR","time":7200}}"#,
),
);
let schedule = block_on(client.estate().get_region_restart_schedule(None))
.unwrap()
.expect("restart schedule");
assert!(!schedule.is_daily());
assert_eq!(
schedule.days(),
RegionRestartDays(RegionRestartDays::TUESDAY.0 | RegionRestartDays::THURSDAY.0)
);
assert_eq!(schedule.time(), Duration::from_hours(2));
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.GetRegionRestartScheduleAsync_DailySchedule_ReturnsAllDays::test cd966b6e3ba470c91ac958bf6c71aeb34f4dab060ad5334cb6060e1954b5c6d0 translated
#[test]
fn get_region_restart_schedule_daily_returns_all_days() {
let (client, _) = client_with_cap(
"RegionSchedule",
true,
response(
200,
"application/json",
r#"{"restart":{"type":"D","time":3600}}"#,
),
);
let schedule = block_on(client.estate().get_region_restart_schedule(None))
.unwrap()
.expect("restart schedule");
assert!(schedule.is_daily());
assert_eq!(schedule.days(), RegionRestartDays::ALL);
assert_eq!(schedule.time(), Duration::from_hours(1));
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.GetRegionRestartScheduleAsync_NoRestartConfigured_ReturnsNull::test 59a4b050a79659ccaf0fff13b750d634d803ed7b9e4940cbad676c1bdc04a5fc translated
#[test]
fn get_region_restart_schedule_no_restart_returns_none() {
let (client, _) = client_with_cap(
"RegionSchedule",
true,
response(200, "application/json", "{}"),
);
assert!(
block_on(client.estate().get_region_restart_schedule(None))
.unwrap()
.is_none()
);
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.GetRegionRestartScheduleAsync_NoCapability_ReturnsNullWithoutRequest::test e169e7846874ed4309a50cb25b03c2e722a8d758a787654a5906610ab3b655f1 translated
#[test]
fn get_region_restart_schedule_no_capability_returns_none_without_request() {
let (client, recording) = client_with_cap(
"RegionSchedule",
false,
response(404, "application/json", ""),
);
assert!(
block_on(client.estate().get_region_restart_schedule(None))
.unwrap()
.is_none()
);
assert!(recording.requests().is_empty());
}
fn schedule(daily: bool, days: RegionRestartDays, time: Duration) -> RegionRestartSchedule {
let mut schedule = RegionRestartSchedule::new().expect("RegionRestartSchedule constructor");
schedule.set_is_daily(daily);
schedule.set_days(days);
schedule.set_time(time);
schedule
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_Weekly_PostsUppercaseDaysAndSeconds::test 153fc0ac84af549c1381d0814a356bf2d9e0ead2c879e503981193366943546c translated
#[test]
fn set_region_restart_schedule_weekly_posts_uppercase_days_and_seconds() {
let (client, recording) = client_with_cap(
"RegionSchedule",
true,
response(200, "application/json", ""),
);
let days = RegionRestartDays(
RegionRestartDays::MONDAY.0 | RegionRestartDays::WEDNESDAY.0 | RegionRestartDays::FRIDAY.0,
);
assert!(
block_on(
client
.estate()
.set_region_restart_schedule(schedule(false, days, Duration::from_mins(90)), None)
)
.unwrap()
);
let body = String::from_utf8(only_request(&recording).body).unwrap();
assert!(body.contains("<key>type</key>"));
assert!(body.contains('W'));
assert!(body.contains("MWF"));
assert!(body.contains("5400"));
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_DailyIgnoresDays_DoesNotPostDaysKey::test b51a1272465c3316194542eb346b2aa15c43fdc8474163ddd851b3f2204d1c2b translated
#[test]
fn set_region_restart_schedule_daily_omits_days() {
let (client, recording) = client_with_cap(
"RegionSchedule",
true,
response(200, "application/json", ""),
);
assert!(
block_on(client.estate().set_region_restart_schedule(
schedule(true, RegionRestartDays::NONE, Duration::from_hours(3)),
None
))
.unwrap()
);
assert!(
!String::from_utf8(only_request(&recording).body)
.unwrap()
.contains("<key>days</key>")
);
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_EmptyDaysClearsSchedule::test 57ef3c0f8d9eeac58a7d1b3e7dbc44413016f39303d304cacd3071bd2c55f320 translated
#[test]
fn set_region_restart_schedule_empty_days_clears_schedule() {
let (client, recording) = client_with_cap(
"RegionSchedule",
true,
response(200, "application/json", ""),
);
assert!(
block_on(client.estate().set_region_restart_schedule(
schedule(false, RegionRestartDays::NONE, Duration::ZERO),
None
))
.unwrap()
);
let body = String::from_utf8(only_request(&recording).body).unwrap();
assert!(body.contains("<key>days</key>"));
assert!(body.contains("<string />") || body.contains("<string></string>"));
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_NoCapability_ReturnsFalseWithoutRequest::test be03507fe574b9f7c7174c39af44b7b693e1b7a496845db46451ef09bf9db206 translated
#[test]
fn set_region_restart_schedule_no_capability_returns_false_without_request() {
let (client, recording) = client_with_cap(
"RegionSchedule",
false,
response(404, "application/json", ""),
);
assert!(
!block_on(client.estate().set_region_restart_schedule(
schedule(true, RegionRestartDays::NONE, Duration::ZERO),
None
))
.unwrap()
);
assert!(recording.requests().is_empty());
}
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_NonSuccessStatus_ReturnsFalse::test 16c8988d7aa15ea9f58deaa2e3f416b6115978e23073fd99749843f4f410dca4 translated
#[test]
fn set_region_restart_schedule_non_success_returns_false() {
let (client, _) = client_with_cap(
"RegionSchedule",
true,
response(500, "application/json", ""),
);
assert!(
!block_on(client.estate().set_region_restart_schedule(
schedule(true, RegionRestartDays::NONE, Duration::ZERO),
None
))
.unwrap()
);
}
// parity-case: LibreMetaverse.Tests/SimConsoleTests.cs::SimConsoleTests.SimConsoleResponseMessage_Deserialize_ReadsBodyField::test 4d367bc3fd012d1579847fd9837bf883c5ea95452776a086bbf856899d7c2099 translated
#[test]
fn sim_console_response_message_deserialize_reads_body_field() {
let map = libremetaverse_structured_data::OSDMap::new_with_dictionary(
std::collections::HashMap::from([(
"body".into(),
OSD::String("console output text".into()),
)]),
)
.unwrap();
let mut message =
SimConsoleResponseMessage::new().expect("SimConsoleResponseMessage constructor");
message.deserialize(map).unwrap();
assert_eq!(message.body, "console output text");
}