Implement estate and marketplace services (#73)
This commit is contained in:
242
tests/compat/tests/user_report_semantics.rs
Normal file
242
tests/compat/tests/user_report_semantics.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use libremetaverse::{GridClient, HttpCapsClient, Simulator, UserReport, UserReportType};
|
||||
use libremetaverse_compat_tests::block_on;
|
||||
use libremetaverse_structured_data::{OSD, OSDParser};
|
||||
use libremetaverse_types::compat::{
|
||||
CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
|
||||
};
|
||||
use libremetaverse_types::{Error, UUID, Vector3};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
const SEED: &str = "http://test.invalid/seed";
|
||||
const CATEGORIES: &str = "http://test.invalid/abuse-categories";
|
||||
const SEND: &str = "http://test.invalid/send-report";
|
||||
const SEND_SCREENSHOT: &str = "http://test.invalid/send-report-with-screenshot";
|
||||
|
||||
fn response(status_code: u16, body: Vec<u8>) -> HttpResponse {
|
||||
HttpResponse {
|
||||
status_code,
|
||||
headers: BTreeMap::new(),
|
||||
content_type: Some("application/llsd+xml".into()),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
fn fixture(
|
||||
capabilities: &[(&str, &str)],
|
||||
categories: OSD,
|
||||
) -> (GridClient, Arc<Mutex<Vec<HttpRequest>>>) {
|
||||
let capabilities = capabilities
|
||||
.iter()
|
||||
.map(|(name, uri)| ((*name).to_owned(), OSD::Uri(Uri((*uri).to_owned()))))
|
||||
.collect::<HashMap<_, _>>();
|
||||
let seed_body =
|
||||
OSDParser::serialize_llsd_xml_bytes(OSD::Map(capabilities)).expect("seed capability LLSD");
|
||||
let categories_body =
|
||||
OSDParser::serialize_llsd_xml_bytes(categories).expect("category response LLSD");
|
||||
let requests = Arc::new(Mutex::new(Vec::new()));
|
||||
let captured = Arc::clone(&requests);
|
||||
let handler = HttpMessageHandler::new(move |request, _token| {
|
||||
let response = if request.uri.0 == SEED {
|
||||
response(200, seed_body.clone())
|
||||
} else if request.uri.0.starts_with(CATEGORIES) {
|
||||
response(200, categories_body.clone())
|
||||
} else if request.uri.0 == SEND || request.uri.0 == SEND_SCREENSHOT {
|
||||
response(200, Vec::new())
|
||||
} else {
|
||||
response(404, Vec::new())
|
||||
};
|
||||
captured.lock().expect("request recording").push(request);
|
||||
async move { response }
|
||||
});
|
||||
let http = HttpCapsClient::new(handler).expect("HTTP fixture");
|
||||
let mut client = GridClient::new().expect("GridClient constructor");
|
||||
client.set_http_caps_client(http.clone());
|
||||
let mut simulator_client = GridClient::new().expect("simulator GridClient constructor");
|
||||
simulator_client.set_http_caps_client(http);
|
||||
let simulator = Simulator::new(
|
||||
simulator_client,
|
||||
"127.0.0.1:0".parse().expect("loopback endpoint"),
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("fixture simulator");
|
||||
simulator
|
||||
.set_seed_caps(Some(Uri(SEED.into())), Some(true))
|
||||
.expect("fixture seed capability");
|
||||
let mut network = client.network();
|
||||
network.set_current_sim(Some(simulator));
|
||||
client.set_network(network);
|
||||
(client, requests)
|
||||
}
|
||||
|
||||
fn report(screenshot_id: UUID) -> UserReport {
|
||||
UserReport {
|
||||
report_type: UserReportType::Complaint,
|
||||
category: 7,
|
||||
screenshot_id,
|
||||
object_id: UUID::random().expect("object UUID"),
|
||||
abuser_id: UUID::random().expect("abuser UUID"),
|
||||
abuse_region_name: "Recorded Region".into(),
|
||||
abuse_region_id: UUID::random().expect("region UUID"),
|
||||
position: Vector3 {
|
||||
x: 12.5,
|
||||
y: 64.0,
|
||||
z: 21.25,
|
||||
},
|
||||
summary: "summary".into(),
|
||||
details: "details".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn categories_preserve_localized_descriptions_and_language_query() {
|
||||
let categories = OSD::Map(HashMap::from([(
|
||||
"categories".into(),
|
||||
OSD::Array(vec![
|
||||
OSD::Map(HashMap::from([
|
||||
(
|
||||
"description_localized".into(),
|
||||
OSD::String("Harassment".into()),
|
||||
),
|
||||
("category".into(), OSD::String("7".into())),
|
||||
])),
|
||||
OSD::Map(HashMap::from([
|
||||
("description_localized".into(), OSD::String("Fraud".into())),
|
||||
("category".into(), OSD::String("11".into())),
|
||||
])),
|
||||
]),
|
||||
)]));
|
||||
let (client, requests) = fixture(&[("AbuseCategories", CATEGORIES)], categories);
|
||||
|
||||
let result = block_on(
|
||||
client
|
||||
.user_reports()
|
||||
.fetch_abuse_report_categories(Some("en-US"), None),
|
||||
)
|
||||
.expect("category request");
|
||||
|
||||
assert_eq!(result.get("Harassment").map(String::as_str), Some("7"));
|
||||
assert_eq!(result.get("Fraud").map(String::as_str), Some("11"));
|
||||
assert!(
|
||||
requests
|
||||
.lock()
|
||||
.expect("request recording")
|
||||
.iter()
|
||||
.any(|request| request.uri.0 == format!("{CATEGORIES}?lc=en-US"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn screenshot_report_selects_capability_and_preserves_exact_payload() {
|
||||
let screenshot_id = UUID::random().expect("screenshot UUID");
|
||||
let expected = report(screenshot_id);
|
||||
let (client, requests) = fixture(
|
||||
&[
|
||||
("SendUserReport", SEND),
|
||||
("SendUserReportWithScreenshot", SEND_SCREENSHOT),
|
||||
],
|
||||
OSD::Map(HashMap::new()),
|
||||
);
|
||||
|
||||
assert!(
|
||||
block_on(
|
||||
client
|
||||
.user_reports()
|
||||
.send_user_report(expected.clone(), None)
|
||||
)
|
||||
.expect("report submission")
|
||||
);
|
||||
let requests = requests.lock().expect("request recording");
|
||||
let request = requests
|
||||
.iter()
|
||||
.find(|request| request.uri.0 == SEND_SCREENSHOT)
|
||||
.expect("screenshot capability POST");
|
||||
assert_eq!(request.method, "POST");
|
||||
let OSD::Map(payload) =
|
||||
OSDParser::deserialize_with_bytes(request.body.clone()).expect("report payload LLSD")
|
||||
else {
|
||||
panic!("report payload must be a map");
|
||||
};
|
||||
assert_eq!(payload.get("report-type").unwrap().as_integer().unwrap(), 3);
|
||||
assert_eq!(payload.get("category").unwrap().as_integer().unwrap(), 7);
|
||||
assert_eq!(payload.get("check-flags").unwrap().as_integer().unwrap(), 0);
|
||||
assert_eq!(
|
||||
payload.get("screenshot-id").unwrap().as_uuid().unwrap(),
|
||||
screenshot_id
|
||||
);
|
||||
assert_eq!(
|
||||
payload.get("object-id").unwrap().as_uuid().unwrap(),
|
||||
expected.object_id
|
||||
);
|
||||
assert_eq!(
|
||||
payload.get("abuser-id").unwrap().as_uuid().unwrap(),
|
||||
expected.abuser_id
|
||||
);
|
||||
assert_eq!(
|
||||
payload
|
||||
.get("abuse-region-name")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap(),
|
||||
""
|
||||
);
|
||||
assert_eq!(
|
||||
payload.get("abuse-region-id").unwrap().as_uuid().unwrap(),
|
||||
UUID::zero()
|
||||
);
|
||||
assert_eq!(
|
||||
payload.get("position").unwrap().as_vector3().unwrap(),
|
||||
expected.position
|
||||
);
|
||||
assert_eq!(
|
||||
payload.get("summary").unwrap().as_string().unwrap(),
|
||||
"summary"
|
||||
);
|
||||
assert_eq!(
|
||||
payload.get("version-string").unwrap().as_string().unwrap(),
|
||||
"<3 LibreMetaverse"
|
||||
);
|
||||
assert_eq!(
|
||||
payload.get("details").unwrap().as_string().unwrap(),
|
||||
"details"
|
||||
);
|
||||
assert!(!requests.iter().any(|request| request.uri.0 == SEND));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_report_submission_requires_explicit_opt_in() {
|
||||
let live_cap = "https://example.com/send-report";
|
||||
let (client, requests) = fixture(&[("SendUserReport", live_cap)], OSD::Map(HashMap::new()));
|
||||
|
||||
let error = block_on(
|
||||
client
|
||||
.user_reports()
|
||||
.send_user_report(report(UUID::zero()), None),
|
||||
)
|
||||
.expect_err("live report must be gated");
|
||||
assert_eq!(error, Error::InvalidOperation);
|
||||
assert!(
|
||||
!requests
|
||||
.lock()
|
||||
.expect("request recording")
|
||||
.iter()
|
||||
.any(|request| request.uri.0 == live_cap)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn category_request_propagates_preexisting_cancellation() {
|
||||
let (client, _) = fixture(&[("AbuseCategories", CATEGORIES)], OSD::Map(HashMap::new()));
|
||||
let cancellation = CancellationTokenSource::new();
|
||||
cancellation.cancel();
|
||||
|
||||
let error = block_on(
|
||||
client
|
||||
.user_reports()
|
||||
.fetch_abuse_report_categories(None, Some(cancellation.token())),
|
||||
)
|
||||
.expect_err("cancellation must be propagated");
|
||||
assert_eq!(error, Error::Cancelled);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use libremetaverse::marketplace::{MarketplaceListing, MarketplaceManager};
|
||||
use libremetaverse::marketplace::{
|
||||
MarketplaceListing, MarketplaceListingStatus, MarketplaceManager,
|
||||
};
|
||||
use libremetaverse::{
|
||||
DirectoryManager, DirectoryManagerAgentSearchData, EstateTask, EstateTools, FriendsManager,
|
||||
GroupManager, GroupProfileEventArgs, ObjectManager, ParcelManager, ParcelSettings, Simulator,
|
||||
@@ -9,14 +11,6 @@ use libremetaverse_types::compat::{
|
||||
CancellationToken, EventHandler, IAsyncEnumerable, Subscription,
|
||||
};
|
||||
|
||||
fn member_id<T>(result: Result<T, libremetaverse::Error>) -> &'static str {
|
||||
result
|
||||
.err()
|
||||
.expect("failure-only shim unexpectedly returned a value")
|
||||
.csharp_member()
|
||||
.expect("failure-only shim error")
|
||||
}
|
||||
|
||||
fn compile_search_call(directory: &DirectoryManager, token: CancellationToken) {
|
||||
let _: Result<IAsyncEnumerable<DirectoryManagerAgentSearchData>, libremetaverse::Error> =
|
||||
directory.search_people(String::new(), Some(token));
|
||||
@@ -67,15 +61,17 @@ fn world_and_social_flows_have_typed_callable_signatures() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn world_facing_constructors_preserve_implemented_and_pending_behavior() {
|
||||
assert_eq!(
|
||||
member_id(EstateTask::new()),
|
||||
"M:LibreMetaverse.EstateTask.#ctor"
|
||||
);
|
||||
assert_eq!(
|
||||
member_id(MarketplaceListing::new()),
|
||||
"M:LibreMetaverse.Marketplace.MarketplaceListing.#ctor"
|
||||
);
|
||||
fn world_facing_constructors_preserve_implemented_defaults() {
|
||||
let task = EstateTask::new().expect("EstateTask constructor");
|
||||
assert_eq!(task.task_id, UUID::zero());
|
||||
assert_eq!(task.task_local_id, 0);
|
||||
assert_eq!((task.score, task.mono_score), (0.0, 0.0));
|
||||
assert!(task.task_name.is_empty());
|
||||
assert!(task.owner_name.is_empty());
|
||||
let listing = MarketplaceListing::new().expect("MarketplaceListing constructor");
|
||||
assert_eq!(listing.status(), MarketplaceListingStatus::Unknown);
|
||||
assert_eq!(listing.listing_id(), 0);
|
||||
assert!(listing.edit_url().is_none());
|
||||
let parcel = ParcelSettings::new().expect("ParcelSettings constructor");
|
||||
assert!(parcel.track_parcels);
|
||||
assert!(parcel.always_request_acl);
|
||||
|
||||
Reference in New Issue
Block a user