// Exact Rust translations of the C# tests that deliberately invoke internal handlers. // Source SHA-256: 74fa1275cd1f6c2a372af2fefc011a3e764f028a8957291d61f599bcb023a8ff // Source SHA-256: 7b67b72c2ad652626126d0e4eda7e8c26fc94c28f0f915238b2925f80a3d4af2 #![allow(clippy::float_cmp)] // Wire fixtures use exactly representable values. use super::*; use libremetaverse_structured_data::{OSD, OSDMap}; use libremetaverse_types::{UUID, Vector3}; use packets::{ EstateOwnerMessagePacket, EstateOwnerMessagePacketAgentDataBlock, EstateOwnerMessagePacketMethodDataBlock, EstateOwnerMessagePacketParamListBlock, }; use std::collections::HashMap; use std::future::Future; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll, Wake, Waker}; use std::time::Duration; fn uuid() -> UUID { UUID::random().expect("UUID Random") } fn ids(values: impl IntoIterator) -> OSD { OSD::Array(values.into_iter().map(OSD::UUID).collect()) } fn map(entries: impl IntoIterator) -> OSDMap { OSDMap::new_with_dictionary( entries .into_iter() .map(|(key, value)| (key.into(), value)) .collect(), ) .expect("OSDMap dictionary constructor") } fn simulator() -> Simulator { Simulator::new( GridClient::new().expect("simulator GridClient constructor"), "127.0.0.1:13".parse().unwrap(), 0, None, None, ) .expect("Simulator constructor") } fn estate_packet(blocked: &[UUID], trusted: &[UUID], allowed: &[UUID]) -> EstateOwnerMessagePacket { let counts = [ "1".to_owned(), "0".to_owned(), blocked.len().to_string(), trusted.len().to_string(), allowed.len().to_string(), ]; let mut parameters = counts .into_iter() .map(|value| EstateOwnerMessagePacketParamListBlock { parameter: value.as_bytes().to_vec(), }) .collect::>(); parameters.extend(blocked.iter().chain(trusted).chain(allowed).map(|id| { EstateOwnerMessagePacketParamListBlock { parameter: id.get_bytes().expect("UUID bytes"), } })); EstateOwnerMessagePacket { agent_data: EstateOwnerMessagePacketAgentDataBlock { agent_id: uuid(), session_id: uuid(), transaction_id: uuid(), }, method_data: EstateOwnerMessagePacketMethodDataBlock { method: b"setexperience".to_vec(), invoice: UUID::zero(), }, param_list: parameters, } } fn block_on_with(future: F, mut after_first_poll: impl FnMut()) -> F::Output { struct ThreadWake(std::thread::Thread); impl Wake for ThreadWake { fn wake(self: Arc) { self.0.unpark(); } } let waker = Waker::from(Arc::new(ThreadWake(std::thread::current()))); let mut context = Context::from_waker(&waker); let mut future = std::pin::pin!(future); let mut invoked = false; loop { match future.as_mut().poll(&mut context) { Poll::Ready(value) => return value, Poll::Pending if !invoked => { invoked = true; after_first_poll(); } Poll::Pending => std::thread::park_timeout(Duration::from_millis(10)), } } } // parity-case: LibreMetaverse.Tests/EstateExperienceReplyTests.cs::EstateExperienceReplyTests.SetExperience_ParsesBlockedTrustedAndAllowedLists::test 49a77aa9d7c5bf1232082a08594e7ebcfafad11ab110c9e15d2ad4ea1831c649 translated #[test] fn set_experience_parses_blocked_trusted_and_allowed_lists() { let blocked = uuid(); let trusted = uuid(); let allowed = [uuid(), uuid()]; let client = GridClient::new().expect("GridClient constructor"); let estate = client.estate(); let received = Arc::new(Mutex::new(None)); let captured = Arc::clone(&received); let _subscription = estate.subscribe_estate_experience_reply(Arc::new(move |reply| { *captured.lock().unwrap() = Some((reply.blocked(), reply.trusted(), reply.allowed())); })); estate .estate_owner_message_handler(estate_packet(&[blocked], &[trusted], &allowed), simulator()) .unwrap(); let reply = received.lock().unwrap(); let (actual_blocked, actual_trusted, actual_allowed) = reply.as_ref().expect("EstateExperienceReply event"); assert_eq!(actual_blocked, &[blocked]); assert_eq!(actual_trusted, &[trusted]); assert_eq!(actual_allowed, &allowed); } // parity-case: LibreMetaverse.Tests/EstateExperienceReplyTests.cs::EstateExperienceReplyTests.SetExperience_AllEmptyLists_ProducesEmptyResult::test 711eaebb11f5d7ece92946d3de240a341a4b1fd8f30617fd97cb059d0dc6a0b9 translated #[test] fn set_experience_all_empty_lists_produces_empty_result() { let client = GridClient::new().expect("GridClient constructor"); let estate = client.estate(); let received = Arc::new(Mutex::new(None)); let captured = Arc::clone(&received); let _subscription = estate.subscribe_estate_experience_reply(Arc::new(move |reply| { *captured.lock().unwrap() = Some((reply.blocked(), reply.trusted(), reply.allowed())); })); estate .estate_owner_message_handler(estate_packet(&[], &[], &[]), simulator()) .unwrap(); let reply = received.lock().unwrap(); let (blocked, trusted, allowed) = reply.as_ref().expect("EstateExperienceReply event"); assert!(blocked.is_empty()); assert!(trusted.is_empty()); assert!(allowed.is_empty()); } #[test] fn land_stat_caps_reply_preserves_mono_score_and_emits_after_decode() { let client = GridClient::new().expect("GridClient constructor"); let estate = client.estate(); let task_id = uuid(); let received = Arc::new(Mutex::new(None)); let captured = Arc::clone(&received); let _subscription = estate.subscribe_top_scripts_reply(Arc::new(move |reply| { *captured.lock().unwrap() = Some((reply.object_count(), reply.tasks())); })); let mut message = messages::linden::LandStatReplyMessage::new().expect("LandStatReplyMessage constructor"); message.report_type = EstateToolsLandStatReportType::TopScripts as u32; message.total_object_count = 1; message.report_data_blocks = vec![messages::linden::LandStatReplyMessageReportDataBlock { location: Vector3 { x: 1.0, y: 2.0, z: 3.0, }, mono_score: 4.5, owner_name: "Owner Resident".into(), score: 9.25, task_id, task_local_id: 17, task_name: "Scripted object".into(), time_stamp: std::time::SystemTime::UNIX_EPOCH, }]; let encoded = message.serialize().expect("serialize LandStatReply"); let mut decoded = messages::linden::LandStatReplyMessage::new().expect("LandStatReplyMessage constructor"); decoded .deserialize(encoded) .expect("deserialize LandStatReply"); client .network() .dispatch_caps_event("LandStatReply", &message, simulator()); let received = received.lock().unwrap(); let (count, tasks) = received.as_ref().expect("TopScriptsReply event"); assert_eq!(*count, 1); let task = tasks.get(&task_id).expect("reported task"); assert_eq!(task.mono_score, 4.5); assert_eq!(task.score, 9.25); assert_eq!(task.task_local_id, 17); assert_eq!(task.task_name, "Scripted object"); assert_eq!(task.owner_name, "Owner Resident"); } // parity-case: LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs::ExperiencePreferencesMessageTests.ExtractExperiencePermission_Allowed_ReturnsAllow::test cf095ab428713004ebac6e1915a47ae0c03bd2958addbba2a39fd2f0edf51a74 translated #[test] fn extract_experience_permission_allowed_returns_allow() { let id = uuid(); assert_eq!( AgentManager::extract_experience_permission(map([("experiences", ids([id]))]), id).unwrap(), "Allow" ); } // parity-case: LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs::ExperiencePreferencesMessageTests.ExtractExperiencePermission_Blocked_ReturnsBlock::test af084d1ab7a11079e6251865e8fd5aecfde38d2df359ebc1e208bc74925935c0 translated #[test] fn extract_experience_permission_blocked_returns_block() { let id = uuid(); assert_eq!( AgentManager::extract_experience_permission(map([("blocked", ids([id]))]), id).unwrap(), "Block" ); } // parity-case: LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs::ExperiencePreferencesMessageTests.ExtractExperiencePermission_NeitherList_ReturnsForget::test 93204469ecfec2f3f4eb6838c99f6e2baf09bf76a075958d1ed369ba03767158 translated #[test] fn extract_experience_permission_neither_list_returns_forget() { let id = uuid(); let input = OSDMap::new_with_dictionary(HashMap::from([ ("experiences".into(), ids([uuid()])), ("blocked".into(), ids([uuid()])), ])) .expect("OSDMap dictionary constructor"); assert_eq!( AgentManager::extract_experience_permission(input, id).unwrap(), "Forget" ); } // parity-case: LibreMetaverse.Tests/SimConsoleTests.cs::SimConsoleTests.SendSimConsoleCommandAsync_IgnoresPostBody_UsesSimConsoleResponseEvent::test df6eb48241f71c34074be66869c9ae1d5980186583259568728261b5eedd7c48 translated #[test] fn send_sim_console_command_ignores_post_body_and_uses_response_event() { let client = GridClient::new().expect("GridClient constructor"); let estate = client.estate(); let network = client.network(); let result = block_on_with( estate.send_sim_console_command("show name".into(), Some(Duration::from_secs(5)), None), || { let mut message = messages::linden::SimConsoleResponseMessage::new() .expect("SimConsoleResponseMessage constructor"); message.body = "Agent Name".into(); network .raise_sim_console_response(message, simulator()) .unwrap(); }, ) .unwrap(); assert_eq!(result.as_deref(), Some("Agent Name")); } // parity-case: LibreMetaverse.Tests/SimConsoleTests.cs::SimConsoleTests.SendSimConsoleCommandAsync_NoResponseEvent_ReturnsNullAfterTimeout::test 2ac5a6c96f3acec341056387d727288fd3661565bd2a7c385078602efe8661da translated #[test] fn send_sim_console_command_no_response_event_returns_none_after_timeout() { let client = GridClient::new().expect("GridClient constructor"); let result = block_on_with( client.estate().send_sim_console_command( "show name".into(), Some(Duration::from_millis(200)), None, ), || {}, ) .unwrap(); assert!(result.is_none()); }