Translate inventory parity tests

This commit is contained in:
2026-08-08 20:54:02 +02:00
parent a78f9fba76
commit d7d12957e6
16 changed files with 2895 additions and 1194 deletions

View File

@@ -2,9 +2,7 @@
// Source: LibreMetaverse 2aa70bb68513b39795da5d13c88f31b86e85a3ba
// Pending cases are catalog entries, not fake executable Rust tests.
// pending-parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.InventoryAISClient_Null_Throws::test ac92e375efc45c50b37e6c82aea372d605fceb470ce053a025e05ff47a5c7121
// pending-parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.GridManager_Null_Throws::test e972a95f334b0f46948cda34c27124c28c44faf4372d0e598d7dc60050b49be4
// pending-parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.InventoryManager_Null_Throws::test 5e7498066c944800354abbc71eea825fe90d0c40a25a6065967a3ebcc9efea74
// pending-parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AppearanceManager_Null_Throws::test a3c872805bc924b5a1a9441d5c9eb61ed6d6e9477e4bb411131359290b144ab4
// pending-parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.ObjectManager_Null_Throws::test 8a11feba0edfb35e5dd99f681182e8f5a5cf8a3f738211b45a683a8a2ad45ddb
// pending-parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AgentManager_Null_Throws::test d86e41b6cdd03063685aa330c31b9a274c1d400b9a461abf3065fe185fe0bbf0
@@ -28,40 +26,6 @@
// pending-parity-case: LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs::ExperiencePreferencesMessageTests.ExtractExperiencePermission_Allowed_ReturnsAllow::test cf095ab428713004ebac6e1915a47ae0c03bd2958addbba2a39fd2f0edf51a74
// pending-parity-case: LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs::ExperiencePreferencesMessageTests.ExtractExperiencePermission_Blocked_ReturnsBlock::test af084d1ab7a11079e6251865e8fd5aecfde38d2df359ebc1e208bc74925935c0
// pending-parity-case: LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs::ExperiencePreferencesMessageTests.ExtractExperiencePermission_NeitherList_ReturnsForget::test 93204469ecfec2f3f4eb6838c99f6e2baf09bf76a075958d1ed369ba03767158
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_ObjectAsset_ResultsInAttachmentInventoryTypeAndParsesFields::test 3ae365ed34f12d036dcd44c94f8568d163cfeec2a58b4975975b77b418770131
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_ItemsKey_ParsesConcreteItems::test d9e7c64106639071e1ec6ada7346bfa050a31183becaac79efefcd4755fe2855
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_MeshAsset_TreatedAsAttachment::test f807110d577c654128dd8d42e618c610c260c4f3367cf2795776564409f876a2
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_TopLevelItemsKey_WorksWithoutEmbedded::test 9c8f2090d59ff5e75e75381a7283c501cadd56e591991cdead23c504738a0a6d
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_TopLevelLinksKey_WorksWithoutEmbedded::test 29afd2bd6a554959d6cfa99b632672d56bbb5bcad2ccbaf8caf4773a514f5d79
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_NullOrEmpty_ReturnsEmptyList::test 198fb619bbf396a4b1a7b4b85b7fba8808ae127e2575b2b84d21f01a2bdbc445
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseEmbedded_CombinesItemsAndLinks::test f2ebf4638c9aef3c846dc182d97d80c95118799428cf8c2bba86163e33196846
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.MoveCategoryLinksAsync_AlwaysReturnsFalse::test bebad05897efc0da2f959b4f567faa3ffee950520d13265ecbab1dee978f4886
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.MoveCategoryLinksAsync_ReturnsCompletedTask::test f9830b11e6a75c90f8394a5ade56bbd542c140be4c050ba7db6c9ee4d2b7a699
// pending-parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventorySkeletonIsPopulatedAfterLogin::test ea1508e3c676639f00bb8a4c4c72d2ee668603bfbff40ee0b93d7f4beb607f75
// pending-parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryRootContainsFolders::test 71f41c718060622e1fd653e6b0fc9fe65f3cd68cab3d128f494cdbc90f9d2726
// pending-parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryFolderContainsItems::test f0d77649b82d4d7d7778acebd851dd4eb44b76eb687af07bccdd20a1efe96223
// pending-parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryItemCanBeFetchedById::test 7fe89086754a673ab69814202b48aab9add30dd63ee98a953bdedd54d6788af2
// pending-parity-case: LibreMetaverse.Tests/InventoryManagerTests.cs::InventoryManagerTests.EncryptDecrypt_RoundTrip::test 7b59c330ffacf1174dd884f369a4e4015e1a212a0e43d7fa8889f7505dd5599d
// pending-parity-case: LibreMetaverse.Tests/InventoryManagerTests.cs::InventoryManagerTests.ItemCRC_IsDeterministic_AndChangesWhenFieldsChange::test dbd322778ad7f110abce5f10a0c83ad002ae76f6a114fa795fb90a799f4ff077
// pending-parity-case: LibreMetaverse.Tests/InventoryManagerTests.cs::InventoryManagerTests.CreateInventoryItem_ReturnsCorrectSubclass::test 219f8c91bc1b3d5b5d3e5b2f2880c5733fd02e6103ccc9c6ecbf8d03328274f6
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.AddAndGetItem::test 24702f0cf9215beee29123ee81dab4a7fe971beb197ecceac9a1761b695bce45
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateItemParent::test ea1a73e28fb92b873ee28102e81811d769b5cd815d2e1d756ece6ea635e5c014
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.InventoryNodeModifyTime_SelfLoop::test f8050cc284f7954c1301091ddc1cbf0fa76073806eee7de4f6cb21063591fbf1
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateNodeFor_SelfLoop::test 14f42e5263c465e8534347c5a3195c195e2d90371dbc2f69d36a40362384505c
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateNodeFor_ComplexSelfLoop::test a2fc7b29b8a7a7767c42ea9c7fb9628451c88991c3eb1ecbd2ce0952ab0b5700
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.RemoveItem::test 1e72f9049b9114f0c56e152d84190d31bca7a862f7ad1459624d753beb6818b4
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.SaveAndRestoreAsync::test bf23b1c67cc9618713d2d8e6653f0ffbbf4c24d7f2c59c5dd639047f7e0c8156
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.GetNodeOrDefault_ReturnsNullWhenMissing::test f5651b5a143470c3ab6ba1f835206a1a55a6218c28242dc12c5d6b5e89519596
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.TryGetValue_Generic::test e6f6d6535af9d011e65cb3c297fe99543f85b64382c47e1756f051d6031886f5
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.RemoveFolderRemovesAllDescendants::test b537223d271b75c11adfa6d87c1e79bb29366f294657ddf71f236fab0dee009f
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.DescendentCountUpdatesOnAddAndRemove::test cfe6571887d0e992ff4d69ccbcdfb34012f9bb5d62118201c33d7d66cc9ddfd8
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.MovingItemUpdatesAncestorDescendentCounts::test 1f4b4189694b9471d48562a434c91a9e455706b83b65bb855c0846388b44c759
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.ConcurrentAddMoveRemoveStress::test 0b76b2fb62a6300e02a02859772f146f107caa77986f4d593e3a4d01966ab4e4
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.ConcurrentLinkAddRemoveStress::test eef3b4eb300427800757dc6cac2dbd9202ed1cf1aac42565b3c26eb11e71bbf1
// pending-parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.LinkIndexUpdateOnAssetChange::test d3e0066e06ab1c48520e5e0bbba8069b74f1a38d37e24d914392406b41b2ea0c
// pending-parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibraryFoldersArePopulatedAfterLogin::test dc865c88bb514bb71157a451e938b5ae3e3adde208d267d6a7196706dc9cfda2
// pending-parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibraryFolderItemsCanBeFetched::test 475561d29dd25aba52d94f8374fe9083fea2ed7af44dfb6d25b55ce487b080ab
// pending-parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibrarySubFolderContainsItems::test a6d02cbcb7fde8248419f1cf7355c3d37f06e8b365dc483fad935dd9a0ac5909
// pending-parity-case: LibreMetaverse.Tests/MarketplaceFolderClassifierTests.cs::MarketplaceFolderClassifierTests.GetRole_ListingsRootFolder_ReturnsListingsRoot::test a3c21e19f21924bffbbafe1199a4b942b545bdb278c3362b2a1ec98b35d672cf
// pending-parity-case: LibreMetaverse.Tests/MarketplaceFolderClassifierTests.cs::MarketplaceFolderClassifierTests.GetRole_ListingFolder_ReturnsListing::test c08d860a4506254d9ac831e91894f2fc34d840c100468ee7e39b505690302368
// pending-parity-case: LibreMetaverse.Tests/MarketplaceFolderClassifierTests.cs::MarketplaceFolderClassifierTests.GetRole_VersionFolder_ReturnsVersion::test 90d792d6858c6a253b9ddf80d3f5e7244570490fa4ef68c1914b342db05c7aa2
@@ -769,10 +733,6 @@
// pending-parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_EmptyDaysClearsSchedule::test 57ef3c0f8d9eeac58a7d1b3e7dbc44413016f39303d304cacd3071bd2c55f320
// pending-parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_NoCapability_ReturnsFalseWithoutRequest::test be03507fe574b9f7c7174c39af44b7b693e1b7a496845db46451ef09bf9db206
// pending-parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_NonSuccessStatus_ReturnsFalse::test 16c8988d7aa15ea9f58deaa2e3f416b6115978e23073fd99749843f4f410dca4
// pending-parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_CapAvailable_ReturnsContentsFolderAndDecryptsShadowId::test c9bfc5526260f9e2dffa8a22938999911fdd80be4b2b6d6487bf6171f8dbb56a
// pending-parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_PlainAssetId_ParsesWithoutDecryption::test 95f39928dae61bca86b7d0a0702307db6c5fdc754e51f73a24bd5ee131e6de3f
// pending-parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NonSuccessStatus_ReturnsContentsFolderOnly::test b8a25891064e470ac8d77ecd8a4847cf773b043de5ebeae275a9dd6669518585
// pending-parity-case: LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NoCapability_DoesNotMakeHttpRequest::test c0c43ebcf781fb81b1fef7ce1dd2774e350d92ef882f49876f1a16d23a338592
// pending-parity-case: LibreMetaverse.Tests/SimConsoleTests.cs::SimConsoleTests.SendSimConsoleCommandAsync_IgnoresPostBody_UsesSimConsoleResponseEvent::test df6eb48241f71c34074be66869c9ae1d5980186583259568728261b5eedd7c48
// pending-parity-case: LibreMetaverse.Tests/SimConsoleTests.cs::SimConsoleTests.SendSimConsoleCommandAsync_NoResponseEvent_ReturnsNullAfterTimeout::test 2ac5a6c96f3acec341056387d727288fd3661565bd2a7c385078602efe8661da
// pending-parity-case: LibreMetaverse.Tests/SimConsoleTests.cs::SimConsoleTests.SimConsoleResponseMessage_Deserialize_ReadsBodyField::test 4d367bc3fd012d1579847fd9837bf883c5ea95452776a086bbf856899d7c2099

