Files
MetaCrate/tests/compat/tests/asset_capability_semantics.rs

829 lines
33 KiB
Rust

#![allow(
clippy::float_cmp,
clippy::needless_pass_by_value,
clippy::too_many_lines
)]
// Exact BSD-3-Clause fixture provenance at LibreMetaverse
// 2aa70bb68513b39795da5d13c88f31b86e85a3ba (see LICENSE.md):
// GLTFMaterialOverrideCapTests.cs 4d717ded7cc7bd4e68210417f722a1ceec6391b19dc3b1dfeb558ae16e6f115b
// GLTFMaterialUploadTests.cs 0602d1504e405868d7bcb608eb7c0db9f47e9ec6636846f34d48669aa5a27025
// TerrainMaterialOverrideTests.cs b9409ba65906e2b9d30d42b350b4b3e9c1655d4a503c4ff87bd548dd229dad88
// SendPostcardTests.cs 56691373dfa8f817f0b1079b8ec40f16e0186d6cfb13f772da0ffe3935188b4d
// UploadThumbnailTests.cs 8a88c5e4d45e7e1d4f8a41092528eefee347c7bd5f5e6f3af171ac19455e894b
// ConstructorNullArgumentTests.cs 28deea0bf4d9353a2424842c6184abab8fe82746ceb12e093912728a247f21bc
use libremetaverse::assets::AssetMaterial;
use libremetaverse::{
AssetManager, GLTFMaterialUpdate, GridClient, HttpCapsClient, InventoryFolder, Simulator,
};
use libremetaverse_compat_tests::block_on;
use libremetaverse_types::compat::{HttpMessageHandler, HttpRequest, HttpResponse, Uri};
use libremetaverse_types::{Color4, Error, UUID, Vector3};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::fmt::Write as _;
use std::sync::{Arc, Mutex};
const MODIFY_MATERIAL: &str = "http://test.invalid/modify-material-params";
const MATERIAL_AGENT: &str = "http://test.invalid/update-material-agent-inventory";
const MATERIAL_TASK: &str = "http://test.invalid/update-material-task-inventory";
const MATERIAL_UPLOADER: &str = "http://test.invalid/update-material/upload/abc123";
const MODIFY_REGION: &str = "http://test.invalid/modify-region";
const POSTCARD: &str = "http://test.invalid/send-postcard";
const POSTCARD_UPLOADER: &str = "http://test.invalid/send-postcard/upload/abc123";
const THUMBNAIL: &str = "http://test.invalid/inventory-thumbnail-upload";
const THUMBNAIL_UPLOADER: &str = "http://test.invalid/inventory-thumbnail-upload/upload/abc123";
const SEED_CAPS: &str = "http://test.invalid/seed";
const OTHER_CAP: &str = "http://test.invalid/other";
#[derive(Clone)]
struct RecordingHttp {
requests: Arc<Mutex<Vec<HttpRequest>>>,
}
impl RecordingHttp {
fn new(
capabilities: &[(&str, &str)],
responses: &[(&str, &str)],
) -> (Self, HttpMessageHandler) {
let requests = Arc::new(Mutex::new(Vec::new()));
let recorded = Arc::clone(&requests);
let mut queued = HashMap::<String, VecDeque<HttpResponse>>::new();
for (uri, body) in responses {
queued
.entry((*uri).into())
.or_default()
.push_back(response(body));
}
let queued = Mutex::new(queued);
let mut seed_body = "<llsd><map>".to_owned();
for (name, uri) in capabilities {
write!(seed_body, "<key>{name}</key><uri>{uri}</uri>")
.expect("writing to a String cannot fail");
}
seed_body.push_str("</map></llsd>");
let handler = HttpMessageHandler::new(move |request, _| {
let uri = request.uri.0.clone();
let response = if uri == SEED_CAPS {
HttpResponse {
status_code: 200,
headers: BTreeMap::new(),
content_type: Some("application/llsd+xml".into()),
body: seed_body.as_bytes().to_vec(),
}
} else {
recorded.lock().expect("request recording").push(request);
queued
.lock()
.expect("response queue")
.get_mut(&uri)
.and_then(VecDeque::pop_front)
.unwrap_or_else(|| HttpResponse {
status_code: 404,
headers: BTreeMap::new(),
content_type: None,
body: Vec::new(),
})
};
async move { response }
});
(Self { requests }, handler)
}
fn requests(&self) -> Vec<HttpRequest> {
self.requests.lock().expect("request recording").clone()
}
}
fn response(body: &str) -> HttpResponse {
HttpResponse {
status_code: 200,
headers: BTreeMap::from([("Content-Length".into(), body.len().to_string())]),
content_type: Some("application/json".into()),
body: body.as_bytes().to_vec(),
}
}
fn client_with_fixture(
capabilities: &[(&str, &str)],
responses: &[(&str, &str)],
) -> (GridClient, RecordingHttp) {
let (recording, handler) = RecordingHttp::new(capabilities, responses);
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(
HttpCapsClient::new(handler.clone()).expect("HttpCapsClient constructor"),
);
if !capabilities.is_empty() {
let mut simulator_client = GridClient::new().expect("simulator GridClient constructor");
simulator_client.set_http_caps_client(
HttpCapsClient::new(handler).expect("simulator HttpCapsClient constructor"),
);
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_CAPS.into())), Some(true))
.expect("seed fixture capabilities");
let mut network = client.network();
network.set_current_sim(Some(simulator));
client.set_network(network);
}
(client, recording)
}
fn body(request: &HttpRequest) -> String {
String::from_utf8(request.body.clone()).expect("UTF-8 request body")
}
fn uuid_eq(actual: &UUID, expected: &UUID) {
assert_eq!(
actual.get_bytes().expect("actual UUID bytes"),
expected.get_bytes().expect("expected UUID bytes")
);
}
fn happy_upload_responses<'a>(
capability: &'a str,
uploader: &'a str,
new_asset: &UUID,
) -> [(&'a str, String); 2] {
[
(
capability,
format!(r#"{{"state":"upload","uploader":"{uploader}"}}"#),
),
(
uploader,
format!(
r#"{{"state":"complete","new_asset":"{}"}}"#,
new_asset.to_string()
),
),
]
}
fn response_refs<'a, const N: usize>(
responses: &'a [(&'a str, String); N],
) -> Vec<(&'a str, &'a str)> {
responses
.iter()
.map(|(uri, body)| (*uri, body.as_str()))
.collect()
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialOverrideCapTests.cs::GLTFMaterialOverrideCapTests.SetMaterialOverrideAsync_HappyPath_PostsBareArrayWithGltfJson::test 0c51b7b793111f5433e52057f26fa5a98d983e7230fefeb61a7b3408e97f28ee translated
#[test]
fn set_material_override_posts_bare_array_with_gltf_json() {
let (client, recording) = client_with_fixture(
&[("ModifyMaterialParams", MODIFY_MATERIAL)],
&[(MODIFY_MATERIAL, r#"{"success":true}"#)],
);
let object_id = UUID::random().expect("UUID Random");
let expected_id = object_id.to_string();
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material
.set_base_color_factor_with_color4_boolean(
Color4::new_with_single_single_single_single(0.1, 0.2, 0.3, 1.0)
.expect("Color4 constructor"),
Some(true),
)
.expect("SetBaseColorFactor");
let result = block_on(client.objects().set_material_override(
client.network().current_sim().expect("current simulator"),
object_id,
3,
material,
None,
))
.expect("SetMaterialOverrideAsync");
assert!(result);
let requests = recording.requests();
assert_eq!(requests.len(), 1);
let body = body(&requests[0]);
assert!(body.contains("<key>object_id</key>"));
assert!(body.contains(&expected_id));
assert!(body.contains("<key>side</key>"));
assert!(body.contains("<key>gltf_json</key>"));
assert!(!body.contains("<key>asset_id</key>"));
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialOverrideCapTests.cs::GLTFMaterialOverrideCapTests.ClearMaterialOverrideAsync_SendsEmptyGltfJson::test 8394d063ea71447d9ce2b76fd00be1d10967f9193e1eb909c5b9f2da56eec812 translated
#[test]
fn clear_material_override_sends_empty_gltf_json() {
let (client, recording) = client_with_fixture(
&[("ModifyMaterialParams", MODIFY_MATERIAL)],
&[(MODIFY_MATERIAL, r#"{"success":true}"#)],
);
assert!(
block_on(client.objects().clear_material_override(
client.network().current_sim().expect("current simulator"),
UUID::random().expect("UUID Random"),
0,
None,
))
.expect("ClearMaterialOverrideAsync")
);
let body = body(&recording.requests()[0]);
assert!(body.contains("<key>gltf_json</key>"));
assert!(body.contains("<string />") || body.contains("<string></string>"));
assert!(!body.contains("<key>asset_id</key>"));
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialOverrideCapTests.cs::GLTFMaterialOverrideCapTests.ApplyMaterialAsync_WithoutOverride_SendsAssetIdOnly::test 1db03ab6710ecf532644398d1fef5e82520e1206058d66fb15c8885fdc8f8363 translated
#[test]
fn apply_material_without_override_sends_asset_id_only() {
let (client, recording) = client_with_fixture(
&[("ModifyMaterialParams", MODIFY_MATERIAL)],
&[(MODIFY_MATERIAL, r#"{"success":true}"#)],
);
let asset_id = UUID::random().expect("UUID Random");
let expected_id = asset_id.to_string();
assert!(
block_on(client.objects().apply_material(
client.network().current_sim().expect("current simulator"),
UUID::random().expect("UUID Random"),
1,
asset_id,
None,
None,
))
.expect("ApplyMaterialAsync")
);
let body = body(&recording.requests()[0]);
assert!(body.contains("<key>asset_id</key>"));
assert!(body.contains(&expected_id));
assert!(!body.contains("<key>gltf_json</key>"));
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialOverrideCapTests.cs::GLTFMaterialOverrideCapTests.SendMaterialUpdatesAsync_MultipleEntries_SendsSingleArrayBody::test 621dca88e7f1c9e024563204ca05c3c8d9a03c4b5a7b31ec1c006abe6732b47b translated
#[test]
fn material_updates_send_one_array_body() {
let (client, recording) = client_with_fixture(
&[("ModifyMaterialParams", MODIFY_MATERIAL)],
&[(MODIFY_MATERIAL, r#"{"success":true}"#)],
);
let mut first = GLTFMaterialUpdate::new().expect("GLTFMaterialUpdate constructor");
first.set_object_id(UUID::random().expect("UUID Random"));
first.set_side(0);
let mut second = GLTFMaterialUpdate::new().expect("GLTFMaterialUpdate constructor");
second.set_object_id(UUID::random().expect("UUID Random"));
second.set_side(1);
second.set_asset_id(Some(Some(UUID::random().expect("UUID Random"))));
assert!(
block_on(client.objects().send_material_updates(
client.network().current_sim().expect("current simulator"),
Box::new(vec![first, second].into_iter()),
None,
))
.expect("SendMaterialUpdatesAsync")
);
assert_eq!(recording.requests().len(), 1);
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialOverrideCapTests.cs::GLTFMaterialOverrideCapTests.SendMaterialUpdatesAsync_ServerRejects_ReturnsFalse::test e9fbd95fff84695e2cb1a35943bda768f00cf9015b25e0ae1505dce4dde603a4 translated
#[test]
fn material_updates_server_rejection_returns_false() {
let (client, _) = client_with_fixture(
&[("ModifyMaterialParams", MODIFY_MATERIAL)],
&[(
MODIFY_MATERIAL,
r#"{"success":false,"message":"no rez rights"}"#,
)],
);
assert!(
!block_on(client.objects().clear_material_override(
client.network().current_sim().expect("current simulator"),
UUID::random().expect("UUID Random"),
0,
None,
))
.expect("ClearMaterialOverrideAsync")
);
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialOverrideCapTests.cs::GLTFMaterialOverrideCapTests.SendMaterialUpdatesAsync_NoCapability_ReturnsFalseWithoutRequest::test fdceb6bf865cb23386a6b82a9741f230e5c22b0ec6d37bc2ac8478f29292a49f translated
#[test]
fn material_updates_without_capability_make_no_request() {
let (client, recording) = client_with_fixture(&[("SomeOtherCap", OTHER_CAP)], &[]);
assert!(
!block_on(client.objects().clear_material_override(
client.network().current_sim().expect("current simulator"),
UUID::random().expect("UUID Random"),
0,
None,
))
.expect("ClearMaterialOverrideAsync")
);
assert!(recording.requests().is_empty());
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialUploadTests.cs::GLTFMaterialUploadTests.RequestUpdateMaterialAgentInventoryAsync_HappyPath_PostsMetadataThenJsonAndReturnsNewAsset::test 5ac0e3c27fa6ebfddc9d7582aefb0684a676413948b055382c43b1b0b1cd7e7e translated
#[test]
fn material_agent_upload_posts_metadata_then_json() {
let item_id = UUID::random().expect("UUID Random");
let new_asset = UUID::random().expect("UUID Random");
let responses = happy_upload_responses(MATERIAL_AGENT, MATERIAL_UPLOADER, &new_asset);
let refs = response_refs(&responses);
let (client, recording) = client_with_fixture(
&[
("UpdateMaterialAgentInventory", MATERIAL_AGENT),
("UpdateMaterialTaskInventory", MATERIAL_TASK),
],
&refs,
);
let result = block_on(client.inventory().request_update_material_agent_inventory(
AssetMaterial::new_with_constructor().expect("AssetMaterial constructor"),
item_id,
None,
None,
))
.expect("RequestUpdateMaterialAgentInventoryAsync");
assert!(result.0);
uuid_eq(&result.2, &new_asset);
let requests = recording.requests();
assert_eq!(requests.len(), 2);
let metadata = body(&requests[0]);
assert!(metadata.contains("<key>item_id</key>"));
assert!(!metadata.contains("<key>task_id</key>"));
assert_eq!(requests[1].uri, Uri(MATERIAL_UPLOADER.into()));
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialUploadTests.cs::GLTFMaterialUploadTests.RequestUpdateMaterialTaskInventoryAsync_HappyPath_PostsItemIdAndTaskId::test 7ac0e7d210b1aefa795e479f4da0d2dd4caae0b3bbe84ca124707c3ae005bbbd translated
#[test]
fn material_task_upload_posts_item_and_task_ids() {
let new_asset = UUID::random().expect("UUID Random");
let responses = happy_upload_responses(MATERIAL_TASK, MATERIAL_UPLOADER, &new_asset);
let refs = response_refs(&responses);
let (client, recording) = client_with_fixture(
&[
("UpdateMaterialAgentInventory", MATERIAL_AGENT),
("UpdateMaterialTaskInventory", MATERIAL_TASK),
],
&refs,
);
let result = block_on(client.inventory().request_update_material_task_inventory(
AssetMaterial::new_with_constructor().expect("AssetMaterial constructor"),
UUID::random().expect("UUID Random"),
UUID::random().expect("UUID Random"),
None,
None,
))
.expect("RequestUpdateMaterialTaskInventoryAsync");
assert!(result.0);
uuid_eq(&result.2, &new_asset);
let metadata = body(&recording.requests()[0]);
assert!(metadata.contains("<key>item_id</key>"));
assert!(metadata.contains("<key>task_id</key>"));
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialUploadTests.cs::GLTFMaterialUploadTests.RequestUpdateMaterialAgentInventoryAsync_NoCapability_Throws::test 325269807d778264e571d1518ca9a2ca69a27ba67ad689e580f2636ab595f335 translated
#[test]
fn material_agent_upload_without_capability_is_invalid_operation() {
let (client, _) = client_with_fixture(&[], &[]);
let result = block_on(client.inventory().request_update_material_agent_inventory(
AssetMaterial::new_with_constructor().expect("AssetMaterial constructor"),
UUID::random().expect("UUID Random"),
None,
None,
));
match result {
Err(error) => assert_eq!(error, Error::InvalidOperation),
Ok(_) => panic!("expected InvalidOperation, got success"),
}
}
// parity-case: LibreMetaverse.Tests/GLTFMaterialUploadTests.cs::GLTFMaterialUploadTests.RequestUpdateMaterialAgentInventoryAsync_UploadedJsonRoundTripsMaterial::test fd059dfc8c9299c3b6583b3b47ba18ae9c92e3ad923f88634019b0ab9f34d401 translated
#[test]
fn uploaded_material_json_round_trips() {
let new_asset = UUID::random().expect("UUID Random");
let responses = happy_upload_responses(MATERIAL_AGENT, MATERIAL_UPLOADER, &new_asset);
let refs = response_refs(&responses);
let (client, recording) = client_with_fixture(
&[
("UpdateMaterialAgentInventory", MATERIAL_AGENT),
("UpdateMaterialTaskInventory", MATERIAL_TASK),
],
&refs,
);
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material.set_name("My Material".into());
material
.set_base_color_factor_with_color4_boolean(
Color4::new_with_single_single_single_single(0.2, 0.4, 0.6, 1.0)
.expect("Color4 constructor"),
None,
)
.expect("SetBaseColorFactor");
block_on(client.inventory().request_update_material_agent_inventory(
material,
UUID::random().expect("UUID Random"),
None,
None,
))
.expect("RequestUpdateMaterialAgentInventoryAsync");
let uploaded = recording.requests()[1].body.clone();
let round_trip =
AssetMaterial::new_with_uuid_bytes(UUID::random().expect("UUID Random"), uploaded)
.expect("AssetMaterial JSON constructor");
assert_eq!(round_trip.name(), "My Material");
assert_eq!(
[
round_trip.base_color_factor().r,
round_trip.base_color_factor().g,
round_trip.base_color_factor().b,
round_trip.base_color_factor().a,
],
[0.2, 0.4, 0.6, 1.0]
);
}
// parity-case: LibreMetaverse.Tests/TerrainMaterialOverrideTests.cs::TerrainMaterialOverrideTests.GetTerrainMaterialOverridesAsync_ParsesFourSlotsWithSomeEmpty::test 0b7f2b5a7a95c70f4ed97b9b028f3ec16f1cd427a9d4d1b133523bd331f6b9f2 translated
#[test]
fn terrain_get_parses_four_slots_with_empty_entries() {
let (client, _) = client_with_fixture(
&[("ModifyRegion", MODIFY_REGION)],
&[(
MODIFY_REGION,
r#"{"success":true,"overrides":[{},{"mf":0.25},{},{}]}"#,
)],
);
let overrides = block_on(client.terrain().get_terrain_material_overrides(None))
.expect("GetTerrainMaterialOverridesAsync")
.expect("successful override response");
assert_eq!(overrides.len(), 4);
assert!(overrides[0].is_none());
assert_eq!(
overrides[1].as_ref().expect("slot one").metallic_factor(),
0.25
);
assert!(overrides[2].is_none());
assert!(overrides[3].is_none());
}
// parity-case: LibreMetaverse.Tests/TerrainMaterialOverrideTests.cs::TerrainMaterialOverrideTests.GetTerrainMaterialOverridesAsync_ServerReportsFailure_ReturnsNull::test c66b84662333288b7328822d1f9fa79450075d44a1178a9b0b661671072efe5b translated
#[test]
fn terrain_get_server_failure_returns_none() {
let (client, _) = client_with_fixture(
&[("ModifyRegion", MODIFY_REGION)],
&[(MODIFY_REGION, r#"{"success":false,"message":"nope"}"#)],
);
assert!(
block_on(client.terrain().get_terrain_material_overrides(None))
.expect("GetTerrainMaterialOverridesAsync")
.is_none()
);
}
// parity-case: LibreMetaverse.Tests/TerrainMaterialOverrideTests.cs::TerrainMaterialOverrideTests.GetTerrainMaterialOverridesAsync_NoCapability_ReturnsNullWithoutRequest::test 677f80689e1b6a67893d100e1116805fb9f63c5b6ec33e64a4dc9a4cf24c1237 translated
#[test]
fn terrain_get_without_capability_makes_no_request() {
let (client, recording) = client_with_fixture(&[("SomeOtherCap", OTHER_CAP)], &[]);
assert!(
block_on(client.terrain().get_terrain_material_overrides(None))
.expect("GetTerrainMaterialOverridesAsync")
.is_none()
);
assert!(recording.requests().is_empty());
}
// parity-case: LibreMetaverse.Tests/TerrainMaterialOverrideTests.cs::TerrainMaterialOverrideTests.SetTerrainMaterialOverridesAsync_MixOfNullAndSet_PostsFourEntries::test a4ce031e565ca7eb1864064d299dbf0c7b715581ce330a57be9c3aa93987e67c translated
#[test]
fn terrain_set_posts_four_mixed_entries() {
let (client, recording) = client_with_fixture(
&[("ModifyRegion", MODIFY_REGION)],
&[(MODIFY_REGION, r#"{"success":true}"#)],
);
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material
.set_roughness_factor_with_single_boolean(0.1, Some(true))
.expect("SetRoughnessFactor");
assert!(
block_on(
client
.terrain()
.set_terrain_material_overrides(vec![None, Some(material), None, None], None,)
)
.expect("SetTerrainMaterialOverridesAsync")
);
let body = body(&recording.requests()[0]);
assert!(body.contains("<key>overrides</key>"));
assert!(body.contains("<key>rf</key>"));
}
// parity-case: LibreMetaverse.Tests/TerrainMaterialOverrideTests.cs::TerrainMaterialOverrideTests.SetTerrainMaterialOverridesAsync_WrongLength_Throws::test 836099a369b37555049b1c1ddfb9562c57cdef88381b7a79e8b80fe62b169a44 translated
#[test]
fn terrain_set_wrong_length_is_argument_error() {
let (client, _) = client_with_fixture(&[("ModifyRegion", MODIFY_REGION)], &[]);
let result = block_on(
client
.terrain()
.set_terrain_material_overrides(vec![None, None], None),
);
match result {
Err(error) => assert_eq!(error, Error::Argument),
Ok(_) => panic!("expected Argument, got success"),
}
}
// parity-case: LibreMetaverse.Tests/TerrainMaterialOverrideTests.cs::TerrainMaterialOverrideTests.SetTerrainMaterialOverridesAsync_NoCapability_ReturnsFalseWithoutRequest::test 37948cbc0240910d63782d88c0fb9f1d239215cea34a5aa84ac68a10d0555e87 translated
#[test]
fn terrain_set_without_capability_makes_no_request() {
let (client, recording) = client_with_fixture(&[("SomeOtherCap", OTHER_CAP)], &[]);
assert!(
!block_on(
client
.terrain()
.set_terrain_material_overrides(vec![None, None, None, None], None,)
)
.expect("SetTerrainMaterialOverridesAsync")
);
assert!(recording.requests().is_empty());
}
// parity-case: LibreMetaverse.Tests/SendPostcardTests.cs::SendPostcardTests.SendPostcardAsync_HappyPath_PostsMetadataThenImageAndReturnsTrue::test 2e02bd16ef1431c2ca82d7804dffc764be0175cff5daff8c38d0a61641439e06 translated
#[test]
fn postcard_posts_metadata_then_image() {
let (mut client, recording) = client_with_fixture(
&[("SendPostcard", POSTCARD)],
&[
(
POSTCARD,
r#"{"state":"upload","uploader":"http://test.invalid/send-postcard/upload/abc123"}"#,
),
(POSTCARD_UPLOADER, r#"{"state":"complete"}"#),
],
);
assert!(
block_on(client.self_().send_postcard(
vec![0xff, 0xd8, 0xff, 0xd9],
"friend@example.com".into(),
"Jim Radford".into(),
"Postcard from the edge".into(),
"Hello, how are you today?".into(),
Vector3::one(),
None,
))
.expect("SendPostcardAsync")
);
let requests = recording.requests();
assert_eq!(requests.len(), 2);
assert_eq!(requests[0].method, "POST");
assert_eq!(requests[0].uri, Uri(POSTCARD.into()));
let metadata = body(&requests[0]);
assert!(!metadata.contains("<key>from</key>"));
for field in ["to", "name", "subject", "msg", "pos-global"] {
assert!(metadata.contains(field));
}
assert_eq!(requests[1].method, "POST");
assert_eq!(requests[1].uri, Uri(POSTCARD_UPLOADER.into()));
}
// parity-case: LibreMetaverse.Tests/SendPostcardTests.cs::SendPostcardTests.SendPostcardAsync_UploaderMissingFromResponse_ReturnsFalseWithoutSecondRequest::test 2f9498d57d7a512399f9b108765e6e8da8e4c937402f9671d580c66332630432 translated
#[test]
fn postcard_missing_uploader_returns_false_after_one_request() {
let (mut client, recording) = client_with_fixture(
&[("SendPostcard", POSTCARD)],
&[(POSTCARD, r#"{"state":"upload"}"#)],
);
assert!(
!block_on(client.self_().send_postcard(
vec![1, 2, 3],
"friend@example.com".into(),
"Jim Radford".into(),
"Subject".into(),
"Message".into(),
Vector3::zero(),
None,
))
.expect("SendPostcardAsync")
);
assert_eq!(recording.requests().len(), 1);
}
// parity-case: LibreMetaverse.Tests/SendPostcardTests.cs::SendPostcardTests.SendPostcardAsync_UploadDoesNotComplete_ReturnsFalse::test 937b682b406506ec9fba9570852bbf8689450a11cbecb85979af97609b1794f9 translated
#[test]
fn postcard_incomplete_upload_returns_false() {
let (mut client, _) = client_with_fixture(
&[("SendPostcard", POSTCARD)],
&[
(
POSTCARD,
r#"{"state":"upload","uploader":"http://test.invalid/send-postcard/upload/abc123"}"#,
),
(POSTCARD_UPLOADER, r#"{"state":"failed"}"#),
],
);
assert!(
!block_on(client.self_().send_postcard(
vec![1, 2, 3],
"friend@example.com".into(),
"Jim Radford".into(),
"Subject".into(),
"Message".into(),
Vector3::zero(),
None,
))
.expect("SendPostcardAsync")
);
}
// parity-case: LibreMetaverse.Tests/SendPostcardTests.cs::SendPostcardTests.SendPostcardAsync_NoCapability_ReturnsFalseWithoutRequest::test 8d3ef84e873096d9388bb7db3eaeeb79c661fd9951e338f71bbf3251ae6580e8 translated
#[test]
fn postcard_without_capability_makes_no_request() {
let (mut client, recording) = client_with_fixture(&[], &[]);
assert!(
!block_on(client.self_().send_postcard(
vec![1, 2, 3],
"friend@example.com".into(),
"Jim Radford".into(),
"Subject".into(),
"Message".into(),
Vector3::zero(),
None,
))
.expect("SendPostcardAsync")
);
assert!(recording.requests().is_empty());
}
fn thumbnail_responses(new_asset: &UUID) -> [(&str, String); 2] {
[
(
THUMBNAIL,
format!(r#"{{"uploader":"{THUMBNAIL_UPLOADER}"}}"#),
),
(
THUMBNAIL_UPLOADER,
format!(
r#"{{"state":"complete","new_asset":"{}"}}"#,
new_asset.to_string()
),
),
]
}
// parity-case: LibreMetaverse.Tests/UploadThumbnailTests.cs::UploadThumbnailTests.UploadThumbnailAsync_AgentInventoryItem_SendsBareItemIdAndReturnsNewAsset::test 40ca874a37dade3bfbb391a2b4a75c684cfd7509b269286ccc14d747340d095d translated
#[test]
fn thumbnail_agent_item_sends_bare_item_id() {
let new_asset = UUID::random().expect("UUID Random");
let responses = thumbnail_responses(&new_asset);
let refs = response_refs(&responses);
let (client, recording) =
client_with_fixture(&[("InventoryThumbnailUpload", THUMBNAIL)], &refs);
let item_id = UUID::random().expect("UUID Random");
let result = block_on(client.inventory().upload_thumbnail(
item_id,
UUID::zero(),
vec![1, 2, 3],
None,
None,
))
.expect("UploadThumbnailAsync")
.flatten()
.expect("new thumbnail asset");
uuid_eq(&result, &new_asset);
let requests = recording.requests();
assert_eq!(requests.len(), 2);
let metadata = body(&requests[0]);
assert!(metadata.contains("<key>item_id</key>"));
assert!(!metadata.contains("<key>task_id</key>"));
assert!(!metadata.contains("<key>category_id</key>"));
assert_eq!(requests[1].uri, Uri(THUMBNAIL_UPLOADER.into()));
}
// parity-case: LibreMetaverse.Tests/UploadThumbnailTests.cs::UploadThumbnailTests.UploadThumbnailAsync_TaskInventoryItem_SendsItemIdAndTaskId::test dae80f9a7ba6222a651165f779c123d8472f88a17490e69a1112acc3cec6d108 translated
#[test]
fn thumbnail_task_item_sends_item_and_task_ids() {
let new_asset = UUID::random().expect("UUID Random");
let responses = thumbnail_responses(&new_asset);
let refs = response_refs(&responses);
let (client, recording) =
client_with_fixture(&[("InventoryThumbnailUpload", THUMBNAIL)], &refs);
let item_id = UUID::random().expect("UUID Random");
let task_id = UUID::random().expect("UUID Random");
let result =
block_on(
client
.inventory()
.upload_thumbnail(item_id, task_id, vec![1, 2, 3], None, None),
)
.expect("UploadThumbnailAsync")
.flatten()
.expect("new thumbnail asset");
uuid_eq(&result, &new_asset);
let metadata = body(&recording.requests()[0]);
assert!(metadata.contains("<key>item_id</key>"));
assert!(metadata.contains("<key>task_id</key>"));
assert!(!metadata.contains("<key>category_id</key>"));
}
// parity-case: LibreMetaverse.Tests/UploadThumbnailTests.cs::UploadThumbnailTests.UploadThumbnailAsync_KnownLocalFolder_SendsCategoryId::test 1ccca44bbead54fd5041c41fc6560478ba17f9e5d9bacff21096b254d2f526db translated
#[test]
fn thumbnail_known_local_folder_sends_category_id() {
let folder_id = UUID::random().expect("UUID Random");
let new_asset = UUID::random().expect("UUID Random");
let responses = thumbnail_responses(&new_asset);
let refs = response_refs(&responses);
let (client, recording) =
client_with_fixture(&[("InventoryThumbnailUpload", THUMBNAIL)], &refs);
let mut folder = InventoryFolder::new(folder_id).expect("InventoryFolder constructor");
folder.base.set_name("Test Folder".into());
folder.base.set_parent_uuid(UUID::zero());
let mut store = client.inventory().store().expect("fixture inventory store");
store.set_item(folder_id, folder.base);
let result = block_on(client.inventory().upload_thumbnail(
folder_id,
UUID::zero(),
vec![1, 2, 3],
None,
None,
))
.expect("UploadThumbnailAsync")
.flatten()
.expect("new thumbnail asset");
uuid_eq(&result, &new_asset);
let metadata = body(&recording.requests()[0]);
assert!(metadata.contains("<key>category_id</key>"));
assert!(!metadata.contains("<key>item_id</key>"));
assert!(!metadata.contains("<key>task_id</key>"));
}
// parity-case: LibreMetaverse.Tests/UploadThumbnailTests.cs::UploadThumbnailTests.UploadThumbnailAsync_NoCapability_ReturnsNullWithoutRequest::test 2bc1ad18b159c5d62a18d2208ed3cf3c88d3f666a0e0dad8730778cd22b59eda translated
#[test]
fn thumbnail_without_capability_makes_no_request() {
let (client, recording) = client_with_fixture(&[], &[]);
let result = block_on(client.inventory().upload_thumbnail(
UUID::random().expect("UUID Random"),
UUID::zero(),
vec![1],
None,
None,
))
.expect("UploadThumbnailAsync")
.flatten();
assert!(result.is_none());
assert!(recording.requests().is_empty());
}
// parity-case: LibreMetaverse.Tests/UploadThumbnailTests.cs::UploadThumbnailTests.UploadThumbnailAsync_MetadataResponseMissingUploader_ReturnsNullWithoutSecondRequest::test 8db3eddf1204ad795e9d5ef15365f422f96191a8b4c24ef3393b03c10d92d662 translated
#[test]
fn thumbnail_missing_uploader_returns_none_after_one_request() {
let (client, recording) = client_with_fixture(
&[("InventoryThumbnailUpload", THUMBNAIL)],
&[(THUMBNAIL, r#"{"state":"upload"}"#)],
);
let result = block_on(client.inventory().upload_thumbnail(
UUID::random().expect("UUID Random"),
UUID::zero(),
vec![1],
None,
None,
))
.expect("UploadThumbnailAsync")
.flatten();
assert!(result.is_none());
assert_eq!(recording.requests().len(), 1);
}
// parity-case: LibreMetaverse.Tests/UploadThumbnailTests.cs::UploadThumbnailTests.UploadThumbnailAsync_UploadDoesNotComplete_ReturnsNull::test e870f6913e3f45676ce490069930427e6c625072fc1d6ebbf16f503be51df4ae translated
#[test]
fn thumbnail_incomplete_upload_returns_none() {
let (client, _) = client_with_fixture(
&[("InventoryThumbnailUpload", THUMBNAIL)],
&[
(
THUMBNAIL,
r#"{"uploader":"http://test.invalid/inventory-thumbnail-upload/upload/abc123"}"#,
),
(
THUMBNAIL_UPLOADER,
r#"{"state":"failed","message":"bad image"}"#,
),
],
);
let result = block_on(client.inventory().upload_thumbnail(
UUID::random().expect("UUID Random"),
UUID::zero(),
vec![1],
None,
None,
))
.expect("UploadThumbnailAsync")
.flatten();
assert!(result.is_none());
}
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AssetManager_Null_Throws::test 4fa1be8b233dacf6a5955359f2197ff77ae85ffbf2c3c68fcd8cbe672c2c5f91 translated
#[test]
fn asset_manager_none_client_is_argument_null() {
match AssetManager::new(None) {
Err(error) => assert_eq!(error, Error::ArgumentNull),
Ok(_) => panic!("expected ArgumentNull, got success"),
}
}