Files
MetaCrate/tests/compat/tests/task_inventory_semantics.rs

225 lines
8.4 KiB
Rust

// Exact Rust translations of LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs.
// Source SHA-256: f444adc7615a805eddeef538ce939375dc2a2fa2cd871aab2ebce37b4e955941
use libremetaverse::{
GridClient, HttpCapsClient, InventoryFolder, InventoryItem, InventoryManager,
InventoryObjectClass, Simulator,
};
use libremetaverse_compat_tests::block_on;
use libremetaverse_types::UUID;
use libremetaverse_types::compat::{
CancellationToken, CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
};
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
const CAP: &str = "http://test.invalid/request-task-inventory";
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 client_with_cap(response: HttpResponse, expose_capability: bool) -> (GridClient, Recording) {
let requests = Arc::new(Mutex::new(Vec::new()));
let recorded = Arc::clone(&requests);
let response = Mutex::new(Some(response));
let seed = if expose_capability {
format!("<llsd><map><key>RequestTaskInventory</key><uri>{CAP}</uri></map></llsd>")
} else {
"<llsd><map></map></llsd>".into()
};
let handler = HttpMessageHandler::new(move |request, _| {
let result = if request.uri.0 == SEED {
HttpResponse {
status_code: 200,
headers: BTreeMap::new(),
content_type: Some("application/llsd+xml".into()),
body: seed.as_bytes().to_vec(),
}
} else {
recorded.lock().unwrap().push(request);
response.lock().unwrap().take().unwrap_or(HttpResponse {
status_code: 404,
headers: BTreeMap::new(),
content_type: None,
body: Vec::new(),
})
};
async move { result }
});
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:0".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 response(status: u16, body: String) -> HttpResponse {
HttpResponse {
status_code: status,
headers: BTreeMap::new(),
content_type: Some("application/json".into()),
body: body.into_bytes(),
}
}
fn uuid() -> UUID {
UUID::random().expect("UUID Random")
}
fn item_json(
item_id: UUID,
parent_id: UUID,
asset_or_shadow_key: &str,
asset_or_shadow_value: UUID,
) -> String {
format!(
concat!(
"{{\"item_id\":\"{}\",\"parent_id\":\"{}\",",
"\"name\":\"Test Item\",\"desc\":\"A test item\",",
"\"type\":7,\"inv_type\":7,\"flags\":0,\"created_at\":1700000000,",
"\"agent_id\":\"11111111-1111-1111-1111-111111111111\",",
"\"{}\":\"{}\",\"permissions\":{{",
"\"creator_id\":\"11111111-1111-1111-1111-111111111111\",",
"\"owner_id\":\"11111111-1111-1111-1111-111111111111\",",
"\"last_owner_id\":\"11111111-1111-1111-1111-111111111111\",",
"\"group_id\":\"00000000-0000-0000-0000-000000000000\",",
"\"base_mask\":2147483647,\"owner_mask\":2147483647,",
"\"group_mask\":0,\"everyone_mask\":0,\"next_owner_mask\":2147483647,",
"\"is_owner_group\":false}},\"sale_info\":{{\"sale_price\":0,\"sale_type\":0}}}}"
),
item_id.to_string(),
parent_id.to_string(),
asset_or_shadow_key,
asset_or_shadow_value.to_string(),
)
}
fn cancellation_after(duration: Duration) -> CancellationToken {
let source = Arc::new(CancellationTokenSource::new());
let token = source.token();
std::thread::spawn(move || {
std::thread::sleep(duration);
source.cancel();
});
token
}
fn single<T: 'static>(entries: &[Box<dyn InventoryObjectClass>]) -> &T {
let mut matching = entries
.iter()
.filter_map(|entry| entry.as_any().downcast_ref::<T>());
let value = matching.next().expect("matching inventory object");
assert!(matching.next().is_none(), "expected exactly one object");
value
}
// parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_CapAvailable_ReturnsContentsFolderAndDecryptsShadowId::test c9bfc5526260f9e2dffa8a22938999911fdd80be4b2b6d6487bf6171f8dbb56a translated
#[test]
fn cap_available_returns_contents_folder_and_decrypts_shadow_id() {
let object_id = uuid();
let item_id = uuid();
let asset_id = uuid();
let shadow_id = InventoryManager::encrypt_asset_id(asset_id).unwrap();
let body = format!(
"{{\"inventory_serial\":3,\"contents\":[{}]}}",
item_json(item_id, object_id, "shadow_id", shadow_id)
);
let (client, recording) = client_with_cap(response(200, body), true);
let result = block_on(
client
.inventory()
.get_task_inventory(object_id, 12_345, None, None),
)
.expect("GetTaskInventoryAsync");
let requests = recording.requests();
assert_eq!(requests.len(), 1);
assert!(
requests[0]
.uri
.0
.contains(&format!("task_id={}", object_id.to_string()))
);
assert_eq!(result.len(), 2);
let folder = single::<InventoryFolder>(&result);
assert_eq!(folder.base.name(), "Contents");
assert_eq!(folder.base.uuid(), object_id);
let item = single::<InventoryItem>(&result);
assert_eq!(item.base.uuid(), item_id);
assert_eq!(item.base.parent_uuid(), object_id);
assert_eq!(item.base.name(), "Test Item");
assert_eq!(item.asset_uuid(), asset_id);
}
// parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_PlainAssetId_ParsesWithoutDecryption::test 95f39928dae61bca86b7d0a0702307db6c5fdc754e51f73a24bd5ee131e6de3f translated
#[test]
fn plain_asset_id_parses_without_decryption() {
let object_id = uuid();
let item_id = uuid();
let asset_id = uuid();
let body = format!(
"{{\"inventory_serial\":1,\"contents\":[{}]}}",
item_json(item_id, object_id, "asset_id", asset_id)
);
let (client, _) = client_with_cap(response(200, body), true);
let result = block_on(
client
.inventory()
.get_task_inventory(object_id, 12_345, None, None),
)
.expect("GetTaskInventoryAsync");
assert_eq!(single::<InventoryItem>(&result).asset_uuid(), asset_id);
}
// parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NonSuccessStatus_ReturnsContentsFolderOnly::test b8a25891064e470ac8d77ecd8a4847cf773b043de5ebeae275a9dd6669518585 translated
#[test]
fn non_success_status_returns_contents_folder_only() {
let object_id = uuid();
let (client, _) = client_with_cap(response(500, String::new()), true);
let result = block_on(
client
.inventory()
.get_task_inventory(object_id, 12_345, None, None),
)
.expect("GetTaskInventoryAsync");
assert_eq!(result.len(), 1);
assert!(result[0].as_any().is::<InventoryFolder>());
}
// parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NoCapability_DoesNotMakeHttpRequest::test c0c43ebcf781fb81b1fef7ce1dd2774e350d92ef882f49876f1a16d23a338592 translated
#[test]
fn no_capability_does_not_make_http_request() {
let (client, recording) = client_with_cap(response(404, String::new()), false);
let result = block_on(client.inventory().get_task_inventory(
uuid(),
12_345,
None,
Some(cancellation_after(Duration::from_millis(50))),
))
.expect("GetTaskInventoryAsync");
assert!(result.is_empty());
assert!(recording.requests().is_empty());
}