View File

@@ -0,0 +1,302 @@
// Exact Rust translations of LibreMetaverse.Tests/ConstructorNullArgumentTests.cs and
// LibreMetaverse.Tests/InventoryAISClientTests.cs inventory-owned cases.
// Source SHA-256: 28deea0bf4d9353a2424842c6184abab8fe82746ceb12e093912728a247f21bc
// Source SHA-256: 6de18630ff0da2612721a9d4e28b5fdee5845c8d9477d322a8d608508762e1ff
use libremetaverse::{Error, GridClient, InventoryAISClient};
use libremetaverse_compat_tests::block_on;
use libremetaverse_structured_data::OSD;
use libremetaverse_types::{AssetType, InventoryType, SaleType, UUID};
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use std::task::{Context, Poll, Waker};
use std::time::{SystemTime, UNIX_EPOCH};
fn uuid() -> UUID {
UUID::random().expect("UUID Random")
}
fn now_seconds() -> i32 {
i32::try_from(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock before Unix epoch")
.as_secs(),
)
.expect("current Unix time fits the C# Int32 fixture")
}
fn map(entries: impl IntoIterator<Item = (&'static str, OSD)>) -> OSD {
OSD::Map(
entries
.into_iter()
.map(|(key, value)| (key.to_owned(), value))
.collect(),
)
}
fn permissions(creator: UUID, last_owner: UUID, owner: Option<UUID>) -> OSD {
map([
("creator_id", OSD::UUID(creator)),
("last_owner_id", OSD::UUID(last_owner)),
("owner_id", OSD::UUID(owner.unwrap_or(last_owner))),
("base_mask", OSD::Integer(0)),
("everyone_mask", OSD::Integer(0)),
("group_mask", OSD::Integer(0)),
("next_owner_mask", OSD::Integer(0)),
("owner_mask", OSD::Integer(0)),
("is_owner_group", OSD::Boolean(false)),
("group_id", OSD::UUID(UUID::zero())),
])
}
fn sale_info() -> OSD {
map([
("sale_price", OSD::Integer(0)),
("sale_type", OSD::Integer(SaleType::Not as i32)),
])
}
fn concrete_item(item_id: UUID, parent_id: UUID, agent_id: UUID, name: &str) -> OSD {
map([
("item_id", OSD::UUID(item_id)),
("parent_id", OSD::UUID(parent_id)),
("name", OSD::String(name.into())),
("desc", OSD::String("desc".into())),
("agent_id", OSD::UUID(agent_id)),
("inv_type", OSD::Integer(i32::from(InventoryType::OBJECT.0))),
("type", OSD::Integer(AssetType::Object as i32)),
("created_at", OSD::Integer(now_seconds())),
("permissions", permissions(uuid(), uuid(), Some(agent_id))),
("sale_info", sale_info()),
])
}
fn link_item(
item_id: UUID,
parent_id: UUID,
agent_id: UUID,
linked_id: UUID,
name: &str,
asset_type: AssetType,
) -> OSD {
map([
("item_id", OSD::UUID(item_id)),
("parent_id", OSD::UUID(parent_id)),
("name", OSD::String(name.into())),
("desc", OSD::String("desc".into())),
("agent_id", OSD::UUID(agent_id)),
("linked_id", OSD::UUID(linked_id)),
(
"inv_type",
OSD::Integer(i32::from(InventoryType::TEXTURE.0)),
),
("type", OSD::Integer(asset_type as i32)),
("created_at", OSD::Integer(now_seconds())),
("permissions", permissions(uuid(), uuid(), None)),
("sale_info", sale_info()),
])
}
fn section(name: &'static str, value: OSD, embedded: bool) -> OSD {
let content = map([(name, value)]);
if embedded {
map([("_embedded", content)])
} else {
content
}
}
fn numbered(value: OSD) -> OSD {
OSD::Map(HashMap::from([("0".into(), value)]))
}
fn client() -> InventoryAISClient {
let grid = GridClient::new().expect("GridClient constructor");
InventoryAISClient::new(Some(Arc::new(grid))).expect("InventoryAISClient constructor")
}
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.InventoryAISClient_Null_Throws::test ac92e375efc45c50b37e6c82aea372d605fceb470ce053a025e05ff47a5c7121 translated
#[test]
fn inventory_ais_client_null_throws() {
match InventoryAISClient::new(None) {
Err(error) => assert_eq!(error, Error::ArgumentNull),
Ok(_) => panic!("expected ArgumentNull for client"),
}
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_ObjectAsset_ResultsInAttachmentInventoryTypeAndParsesFields::test 3ae365ed34f12d036dcd44c94f8568d163cfeec2a58b4975975b77b418770131 translated
#[test]
fn parse_links_object_asset_results_in_attachment_type_and_parses_fields() {
let item_id = uuid();
let parent_id = uuid();
let agent_id = uuid();
let linked_id = uuid();
let response = section(
"links",
numbered(link_item(
item_id,
parent_id,
agent_id,
linked_id,
"TestObject",
AssetType::Object,
)),
true,
);
let result = client()
.parse_links_from_embedded(Some(response))
.expect("ParseLinksFromEmbedded");
assert_eq!(result.len(), 1);
let parsed = &result[0];
assert_eq!(parsed.base.name(), "TestObject");
assert_eq!(parsed.base.parent_uuid(), parent_id);
assert_eq!(parsed.asset_uuid(), linked_id);
assert_eq!(parsed.base.owner_id(), agent_id);
assert_ne!(parsed.creation_date(), UNIX_EPOCH);
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_ItemsKey_ParsesConcreteItems::test d9e7c64106639071e1ec6ada7346bfa050a31183becaac79efefcd4755fe2855 translated
#[test]
fn parse_items_items_key_parses_concrete_items() {
let item_id = uuid();
let parent_id = uuid();
let agent_id = uuid();
let response = section(
"items",
numbered(concrete_item(item_id, parent_id, agent_id, "ConcreteItem")),
true,
);
let result = client()
.parse_items_from_embedded(Some(response))
.expect("ParseItemsFromEmbedded");
assert_eq!(result.len(), 1);
let parsed = &result[0];
assert_eq!(parsed.base.name(), "ConcreteItem");
assert_eq!(parsed.base.parent_uuid(), parent_id);
assert_eq!(parsed.base.owner_id(), agent_id);
assert_ne!(parsed.creation_date(), UNIX_EPOCH);
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_MeshAsset_TreatedAsAttachment::test f807110d577c654128dd8d42e618c610c260c4f3367cf2795776564409f876a2 translated
#[test]
fn parse_links_mesh_asset_treated_as_attachment() {
let item_id = uuid();
let parent_id = uuid();
let agent_id = uuid();
let linked_id = uuid();
let response = section(
"links",
numbered(link_item(
item_id,
parent_id,
agent_id,
linked_id,
"MeshLink",
AssetType::Mesh,
)),
false,
);
let result = client()
.parse_links_from_embedded(Some(response))
.expect("ParseLinksFromEmbedded");
assert_eq!(result.len(), 1);
let parsed = &result[0];
assert_eq!(parsed.base.name(), "MeshLink");
assert_eq!(parsed.base.parent_uuid(), parent_id);
assert_eq!(parsed.asset_uuid(), linked_id);
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_TopLevelItemsKey_WorksWithoutEmbedded::test 9c8f2090d59ff5e75e75381a7283c501cadd56e591991cdead23c504738a0a6d translated
#[test]
fn parse_items_top_level_items_key_works_without_embedded() {
let response = section(
"items",
numbered(concrete_item(uuid(), uuid(), uuid(), "TopLevelItem")),
false,
);
let result = client()
.parse_items_from_embedded(Some(response))
.expect("ParseItemsFromEmbedded");
assert_eq!(result.len(), 1);
assert_eq!(result[0].base.name(), "TopLevelItem");
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_TopLevelLinksKey_WorksWithoutEmbedded::test 29afd2bd6a554959d6cfa99b632672d56bbb5bcad2ccbaf8caf4773a514f5d79 translated
#[test]
fn parse_links_top_level_links_key_works_without_embedded() {
let response = section(
"links",
numbered(link_item(
uuid(),
uuid(),
uuid(),
uuid(),
"TopLevelLink",
AssetType::Object,
)),
false,
);
let result = client()
.parse_links_from_embedded(Some(response))
.expect("ParseLinksFromEmbedded");
assert_eq!(result.len(), 1);
assert_eq!(result[0].base.name(), "TopLevelLink");
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_NullOrEmpty_ReturnsEmptyList::test 198fb619bbf396a4b1a7b4b85b7fba8808ae127e2575b2b84d21f01a2bdbc445 translated
#[test]
fn parse_items_null_or_empty_returns_empty_list() {
let ais = client();
assert!(
ais.parse_items_from_embedded(None)
.expect("ParseItemsFromEmbedded null")
.is_empty()
);
assert!(
ais.parse_items_from_embedded(Some(OSD::Map(HashMap::new())))
.expect("ParseItemsFromEmbedded empty")
.is_empty()
);
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseEmbedded_CombinesItemsAndLinks::test f2ebf4638c9aef3c846dc182d97d80c95118799428cf8c2bba86163e33196846 translated
#[test]
fn parse_embedded_combines_items_and_links() {
let item = concrete_item(uuid(), uuid(), uuid(), "I");
let link = link_item(uuid(), uuid(), uuid(), uuid(), "L", AssetType::Object);
let response = map([(
"_embedded",
map([("items", numbered(item)), ("links", numbered(link))]),
)]);
let mut folders = None;
let mut items = None;
let mut links = None;
client()
.parse_embedded(response, &mut folders, &mut items, &mut links)
.expect("ParseEmbedded");
assert_eq!(items.expect("items output").len(), 1);
assert_eq!(links.expect("links output").len(), 1);
assert!(folders.is_some(), "folders output must not be null");
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.MoveCategoryLinksAsync_AlwaysReturnsFalse::test bebad05897efc0da2f959b4f567faa3ffee950520d13265ecbab1dee978f4886 translated
#[test]
fn move_category_links_always_returns_false() {
let result = block_on(client().move_category_links("outfit".into(), uuid(), None))
.expect("MoveCategoryLinksAsync");
assert!(!result, "AIS v3 has no MoveCategoryLinks equivalent");
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.MoveCategoryLinksAsync_ReturnsCompletedTask::test f9830b11e6a75c90f8394a5ade56bbd542c140be4c050ba7db6c9ee4d2b7a699 translated
#[test]
fn move_category_links_returns_completed_future() {
let ais = client();
let mut future = std::pin::pin!(ais.move_category_links("outfit".into(), uuid(), None));
let mut context = Context::from_waker(Waker::noop());
assert!(
matches!(future.as_mut().poll(&mut context), Poll::Ready(Ok(false))),
"MoveCategoryLinksAsync should be immediately ready with false"
);
}

View File

@@ -0,0 +1,357 @@
// Exact Rust translations of LibreMetaverse.Tests/InventoryFetchTests.cs and
// LibreMetaverse.Tests/LibraryInventoryTests.cs.
// Source SHA-256: f5876550ab924ad034ade848e562ee70bbb4e004299d928f43c45cadd1a01465
// Source SHA-256: 1d65ebbaf8fd22427371e2a7f1ef76a4f9a70df0bea7fa53f8f5d3fd3044fa53
use libremetaverse::{
GridClient, InventoryFolder, InventoryItem, InventoryObjectClass, InventorySortOrder,
NetworkManager,
};
use libremetaverse_compat_tests::{block_on, live_grid_credentials};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::{CancellationToken, CancellationTokenSource};
use std::sync::Arc;
use std::time::Duration;
struct LiveInventory {
client: GridClient,
}
impl LiveInventory {
fn login() -> Self {
let mut client = GridClient::new().expect("GridClient constructor");
client.settings().timing().login_timeout = 30_000;
let network = client.network();
let (first, last, password, login_url) = live_grid_credentials();
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
.expect("NetworkManager StartLocation");
let mut login = network
.default_login_params(
first,
last,
password,
"Unit Test Framework".into(),
"admin@radegast.life".into(),
)
.expect("NetworkManager DefaultLoginParams");
login.start = start;
login.uri = login_url;
let logged_in = block_on(network.login_with_login_params_cancellation_token(login, None))
.expect("NetworkManager LoginAsync");
assert!(
logged_in,
"client failed to login: {}",
network.login_message()
);
assert!(network.connected(), "client is not connected to the grid");
assert!(
network.current_sim().is_some(),
"CurrentSim is null after successful login"
);
Self { client }
}
fn agent_id(&mut self) -> UUID {
self.client.self_().agent_id()
}
}
impl Drop for LiveInventory {
fn drop(&mut self) {
let _ = self.client.network().logout_with_method();
let _ = self.client.dispose_with_method();
}
}
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 folders(entries: &[Box<dyn InventoryObjectClass>]) -> impl Iterator<Item = &InventoryFolder> {
entries
.iter()
.filter_map(|entry| entry.as_any().downcast_ref::<InventoryFolder>())
}
fn items(entries: &[Box<dyn InventoryObjectClass>]) -> impl Iterator<Item = &InventoryItem> {
entries
.iter()
.filter_map(|entry| entry.as_any().downcast_ref::<InventoryItem>())
}
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventorySkeletonIsPopulatedAfterLogin::test ea1508e3c676639f00bb8a4c4c72d2ee668603bfbff40ee0b93d7f4beb607f75 ignored-live
#[test]
#[cfg_attr(
not(live_grid_credentials),
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
)]
fn inventory_skeleton_is_populated_after_login() {
let mut live = LiveInventory::login();
let agent_id = live.agent_id();
let store = live.client.inventory().store().expect("inventory store");
let root = store.root_folder().expect("inventory root folder");
assert_ne!(root.base.uuid(), UUID::zero());
let children = store
.get_contents_with_inventory_folder(&root)
.expect("inventory root contents");
assert!(!children.is_empty(), "inventory root has no children");
assert_eq!(folders(&children).count(), children.len());
let first = folders(&children).next().expect("inventory sub-folder");
assert_eq!(first.base.owner_id(), agent_id);
}
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryRootContainsFolders::test 71f41c718060622e1fd653e6b0fc9fe65f3cd68cab3d128f494cdbc90f9d2726 ignored-live
#[test]
#[cfg_attr(
not(live_grid_credentials),
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
)]
fn inventory_root_contains_folders() {
let mut live = LiveInventory::login();
let agent_id = live.agent_id();
let root = live
.client
.inventory()
.store()
.expect("inventory store")
.root_folder()
.expect("inventory root folder");
assert_ne!(root.base.uuid(), UUID::zero());
let contents = block_on(live.client.inventory().folder_contents(
root.base.uuid(),
agent_id,
true,
true,
InventorySortOrder::BY_NAME,
Some(cancellation_after(Duration::from_secs(30))),
None,
))
.expect("FolderContentsAsync");
if contents.is_empty() {
eprintln!("inventory root fetch returned no results; transient server issue");
return;
}
assert!(folders(&contents).next().is_some());
}
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryFolderContainsItems::test f0d77649b82d4d7d7778acebd851dd4eb44b76eb687af07bccdd20a1efe96223 ignored-live
#[test]
#[cfg_attr(
not(live_grid_credentials),
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
)]
fn inventory_folder_contains_items() {
let mut live = LiveInventory::login();
let agent_id = live.agent_id();
let store = live.client.inventory().store().expect("inventory store");
let root = store.root_folder().expect("inventory root folder");
assert_ne!(root.base.uuid(), UUID::zero());
let skeleton = store
.get_contents_with_inventory_folder(&root)
.expect("inventory skeleton");
let subfolders: Vec<_> = folders(&skeleton).collect();
assert!(!subfolders.is_empty(), "no inventory sub-folders");
let token = cancellation_after(Duration::from_secs(30));
let mut found = None;
for folder in subfolders {
if token.is_cancellation_requested() {
break;
}
let Ok(contents) = block_on(live.client.inventory().folder_contents(
folder.base.uuid(),
agent_id,
true,
true,
InventorySortOrder::BY_NAME,
Some(token.clone()),
None,
)) else {
continue;
};
let found_items: Vec<_> = items(&contents).collect();
if let Some(first) = found_items.first() {
found = Some((
folder.base.name(),
found_items.len(),
first.base.name(),
first.asset_type(),
));
break;
}
}
let Some((folder_name, count, first_name, first_type)) = found else {
eprintln!("no items found in any inventory sub-folder");
return;
};
assert!(count > 0, "folder {folder_name} has no items");
eprintln!("found {count} items in {folder_name}; first: {first_name} ({first_type:?})");
}
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryItemCanBeFetchedById::test 7fe89086754a673ab69814202b48aab9add30dd63ee98a953bdedd54d6788af2 ignored-live
#[test]
#[cfg_attr(
not(live_grid_credentials),
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
)]
fn inventory_item_can_be_fetched_by_id() {
let mut live = LiveInventory::login();
let agent_id = live.agent_id();
let store = live.client.inventory().store().expect("inventory store");
let root = store.root_folder().expect("inventory root folder");
let skeleton = store
.get_contents_with_inventory_folder(&root)
.expect("inventory skeleton");
let subfolders: Vec<_> = folders(&skeleton).collect();
assert!(!subfolders.is_empty(), "no inventory sub-folders");
let token = cancellation_after(Duration::from_secs(30));
let mut target = None;
for folder in subfolders {
if token.is_cancellation_requested() {
break;
}
let Ok(contents) = block_on(live.client.inventory().folder_contents(
folder.base.uuid(),
agent_id,
true,
true,
InventorySortOrder::BY_NAME,
Some(token.clone()),
None,
)) else {
continue;
};
if let Some(item) = items(&contents).next() {
target = Some((item.base.uuid(), item.base.name()));
break;
}
}
let Some((target_id, target_name)) = target else {
eprintln!("no inventory item found to fetch by UUID");
return;
};
let fetched = block_on(live.client.inventory().fetch_item_http(
target_id,
agent_id,
Some(token),
))
.expect("FetchItemHttpAsync")
.expect("fetched inventory item");
assert_eq!(fetched.base.uuid(), target_id);
assert_eq!(fetched.base.name(), target_name);
}
// parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibraryFoldersArePopulatedAfterLogin::test dc865c88bb514bb71157a451e938b5ae3e3adde208d267d6a7196706dc9cfda2 ignored-live
#[test]
#[cfg_attr(
not(live_grid_credentials),
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
)]
fn library_folders_are_populated_after_login() {
let live = LiveInventory::login();
let store = live.client.inventory().store().expect("inventory store");
let root = store.library_folder().expect("library root folder");
assert_ne!(root.base.uuid(), UUID::zero());
let children = store
.get_contents_with_inventory_folder(&root)
.expect("library root contents");
assert!(!children.is_empty(), "library root has no children");
assert_eq!(folders(&children).count(), children.len());
assert_ne!(
folders(&children)
.next()
.expect("library sub-folder")
.base
.owner_id(),
UUID::zero()
);
}
// parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibraryFolderItemsCanBeFetched::test 475561d29dd25aba52d94f8374fe9083fea2ed7af44dfb6d25b55ce487b080ab ignored-live
#[test]
#[cfg_attr(
not(live_grid_credentials),
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
)]
fn library_folder_items_can_be_fetched() {
let live = LiveInventory::login();
let store = live.client.inventory().store().expect("inventory store");
let root = store.library_folder().expect("library root folder");
let skeleton = store
.get_contents_with_inventory_folder(&root)
.expect("library skeleton");
let owner = folders(&skeleton)
.map(|folder| folder.base.owner_id())
.find(|owner| *owner != UUID::zero())
.expect("library owner UUID");
let contents = block_on(live.client.inventory().folder_contents(
root.base.uuid(),
owner,
true,
true,
InventorySortOrder::BY_NAME,
Some(cancellation_after(Duration::from_secs(30))),
None,
))
.expect("FolderContentsAsync");
if contents.is_empty() {
eprintln!("library root fetch returned no results; transient server issue");
return;
}
assert!(folders(&contents).next().is_some());
}
// parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibrarySubFolderContainsItems::test a6d02cbcb7fde8248419f1cf7355c3d37f06e8b365dc483fad935dd9a0ac5909 ignored-live
#[test]
#[cfg_attr(
not(live_grid_credentials),
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
)]
fn library_sub_folder_contains_items() {
let live = LiveInventory::login();
let store = live.client.inventory().store().expect("inventory store");
let root = store.library_folder().expect("library root folder");
let skeleton = store
.get_contents_with_inventory_folder(&root)
.expect("library skeleton");
let subfolders: Vec<_> = folders(&skeleton).collect();
assert!(!subfolders.is_empty(), "no library sub-folders");
let owner = subfolders
.iter()
.map(|folder| folder.base.owner_id())
.find(|owner| *owner != UUID::zero())
.expect("library owner UUID");
let token = cancellation_after(Duration::from_secs(30));
let mut found = None;
for folder in subfolders {
if token.is_cancellation_requested() {
break;
}
let Ok(contents) = block_on(live.client.inventory().folder_contents(
folder.base.uuid(),
owner,
true,
true,
InventorySortOrder::BY_NAME,
Some(token.clone()),
None,
)) else {
continue;
};
let count = items(&contents).count();
if count > 0 {
found = Some((folder.base.name(), count));
break;
}
}
let Some((folder_name, count)) = found else {
eprintln!("no items found in any library sub-folder");
return;
};
assert!(count > 0, "folder {folder_name} has no items");
}

