Translate world-state parity tests
This commit is contained in:
489
tests/compat/tests/world_capability_semantics.rs
Normal file
489
tests/compat/tests/world_capability_semantics.rs
Normal file
@@ -0,0 +1,489 @@
|
||||
// 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_secs(2 * 60 * 60));
|
||||
}
|
||||
|
||||
// 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_secs(60 * 60));
|
||||
}
|
||||
|
||||
// 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_secs(5400)),
|
||||
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_secs(3 * 60 * 60)
|
||||
),
|
||||
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");
|
||||
}
|
||||
Reference in New Issue
Block a user