#![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>>, } 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::>::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 = "".to_owned(); for (name, uri) in capabilities { write!(seed_body, "{name}{uri}") .expect("writing to a String cannot fail"); } seed_body.push_str(""); 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 { 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("object_id")); assert!(body.contains(&expected_id)); assert!(body.contains("side")); assert!(body.contains("gltf_json")); assert!(!body.contains("asset_id")); } // 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("gltf_json")); assert!(body.contains("") || body.contains("")); assert!(!body.contains("asset_id")); } // 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("asset_id")); assert!(body.contains(&expected_id)); assert!(!body.contains("gltf_json")); } // 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("item_id")); assert!(!metadata.contains("task_id")); 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("item_id")); assert!(metadata.contains("task_id")); } // 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("overrides")); assert!(body.contains("rf")); } // 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("from")); 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("item_id")); assert!(!metadata.contains("task_id")); assert!(!metadata.contains("category_id")); 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("item_id")); assert!(metadata.contains("task_id")); assert!(!metadata.contains("category_id")); } // 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 store = client.inventory().store().expect("fixture inventory store"); store .update_node_for(&folder) .expect("install fixture inventory folder"); 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("category_id")); assert!(!metadata.contains("item_id")); assert!(!metadata.contains("task_id")); } // 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"), } }