View File

@@ -0,0 +1,75 @@
// Exact Rust translations of LibreMetaverse.Tests/ConstructorNullArgumentTests.cs and
// LibreMetaverse.Tests/InventoryManagerTests.cs inventory-owned cases.
// Source SHA-256: 28deea0bf4d9353a2424842c6184abab8fe82746ceb12e093912728a247f21bc
// Source SHA-256: 9fd33d6a4e9f3aa2d414439203eecda605ede3eb6e848533ef0778b023407c3d
use libremetaverse::{
Error, InventoryItem, InventoryManager, InventoryNotecard, InventoryTexture, Permissions,
};
use libremetaverse_types::{AssetType, InventoryType, SaleType, UUID};
use std::time::SystemTime;
fn uuid() -> UUID {
UUID::random().expect("UUID Random")
}
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.InventoryManager_Null_Throws::test 5e7498066c944800354abbc71eea825fe90d0c40a25a6065967a3ebcc9efea74 translated
#[test]
fn inventory_manager_null_throws() {
match InventoryManager::new(None) {
Err(error) => assert_eq!(error, Error::ArgumentNull),
Ok(_) => panic!("expected ArgumentNull for client"),
}
}
// parity-case: LibreMetaverse.Tests/InventoryManagerTests.cs::InventoryManagerTests.EncryptDecrypt_RoundTrip::test 7b59c330ffacf1174dd884f369a4e4015e1a212a0e43d7fa8889f7505dd5599d translated
#[test]
fn encrypt_decrypt_round_trip() {
let asset_id = uuid();
let shadow = InventoryManager::encrypt_asset_id(asset_id).expect("EncryptAssetID");
let decrypted = InventoryManager::decrypt_shadow_id(shadow).expect("DecryptShadowID");
assert_eq!(decrypted, asset_id);
}
// parity-case: LibreMetaverse.Tests/InventoryManagerTests.cs::InventoryManagerTests.ItemCRC_IsDeterministic_AndChangesWhenFieldsChange::test dbd322778ad7f110abce5f10a0c83ad002ae76f6a114fa795fb90a799f4ff077 translated
#[test]
fn item_crc_is_deterministic_and_changes_when_fields_change() {
let mut item = InventoryItem::new_with_uuid(uuid()).expect("InventoryItem constructor");
item.set_asset_uuid(uuid());
item.base.set_parent_uuid(uuid());
item.set_creator_id(uuid());
item.base.set_owner_id(uuid());
item.set_group_id(uuid());
item.set_flags(0x1234_5678);
item.set_inventory_type(InventoryType::OBJECT);
item.set_asset_type(AssetType::Object);
item.set_creation_date(SystemTime::now());
item.set_sale_price(42);
item.set_sale_type(SaleType::Not);
item.set_permissions(Permissions::full_permissions());
let crc1 = InventoryManager::item_crc(&item).expect("ItemCRC first");
let crc2 = InventoryManager::item_crc(&item).expect("ItemCRC second");
assert_eq!(crc2, crc1, "ItemCRC must be deterministic");
item.set_asset_uuid(uuid());
let crc3 = InventoryManager::item_crc(&item).expect("ItemCRC changed");
assert_ne!(crc3, crc1, "ItemCRC must include AssetUUID");
}
// parity-case: LibreMetaverse.Tests/InventoryManagerTests.cs::InventoryManagerTests.CreateInventoryItem_ReturnsCorrectSubclass::test 219f8c91bc1b3d5b5d3e5b2f2880c5733fd02e6103ccc9c6ecbf8d03328274f6 translated
#[test]
fn create_inventory_item_returns_correct_subclass() {
let id = uuid();
let texture = InventoryManager::create_inventory_item(InventoryType::TEXTURE, id)
.expect("CreateInventoryItem texture");
let notecard = InventoryManager::create_inventory_item(InventoryType::NOTECARD, id)
.expect("CreateInventoryItem notecard");
// C# explicitly casts 999 to the signed-byte-backed InventoryType, yielding -25.
let unknown = InventoryManager::create_inventory_item(InventoryType(-25), id)
.expect("CreateInventoryItem unknown");
assert!(texture.as_any().is::<InventoryTexture>());
assert!(notecard.as_any().is::<InventoryNotecard>());
assert!(unknown.as_any().is::<InventoryItem>());
}

