225 lines
8.4 KiB
Rust
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());
|
|
}
|