View File

@@ -0,0 +1,545 @@
// Exact Rust translations of LibreMetaverse.Tests/InventoryTests.cs.
// Source SHA-256: 378ab668bdea082d90fb0105e2bcd6b7734d7fe9eb9c1bf550554bd4ef700953
use libremetaverse::{GridClient, Inventory, InventoryFolder, InventoryItem, InventoryNode};
use libremetaverse_compat_tests::block_on;
use libremetaverse_types::{AssetType, UUID};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
fn uuid() -> UUID {
UUID::random().expect("UUID Random")
}
struct Fixture {
client: Arc<GridClient>,
inventory: Inventory,
}
impl Fixture {
fn new() -> Self {
let client = Arc::new(GridClient::new().expect("GridClient constructor"));
let inventory = Inventory::new_with_grid_client_uuid(Arc::clone(&client), uuid())
.expect("Inventory constructor");
Self { client, inventory }
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = self.client.dispose_with_method();
}
}
fn folder(name: &str, parent: UUID) -> InventoryFolder {
let mut folder = InventoryFolder::new(uuid()).expect("InventoryFolder constructor");
folder.base.set_name(name.into());
folder.base.set_parent_uuid(parent);
folder
}
fn item(name: &str, parent: UUID) -> InventoryItem {
let mut item = InventoryItem::new_with_uuid(uuid()).expect("InventoryItem constructor");
item.base.set_name(name.into());
item.base.set_parent_uuid(parent);
item
}
struct TempDirectory(PathBuf);
impl TempDirectory {
fn new() -> Self {
let path = std::env::temp_dir().join(format!("metacrate-inventory-{}", uuid().to_string()));
std::fs::create_dir(&path).expect("create isolated temporary directory");
Self(path)
}
fn file(&self, name: &str) -> PathBuf {
self.0.join(name)
}
}
impl Drop for TempDirectory {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn path_string(path: &Path) -> String {
path.to_string_lossy().into_owned()
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.AddAndGetItem::test 24702f0cf9215beee29123ee81dab4a7fe971beb197ecceac9a1761b695bce45 translated
#[test]
fn add_and_get_item() {
let fixture = Fixture::new();
let folder = folder("TestFolder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("Item1", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
assert!(fixture.inventory.contains_with_uuid(item_id).unwrap());
let mut found = None;
assert!(
fixture
.inventory
.try_get_value_with_uuid_inventory_base(item_id, &mut found)
.unwrap()
);
let found = found.expect("inventory item");
assert!(found.as_any().is::<InventoryItem>());
assert_eq!(found.inventory_base().name(), "Item1");
let contents = fixture
.inventory
.get_contents_with_inventory_folder(&folder)
.unwrap();
assert_eq!(contents.len(), 1);
assert_eq!(contents[0].inventory_base().uuid(), item_id);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateItemParent::test ea1a73e28fb92b873ee28102e81811d769b5cd815d2e1d756ece6ea635e5c014 translated
#[test]
fn update_item_parent() {
let fixture = Fixture::new();
let folder1 = folder("F1", UUID::zero());
let folder2 = folder("F2", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
let mut item = item("Item", folder1_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
assert_eq!(item.base.parent_uuid(), folder1_id);
item.base.set_parent_uuid(folder2_id);
fixture.inventory.update_node_for(&item).unwrap();
let contents1 = fixture
.inventory
.get_contents_with_inventory_folder(&folder1)
.unwrap();
let contents2 = fixture
.inventory
.get_contents_with_inventory_folder(&folder2)
.unwrap();
assert!(
contents1
.iter()
.all(|entry| entry.inventory_base().uuid() != item_id)
);
assert!(
contents2
.iter()
.any(|entry| entry.inventory_base().uuid() == item_id)
);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.InventoryNodeModifyTime_SelfLoop::test f8050cc284f7954c1301091ddc1cbf0fa76073806eee7de4f6cb21063591fbf1 translated
#[test]
fn inventory_node_modify_time_self_loop() {
let folder_id = uuid();
let mut folder = InventoryFolder::new(folder_id).unwrap();
folder.base.set_name("F1".into());
folder.base.set_parent_uuid(folder_id);
let node = InventoryNode::new_with_inventory_base(&folder).unwrap();
assert_eq!(node.modify_time(), std::time::SystemTime::UNIX_EPOCH);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateNodeFor_SelfLoop::test 14f42e5263c465e8534347c5a3195c195e2d90371dbc2f69d36a40362384505c translated
#[test]
fn update_node_for_self_loop() {
let fixture = Fixture::new();
let folder_id = uuid();
let mut folder = InventoryFolder::new(folder_id).unwrap();
folder.base.set_name("F1".into());
folder.base.set_parent_uuid(folder_id);
fixture.inventory.update_node_for(&folder).unwrap();
assert!(fixture.inventory.contains_with_uuid(folder_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateNodeFor_ComplexSelfLoop::test a2fc7b29b8a7a7767c42ea9c7fb9628451c88991c3eb1ecbd2ce0952ab0b5700 translated
#[test]
fn update_node_for_complex_self_loop() {
let fixture = Fixture::new();
let folder1_id = uuid();
let folder2_id = uuid();
let mut folder1 = InventoryFolder::new(folder1_id).unwrap();
folder1.base.set_name("F1".into());
folder1.base.set_parent_uuid(folder2_id);
let mut folder2 = InventoryFolder::new(folder2_id).unwrap();
folder2.base.set_name("F2".into());
folder2.base.set_parent_uuid(folder1_id);
fixture.inventory.update_node_for(&folder1).unwrap();
assert!(fixture.inventory.contains_with_uuid(folder1_id).unwrap());
assert!(fixture.inventory.contains_with_uuid(folder2_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.RemoveItem::test 1e72f9049b9114f0c56e152d84190d31bca7a862f7ad1459624d753beb6818b4 translated
#[test]
fn remove_item() {
let fixture = Fixture::new();
let folder = folder("Folder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("ToRemove", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
assert!(fixture.inventory.contains_with_uuid(item_id).unwrap());
fixture.inventory.remove_node_for(&item).unwrap();
assert!(!fixture.inventory.contains_with_uuid(item_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.SaveAndRestoreAsync::test bf23b1c67cc9618713d2d8e6653f0ffbbf4c24d7f2c59c5dd639047f7e0c8156 translated
#[test]
fn save_and_restore_async() {
let fixture = Fixture::new();
let folder = folder("FolderSave", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("SaveItem", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
let temporary = TempDirectory::new();
let file = path_string(&temporary.file("inventory.bin"));
block_on(
fixture
.inventory
.save_to_disk_with_string_cancellation_token(file.clone(), None),
)
.unwrap();
fixture.inventory.clear().unwrap();
assert!(!fixture.inventory.contains_with_uuid(item_id).unwrap());
let count = block_on(
fixture
.inventory
.restore_from_disk_with_string_cancellation_token(file, None),
)
.unwrap();
assert!(count >= 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.GetNodeOrDefault_ReturnsNullWhenMissing::test f5651b5a143470c3ab6ba1f835206a1a55a6218c28242dc12c5d6b5e89519596 translated
#[test]
fn get_node_or_default_returns_null_when_missing() {
assert!(
Fixture::new()
.inventory
.get_node_or_default(uuid())
.unwrap()
.is_none()
);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.TryGetValue_Generic::test e6f6d6535af9d011e65cb3c297fe99543f85b64382c47e1756f051d6031886f5 translated
#[test]
fn try_get_value_generic() {
let fixture = Fixture::new();
let folder = folder("GenFolder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("GenItem", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
let mut found = None;
assert!(
fixture
.inventory
.try_get_value_with_uuid_t::<InventoryItem>(item_id, &mut found)
);
let found = found.expect("generic inventory item");
assert_eq!(found.base.uuid(), item_id);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.RemoveFolderRemovesAllDescendants::test b537223d271b75c11adfa6d87c1e79bb29366f294657ddf71f236fab0dee009f translated
#[test]
fn remove_folder_removes_all_descendants() {
let fixture = Fixture::new();
let parent = folder("Parent", UUID::zero());
let parent_id = parent.base.uuid();
let child = folder("ChildFolder", parent_id);
let child_id = child.base.uuid();
let item = item("NestedItem", child_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&parent).unwrap();
fixture.inventory.update_node_for(&child).unwrap();
fixture.inventory.update_node_for(&item).unwrap();
assert!(fixture.inventory.contains_with_uuid(parent_id).unwrap());
assert!(fixture.inventory.contains_with_uuid(child_id).unwrap());
assert!(fixture.inventory.contains_with_uuid(item_id).unwrap());
fixture.inventory.remove_node_for(&parent).unwrap();
assert!(!fixture.inventory.contains_with_uuid(parent_id).unwrap());
assert!(!fixture.inventory.contains_with_uuid(child_id).unwrap());
assert!(!fixture.inventory.contains_with_uuid(item_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.DescendentCountUpdatesOnAddAndRemove::test cfe6571887d0e992ff4d69ccbcdfb34012f9bb5d62118201c33d7d66cc9ddfd8 translated
#[test]
fn descendent_count_updates_on_add_and_remove() {
let fixture = Fixture::new();
let folder = folder("Folder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().expect("folder node data");
let folder_data = data
.as_any()
.downcast_ref::<InventoryFolder>()
.expect("InventoryFolder data");
assert_eq!(folder_data.descendent_count(), 0);
let item = item("Item", folder_id);
fixture.inventory.update_node_for(&item).unwrap();
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().expect("folder node data after add");
let folder_data = data.as_any().downcast_ref::<InventoryFolder>().unwrap();
assert!(folder_data.descendent_count() > 0);
fixture.inventory.remove_node_for(&item).unwrap();
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().expect("folder node data after remove");
let folder_data = data.as_any().downcast_ref::<InventoryFolder>().unwrap();
assert_eq!(folder_data.descendent_count(), 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.MovingItemUpdatesAncestorDescendentCounts::test 1f4b4189694b9471d48562a434c91a9e455706b83b65bb855c0846388b44c759 translated
#[test]
fn moving_item_updates_ancestor_descendent_counts() {
let fixture = Fixture::new();
let folder1 = folder("F1", UUID::zero());
let folder2 = folder("F2", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
let mut item = item("Movable", folder1_id);
fixture.inventory.update_node_for(&item).unwrap();
let first = fixture.inventory.get_node_for(folder1_id).unwrap();
let first_data = first.data().unwrap();
let first_folder = first_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
let second = fixture.inventory.get_node_for(folder2_id).unwrap();
let second_data = second.data().unwrap();
let second_folder = second_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
assert!(first_folder.descendent_count() > 0);
assert_eq!(second_folder.descendent_count(), 0);
item.base.set_parent_uuid(folder2_id);
fixture.inventory.update_node_for(&item).unwrap();
let first = fixture.inventory.get_node_for(folder1_id).unwrap();
let first_data = first.data().unwrap();
let first_folder = first_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
let second = fixture.inventory.get_node_for(folder2_id).unwrap();
let second_data = second.data().unwrap();
let second_folder = second_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
assert_eq!(first_folder.descendent_count(), 0);
assert!(second_folder.descendent_count() > 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.ConcurrentAddMoveRemoveStress::test 0b76b2fb62a6300e02a02859772f146f107caa77986f4d593e3a4d01966ab4e4 translated
#[test]
fn concurrent_add_move_remove_stress() {
const ITEM_COUNT: usize = 500;
let fixture = Fixture::new();
let folder1 = folder("Folder1", UUID::zero());
let folder2 = folder("Folder2", UUID::zero());
let folder3 = folder("Folder3", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
let folder3_id = folder3.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
fixture.inventory.update_node_for(&folder3).unwrap();
let items: Vec<_> = (0..ITEM_COUNT)
.map(|index| Arc::new(Mutex::new(item(&format!("Item{index}"), folder1_id))))
.collect();
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.update_node_for(&*item.lock().unwrap()).unwrap());
}
});
for item in &items {
assert!(
fixture
.inventory
.contains_with_uuid(item.lock().unwrap().base.uuid())
.unwrap()
);
}
std::thread::scope(|scope| {
for (index, item) in items.iter().enumerate() {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || {
let mut item = item.lock().unwrap();
item.base.set_parent_uuid(if index % 2 == 0 {
folder2_id
} else {
folder3_id
});
inventory.update_node_for(&*item).unwrap();
});
}
});
let count = |folder_id| {
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().unwrap();
data.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap()
.descendent_count()
};
assert_eq!(count(folder1_id), 0);
assert_eq!(
count(folder2_id) + count(folder3_id),
i32::try_from(ITEM_COUNT).unwrap()
);
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.remove_node_for(&*item.lock().unwrap()).unwrap());
}
});
for item in &items {
assert!(
!fixture
.inventory
.contains_with_uuid(item.lock().unwrap().base.uuid())
.unwrap()
);
}
assert_eq!(count(folder1_id), 0);
assert_eq!(count(folder2_id), 0);
assert_eq!(count(folder3_id), 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.ConcurrentLinkAddRemoveStress::test eef3b4eb300427800757dc6cac2dbd9202ed1cf1aac42565b3c26eb11e71bbf1 translated
#[test]
fn concurrent_link_add_remove_stress() {
const ITEM_COUNT: usize = 200;
let fixture = Fixture::new();
let folder1 = folder("LinkFolder", UUID::zero());
let folder2 = folder("LinkFolder2", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
let asset_id = uuid();
let items: Vec<_> = (0..ITEM_COUNT)
.map(|index| {
let mut item = item(&format!("L{index}"), folder1_id);
item.set_asset_type(AssetType::Link);
item.set_asset_uuid(asset_id);
Arc::new(Mutex::new(item))
})
.collect();
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.update_node_for(&*item.lock().unwrap()).unwrap());
}
});
assert_eq!(
fixture.inventory.find_all_links(asset_id).unwrap().len(),
ITEM_COUNT
);
std::thread::scope(|scope| {
for (index, item) in items.iter().enumerate() {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || {
let mut item = item.lock().unwrap();
item.base.set_parent_uuid(if index % 2 == 0 {
folder2_id
} else {
folder1_id
});
inventory.update_node_for(&*item).unwrap();
});
}
});
assert_eq!(
fixture.inventory.find_all_links(asset_id).unwrap().len(),
ITEM_COUNT
);
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.remove_node_for(&*item.lock().unwrap()).unwrap());
}
});
assert!(
fixture
.inventory
.find_all_links(asset_id)
.unwrap()
.is_empty()
);
for item in &items {
assert!(
!fixture
.inventory
.contains_with_uuid(item.lock().unwrap().base.uuid())
.unwrap()
);
}
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.LinkIndexUpdateOnAssetChange::test d3e0066e06ab1c48520e5e0bbba8069b74f1a38d37e24d914392406b41b2ea0c translated
#[test]
fn link_index_updates_on_asset_change() {
let fixture = Fixture::new();
let folder = folder("LUpdate", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let asset1 = uuid();
let asset2 = uuid();
let mut item = item("LinkItem", folder_id);
let item_id = item.base.uuid();
item.set_asset_type(AssetType::Link);
item.set_asset_uuid(asset1);
fixture.inventory.update_node_for(&item).unwrap();
let found1 = fixture.inventory.find_all_links(asset1).unwrap();
assert_eq!(found1.len(), 1);
let data = found1[0].data().expect("link node data");
assert_eq!(data.inventory_base().uuid(), item_id);
item.set_asset_uuid(asset2);
fixture.inventory.update_node_for(&item).unwrap();
let found1 = fixture.inventory.find_all_links(asset1).unwrap();
let found2 = fixture.inventory.find_all_links(asset2).unwrap();
assert!(found1.is_empty());
assert_eq!(found2.len(), 1);
let data = found2[0].data().expect("updated link node data");
assert_eq!(data.inventory_base().uuid(), item_id);
}

View File

@@ -6,10 +6,14 @@ use libremetaverse::{
InventoryWearable,
};
use libremetaverse_types::{AssetType, AttachmentPoint, UUID, WearableType};
use std::sync::Arc;
fn store() -> Inventory {
Inventory::new_with_grid_client_uuid(GridClient::new().unwrap(), UUID::random().unwrap())
.unwrap()
Inventory::new_with_grid_client_uuid(
Arc::new(GridClient::new().unwrap()),
UUID::random().unwrap(),
)
.unwrap()
}
fn texture_link(target: UUID) -> InventoryTexture {
@@ -52,7 +56,7 @@ fn inventory_store_contains_finds_target_by_resolved_item_id() {
target.set_wearable_type(WearableType::Shirt);
target.base.set_asset_type(AssetType::Clothing);
target.base.set_asset_uuid(UUID::random().unwrap());
store.update_node_for(target.base.base).unwrap();
store.update_node_for(&target).unwrap();
let link = texture_link(target_id);
assert!(
store
@@ -71,7 +75,7 @@ fn inventory_store_indexer_returns_wearable_when_target_is_wearable() {
target.base.set_asset_type(AssetType::Clothing);
target.base.set_asset_uuid(UUID::random().unwrap());
target.base.base.set_name("Test Shirt".into());
store.update_node_for(target.base.base).unwrap();
store.update_node_for(&target).unwrap();
let link = texture_link(target_id);
let resolved = store
.get_value_or_default_with_uuid_a7c63fbe::<InventoryWearable>(link.base.resolved_item_id())
@@ -93,7 +97,7 @@ fn inventory_store_indexer_returns_attachment_when_target_is_attachment() {
target.base.set_asset_type(AssetType::Object);
target.base.set_asset_uuid(UUID::random().unwrap());
target.base.base.set_name("Test Attachment".into());
store.update_node_for(target.base.base).unwrap();
store.update_node_for(&target).unwrap();
let link = texture_link(target_id);
let resolved = store
.get_value_or_default_with_uuid_a7c63fbe::<InventoryAttachment>(
@@ -117,7 +121,7 @@ fn inventory_store_indexer_returns_object_when_target_is_object() {
target.base.set_asset_type(AssetType::Object);
target.base.set_asset_uuid(UUID::random().unwrap());
target.base.base.set_name("Test Object".into());
store.update_node_for(target.base.base).unwrap();
store.update_node_for(&target).unwrap();
let link = texture_link(target_id);
let resolved = store
.get_value_or_default_with_uuid_a7c63fbe::<InventoryObject>(link.base.resolved_item_id())

View File

@@ -0,0 +1,224 @@
// 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());
}