From d905210ecf3dde2ca6660d60b396a3b8c02875bf Mon Sep 17 00:00:00 2001 From: nooperation Date: Sun, 17 Aug 2025 19:55:33 -0400 Subject: [PATCH] Initial commit of LibreMetaverse.RLV and LibreMetaverse.RLV.Tests. This library provides RLV command processing and ease of use for checking current RLV permissions and restrictions --- LibreMetaverse.RLV.Tests/.editorconfig | 223 +++++ .../Commands/AdjustHeightCommandTests.cs | 46 + .../Commands/AttachAllCommandTests.cs | 200 ++++ .../Commands/AttachAllThisCommandTests.cs | 412 ++++++++ .../Commands/AttachCommandTests.cs | 280 ++++++ .../Commands/AttachThisCommandTests.cs | 474 +++++++++ .../Commands/ClearCommandTests.cs | 70 ++ .../Commands/DetachAllCommandTests.cs | 292 ++++++ .../Commands/DetachAllThisCommandTests.cs | 363 +++++++ .../Commands/DetachMeCommandTests.cs | 137 +++ .../Commands/DetachThisCommandTests.cs | 361 +++++++ .../Commands/RemAttachCommandTests.cs | 648 +++++++++++++ .../Commands/RemOutfitCommandTests.cs | 461 +++++++++ .../Commands/SetDebugCommandTests.cs | 44 + .../Commands/SetEnvCommandTests.cs | 32 + .../Commands/SetGroupCommandTests.cs | 87 ++ .../Commands/SetRotCommandTests.cs | 28 + .../Commands/SitCommandTests.cs | 148 +++ .../Commands/SitGroundCommandTests.cs | 24 + .../Commands/TpToCommandTests.cs | 97 ++ .../Commands/UnsitCommandTests.cs | 23 + .../AcceptPermissionExceptionTests.cs | 18 + .../Exceptions/AcceptTpExceptionTests.cs | 35 + .../AcceptTpRequestExceptionTests.cs | 35 + .../AttachAllThisExceptExceptionTests.cs | 290 ++++++ .../AttachThisExceptExceptionTests.cs | 210 ++++ .../DenyPermissionExceptionTests.cs | 17 + .../DetachAllThisExceptExceptionTests.cs | 253 +++++ .../DetachThisExceptExceptionTests.cs | 160 +++ .../Exceptions/EditExceptionTests.cs | 50 + .../Exceptions/EmoteExceptionTests.cs | 14 + .../Exceptions/RecvChatExceptionTests.cs | 19 + .../Exceptions/RecvEmoteExceptionTests.cs | 24 + .../Exceptions/RecvImExceptionTests.cs | 42 + .../Exceptions/RedirChatExceptionTests.cs | 101 ++ .../Exceptions/RedirEmoteExceptionTests.cs | 99 ++ .../SendChannelExceptExceptionTests.cs | 16 + .../Exceptions/SendChannelExceptionTests.cs | 16 + .../Exceptions/SendImExceptionTests.cs | 41 + .../Exceptions/ShareExceptionTests.cs | 22 + .../Exceptions/ShowNameTagsExceptionTests.cs | 21 + .../Exceptions/ShowNamesExceptionTests.cs | 21 + .../Exceptions/StartImExceptionTests.cs | 20 + .../Exceptions/TouchMeExceptionTests.cs | 30 + .../Exceptions/TouchThisExceptionTests.cs | 31 + .../Exceptions/TouchWorldExceptionTests.cs | 30 + .../Exceptions/TpLureExceptionTests.cs | 22 + LibreMetaverse.RLV.Tests/Extensions.cs | 24 + LibreMetaverse.RLV.Tests/InventoryMapTests.cs | 587 +++++++++++ .../LibreMetaverse.RLV.Tests.csproj | 39 + LibreMetaverse.RLV.Tests/PermissionsTests.cs | 168 ++++ .../Queries/FindFolderQueryTests.cs | 116 +++ .../Queries/FindFoldersQueryTests.cs | 88 ++ .../Queries/GetAttachQueryTests.cs | 163 ++++ .../Queries/GetBlacklistQueryTests.cs | 50 + .../Queries/GetDebugQueryTests.cs | 30 + .../Queries/GetEnvQueryTests.cs | 32 + .../Queries/GetGroupQueryTests.cs | 49 + .../Queries/GetInvQueryTests.cs | 217 +++++ .../Queries/GetInvWornQueryTests.cs | 260 +++++ .../Queries/GetOutfitQueryTests.cs | 173 ++++ .../Queries/GetPathNewQueryTests.cs | 239 +++++ .../Queries/GetSitIdQueryTests.cs | 51 + .../Queries/GetStatusAllQueryTests.cs | 30 + .../Queries/GetStatusQueryTests.cs | 73 ++ .../Queries/NotifyQueryTests.cs | 424 ++++++++ .../Queries/VersionNumBlQueryTests.cs | 27 + .../Queries/VersionQueryTests.cs | 43 + .../Restrictions/AddAttachRestrictionTests.cs | 60 ++ .../Restrictions/AddOutfitRestrictionTests.cs | 91 ++ .../Restrictions/AllowIdleRestrictionTests.cs | 13 + .../Restrictions/AlwaysRunRestrictionTests.cs | 13 + .../AttachAllThisRestrictionTests.cs | 465 +++++++++ .../AttachThisRestrictionTests.cs | 409 ++++++++ .../Restrictions/ChatNormalTests.cs | 14 + .../Restrictions/ChatShoutRestrictionTests.cs | 14 + .../ChatWhisperRestrictionTests.cs | 14 + .../DefaultWearRestrictionTests.cs | 21 + .../DetachAllThisRestrictionTests.cs | 462 +++++++++ .../Restrictions/DetachRestrictionTests.cs | 77 ++ .../DetachThisRestrictionTests.cs | 411 ++++++++ .../EditAttachRestrictionTests.cs | 24 + .../Restrictions/EditRestrictionTests.cs | 24 + .../Restrictions/EditWorldRestrictionTests.cs | 23 + .../Restrictions/FarTouchRestrictionTests.cs | 63 ++ .../Restrictions/FlyRestrictionTests.cs | 13 + .../Restrictions/InteractRestrictionTests.cs | 37 + .../Restrictions/JumpRestrictionTests.cs | 13 + .../PermissiveRestrictionTests.cs | 24 + .../Restrictions/RecvChatExceptionTests.cs | 15 + .../RecvChatFromRestrictionTests.cs | 24 + .../Restrictions/RecvChatSecExceptionTests.cs | 23 + .../RecvEmoteFromRestrictionTests.cs | 22 + .../Restrictions/RecvEmoteRestrictionTests.cs | 18 + .../RecvEmoteSecRestrictionTests.cs | 26 + .../RecvImFromRestrictionTests.cs | 43 + .../Restrictions/RecvImRestrictionTests.cs | 19 + .../Restrictions/RecvImSecRestrictionTests.cs | 51 + .../Restrictions/RemAttachRestrictionTests.cs | 96 ++ .../Restrictions/RemOutfitRestrictionTests.cs | 95 ++ .../Restrictions/RezRestrictionTests.cs | 14 + .../SendChannelRestrictionTests.cs | 15 + .../SendChannelSecRestrictionTests.cs | 38 + .../Restrictions/SendChatRestrictionTests.cs | 33 + .../SendGestureRestrictionTests.cs | 16 + .../Restrictions/SendImRestrictionTests.cs | 18 + .../Restrictions/SendImSecRestrictionTests.cs | 52 + .../Restrictions/SendImToRestrictionTests.cs | 45 + .../Restrictions/SetDebugRestrictionTests.cs | 14 + .../Restrictions/SetEnvRestrictionTests.cs | 13 + .../Restrictions/SetGroupRestrictionTests.cs | 13 + .../Restrictions/ShareRestrictionTests.cs | 19 + .../Restrictions/ShareSecRestrictionTests.cs | 38 + .../ShowHoverTextAllRestrictionTests.cs | 20 + .../ShowHoverTextHudRestrictionTests.cs | 24 + .../ShowHoverTextRestrictionTests.cs | 22 + .../ShowHoverTextWorldRestrictionTests.cs | 24 + .../Restrictions/ShowInvRestrictionTests.cs | 14 + .../Restrictions/ShowLocRestrictionTests.cs | 14 + .../ShowMiniMapRestrictionTests.cs | 13 + .../ShowNameTagsRestrictionTests.cs | 18 + .../Restrictions/ShowNamesRestrictionTests.cs | 18 + .../ShowNamesSecRestrictionTests.cs | 52 + .../ShowNearbyRestrictionTests.cs | 13 + .../ShowWorldMapRestrictionTests.cs | 13 + .../Restrictions/SitRestrictionTests.cs | 14 + .../Restrictions/SitTpRestrictionTests.cs | 73 ++ .../Restrictions/StandTpRestrictionTests.cs | 13 + .../Restrictions/StartImRestrictionTests.cs | 18 + .../Restrictions/StartImToRestrictionTests.cs | 20 + .../Restrictions/TempRunRestrictionTests.cs | 13 + .../Restrictions/TouchAllRestrictionTests.cs | 36 + .../TouchAttachOtherRestrictionTests.cs | 39 + .../TouchAttachRestrictionTests.cs | 24 + .../TouchAttachSelfRestrictionTests.cs | 24 + .../Restrictions/TouchHudRestrictionTests.cs | 40 + .../TouchWorldRestrictionTests.cs | 22 + .../Restrictions/TpLmRestrictionTests.cs | 13 + .../Restrictions/TpLocRestrictionTests.cs | 13 + .../Restrictions/TpLocalRestrictionTests.cs | 25 + .../Restrictions/TpLureRestrictionTests.cs | 28 + .../Restrictions/TpLureSecRestrictionTests.cs | 40 + .../Restrictions/TpRequestRestrictionTests.cs | 33 + .../TpRequestSecRestrictionTests.cs | 39 + .../Restrictions/UnsitRestrictionTests.cs | 13 + .../Restrictions/ViewNoteRestrictionTests.cs | 13 + .../ViewScriptRestrictionTests.cs | 13 + .../ViewTextureRestrictionTests.cs | 13 + LibreMetaverse.RLV.Tests/RestrictionsBase.cs | 98 ++ .../RestrictionsCameraTests.cs | 679 +++++++++++++ LibreMetaverse.RLV.Tests/RlvBlacklistTests.cs | 293 ++++++ LibreMetaverse.RLV.Tests/RlvCommonTests.cs | 38 + .../RlvRestrictionManagerTests.cs | 318 ++++++ .../SampleInventoryTree.cs | 91 ++ LibreMetaverse.RLV/.editorconfig | 223 +++++ LibreMetaverse.RLV/CameraRestrictions.cs | 142 +++ LibreMetaverse.RLV/CameraSettings.cs | 22 + .../RestrictionUpdatedEventArgs.cs | 18 + LibreMetaverse.RLV/IBlacklistProvider.cs | 9 + LibreMetaverse.RLV/IRestrictionProvider.cs | 14 + LibreMetaverse.RLV/IRlvActionCallbacks.cs | 132 +++ LibreMetaverse.RLV/IRlvQueryCallbacks.cs | 107 ++ LibreMetaverse.RLV/InventoryMap.cs | 338 +++++++ LibreMetaverse.RLV/LibreMetaverse.RLV.csproj | 21 + LibreMetaverse.RLV/LockedFolder.cs | 24 + LibreMetaverse.RLV/LockedFolderManager.cs | 275 ++++++ LibreMetaverse.RLV/LockedFolderPublic.cs | 76 ++ LibreMetaverse.RLV/NullableAttributes.cs | 221 +++++ LibreMetaverse.RLV/RlvAttachmentPoint.cs | 62 ++ LibreMetaverse.RLV/RlvBlacklist.cs | 84 ++ LibreMetaverse.RLV/RlvCallbacksDefault.cs | 242 +++++ LibreMetaverse.RLV/RlvCommandProcessor.cs | 774 +++++++++++++++ LibreMetaverse.RLV/RlvCommon.cs | 118 +++ LibreMetaverse.RLV/RlvDataRequestType.cs | 22 + LibreMetaverse.RLV/RlvGetDebugType.cs | 11 + LibreMetaverse.RLV/RlvGetEnvType.cs | 72 ++ LibreMetaverse.RLV/RlvGetRequestHandler.cs | 654 +++++++++++++ LibreMetaverse.RLV/RlvInventoryItem.cs | 65 ++ LibreMetaverse.RLV/RlvMessage.cs | 27 + LibreMetaverse.RLV/RlvPermissionsService.cs | 913 ++++++++++++++++++ LibreMetaverse.RLV/RlvRestriction.cs | 554 +++++++++++ LibreMetaverse.RLV/RlvRestrictionManager.cs | 515 ++++++++++ LibreMetaverse.RLV/RlvRestrictionType.cs | 125 +++ LibreMetaverse.RLV/RlvService.cs | 458 +++++++++ LibreMetaverse.RLV/RlvSharedFolder.cs | 123 +++ LibreMetaverse.RLV/RlvWearableType.cs | 24 + LibreMetaverse.sln | 12 + 187 files changed, 20833 insertions(+) create mode 100644 LibreMetaverse.RLV.Tests/.editorconfig create mode 100644 LibreMetaverse.RLV.Tests/Commands/AdjustHeightCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/AttachAllCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/AttachAllThisCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/AttachCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/AttachThisCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/ClearCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/DetachAllCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/DetachAllThisCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/DetachMeCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/DetachThisCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/RemAttachCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/RemOutfitCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/SetDebugCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/SetEnvCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/SetGroupCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/SetRotCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/SitCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/SitGroundCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/TpToCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Commands/UnsitCommandTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/AcceptPermissionExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/AcceptTpExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/AcceptTpRequestExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/AttachAllThisExceptExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/AttachThisExceptExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/DenyPermissionExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/DetachAllThisExceptExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/DetachThisExceptExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/EditExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/EmoteExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/RecvChatExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/RecvEmoteExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/RecvImExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/RedirChatExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/RedirEmoteExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/SendImExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/ShareExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/ShowNameTagsExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/ShowNamesExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/StartImExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/TouchMeExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/TouchThisExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/TouchWorldExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Exceptions/TpLureExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Extensions.cs create mode 100644 LibreMetaverse.RLV.Tests/InventoryMapTests.cs create mode 100644 LibreMetaverse.RLV.Tests/LibreMetaverse.RLV.Tests.csproj create mode 100644 LibreMetaverse.RLV.Tests/PermissionsTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/FindFolderQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/FindFoldersQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetAttachQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetBlacklistQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetDebugQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetEnvQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetGroupQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetInvQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetInvWornQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetOutfitQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetPathNewQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetSitIdQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetStatusAllQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/GetStatusQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/NotifyQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/VersionNumBlQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Queries/VersionQueryTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/AddAttachRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/AddOutfitRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/AllowIdleRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/AlwaysRunRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/AttachAllThisRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/AttachThisRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ChatNormalTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ChatShoutRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ChatWhisperRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/DefaultWearRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/DetachAllThisRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/DetachRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/DetachThisRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/EditAttachRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/EditRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/EditWorldRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/FarTouchRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/FlyRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/InteractRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/JumpRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/PermissiveRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvChatExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvChatFromRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvChatSecExceptionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteFromRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvImFromRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvImRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RecvImSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RemAttachRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RemOutfitRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/RezRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SendChannelRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SendChannelSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SendChatRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SendGestureRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SendImRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SendImSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SendImToRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SetDebugRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SetEnvRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SetGroupRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShareRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShareSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextAllRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextHudRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextWorldRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowInvRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowLocRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowMiniMapRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowNameTagsRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowNamesRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowNamesSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowNearbyRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ShowWorldMapRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SitRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/SitTpRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/StandTpRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/StartImRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/StartImToRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TempRunRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TouchAllRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TouchAttachOtherRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TouchAttachRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TouchAttachSelfRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TouchHudRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TouchWorldRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TpLmRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TpLocRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TpLocalRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TpLureRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TpLureSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TpRequestRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/TpRequestSecRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/UnsitRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ViewNoteRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ViewScriptRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/Restrictions/ViewTextureRestrictionTests.cs create mode 100644 LibreMetaverse.RLV.Tests/RestrictionsBase.cs create mode 100644 LibreMetaverse.RLV.Tests/RestrictionsCameraTests.cs create mode 100644 LibreMetaverse.RLV.Tests/RlvBlacklistTests.cs create mode 100644 LibreMetaverse.RLV.Tests/RlvCommonTests.cs create mode 100644 LibreMetaverse.RLV.Tests/RlvRestrictionManagerTests.cs create mode 100644 LibreMetaverse.RLV.Tests/SampleInventoryTree.cs create mode 100644 LibreMetaverse.RLV/.editorconfig create mode 100644 LibreMetaverse.RLV/CameraRestrictions.cs create mode 100644 LibreMetaverse.RLV/CameraSettings.cs create mode 100644 LibreMetaverse.RLV/EventArguments/RestrictionUpdatedEventArgs.cs create mode 100644 LibreMetaverse.RLV/IBlacklistProvider.cs create mode 100644 LibreMetaverse.RLV/IRestrictionProvider.cs create mode 100644 LibreMetaverse.RLV/IRlvActionCallbacks.cs create mode 100644 LibreMetaverse.RLV/IRlvQueryCallbacks.cs create mode 100644 LibreMetaverse.RLV/InventoryMap.cs create mode 100644 LibreMetaverse.RLV/LibreMetaverse.RLV.csproj create mode 100644 LibreMetaverse.RLV/LockedFolder.cs create mode 100644 LibreMetaverse.RLV/LockedFolderManager.cs create mode 100644 LibreMetaverse.RLV/LockedFolderPublic.cs create mode 100644 LibreMetaverse.RLV/NullableAttributes.cs create mode 100644 LibreMetaverse.RLV/RlvAttachmentPoint.cs create mode 100644 LibreMetaverse.RLV/RlvBlacklist.cs create mode 100644 LibreMetaverse.RLV/RlvCallbacksDefault.cs create mode 100644 LibreMetaverse.RLV/RlvCommandProcessor.cs create mode 100644 LibreMetaverse.RLV/RlvCommon.cs create mode 100644 LibreMetaverse.RLV/RlvDataRequestType.cs create mode 100644 LibreMetaverse.RLV/RlvGetDebugType.cs create mode 100644 LibreMetaverse.RLV/RlvGetEnvType.cs create mode 100644 LibreMetaverse.RLV/RlvGetRequestHandler.cs create mode 100644 LibreMetaverse.RLV/RlvInventoryItem.cs create mode 100644 LibreMetaverse.RLV/RlvMessage.cs create mode 100644 LibreMetaverse.RLV/RlvPermissionsService.cs create mode 100644 LibreMetaverse.RLV/RlvRestriction.cs create mode 100644 LibreMetaverse.RLV/RlvRestrictionManager.cs create mode 100644 LibreMetaverse.RLV/RlvRestrictionType.cs create mode 100644 LibreMetaverse.RLV/RlvService.cs create mode 100644 LibreMetaverse.RLV/RlvSharedFolder.cs create mode 100644 LibreMetaverse.RLV/RlvWearableType.cs diff --git a/LibreMetaverse.RLV.Tests/.editorconfig b/LibreMetaverse.RLV.Tests/.editorconfig new file mode 100644 index 00000000..8d370632 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/.editorconfig @@ -0,0 +1,223 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +dotnet_style_operator_placement_when_wrapping = end_of_line +tab_width = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = false:silent +dotnet_style_prefer_conditional_expression_over_return = false:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = false:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = false:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = false +dotnet_style_namespace_match_folder = true:suggestion + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_elsewhere = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = camel_case_underscore_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = false:suggestion +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = false:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = false:silent +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = false:silent +dotnet_style_prefer_conditional_expression_over_return = false:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = false:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:none +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_unused_value_expression_statement_preference = unused_local_variable:silent + +# IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = none +csharp_style_prefer_primary_constructors = false +csharp_prefer_system_threading_lock = true:suggestion + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf diff --git a/LibreMetaverse.RLV.Tests/Commands/AdjustHeightCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/AdjustHeightCommandTests.cs new file mode 100644 index 00000000..6d29f358 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/AdjustHeightCommandTests.cs @@ -0,0 +1,46 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class AdjustHeightCommandTests : RestrictionsBase + { + #region @adjustheight:;[;delta_in_meters]=force + [Fact] + public async Task AdjustHeight() + { + _actionCallbacks + .Setup(e => e.AdjustHeightAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@adjustheight:4.3;1.25=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AdjustHeightAsync(4.3f, 1.25f, 0.0f, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task AdjustHeight_WithDelta() + { + _actionCallbacks + .Setup(e => e.AdjustHeightAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@adjustheight:4.3;1.25;12.34=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AdjustHeightAsync(4.3f, 1.25f, 12.34f, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/AttachAllCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/AttachAllCommandTests.cs new file mode 100644 index 00000000..a2c9f08a --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/AttachAllCommandTests.cs @@ -0,0 +1,200 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class AttachAllCommandTests : RestrictionsBase + { + #region @attachallover @attachalloverorreplace @attachall:=force + + [Theory] + [InlineData("attachall", true)] + [InlineData("attachalloverorreplace", true)] + [InlineData("attachallover", false)] + public async Task AttachForce_Recursive(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything inside of of the Clothing folder, and all of its subfolders recursively + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:Clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachall", true)] + [InlineData("attachalloverorreplace", true)] + [InlineData("attachallover", false)] + public async Task AttachForce_Recursive_WithHiddenSubfolder(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = ".hats"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything inside of of the Clothing folder, and all of its subfolders recursively. The hats folder has a special . prefix, which means it will be ignored + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:Clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachall", true)] + [InlineData("attachalloverorreplace", true)] + [InlineData("attachallover", false)] + public async Task AttachForce_Recursive_FolderNameSpecifiesToAddInsteadOfReplace(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = "+hats"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything inside of of the Clothing folder, and all of its subfolders recursively. The hats folder has a special + prefix, which means it will use 'add to' logic instead of 'replace' logic when attaching + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, false), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, false), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:Clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachall", true)] + [InlineData("attachalloverorreplace", true)] + [InlineData("attachallover", false)] + public async Task AttachForce_Recursive_SourceFolderPrivate(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = ".auto_attach"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything inside of of the Clothing folder, and all of its subfolders recursively. The hats folder has a special + prefix, which means it will use 'add to' logic instead of 'replace' logic when attaching + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:.auto_attach=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/AttachAllThisCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/AttachAllThisCommandTests.cs new file mode 100644 index 00000000..c58e64cf --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/AttachAllThisCommandTests.cs @@ -0,0 +1,412 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class AttachAllThisCommandTests : RestrictionsBase + { + #region @attachallthisover @attachallthisoverorreplace @attachallthis[: or ]=force + [Theory] + [InlineData("attachallthis", true)] + [InlineData("attachallthisoverorreplace", true)] + [InlineData("attachallthisover", false)] + public async Task AttachAllThisForce_Recursive(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected attach + // | |= Happy Shirt (SENDER, Worn on chest) + // | |= Retro Pants <-- Expected attach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- Expected attach + // | \= Party Hat <-- Expected attach + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}=force", sampleTree.Root_Clothing_HappyShirt.AttachedPrimId!.Value, sampleTree.Root_Clothing_HappyShirt.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachallthis", true)] + [InlineData("attachallthisoverorreplace", true)] + [InlineData("attachallthisover", false)] + public async Task AttachAllThisForce_Recursive_ById(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected attach + // | |= Happy Shirt (SENDER, Worn on chest) + // | |= Retro Pants <-- Expected attach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- Expected attach + // | \= Party Hat <-- Expected attach + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:{sampleTree.Root_Clothing_HappyShirt.AttachedPrimId}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachallthis", true)] + [InlineData("attachallthisoverorreplace", true)] + [InlineData("attachallthisover", false)] + public async Task AttachAllThisForce_Recursive_WithHiddenSubfolder(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected attach + // | |= Happy Shirt (SENDER, Worn on chest) + // | |= Retro Pants <-- Expected attach + // | \- .hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = ".hats"; + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}=force", sampleTree.Root_Clothing_HappyShirt.AttachedPrimId!.Value, sampleTree.Root_Clothing_HappyShirt.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachallthis", true)] + [InlineData("attachallthisoverorreplace", true)] + [InlineData("attachallthisover", false)] + public async Task AttachAllThisForce_Recursive_FolderNameSpecifiesToAddInsteadOfReplace(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected attach + // | |= Happy Shirt (SENDER, Worn on chest) + // | |= Retro Pants <-- Expected attach + // | \- +hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- Expected add-to attach + // | \= Party Hat <-- Expected add-to attach + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = "+hats"; + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, false), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, false), + }; + + // Act + await _rlv.ProcessMessage($"@{command}=force", sampleTree.Root_Clothing_HappyShirt.AttachedPrimId!.Value, sampleTree.Root_Clothing_HappyShirt.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachallthis", true)] + [InlineData("attachallthisoverorreplace", true)] + [InlineData("attachallthisover", false)] + public async Task AttachAllThisForce_AttachPoint(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected attach + // | |= Happy Shirt (SENDER, attached to spine) + // | |= Retro Pants <-- Expected attach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- Expected attach + // | \= Party Hat <-- Expected attach + // \-Accessories + // |= Watch (Attached to spine) + // \= Glasses <-- Expected attach + + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.Name = "Happy Shirt (skull)"; + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Skull; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Watch.Name = "Watch (skull)"; + sampleTree.Root_Accessories_Watch.AttachedTo = RlvAttachmentPoint.Skull; + sampleTree.Root_Accessories_Watch.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + new(sampleTree.Root_Accessories_Glasses.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:skull=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachallthis", true)] + [InlineData("attachallthisoverorreplace", true)] + [InlineData("attachallthisover", false)] + public async Task AttachAllThisForce_RlvWearableType(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected attach + // | |= Happy Shirt <-- Expected attach + // | |= Retro Pants (Worn as Tattoo) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- Expected attach + // | \= Party Hat <-- Expected attach + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:tattoo=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/AttachCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/AttachCommandTests.cs new file mode 100644 index 00000000..3d6a0f34 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/AttachCommandTests.cs @@ -0,0 +1,280 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class AttachCommandTests : RestrictionsBase + { + #region @attachover @attachoverorreplace @attach:=force + [Theory] + [InlineData("attach", true)] + [InlineData("attachoverorreplace", true)] + [InlineData("attachover", false)] + public async Task AttachForce(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything in the Clothing/Hats folder + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:Clothing/Hats=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attach", true)] + [InlineData("attachoverorreplace", true)] + [InlineData("attachover", false)] + public async Task AttachForce_WithClothing(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything in the Clothing folder. Make sure clothing types (RlvWearableType) are also included + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:Clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attach")] + [InlineData("attachoverorreplace")] + [InlineData("attachover")] + public async Task AttachForce_AlreadyAttached(string command) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Groin; + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach nothing because everything in this folder is already attached + var expected = new HashSet() + { + }; + + // Act + await _rlv.ProcessMessage($"@{command}:Clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attach", true)] + [InlineData("attachoverorreplace", true)] + [InlineData("attachover", false)] + public async Task AttachForce_PositionFromFolderName(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = "Hats (spine)"; + + // Item name overrides folder name + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Name = "Fancy Hat (skull)"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything under the "Clothing/Hats (spine)" folder, attaching everything to the Spine point unless the item explicitly specifies a different attachment point such as "Fancy Hat (skull)". + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Skull, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:{sampleTree.Clothing_Folder.Name}/{sampleTree.Clothing_Hats_Folder.Name}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attach")] + [InlineData("attachoverorreplace")] + [InlineData("attachover")] + public async Task AttachForce_FolderNameSpecifiesToAddInsteadOfReplace(string command) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = "+Hats"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything inside of the Clothing/Hats folder, but force 'add to' logic due to the + prefix on the hats folder + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, false), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, false), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:{sampleTree.Clothing_Folder.Name}/{sampleTree.Clothing_Hats_Folder.Name}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attach", true)] + [InlineData("attachoverorreplace", true)] + [InlineData("attachover", false)] + public async Task AttachForce_AttachPrivateParentFolder(string command, bool replaceExistingAttachments) + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = ".clothing"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // This is allowed even though we're targeting a private folder. Only private subfolders are ignored + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:{sampleTree.Clothing_Folder.Name}/{sampleTree.Clothing_Hats_Folder.Name}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/AttachThisCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/AttachThisCommandTests.cs new file mode 100644 index 00000000..77755cbf --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/AttachThisCommandTests.cs @@ -0,0 +1,474 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class AttachThisCommandTests : RestrictionsBase + { + #region @attachthisoverorreplace @attachthisover @attachthis[: or or ]=force + [Theory] + [InlineData("attachthis", true)] + [InlineData("attachthisoverorreplace", true)] + [InlineData("attachthisover", false)] + public async Task AttachThis_Default(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to chin) + // | \= Party Hat <-- Expect attach to spine + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything in #RLV/Clothing/Hats because that's where the source item (fancy hat) is calling @attachthis from + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}=force", sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_FancyHat_Chin.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachthis", true)] + [InlineData("attachthisoverorreplace", true)] + [InlineData("attachthisover", false)] + public async Task AttachThis_ById(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expect attach + // | |= Happy Shirt (Attached to chest) + // | |= Retro Pants <-- Expect attach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:{sampleTree.Root_Clothing_HappyShirt.AttachedPrimId}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + + [Theory] + [InlineData("attachthis")] + [InlineData("attachthisoverorreplace")] + [InlineData("attachthisover")] + public async Task AttachThis_FolderNameSpecifiesToAddInsteadOfReplace(string command) + { + // #RLV + // | + // |- .private + // | + // |- +clothing + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt <-- Expect 'add-to' default + // | |= Retro Pants <-- Expect request 'add-to' default + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = "+clothing"; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything in #RLV/+clothing because that's where the source item (business pants) is calling @attachthis from, but use 'add-to' logic instead of 'replace' logic + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, false), + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, false), + }; + + // Act + await _rlv.ProcessMessage($"@{command}=force", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachthis", true)] + [InlineData("attachthisoverorreplace", true)] + [InlineData("attachthisover", false)] + public async Task AttachThis_FolderNameSpecifiesRlvAttachmentPoint(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- (skull) hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name = "Party Hat"; + sampleTree.Clothing_Hats_Folder.Name = "(skull) hats"; + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Attach everything in #RLV/Clothing/+Hats because that's where the source item (fancy hat) is calling @attachthis from,+ + // but attach "party hat" to the skull because it doesn't specify an attachment point but the folder name does + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Skull, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}=force", sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_FancyHat_Chin.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachthis", true)] + [InlineData("attachthisoverorreplace", true)] + [InlineData("attachthisover", false)] + public async Task AttachThis_FromHiddenSubfolder(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- .hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = ".hats"; + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Name = "Fancy Hat (chin)"; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_Hats_PartyHat_Spine.Id, RlvAttachmentPoint.Spine, replaceExistingAttachments) + }; + + // Act + await _rlv.ProcessMessage($"@{command}=force", sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_FancyHat_Chin.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachthis", true)] + [InlineData("attachthisoverorreplace", true)] + [InlineData("attachthisover", false)] + public async Task AttachThis_AttachPoint(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected request to attach + // | |= Happy Shirt (attached to 'spine') + // | |= Retro Pants <-- Expected request to attach + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- Expected request to attach + // | \= Party Hat (attached to 'spine') + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.Name = "Happy Shirt (spine)"; + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name = "Party Hat (spine)"; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Clothing_RetroPants.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + new(sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, RlvAttachmentPoint.Chin, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:spine=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("attachthis", true)] + [InlineData("attachthisoverorreplace", true)] + [InlineData("attachthisover", false)] + public async Task AttachThis_RlvWearableType(string command, bool replaceExistingAttachments) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants <-- Expected to be attached + // | |= Happy Shirt <-- Expected to be attached + // | |= Retro Pants (Worn as Tattoo) + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses <-- Expected to be attached + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.AttachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + new(sampleTree.Root_Accessories_Glasses.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_HappyShirt.Id, RlvAttachmentPoint.Default, replaceExistingAttachments), + new(sampleTree.Root_Clothing_BusinessPants_Pelvis.Id, RlvAttachmentPoint.Pelvis, replaceExistingAttachments), + }; + + // Act + await _rlv.ProcessMessage($"@{command}:tattoo=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.AttachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/ClearCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/ClearCommandTests.cs new file mode 100644 index 00000000..7a63b86d --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/ClearCommandTests.cs @@ -0,0 +1,70 @@ +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class ClearCommandTests : RestrictionsBase + { + #region @Clear + + [Fact] + public async Task Clear() + { + await _rlv.ProcessMessage("@tploc=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplm=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + + await _rlv.ProcessMessage("@clear", _sender.Id, _sender.Name); + + var restrictions = _rlv.Restrictions.FindRestrictions(); + Assert.Empty(restrictions); + } + + [Fact] + public async Task Clear_CaseInSensitive() + { + await _rlv.ProcessMessage("@tploc=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplm=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + + await _rlv.ProcessMessage("@cLEaR", _sender.Id, _sender.Name); + + var restrictions = _rlv.Restrictions.FindRestrictions(); + Assert.Empty(restrictions); + } + + [Fact] + public async Task Clear_SenderBased() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + await _rlv.ProcessMessage("@tploc=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplm=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@unsit=n", sender2.Id, sender2.Name); + await _rlv.ProcessMessage("@fly=n", sender2.Id, sender2.Name); + + await _rlv.ProcessMessage("@clear", sender2.Id, sender2.Name); + + Assert.False(_rlv.Permissions.CanTpLoc()); + Assert.False(_rlv.Permissions.CanTpLm()); + Assert.True(_rlv.Permissions.CanUnsit()); + Assert.True(_rlv.Permissions.CanFly()); + } + + [Fact] + public async Task Clear_Filtered() + { + await _rlv.ProcessMessage("@tploc=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplm=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + + await _rlv.ProcessMessage("@clear=tp", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTpLoc()); + Assert.True(_rlv.Permissions.CanTpLm()); + Assert.False(_rlv.Permissions.CanUnsit()); + Assert.False(_rlv.Permissions.CanFly()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/DetachAllCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/DetachAllCommandTests.cs new file mode 100644 index 00000000..e45b0cac --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/DetachAllCommandTests.cs @@ -0,0 +1,292 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class DetachAllCommandTests : RestrictionsBase + { + #region @detachall:=force + [Fact] + public async Task DetachAllForce_Recursive() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expected detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the clothing folder, and all of its subfolders will be removed + var expected = new HashSet() + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachall:Clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachAllForce_Recursive_IgnoreRestrictions() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expected detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the clothing folder, and all of its subfolders will be removed even though the clothing folder is restricted from + // being detached - commands bypass these restrictions + var expected = new HashSet() + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + }; + await _rlv.ProcessMessage("@detachthis:Clothing=n", _sender.Id, _sender.Name); + + // Act + await _rlv.ProcessMessage("@detachall:Clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachAllForce_Recursive_PrivateTargetDir() + { + // #RLV + // | + // |- .private + // | + // |- .clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expected detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = ".clothing"; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the clothing folder, and all of its subfolders will be removed + var expected = new HashSet() + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachall:.clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachAllForce_Recursive_PrivateSubFolders() + { + // #RLV + // | + // |- .private + // | + // |- .clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- .hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = ".clothing"; + sampleTree.Clothing_Hats_Folder.Name = ".hats"; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the .clothing folder, and all of its non-private subfolders will be removed, except the private .hats folder + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachall:.clothing=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/DetachAllThisCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/DetachAllThisCommandTests.cs new file mode 100644 index 00000000..39ce4d52 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/DetachAllThisCommandTests.cs @@ -0,0 +1,363 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class DetachAllThisCommandTests : RestrictionsBase + { + #region @detachallthis[: or ]=force + [Fact] + public async Task DetachAllThisForce_Default() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt [SENDER] (attached chest) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expected detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (attached chest) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0004-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachallthis=force", sampleTree.Root_Clothing_HappyShirt.AttachedPrimId!.Value, sampleTree.Root_Clothing_HappyShirt.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachAllThisForce_ById() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expected detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (attached chest) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0004-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + }; + + // Act + await _rlv.ProcessMessage($"@detachallthis:{sampleTree.Root_Clothing_HappyShirt.AttachedPrimId}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachThisAllForce_ByRlvAttachmentPoint() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expected detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (attached chest) <-- Expected detach + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0004-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Accessories_Glasses.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachallthis:chest=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachAllThisForce_ByRlvWearableType() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expected detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) <-- Expected detach + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Accessories_Glasses.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachallthis:pants=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachAllThisForce_ByRlvWearableType_PrivateFolder() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- .hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) <-- Expected detach + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Hats_Folder.Name = ".hats"; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the clothing and accessories folder will be detached, recursive. Hats will be excluded because they are in a private folder ".hats" + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Accessories_Glasses.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachallthis:pants=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/DetachMeCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/DetachMeCommandTests.cs new file mode 100644 index 00000000..09faec31 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/DetachMeCommandTests.cs @@ -0,0 +1,137 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class DetachMeCommandTests : RestrictionsBase + { + #region @detachme=force + [Fact] + public async Task DetachMeForce() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached spine) + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_RetroPants.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachme=force", sampleTree.Root_Clothing_RetroPants.AttachedPrimId!.Value, sampleTree.Root_Clothing_RetroPants.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachMeForce_IgnoreNoStrip() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached spine) + // | |= nostrip Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.Name = "nostrip Retro Pants"; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_RetroPants.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachme=force", sampleTree.Root_Clothing_RetroPants.AttachedPrimId!.Value, sampleTree.Root_Clothing_RetroPants.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/DetachThisCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/DetachThisCommandTests.cs new file mode 100644 index 00000000..7efd8ad1 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/DetachThisCommandTests.cs @@ -0,0 +1,361 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class DetachThisCommandTests : RestrictionsBase + { + #region @detachthis[: or or or ]=force + [Fact] + public async Task DetachThisForce_Default() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt [SENDER] (attached chest) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (attached chest) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0004-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the clothing folder will be detached because happy shirt exists in the clothing folder + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachthis=force", sampleTree.Root_Clothing_HappyShirt.AttachedPrimId!.Value, sampleTree.Root_Clothing_HappyShirt.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachThisForce_ById() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (attached chest) + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0004-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + }; + + // Act + await _rlv.ProcessMessage($"@detachthis:{sampleTree.Root_Clothing_HappyShirt.AttachedPrimId}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachThisForce_ByRlvAttachmentPoint() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (attached chest) <-- Expected detach + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0004-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the clothing and accessories folder will be detached, not recursive + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Accessories_Glasses.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachthis:chest=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachThisForce_ByRlvWearableType() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) <-- Expected detach + // | |= Retro Pants (attached pelvis) <-- Expected detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) <-- Expected detach + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Everything under the clothing and accessories folder will be detached, not recursive + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Accessories_Glasses.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachthis:pants=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task DetachThisForce_ByRlvWearableType_PrivateFolder() + { + // #RLV + // | + // |- .private + // | + // |- .clothing + // | |= Business Pants + // | |= Happy Shirt (worn pants) + // | |= Retro Pants (attached pelvis) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (worn pants) <-- Expected detach + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = ".clothing"; + + sampleTree.Root_Clothing_RetroPants.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_RetroPants.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + // Only accessories will be removed even though pants exist in our clothing folder. The clothing folder is private ".clothing" + var expected = new HashSet() + { + sampleTree.Root_Accessories_Glasses.Id, + }; + + // Act + await _rlv.ProcessMessage("@detachthis:pants=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/RemAttachCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/RemAttachCommandTests.cs new file mode 100644 index 00000000..bf98d397 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/RemAttachCommandTests.cs @@ -0,0 +1,648 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class RemAttachCommandTests : RestrictionsBase + { + #region @detach @remattach[:]=force + [Theory] + [InlineData("@detach=force")] + [InlineData("@remattach=force")] + public async Task RemAttach_RemoveAllAttachments(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expect detach + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expect detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Clothing_HappyShirt.Id, + }; + + // Act + await _rlv.ProcessMessage(command, _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("@detach=force")] + [InlineData("@remattach=force")] + public async Task RemAttach_RemoveAllAttachments_ExternalItems(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expect detach + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expect detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + // External + // |= External Tattoo (worn tattoo) + // \= External Jaw Thing (attached jaw) <-- Expect detach + // + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + var externalWearable = new RlvInventoryItem( + new Guid("12312312-0001-4aaa-8aaa-aaaaaaaaaaaa"), + "External Tattoo", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + null, + null, + RlvWearableType.Tattoo); + var externalAttachable = new RlvInventoryItem( + new Guid("12312312-0002-4aaa-8aaa-aaaaaaaaaaaa"), + "External Jaw Thing", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + RlvAttachmentPoint.Jaw, + new Guid("12312312-0002-4aaa-8aaa-ffffffffffff"), + null); + + currentOutfit.Add(externalWearable); + currentOutfit.Add(externalAttachable); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Clothing_HappyShirt.Id, + externalAttachable.Id, + }; + + // Act + await _rlv.ProcessMessage(command, _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("@detach:Clothing/Hats=force")] + [InlineData("@remattach:Clothing/Hats=force")] + public async Task RemAttach_ByFolder(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) <-- Expect detach + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + }; + + // Act + await _rlv.ProcessMessage(command, _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("@detach:chest=force")] + [InlineData("@remattach:chest=force")] + public async Task RemAttach_RemoveRlvAttachmentPoint(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expect detach + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + // External + // \= External Chest Thing (attached chest) <-- Expect detach + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + var externalAttachable = new RlvInventoryItem( + new Guid("12312312-0002-4aaa-8aaa-aaaaaaaaaaaa"), + "External Chest Thing", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + RlvAttachmentPoint.Chest, + new Guid("12312312-0002-4aaa-8aaa-ffffffffffff"), + null); + + currentOutfit.Add(externalAttachable); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id, + externalAttachable.Id + }; + + // Act + await _rlv.ProcessMessage(command, _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("@detach:skull=force")] + [InlineData("@remattach:skull=force")] + public async Task RemAttach_RemoveNone(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + }; + + // Act + await _rlv.ProcessMessage(command, _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("detach")] + [InlineData("remattach")] + public async Task RemAttach_RemoveByUUID(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expected detach + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id + }; + + // Act + await _rlv.ProcessMessage($"@{command}:{sampleTree.Root_Clothing_HappyShirt.AttachedPrimId}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("detach")] + [InlineData("remattach")] + public async Task RemAttach_RemoveByUUID_IgnoreRestrictions(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) <-- Expected detach + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + sampleTree.Root_Clothing_HappyShirt.Id + }; + + await _rlv.ProcessMessage($"@detachallthis:{sampleTree.Clothing_Folder.Name}=n", _sender.Id, _sender.Name); + + // Act + await _rlv.ProcessMessage($"@{command}:{sampleTree.Root_Clothing_HappyShirt.AttachedPrimId}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData("detach")] + [InlineData("remattach")] + public async Task RemAttach_RemoveByUUID_External(string command) + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (attached chest) + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + // External + // \= External Chest Thing (attached chest) <-- Expect detach + // + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + var externalAttachable = new RlvInventoryItem( + new Guid("12312312-0002-4aaa-8aaa-aaaaaaaaaaaa"), + "External Chest Thing", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + RlvAttachmentPoint.Chest, + new Guid("12312312-0002-4aaa-8aaa-ffffffffffff"), + null); + + currentOutfit.Add(externalAttachable); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.DetachAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet() + { + externalAttachable.Id + }; + + // Act + await _rlv.ProcessMessage($"@{command}:{externalAttachable.AttachedPrimId}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.DetachAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/RemOutfitCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/RemOutfitCommandTests.cs new file mode 100644 index 00000000..8b4d4cf1 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/RemOutfitCommandTests.cs @@ -0,0 +1,461 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class RemOutfitCommandTests : RestrictionsBase + { + #region @remoutfit[:]=force + [Fact] + public async Task RemOutfitForce() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn shirt) <-- Expect removed + // | |= Retro Pants (worn pants) <-- Expect removed + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (worn tattoo) <-- Expect removed + // | \= Party Hat (worn skin, must not be removed) + // \-Accessories + // |= Watch (worn tattoo) <-- Expect removed + // \= Glasses + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.WornOn = RlvWearableType.Skin; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.RemOutfitAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet + { + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Accessories_Watch.Id, + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + }; + + // Act + await _rlv.ProcessMessage("@remoutfit=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.RemOutfitAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RemOutfitForce_ExternalItems() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn shirt) <-- Expect removed + // | |= Retro Pants (worn pants) <-- Expect removed + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (worn tattoo) <-- Expect removed + // | \= Party Hat (worn skin, must not be removed) + // \-Accessories + // |= Watch (worn tattoo) + // \= Glasses + // + // External + // \= External Tattoo (worn tattoo) <-- Expect removed + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.WornOn = RlvWearableType.Skin; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + var externalWearable = new RlvInventoryItem( + new Guid("12312312-0001-4aaa-8aaa-aaaaaaaaaaaa"), + "External Tattoo", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + null, + null, + RlvWearableType.Tattoo); + + currentOutfit.Add(externalWearable); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.RemOutfitAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet + { + sampleTree.Root_Clothing_RetroPants.Id, + sampleTree.Root_Clothing_HappyShirt.Id, + sampleTree.Root_Accessories_Watch.Id, + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + externalWearable.Id + }; + + // Act + await _rlv.ProcessMessage("@remoutfit=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.RemOutfitAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RemOutfitForce_ExternalItems_ByType() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn shirt) + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (worn tattoo) <-- Expect removed + // | \= Party Hat (worn skin, must not be removed) + // \-Accessories + // |= Watch (worn tattoo) <-- Expect removed + // \= Glasses + // + // External + // \= External Tattoo (worn tattoo) <-- Expect removed + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.WornOn = RlvWearableType.Skin; + + var externalWearable = new RlvInventoryItem( + new Guid("12312312-0001-4aaa-8aaa-aaaaaaaaaaaa"), + "External Tattoo", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + null, + null, + RlvWearableType.Tattoo); + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + currentOutfit.Add(externalWearable); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.RemOutfitAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Accessories_Watch.Id, + externalWearable.Id + }; + + // Act + await _rlv.ProcessMessage("@remoutfit:tattoo=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.RemOutfitAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RemOutfitForce_Folder() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn shirt) + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (worn tattoo) <-- Expect removed + // | \= Party Hat (worn skin, must not be removed) + // \-Accessories + // |= Watch (worn tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.WornOn = RlvWearableType.Skin; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.RemOutfitAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + }; + + // Act + await _rlv.ProcessMessage("@remoutfit:Clothing/Hats=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.RemOutfitAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RemOutfitForce_Specific() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn shirt) + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (worn tattoo) <-- Expect removed + // | \= Party Hat (worn skin, must not be removed) + // \-Accessories + // |= Watch (worn tattoo) <-- Expect removed + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.WornOn = RlvWearableType.Skin; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.RemOutfitAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet + { + sampleTree.Root_Clothing_Hats_FancyHat_Chin.Id, + sampleTree.Root_Accessories_Watch.Id, + }; + + // Act + await _rlv.ProcessMessage("@remoutfit:tattoo=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.RemOutfitAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RemOutfitForce_BodyPart_Specific() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (worn shirt) + // | |= Retro Pants (worn pants) + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (worn tattoo) + // | \= Party Hat (worn skin, must not be removed) + // \-Accessories + // |= Watch (worn tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.WornOn = RlvWearableType.Skin; + + var currentOutfit = SampleInventoryTree.BuildCurrentOutfit(sampleTree.Root); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + _actionCallbacks.Setup(e => + e.RemOutfitAsync(It.IsAny>(), It.IsAny()) + ).Returns(Task.CompletedTask); + + var expected = new HashSet(); + + // Act + await _rlv.ProcessMessage("@remoutfit:skin=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.RemOutfitAsync( + It.Is>(ids => + ids != null && + ids.Count == expected.Count && + expected.SetEquals(ids) + ), + It.IsAny() + ), + Times.Once + ); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/SetDebugCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/SetDebugCommandTests.cs new file mode 100644 index 00000000..f888bb27 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/SetDebugCommandTests.cs @@ -0,0 +1,44 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class SetDebugCommandTests : RestrictionsBase + { + + #region @setdebug_:=force + [Theory] + [InlineData("RenderResolutionDivisor", "RenderResolutionDivisor Success")] + [InlineData("Unknown Setting", "Unknown Setting Success")] + public async Task SetDebug_Default(string settingName, string settingValue) + { + _actionCallbacks + .Setup(e => e.SetDebugAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage($"@setdebug_{settingName}:{settingValue}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SetDebugAsync(settingName.ToLower(), settingValue, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task SetDebug_Invalid() + { + _actionCallbacks + .Setup(e => e.SetDebugAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + Assert.False(await _rlv.ProcessMessage($"@setdebug_:42=force", _sender.Id, _sender.Name)); + + // Assert + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/SetEnvCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/SetEnvCommandTests.cs new file mode 100644 index 00000000..67947880 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/SetEnvCommandTests.cs @@ -0,0 +1,32 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class SetEnvCommandTests : RestrictionsBase + { + + #region @setenv_:=force + + [Theory] + [InlineData("Daytime", "Daytime Success")] + [InlineData("Unknown Setting", "Unknown Setting Success")] + public async Task SetEnv_Default(string settingName, string settingValue) + { + _actionCallbacks + .Setup(e => e.SetEnvAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage($"@setenv_{settingName}:{settingValue}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SetEnvAsync(settingName.ToLower(), settingValue, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/SetGroupCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/SetGroupCommandTests.cs new file mode 100644 index 00000000..54529a38 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/SetGroupCommandTests.cs @@ -0,0 +1,87 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class SetGroupCommandTests : RestrictionsBase + { + #region @setgroup:[;]=force + + [Fact] + public async Task SetGroup_ByName() + { + _actionCallbacks + .Setup(e => e.SetGroupAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@setgroup:Group Name=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SetGroupAsync("Group Name", null, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task SetGroup_ByNameAndRole() + { + _actionCallbacks + .Setup(e => e.SetGroupAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@setgroup:Group Name;Admin Role=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SetGroupAsync("Group Name", "Admin Role", It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task SetGroup_ById() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + _actionCallbacks + .Setup(e => e.SetGroupAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage($"@setgroup:{objectId1}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SetGroupAsync(objectId1, null, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task SetGroup_ByIdAndRole() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + _actionCallbacks + .Setup(e => e.SetGroupAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage($"@setgroup:{objectId1};Admin Role=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SetGroupAsync(objectId1, "Admin Role", It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/SetRotCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/SetRotCommandTests.cs new file mode 100644 index 00000000..80850b84 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/SetRotCommandTests.cs @@ -0,0 +1,28 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class SetRotCommandTests : RestrictionsBase + { + + #region @setrot:=force + [Fact] + public async Task SetRot() + { + _actionCallbacks + .Setup(e => e.SetRotAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@setrot:1.5=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SetRotAsync(1.5f, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/SitCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/SitCommandTests.cs new file mode 100644 index 00000000..9194a3b4 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/SitCommandTests.cs @@ -0,0 +1,148 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class SitCommandTests : RestrictionsBase + { + #region @sit:=force + private void SetObjectExists(Guid objectId) + { + _queryCallbacks.Setup(e => + e.ObjectExistsAsync(objectId, default) + ).ReturnsAsync(true); + } + + private void SetIsSitting(bool isCurrentlySitting) + { + _queryCallbacks.Setup(e => + e.IsSittingAsync(default) + ).ReturnsAsync(isCurrentlySitting); + } + + + [Fact] + public async Task ForceSit_Default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + SetObjectExists(objectId1); + SetIsSitting(false); + + _actionCallbacks + .Setup(e => e.SitAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage($"@sit:{objectId1}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SitAsync(objectId1, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ForceSit_RestrictedUnsit_WhileStanding() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + SetObjectExists(objectId1); + SetIsSitting(false); + + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + + _actionCallbacks + .Setup(e => e.SitAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage($"@sit:{objectId1}=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.SitAsync(objectId1, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ForceSit_RestrictedUnsit_WhileSeated() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + SetObjectExists(objectId1); + SetIsSitting(true); + + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + + _actionCallbacks + .Setup(e => e.SitAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + Assert.False(await _rlv.ProcessMessage($"@sit:{objectId1}=force", _sender.Id, _sender.Name)); + + // Assert + _actionCallbacks.VerifyNoOtherCalls(); + } + + + [Fact] + public async Task ForceSit_RestrictedSit() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + SetObjectExists(objectId1); + SetIsSitting(true); + + await _rlv.ProcessMessage("@sit=n", _sender.Id, _sender.Name); + + _actionCallbacks + .Setup(e => e.SitAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + Assert.False(await _rlv.ProcessMessage($"@sit:{objectId1}=force", _sender.Id, _sender.Name)); + + // Assert + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ForceSit_RestrictedStandTp() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + SetObjectExists(objectId1); + SetIsSitting(true); + + await _rlv.ProcessMessage("@standtp=n", _sender.Id, _sender.Name); + + _actionCallbacks + .Setup(e => e.SitAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + Assert.False(await _rlv.ProcessMessage($"@sit:{objectId1}=force", _sender.Id, _sender.Name)); + + // Assert + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ForceSit_InvalidObject() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + // SetupSitTarget(objectId1, true); <-- Don't setup sit target for this test + + _actionCallbacks + .Setup(e => e.SitAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + Assert.False(await _rlv.ProcessMessage($"@sit:{objectId1}=force", _sender.Id, _sender.Name)); + + // Assert + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/SitGroundCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/SitGroundCommandTests.cs new file mode 100644 index 00000000..197cbe99 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/SitGroundCommandTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class SitGroundCommandTests : RestrictionsBase + { + + #region @sitground=force + + [Fact] + public async Task ForceSitGround() + { + Assert.True(await _rlv.ProcessMessage("@sitground=force", _sender.Id, _sender.Name)); + } + + [Fact] + public async Task ForceSitGround_RestrictedSit() + { + await _rlv.ProcessMessage("@sit=n", _sender.Id, _sender.Name); + + Assert.False(await _rlv.ProcessMessage("@sitground=force", _sender.Id, _sender.Name)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/TpToCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/TpToCommandTests.cs new file mode 100644 index 00000000..944758e0 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/TpToCommandTests.cs @@ -0,0 +1,97 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class TpToCommandTests : RestrictionsBase + { + #region @tpto:///[;lookat]=force + + [Fact] + public async Task TpTo_Default() + { + _actionCallbacks + .Setup(e => e.TpToAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@tpto:1.5/2.5/3.5=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.TpToAsync(1.5f, 2.5f, 3.5f, null, null, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task TpTo_WithRegion() + { + _actionCallbacks + .Setup(e => e.TpToAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@tpto:Region Name/1.5/2.5/3.5=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.TpToAsync(1.5f, 2.5f, 3.5f, "Region Name", null, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task TpTo_WithRegionAndLookAt() + { + _actionCallbacks + .Setup(e => e.TpToAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await _rlv.ProcessMessage("@tpto:Region Name/1.5/2.5/3.5;3.1415=force", _sender.Id, _sender.Name); + + // Assert + _actionCallbacks.Verify(e => + e.TpToAsync(1.5f, 2.5f, 3.5f, "Region Name", 3.1415f, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task TpTo_RestrictedUnsit() + { + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + + _actionCallbacks + .Setup(e => e.TpToAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + Assert.False(await _rlv.ProcessMessage("@tpto:1.5/2.5/3.5=force", _sender.Id, _sender.Name)); + + // Assert + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task TpTo_RestrictedTpLoc() + { + await _rlv.ProcessMessage("@tploc=n", _sender.Id, _sender.Name); + + _actionCallbacks + .Setup(e => e.TpToAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + Assert.False(await _rlv.ProcessMessage("@tpto:1.5/2.5/3.5=force", _sender.Id, _sender.Name)); + + // Assert + _actionCallbacks.VerifyNoOtherCalls(); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Commands/UnsitCommandTests.cs b/LibreMetaverse.RLV.Tests/Commands/UnsitCommandTests.cs new file mode 100644 index 00000000..82f56067 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Commands/UnsitCommandTests.cs @@ -0,0 +1,23 @@ +namespace LibreMetaverse.RLV.Tests.Commands +{ + public class UnsitCommandTests : RestrictionsBase + { + #region @unsit=force + + [Fact] + public async Task ForceUnSit() + { + Assert.True(await _rlv.ProcessMessage("@unsit=force", _sender.Id, _sender.Name)); + } + + [Fact] + public async Task ForceUnSit_RestrictedUnsit() + { + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + + Assert.False(await _rlv.ProcessMessage("@unsit=force", _sender.Id, _sender.Name)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/AcceptPermissionExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/AcceptPermissionExceptionTests.cs new file mode 100644 index 00000000..d8712824 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/AcceptPermissionExceptionTests.cs @@ -0,0 +1,18 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class AcceptPermissionExceptionTests : RestrictionsBase + { + #region @acceptpermission= + [Fact] + public async Task AcceptPermission() + { + Assert.True(await _rlv.ProcessMessage($"@acceptpermission=add", _sender.Id, _sender.Name)); + Assert.True(_rlv.Permissions.IsAutoAcceptPermissions()); + + Assert.True(await _rlv.ProcessMessage($"@acceptpermission=rem", _sender.Id, _sender.Name)); + Assert.False(_rlv.Permissions.IsAutoAcceptPermissions()); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/AcceptTpExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/AcceptTpExceptionTests.cs new file mode 100644 index 00000000..eb98d5de --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/AcceptTpExceptionTests.cs @@ -0,0 +1,35 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class AcceptTpExceptionTests : RestrictionsBase + { + #region @accepttp[:]= + + [Fact] + public async Task CanAutoAcceptTp_User() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@accepttp:{userId1}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.IsAutoAcceptTp(userId1)); + Assert.False(_rlv.Permissions.IsAutoAcceptTp(userId2)); + Assert.False(_rlv.Permissions.IsAutoAcceptTp()); + } + + [Fact] + public async Task CanAutoAcceptTp_All() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@accepttp=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.IsAutoAcceptTp(userId1)); + Assert.True(_rlv.Permissions.IsAutoAcceptTp(userId2)); + Assert.True(_rlv.Permissions.IsAutoAcceptTp()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/AcceptTpRequestExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/AcceptTpRequestExceptionTests.cs new file mode 100644 index 00000000..91237b28 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/AcceptTpRequestExceptionTests.cs @@ -0,0 +1,35 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class AcceptTpRequestExceptionTests : RestrictionsBase + { + #region @accepttprequest[:]= + + [Fact] + public async Task CanAutoAcceptTpRequest_User() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@accepttprequest:{userId1}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.IsAutoAcceptTpRequest(userId1)); + Assert.False(_rlv.Permissions.IsAutoAcceptTpRequest(userId2)); + Assert.False(_rlv.Permissions.IsAutoAcceptTpRequest()); + } + + [Fact] + public async Task CanAutoAcceptTpRequest_All() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@accepttprequest=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.IsAutoAcceptTpRequest(userId1)); + Assert.True(_rlv.Permissions.IsAutoAcceptTpRequest(userId2)); + Assert.True(_rlv.Permissions.IsAutoAcceptTpRequest()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/AttachAllThisExceptExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/AttachAllThisExceptExceptionTests.cs new file mode 100644 index 00000000..5be0bdd1 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/AttachAllThisExceptExceptionTests.cs @@ -0,0 +1,290 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class AttachAllThisExceptExceptionTests : RestrictionsBase + { + #region @attachallthis_except:= + + [Fact] + public async Task AttachAllThis_Recursive_ExceptAll() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked, no-attach] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked, but has exceptions] + // | | + // | |- Sub Hats [Expected locked, but has exceptions] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@attachallthis_except:Clothing/Hats=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat () - Parent folder locked recursively, but has exception + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () - Parent folder locked recursively, but has exception + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Single(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Single(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachAllThis_Recursive_ExceptAll_Recursive() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked, but has exceptions] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked, but has exceptions] + // | | + // | |- Sub Hats [Expected locked, but has exceptions] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@attachallthis_except:Clothing=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat () - Parent folder locked recursively, but parent has recursive exception + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () - Parent folder locked recursively, but parent has recursive exception + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () - Locked, but folder has exception + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () - Locked, but folder has exception + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () - Locked, but folder has exception + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.True(clothingFolderLocked.CanDetach); + Assert.True(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.True(hatsFolderLocked.CanDetach); + Assert.True(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.True(subhatsFolderLocked.CanDetach); + Assert.True(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Single(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Single(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Single(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachAllThis_Recursive_ExceptAll_Recursive_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked] + // | | + // | |- Sub Hats [Expected locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@attachallthis_except:Clothing=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@attachallthis_except:Clothing=rem", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat () - Parent folder locked recursively, but parent has recursive exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () - Parent folder locked recursively, but parent has recursive exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.True(clothingFolderLocked.CanDetach); + Assert.False(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.True(hatsFolderLocked.CanDetach); + Assert.False(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.True(subhatsFolderLocked.CanDetach); + Assert.False(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + #endregion + + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/AttachThisExceptExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/AttachThisExceptExceptionTests.cs new file mode 100644 index 00000000..a66cce5c --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/AttachThisExceptExceptionTests.cs @@ -0,0 +1,210 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class AttachThisExceptExceptionTests : RestrictionsBase + { + + #region @attachthis_except:= + + [Fact] + public async Task AttachThisExcept_Default() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked, but has exceptions] + // | | + // | |- Sub Hats [Expected locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@attachthis_except:Clothing/Hats=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.True(clothingFolderLocked.CanDetach); + Assert.False(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.True(hatsFolderLocked.CanDetach); + Assert.True(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.True(subhatsFolderLocked.CanDetach); + Assert.False(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Single(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachThisExcept_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked] + // | | + // | |- Sub Hats [Expected locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@attachthis_except:Clothing/Hats=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@attachthis_except:Clothing/Hats=rem", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.True(clothingFolderLocked.CanDetach); + Assert.False(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.True(hatsFolderLocked.CanDetach); + Assert.False(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.True(subhatsFolderLocked.CanDetach); + Assert.False(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/DenyPermissionExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/DenyPermissionExceptionTests.cs new file mode 100644 index 00000000..02bf23a2 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/DenyPermissionExceptionTests.cs @@ -0,0 +1,17 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class DenyPermissionExceptionTests : RestrictionsBase + { + #region @denypermission= + [Fact] + public async Task DenyPermission() + { + Assert.True(await _rlv.ProcessMessage($"@denypermission=add", _sender.Id, _sender.Name)); + Assert.True(_rlv.Permissions.IsAutoDenyPermissions()); + + Assert.True(await _rlv.ProcessMessage($"@denypermission=rem", _sender.Id, _sender.Name)); + Assert.False(_rlv.Permissions.IsAutoDenyPermissions()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/DetachAllThisExceptExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/DetachAllThisExceptExceptionTests.cs new file mode 100644 index 00000000..65be44d5 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/DetachAllThisExceptExceptionTests.cs @@ -0,0 +1,253 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class DetachAllThisExceptExceptionTests : RestrictionsBase + { + #region @detachallthis_except:= + + [Fact] + public async Task DetachAllThis_Recursive_ExceptAll() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked, but has exceptions] + // | | + // | |- Sub Hats [Expected locked, but has exceptions] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@detachallthis_except:Clothing/Hats=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat () - Parent folder locked recursively, but has exception + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () - Parent folder locked recursively, but has exception + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + } + + [Fact] + public async Task DetachAllThis_Recursive_ExceptAll_Recursive() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked, but has exceptions] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked, but has exceptions] + // | | + // | |- Sub Hats [Expected locked, but has exceptions] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@detachallthis_except:Clothing=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat () - Parent folder locked recursively, but parent has recursive exception + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () - Parent folder locked recursively, but parent has recursive exception + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () - Locked, but folder has exception + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () - Locked, but folder has exception + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () - Locked, but folder has exception + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.True(clothingFolderLocked.CanDetach); + Assert.True(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.True(hatsFolderLocked.CanDetach); + Assert.True(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.True(subhatsFolderLocked.CanDetach); + Assert.True(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Single(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Single(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Single(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachAllThis_Recursive_ExceptAll_Recursive_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked] + // | | + // | |- Sub Hats [Expected locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@detachallthis_except:Clothing=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@detachallthis_except:Clothing=rem", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.False(clothingFolderLocked.CanDetach); + Assert.True(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.False(hatsFolderLocked.CanDetach); + Assert.True(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.False(subhatsFolderLocked.CanDetach); + Assert.True(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/DetachThisExceptExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/DetachThisExceptExceptionTests.cs new file mode 100644 index 00000000..e0ca7571 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/DetachThisExceptExceptionTests.cs @@ -0,0 +1,160 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class DetachThisExceptExceptionTests : RestrictionsBase + { + #region @detachthis_except:= + + [Fact] + public async Task DetachAllThis_Recursive_Except() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked, but has exceptions] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked, but has exceptions] + // | | + // | |- Sub Hats [Expected locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@detachthis_except:Clothing/Hats=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () - Locked, but folder has exception + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Single(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachAllThis_Recursive_Except_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Expected locked, but has exceptions] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Expected locked, but has exceptions] + // | | + // | |- Sub Hats [Expected locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@detachthis_except:Clothing/Hats=add", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage($"@detachthis_except:Clothing/Hats=rem", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/EditExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/EditExceptionTests.cs new file mode 100644 index 00000000..d7ffa93c --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/EditExceptionTests.cs @@ -0,0 +1,50 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class EditExceptionTests : RestrictionsBase + { + #region @edit:= + [Fact] + public async Task CanEdit_Exception() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@edit=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@edit:{objectId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, null)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, null)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, null)); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId1)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId1)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId1)); + + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId2)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId2)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId2)); + } + + [Fact] + public async Task CanEdit_Specific() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@editobj:{objectId1}=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, null)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, null)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, null)); + + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId1)); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId2)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId2)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/EmoteExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/EmoteExceptionTests.cs new file mode 100644 index 00000000..56a3200b --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/EmoteExceptionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class EmoteExceptionTests : RestrictionsBase + { + #region @emote= + [Fact] + public async Task CanEmote() + { + await CheckSimpleCommand("emote", m => m.CanEmote()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/RecvChatExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/RecvChatExceptionTests.cs new file mode 100644 index 00000000..90f42af6 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/RecvChatExceptionTests.cs @@ -0,0 +1,19 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class RecvChatExceptionTests : RestrictionsBase + { + #region @recvchat:= + [Fact] + public async Task CanRecvChat_Except() + { + var userId = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@recvchat=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvchat:{userId}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId)); + Assert.True(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/RecvEmoteExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/RecvEmoteExceptionTests.cs new file mode 100644 index 00000000..1e9a6200 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/RecvEmoteExceptionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class RecvEmoteExceptionTests : RestrictionsBase + { + #region @recvemote:= + + [Fact] + public async Task CanRecvChat_RecvEmote_Except() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@recvemote=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvemote:{userId1}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId2)); + Assert.True(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId1)); + Assert.False(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/RecvImExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/RecvImExceptionTests.cs new file mode 100644 index 00000000..446e77cc --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/RecvImExceptionTests.cs @@ -0,0 +1,42 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class RecvImExceptionTests : RestrictionsBase + { + #region @recvim:= + [Fact] + public async Task CanReceiveIM_Exception() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@recvim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:{userId1}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", userId1)); + } + + + [Fact] + public async Task CanReceiveIM_Exception_SingleGroup() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@recvim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:Group Name=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "Group Name")); + } + + [Fact] + public async Task CanReceiveIM_Exception_AllGroups() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@recvim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:allgroups=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "Group name")); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/RedirChatExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/RedirChatExceptionTests.cs new file mode 100644 index 00000000..1946a077 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/RedirChatExceptionTests.cs @@ -0,0 +1,101 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class RedirChatExceptionTests : RestrictionsBase + { + #region @redirchat:= + + [Fact] + public async Task IsRedirChat() + { + await _rlv.ProcessMessage("@redirchat:1234=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetRedirChatChannels(out var channels)); + + var expected = new List + { + 1234, + }; + + Assert.Equal(expected, channels); + } + + [Fact] + public async Task IsRedirChat_Removed() + { + await _rlv.ProcessMessage("@redirchat:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@redirchat:1234=rem", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.TryGetRedirChatChannels(out var channels)); + } + + [Fact] + public async Task IsRedirChat_MultipleChannels() + { + await _rlv.ProcessMessage("@redirchat:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@redirchat:12345=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetRedirChatChannels(out var channels)); + + var expected = new List + { + 1234, + 12345, + }; + + Assert.Equal(expected, channels); + } + + [Fact] + public async Task IsRedirChat_RedirectChat() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@redirchat:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportSendPublicMessage("Hello World"); + + Assert.True(_rlv.Permissions.TryGetRedirChatChannels(out var channels)); + var expected = new List<(int Channel, string Text)> + { + (1234, "Hello World"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task IsRedirChat_RedirectChatMultiple() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@redirchat:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@redirchat:5678=add", _sender.Id, _sender.Name); + + await _rlv.ReportSendPublicMessage("Hello World"); + _rlv.Permissions.TryGetRedirChatChannels(out var channels); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Hello World"), + (5678, "Hello World"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task IsRedirChat_RedirectChatEmote() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@redirchat:1234=add", _sender.Id, _sender.Name); + + await _rlv.ReportSendPublicMessage("/me says Hello World"); + + Assert.True(_rlv.Permissions.TryGetRedirChatChannels(out var channels)); + Assert.Empty(actual); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/RedirEmoteExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/RedirEmoteExceptionTests.cs new file mode 100644 index 00000000..cc2f66c1 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/RedirEmoteExceptionTests.cs @@ -0,0 +1,99 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class RedirEmoteExceptionTests : RestrictionsBase + { + #region @rediremote:= + [Fact] + public async Task IsRedirEmote() + { + await _rlv.ProcessMessage("@rediremote:1234=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetRedirEmoteChannels(out var channels)); + + var expected = new List + { + 1234, + }; + + Assert.Equal(expected, channels); + } + + [Fact] + public async Task IsRedirEmote_Removed() + { + await _rlv.ProcessMessage("@rediremote:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@rediremote:1234=rem", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.TryGetRedirEmoteChannels(out var channels)); + } + + [Fact] + public async Task IsRedirEmote_MultipleChannels() + { + await _rlv.ProcessMessage("@rediremote:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@rediremote:12345=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetRedirEmoteChannels(out var channels)); + + var expected = new List + { + 1234, + 12345, + }; + + Assert.Equal(expected, channels); + } + + [Fact] + public async Task IsRedirEmote_RedirectEmote() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@rediremote:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportSendPublicMessage("/me says Hello World"); + + Assert.True(_rlv.Permissions.TryGetRedirEmoteChannels(out var channels)); + var expected = new List<(int Channel, string Text)> + { + (1234, "/me says Hello World"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task IsRedirEmote_RedirectEmoteMultiple() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@rediremote:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@rediremote:5678=n", _sender.Id, _sender.Name); + + await _rlv.ReportSendPublicMessage("/me says Hello World"); + _rlv.Permissions.TryGetRedirEmoteChannels(out var channels); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/me says Hello World"), + (5678, "/me says Hello World"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task IsRedirEmote_RedirectEmoteChat() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@rediremote:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportSendPublicMessage("Hello World"); + + Assert.True(_rlv.Permissions.TryGetRedirEmoteChannels(out var channels)); + Assert.Empty(actual); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptExceptionTests.cs new file mode 100644 index 00000000..e49d72a3 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptExceptionTests.cs @@ -0,0 +1,16 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class SendChannelExceptExceptionTests : RestrictionsBase + { + #region @sendchannel_except:= + [Fact] + public async Task CanSendChannelExcept() + { + await _rlv.ProcessMessage("@sendchannel_except:456=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanChat(123, "Hello world")); + Assert.False(_rlv.Permissions.CanChat(456, "Hello world")); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptionTests.cs new file mode 100644 index 00000000..ffa2507e --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/SendChannelExceptionTests.cs @@ -0,0 +1,16 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class SendChannelExceptionTests : RestrictionsBase + { + #region @sendchannel:= + [Fact] + public async Task CanSendChannel_Exception() + { + await _rlv.ProcessMessage("@sendchannel=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendchannel:123=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanChat(123, "Hello world")); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/SendImExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/SendImExceptionTests.cs new file mode 100644 index 00000000..8f4a9bb4 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/SendImExceptionTests.cs @@ -0,0 +1,41 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class SendImExceptionTests : RestrictionsBase + { + #region @sendim:= + [Fact] + public async Task CanSendIM_Exception() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:{userId1}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanSendIM("Hello world", userId1)); + } + + [Fact] + public async Task CanSendIM_Exception_SingleGroup() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:Group Name=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanSendIM("Hello world", groupId1, "Group Name")); + } + + [Fact] + public async Task CanSendIM_Exception_AllGroups() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:allgroups=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanSendIM("Hello world", groupId1, "Group name")); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/ShareExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/ShareExceptionTests.cs new file mode 100644 index 00000000..2bd58216 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/ShareExceptionTests.cs @@ -0,0 +1,22 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class ShareExceptionTests : RestrictionsBase + { + + #region @share:= + [Fact] + public async Task CanShare_Except() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@share=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@share:{userId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShare(null)); + Assert.True(_rlv.Permissions.CanShare(userId1)); + Assert.False(_rlv.Permissions.CanShare(userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/ShowNameTagsExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/ShowNameTagsExceptionTests.cs new file mode 100644 index 00000000..3528ff5e --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/ShowNameTagsExceptionTests.cs @@ -0,0 +1,21 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class ShowNameTagsExceptionTests : RestrictionsBase + { + #region @shownametags:uuid= + [Fact] + public async Task CanShowNameTags_Except() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@shownametags=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@shownametags:{userId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowNameTags(null)); + Assert.True(_rlv.Permissions.CanShowNameTags(userId1)); + Assert.False(_rlv.Permissions.CanShowNameTags(userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/ShowNamesExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/ShowNamesExceptionTests.cs new file mode 100644 index 00000000..f0b38141 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/ShowNamesExceptionTests.cs @@ -0,0 +1,21 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class ShowNamesExceptionTests : RestrictionsBase + { + #region @shownames:uuid= + [Fact] + public async Task CanShowNames_Except() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@shownames=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@shownames:{userId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowNames(null)); + Assert.True(_rlv.Permissions.CanShowNames(userId1)); + Assert.False(_rlv.Permissions.CanShowNames(userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/StartImExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/StartImExceptionTests.cs new file mode 100644 index 00000000..e0681404 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/StartImExceptionTests.cs @@ -0,0 +1,20 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class StartImExceptionTests : RestrictionsBase + { + #region @startim:= + [Fact] + public async Task CanStartIM_Exception() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@startim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@startim:{userId1}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanStartIM(userId1)); + Assert.False(_rlv.Permissions.CanStartIM(userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/TouchMeExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/TouchMeExceptionTests.cs new file mode 100644 index 00000000..73783c24 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/TouchMeExceptionTests.cs @@ -0,0 +1,30 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class TouchMeExceptionTests : RestrictionsBase + { + #region @touchme= + + [Fact] + public async Task TouchMe_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage("@touchall=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@touchme=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, _sender.Id, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, _sender.Id, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, _sender.Id, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, _sender.Id, null, null)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/TouchThisExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/TouchThisExceptionTests.cs new file mode 100644 index 00000000..93aaaebc --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/TouchThisExceptionTests.cs @@ -0,0 +1,31 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class TouchThisExceptionTests : RestrictionsBase + { + #region @touchthis:= + + [Fact] + public async Task TouchThis_default() + { + var objectPrimId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectPrimId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage($"@touchthis:{objectPrimId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectPrimId1, null, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectPrimId1, userId1, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectPrimId1, null, 5.0f)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectPrimId1, null, null)); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectPrimId2, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectPrimId2, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectPrimId2, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectPrimId2, null, null)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/TouchWorldExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/TouchWorldExceptionTests.cs new file mode 100644 index 00000000..8e3baa89 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/TouchWorldExceptionTests.cs @@ -0,0 +1,30 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class TouchWorldExceptionTests : RestrictionsBase + { + + #region @touchworld:= + [Fact] + public async Task TouchWorld_Exception() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage("@touchworld=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@touchworld:{objectId2}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId2, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId2, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId2, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId2, null, null)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Exceptions/TpLureExceptionTests.cs b/LibreMetaverse.RLV.Tests/Exceptions/TpLureExceptionTests.cs new file mode 100644 index 00000000..e41794c6 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Exceptions/TpLureExceptionTests.cs @@ -0,0 +1,22 @@ +namespace LibreMetaverse.RLV.Tests.Exceptions +{ + public class TpLureExceptionTests : RestrictionsBase + { + #region @tplure:= + + [Fact] + public async Task CanTpLure_Except() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@tplure=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@tplure:{userId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTPLure(null)); + Assert.True(_rlv.Permissions.CanTPLure(userId1)); + Assert.False(_rlv.Permissions.CanTPLure(userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Extensions.cs b/LibreMetaverse.RLV.Tests/Extensions.cs new file mode 100644 index 00000000..79f7f132 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Extensions.cs @@ -0,0 +1,24 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests +{ + public static class CallbackMockExtensions + { + public static List<(int Channel, string Text)> RecordReplies(this Mock mock) + { + var list = new List<(int Channel, string Text)>(); + + mock + .Setup(m => m.SendReplyAsync( + It.IsAny(), + It.IsAny(), + It.IsAny()) + ) + .Callback( + (ch, txt, _) => list.Add((ch, txt)) + ); + + return list; + } + } +} diff --git a/LibreMetaverse.RLV.Tests/InventoryMapTests.cs b/LibreMetaverse.RLV.Tests/InventoryMapTests.cs new file mode 100644 index 00000000..817b7143 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/InventoryMapTests.cs @@ -0,0 +1,587 @@ +namespace LibreMetaverse.RLV.Tests +{ + public class InventoryMapTests + { + #region TryGetFolderFromPath + [Fact] + public void TryGetFolderFromPath_Normal() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath("Clothing/Hats", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Hats_Folder); + } + + [Fact] + public void TryGetFolderFromPath_EmptyPath() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.False(inventoryMap.TryGetFolderFromPath("", true, out var foundFolder)); + Assert.Null(foundFolder); + } + + [Fact] + public void TryGetFolderFromPath_FolderNameContainsForwardSlash() + { + // #RLV + // | + // |- .private + // | + // |- Clo/thing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = "Clo/thing"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath("Clo/thing/Hats", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Hats_Folder); + } + + [Fact] + public void TryGetFolderFromPath_FolderNameContainsForwardSlashes() + { + // #RLV + // | + // |- .private + // | + // |- /Clo//thing// + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- //h/ats/ + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = "/Clo//thing//"; + sampleTree.Clothing_Hats_Folder.Name = "//h/ats/"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath($"{sampleTree.Clothing_Folder.Name}/{sampleTree.Clothing_Hats_Folder.Name}", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Hats_Folder); + } + + [Fact] + public void TryGetFolderFromPath_FolderNameWithSlashPrefix() + { + // #RLV + // | + // |- .private + // | + // |- /Clo//thing// + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- //h/ats/ + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = "/Clothing"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath($"{sampleTree.Clothing_Folder.Name}", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Folder); + } + + [Fact] + public void TryGetFolderFromPath_FolderNameWithSlashSuffix() + { + // #RLV + // | + // |- .private + // | + // |- /Clo//thing// + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- //h/ats/ + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = "Clothing/"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath($"{sampleTree.Clothing_Folder.Name}", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Folder); + } + + [Fact] + public void TryGetFolderFromPath_FolderNameWithSlashAffix() + { + // #RLV + // | + // |- .private + // | + // |- /Clo//thing// + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- //h/ats/ + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = "/Clothing/"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath($"{sampleTree.Clothing_Folder.Name}", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Folder); + } + + + [Fact] + public void TryGetFolderFromPath_ContendingFoldersWithSlashes() + { + // #RLV + // | + // |- .private + // | + // |- Clothing/// + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- //h/ats/ + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var contendingTree1 = sharedFolder.AddChild(new Guid("12345678-0001-4ddd-8ddd-dddddddddddd"), "Clothing"); + var contendingTree3 = sharedFolder.AddChild(new Guid("12345678-0002-4ddd-8ddd-dddddddddddd"), "+Clothing///"); + var contendingTree4 = sharedFolder.AddChild(new Guid("12345678-0003-4ddd-8ddd-dddddddddddd"), "+Clothing///"); + + sampleTree.Clothing_Folder.Name = "Clothing///"; + sampleTree.Clothing_Hats_Folder.Name = "//h/ats/"; + + var inventoryMap = new InventoryMap(sharedFolder); + + // We prefer the exact match of "Clothing///" over the not so exact match of "+Clothing///" since it's exactly what we're searching for + Assert.True(inventoryMap.TryGetFolderFromPath($"{sampleTree.Clothing_Folder.Name}/{sampleTree.Clothing_Hats_Folder.Name}", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Hats_Folder); + } + + [Fact] + public void TryGetFolderFromPath_InvalidPath() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.False(inventoryMap.TryGetFolderFromPath("Clothing/Hats123", true, out var foundFolder)); + } + + [Fact] + public void TryGetFolderFromPath_IgnoreFolderPrefix() + { + // #RLV + // | + // |- .private + // | + // |- ~Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- +Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = "~Clothing"; + sampleTree.Clothing_Hats_Folder.Name = "+Hats"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath("Clothing/Hats", true, out var foundFolder)); + Assert.Equal(foundFolder, sampleTree.Clothing_Hats_Folder); + } + + [Fact] + public void TryGetFolderFromPath_FailOnHiddenFolder() + { + // #RLV + // | + // |- .private + // | + // |- .Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = ".Clothing"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.False(inventoryMap.TryGetFolderFromPath(".Clothing", true, out var foundFolder)); + } + + [Fact] + public void TryGetFolderFromPath_AllowHiddenFolder() + { + // #RLV + // | + // |- .private + // | + // |- .Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Clothing_Folder.Name = ".Clothing"; + + var inventoryMap = new InventoryMap(sharedFolder); + + Assert.True(inventoryMap.TryGetFolderFromPath(".Clothing", false, out var foundFolder)); + Assert.Equal(sampleTree.Clothing_Folder, foundFolder); + } + + #endregion + + #region FindFoldersContaining + [Fact] + public void FindFoldersContaining_ById() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + var inventoryMap = new InventoryMap(sharedFolder); + + var actual = inventoryMap.FindFoldersContaining(false, sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId, null, null); + + var expected = new[] { + sampleTree.Clothing_Hats_Folder + }; + + Assert.Equal(expected.OrderBy(n => n.Id), actual.OrderBy(n => n.Id)); + } + + [Fact] + public void FindFoldersContaining_ByAttachmentType() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (Attached to chin) + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + var inventoryMap = new InventoryMap(sharedFolder); + + var actual = inventoryMap.FindFoldersContaining(false, null, RlvAttachmentPoint.Chin, null); + + var expected = new[] { + sampleTree.Clothing_Folder, + sampleTree.Clothing_Hats_Folder + }; + + Assert.Equal(expected.OrderBy(n => n.Id), actual.OrderBy(n => n.Id)); + } + + [Fact] + public void FindFoldersContaining_ByAttachmentType_SingleResult() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (Attached to chin) + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to chin) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + var inventoryMap = new InventoryMap(sharedFolder); + + // What this returns doesn't seem to be really defined. 'return single result' is deprecated since there can be multiple results nowadays + var actual = inventoryMap.FindFoldersContaining(true, null, RlvAttachmentPoint.Chin, null); + + var expected = new[] { + sampleTree.Clothing_Hats_Folder, + }; + + Assert.Equal(expected.OrderBy(n => n.Id), actual.OrderBy(n => n.Id)); + } + + [Fact] + public void FindFoldersContaining_ByWearType() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (Worn as pants) + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Worn as Hair) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Hair; + + var inventoryMap = new InventoryMap(sharedFolder); + + var actual = inventoryMap.FindFoldersContaining(false, null, null, RlvWearableType.Pants); + + var expected = new[] { + sampleTree.Clothing_Folder + }; + + Assert.Equal(expected.OrderBy(n => n.Id), actual.OrderBy(n => n.Id)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/LibreMetaverse.RLV.Tests.csproj b/LibreMetaverse.RLV.Tests/LibreMetaverse.RLV.Tests.csproj new file mode 100644 index 00000000..3c8675db --- /dev/null +++ b/LibreMetaverse.RLV.Tests/LibreMetaverse.RLV.Tests.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + diff --git a/LibreMetaverse.RLV.Tests/PermissionsTests.cs b/LibreMetaverse.RLV.Tests/PermissionsTests.cs new file mode 100644 index 00000000..6682c693 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/PermissionsTests.cs @@ -0,0 +1,168 @@ +namespace LibreMetaverse.RLV.Tests +{ + public class PermissionsTests : RestrictionsBase + { + + [Fact] + public void CamZoomMin_Default() + { + var cameraRestrictions = _rlv.Permissions.GetCameraRestrictions(); + + Assert.Null(cameraRestrictions.ZoomMin); + } + + [Fact] + public void CanShowHoverTextWorld_Default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + } + + [Fact] + public void CanShowHoverTextHud_Default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + } + + [Fact] + public void CanShowHoverText_Default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + } + + + [Fact] + public void CanShowHoverTextAll_Default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + } + + + [Fact] + public void CanShowNameTags_Default() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.True(_rlv.Permissions.CanShowNameTags(null)); + Assert.True(_rlv.Permissions.CanShowNameTags(userId1)); + } + + [Fact] + public void CanShare_Default() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.True(_rlv.Permissions.CanShare(null)); + Assert.True(_rlv.Permissions.CanShare(userId1)); + } + + [Fact] + public void CanShowNames_Default() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.True(_rlv.Permissions.CanShowNames(null)); + Assert.True(_rlv.Permissions.CanShowNames(userId1)); + } + + [Fact] + public void CanEdit_Default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, null)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, null)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, null)); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId1)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId1)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId1)); + } + + [Fact] + public void CanRecvChat_Default() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + var userId2 = new Guid("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"); + + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId2)); + } + + [Fact] + public void CanAutoAcceptTpRequest_Default() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + Assert.False(_rlv.Permissions.IsAutoAcceptTpRequest(userId1)); + Assert.False(_rlv.Permissions.IsAutoAcceptTpRequest(userId2)); + Assert.False(_rlv.Permissions.IsAutoAcceptTpRequest()); + } + + [Fact] + public void CanTpRequest_Default() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.True(_rlv.Permissions.CanTpRequest(null)); + Assert.True(_rlv.Permissions.CanTpRequest(userId1)); + } + + [Fact] + public void CanAutoAcceptTp_Default() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + Assert.False(_rlv.Permissions.IsAutoAcceptTp(userId1)); + Assert.False(_rlv.Permissions.IsAutoAcceptTp(userId2)); + Assert.False(_rlv.Permissions.IsAutoAcceptTp()); + } + + [Fact] + public void CanChat_Default() + { + Assert.True(_rlv.Permissions.CanChat(0, "Hello")); + Assert.True(_rlv.Permissions.CanChat(0, "/me says Hello")); + Assert.True(_rlv.Permissions.CanChat(5, "Hello")); + } + + [Fact] + public void CanSendIM_Default() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanSendIM("Hello", userId1)); + Assert.True(_rlv.Permissions.CanSendIM("Hello", userId1, "Group Name")); + } + + [Fact] + public void CanStartIM_Default() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanStartIM(userId1)); + } + + [Fact] + public void CanReceiveIM_Default() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + Assert.True(_rlv.Permissions.CanReceiveIM("Hello", userId1)); + Assert.True(_rlv.Permissions.CanReceiveIM("Hello", userId1, "Group Name")); + } + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/FindFolderQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/FindFolderQueryTests.cs new file mode 100644 index 00000000..df1dce42 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/FindFolderQueryTests.cs @@ -0,0 +1,116 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class FindFolderQueryTests : RestrictionsBase + { + #region @findfolder:part1[&&...&&partN]= + [Fact] + public async Task FindFolder_MultipleTerms() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (attached to 'groin') + // | |= Happy Shirt (attached to 'chest') + // | |= Retro Pants (worn on 'pants') + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached to 'chin') + // | \= Party Hat (attached to 'groin') + // \-Accessories + // |= Watch (worn on 'tattoo') + // \= Glasses (attached to 'chin') + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Clothing/Hats/Sub Hats"), + }; + + Assert.True(await _rlv.ProcessMessage("@findfolder:at&&ub=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + [Fact] + public async Task FindFolder_SearchOrder() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Clothing/Hats"), + }; + + Assert.True(await _rlv.ProcessMessage("@findfolder:at=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task FindFolder_IgnorePrivate() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var clothingFolder = sampleTree.Root.Children.Where(n => n.Name == "Clothing").First(); + var hatsFolder = clothingFolder.Children.Where(n => n.Name == "Hats").First(); + hatsFolder.Name = ".Hats"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, ""), + }; + + Assert.True(await _rlv.ProcessMessage("@findfolder:at=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task FindFolder_IgnoreTildePrefix() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var clothingFolder = sampleTree.Root.Children.Where(n => n.Name == "Clothing").First(); + var hatsFolder = clothingFolder.Children.Where(n => n.Name == "Hats").First(); + hatsFolder.Name = "~Hats"; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, ""), + }; + + Assert.True(await _rlv.ProcessMessage("@findfolder:at=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/FindFoldersQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/FindFoldersQueryTests.cs new file mode 100644 index 00000000..2b72bdcd --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/FindFoldersQueryTests.cs @@ -0,0 +1,88 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class FindFoldersQueryTests : RestrictionsBase + { + #region @findfolders:part1[&&...&&partN][;output_separator]= + [Fact] + public async Task FindFolders() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (attached to 'groin') + // | |= Happy Shirt (attached to 'chest') + // | |= Retro Pants (worn on 'pants') + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached to 'chin') + // | \= Party Hat (attached to 'groin') + // \-Accessories + // |= Watch (worn on 'tattoo') + // \= Glasses (attached to 'chin') + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Clothing/Hats,Clothing/Hats/Sub Hats"), + }; + + Assert.True(await _rlv.ProcessMessage("@findfolders:at=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task FindFolders_Separator() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (attached to 'groin') + // | |= Happy Shirt (attached to 'chest') + // | |= Retro Pants (worn on 'pants') + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached to 'chin') + // | \= Party Hat (attached to 'groin') + // \-Accessories + // |= Watch (worn on 'tattoo') + // \= Glasses (attached to 'chin') + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Clothing/Hats AND Clothing/Hats/Sub Hats"), + }; + + Assert.True(await _rlv.ProcessMessage("@findfolders:at; AND =1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetAttachQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetAttachQueryTests.cs new file mode 100644 index 00000000..9a7543e3 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetAttachQueryTests.cs @@ -0,0 +1,163 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetAttachQueryTests : RestrictionsBase + { + #region @getattach[:attachpt]= + [Fact] + public async Task GetAttach_WearingNothing() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List(); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "00000000000000000000000000000000000000000000000000000000"), + }; + + Assert.True(await _rlv.ProcessMessage("@getattach=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetAttach_ExternalItems() + { + var actual = _actionCallbacks.RecordReplies(); + + var currentOutfit = new List(); + var externalWearable = new RlvInventoryItem( + new Guid("12312312-0001-4aaa-8aaa-aaaaaaaaaaaa"), + "External Tattoo", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + null, + null, + RlvWearableType.Tattoo); + var externalAttachable = new RlvInventoryItem( + new Guid("12312312-0002-4aaa-8aaa-aaaaaaaaaaaa"), + "External Jaw Thing", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + RlvAttachmentPoint.Jaw, + new Guid("12312312-0002-4aaa-8aaa-ffffffffffff"), + null); + + currentOutfit.Add(externalWearable); + currentOutfit.Add(externalAttachable); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "00000000000000000000000000000000000000000000000100000000"), + }; + + Assert.True(await _rlv.ProcessMessage("@getattach=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetAttach_WearingSomeItems() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List() + { + new(new Guid($"c0000000-cccc-4ccc-8ccc-cccccccccccc"), "My Socks", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), RlvAttachmentPoint.LeftFoot, new Guid($"c0000000-cccc-4ccc-8ccc-ffffffffffff"), null ), + new(new Guid($"c0000001-cccc-4ccc-8ccc-cccccccccccc"), "My Hair", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), RlvAttachmentPoint.Skull, new Guid($"c0000001-cccc-4ccc-8ccc-ffffffffffff"), null) + }; + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "00100001000000000000000000000000000000000000000000000000"), + }; + + Assert.True(await _rlv.ProcessMessage("@getattach=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetAttach_WearingEverything() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List(); + foreach (var item in Enum.GetValues()) + { + currentOutfit.Add(new RlvInventoryItem( + new Guid($"c{(int)item:D7}-cccc-4ccc-8ccc-cccccccccccc"), + $"My {item}", + new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), + item, + new Guid($"c{(int)item:D7}-cccc-4ccc-8ccc-ffffffffffff"), + null + )); + } + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "11111111111111111111111111111111111111111111111111111111"), + }; + + Assert.True(await _rlv.ProcessMessage("@getattach=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetAttach_Specific_Exists() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List() + { + new(new Guid($"c0000000-cccc-4ccc-8ccc-cccccccccccc"), "My Socks", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), RlvAttachmentPoint.LeftFoot, new Guid($"c0000000-cccc-4ccc-8ccc-ffffffffffff"), null ), + }; + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "1"), + }; + + Assert.True(await _rlv.ProcessMessage("@getattach:left foot=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetAttach_Specific_NotExists() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List() + { + new(new Guid($"c0000001-cccc-4ccc-8ccc-cccccccccccc"), "My Hair", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), RlvAttachmentPoint.Skull, new Guid($"c0000001-cccc-4ccc-8ccc-ffffffffffff"), null) + }; + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "0"), + }; + + Assert.True(await _rlv.ProcessMessage("@getattach:left foot=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetBlacklistQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetBlacklistQueryTests.cs new file mode 100644 index 00000000..e39c8b8d --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetBlacklistQueryTests.cs @@ -0,0 +1,50 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetBlacklistQueryTests : RestrictionsBase + { + #region @getblacklist[:filter]= + [Theory] + [InlineData("@getblacklist", 1234, "sendim,recvim", "recvim,sendim")] + [InlineData("@getblacklist:im", 1234, "sendim,recvim", "recvim,sendim")] + [InlineData("@getblacklist:send", 1234, "sendim,recvim", "sendim")] + [InlineData("@getblacklist:tpto", 1234, "sendim,recvim", "")] + [InlineData("@getblacklist", 1234, "", "")] + public async Task GetBlacklist(string command, int channel, string seed, string expectedResponse) + { + var actual = _actionCallbacks.RecordReplies(); + SeedBlacklist(seed); + + await _rlv.ProcessMessage($"{command}={channel}", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (channel, expectedResponse), + }; + + Assert.Equal(expected, actual); + } + #endregion + + #region @getblacklist Manual + [Theory] + [InlineData("@getblacklist", "sendim,recvim", "recvim,sendim")] + [InlineData("@getblacklist", "", "")] + public async Task ManualBlacklist(string command, string seed, string expected) + { + _rlv.EnableInstantMessageProcessing = true; + + SeedBlacklist(seed); + + await _rlv.ProcessInstantMessage(command, _sender.Id); + + _actionCallbacks.Verify(c => + c.SendInstantMessageAsync(_sender.Id, expected, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetDebugQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetDebugQueryTests.cs new file mode 100644 index 00000000..dacc7790 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetDebugQueryTests.cs @@ -0,0 +1,30 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetDebugQueryTests : RestrictionsBase + { + #region @getdebug_= + [Theory] + [InlineData("RenderResolutionDivisor", "RenderResolutionDivisor Success")] + [InlineData("Unknown Setting", "Unknown Setting Success")] + public async Task GetDebug_Default(string settingName, string settingValue) + { + var actual = _actionCallbacks.RecordReplies(); + + _queryCallbacks.Setup(e => + e.TryGetDebugSettingValueAsync(settingName.ToLower(), default) + ).ReturnsAsync((true, settingValue)); + + var expected = new List<(int Channel, string Text)> + { + (1234, settingValue), + }; + + Assert.True(await _rlv.ProcessMessage($"@getdebug_{settingName}=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetEnvQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetEnvQueryTests.cs new file mode 100644 index 00000000..6ec89614 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetEnvQueryTests.cs @@ -0,0 +1,32 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetEnvQueryTests : RestrictionsBase + { + #region @getenv_= + + [Theory] + [InlineData("Daytime", "Daytime Success")] + [InlineData("Unknown Setting", "Unknown Setting Success")] + public async Task GetEnv_Default(string settingName, string settingValue) + { + var actual = _actionCallbacks.RecordReplies(); + + _queryCallbacks.Setup(e => + e.TryGetEnvironmentSettingValueAsync(settingName.ToLower(), default) + ).ReturnsAsync((true, settingValue)); + + var expected = new List<(int Channel, string Text)> + { + (1234, settingValue), + }; + + Assert.True(await _rlv.ProcessMessage($"@getenv_{settingName}=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetGroupQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetGroupQueryTests.cs new file mode 100644 index 00000000..077a3dd2 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetGroupQueryTests.cs @@ -0,0 +1,49 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetGroupQueryTests : RestrictionsBase + { + #region @getgroup= + + [Fact] + public async Task GetGroup_Default() + { + var actual = _actionCallbacks.RecordReplies(); + var actualGroupName = "Group Name"; + + _queryCallbacks.Setup(e => + e.TryGetActiveGroupNameAsync(default) + ).ReturnsAsync((true, actualGroupName)); + + var expected = new List<(int Channel, string Text)> + { + (1234, actualGroupName), + }; + + Assert.True(await _rlv.ProcessMessage("@getgroup=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetGroup_NoGroup() + { + var actual = _actionCallbacks.RecordReplies(); + var actualGroupName = ""; + + _queryCallbacks.Setup(e => + e.TryGetActiveGroupNameAsync(default) + ).ReturnsAsync((false, actualGroupName)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "none"), + }; + + Assert.True(await _rlv.ProcessMessage("@getgroup=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetInvQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetInvQueryTests.cs new file mode 100644 index 00000000..06a5c6ee --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetInvQueryTests.cs @@ -0,0 +1,217 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetInvQueryTests : RestrictionsBase + { + #region @getinv[:folder1/.../folderN]= + [Fact] + public async Task GetInv() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Clothing,Accessories"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Outfits() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var outfitsFolderId = new Guid("12312399-9999-4999-8999-999999999999"); + var outfitSubfolder1Id = new Guid("12312399-0001-4999-8999-999999999999"); + var outfitSubfolder2Id = new Guid("12312399-0002-4999-8999-999999999999"); + + var outfitsFolder = sampleTree.Root.AddChild(outfitsFolderId, ".outfits"); + var outfitSubfolder1 = outfitsFolder.AddChild(outfitSubfolder1Id, "First outfit"); + var outfitSubfolder2 = outfitsFolder.AddChild(outfitSubfolder2Id, "Second outfit"); + + var item1 = outfitsFolder.AddItem(new Guid("12312399-0001-0001-8999-999999999999"), "First Item", null, null, null); + var item2 = outfitsFolder.AddItem(new Guid("12312399-0001-0002-8999-999999999999"), "Second Item", null, null, null); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"{outfitSubfolder1.Name},{outfitSubfolder2.Name}"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:.outfits=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Outfits_IgnoreLeadingSlash() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var outfitsFolder = sampleTree.Root.AddChild(new Guid("12312399-9999-4999-8999-999999999999"), "~MyOutfits"); + var outfitSubfolder1 = outfitsFolder.AddChild(new Guid("12312399-0001-4999-8999-999999999999"), "First outfit"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"{outfitSubfolder1.Name}"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:/~MyOutfits=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Outfits_IgnoreTrailingSlash() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var outfitsFolder = sampleTree.Root.AddChild(new Guid("12312399-9999-4999-8999-999999999999"), "~MyOutfits"); + var outfitSubfolder1 = outfitsFolder.AddChild(new Guid("12312399-0001-4999-8999-999999999999"), "First outfit"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"{outfitSubfolder1.Name}"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:~MyOutfits/=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Outfits_IgnoreLeadingAndTrailingSlash() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var outfitsFolder = sampleTree.Root.AddChild(new Guid("12312399-9999-4999-8999-999999999999"), "~MyOutfits"); + var outfitSubfolder1 = outfitsFolder.AddChild(new Guid("12312399-0001-4999-8999-999999999999"), "First outfit"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"{outfitSubfolder1.Name}"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:/~MyOutfits/=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Outfits_Inventory() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + var outfitsFolderId = new Guid("12312399-9999-4999-8999-999999999999"); + var outfitSubfolder1Id = new Guid("12312399-0001-4999-8999-999999999999"); + + var outfitsFolder = sampleTree.Root.AddChild(outfitsFolderId, ".outfits"); + var outfitSubfolder1 = outfitsFolder.AddChild(outfitSubfolder1Id, "First outfit"); + var item1 = outfitSubfolder1.AddItem(new Guid("12312399-0001-0001-8999-999999999999"), "First Item", null, null, null); + var item2 = outfitSubfolder1.AddItem(new Guid("12312399-0001-0002-8999-999999999999"), "Second Item", null, null, null); + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, $""), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:.outfits/First outfit=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Subfolder() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Sub Hats"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:Clothing/Hats=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Empty() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, ""), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:Clothing/Hats/Sub Hats=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInv_Invalid() + { + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, ""), + }; + + Assert.True(await _rlv.ProcessMessage("@getinv:Invalid Folder=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetInvWornQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetInvWornQueryTests.cs new file mode 100644 index 00000000..f1564edf --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetInvWornQueryTests.cs @@ -0,0 +1,260 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetInvWornQueryTests : RestrictionsBase + { + #region @getinvworn[:folder1/.../folderN]= + [Fact] + public async Task GetInvWorn() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (attached to 'groin') + // | |= Happy Shirt (attached to 'chest') + // | |= Retro Pants (worn on 'pants') + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached to 'chin') + // | \= Party Hat (attached to 'groin') + // \-Accessories + // |= Watch (worn on 'tattoo') + // \= Glasses (attached to 'chin') + // + // 0: No item is present in that folder + // 1: Some items are present in that folder, but none of them is worn + // 2: Some items are present in that folder, and some of them are worn + // 3: Some items are present in that folder, and all of them are worn + // + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0004-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0005-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.WornOn = RlvWearableType.Pants; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "|03,Clothing|33,Accessories|33"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinvworn=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInvWorn_PartialRoot() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (attached to 'pelvis') + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (worn on 'tattoo') + // \= Glasses + // + // 0: No item is present in that folder + // 1: Some items are present in that folder, but none of them is worn + // 2: Some items are present in that folder, and some of them are worn + // 3: Some items are present in that folder, and all of them are worn + // + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "|02,Clothing|22,Accessories|22"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinvworn=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInvWorn_Naked() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + // 0: No item is present in that folder + // 1: Some items are present in that folder, but none of them is worn + // 2: Some items are present in that folder, and some of them are worn + // 3: Some items are present in that folder, and all of them are worn + // + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "|01,Clothing|11,Accessories|11"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinvworn=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInvWorn_EmptyFolder() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + // 0: No item is present in that folder + // 1: Some items are present in that folder, but none of them is worn + // 2: Some items are present in that folder, and some of them are worn + // 3: Some items are present in that folder, and all of them are worn + // + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "|00"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinvworn:Clothing/Hats/Sub Hats=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetInvWorn_PartialWorn() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached to 'Chin') + // | \= Party Hat (attached to 'Spine') + // \-Accessories + // |= Watch + // \= Glasses + // + // 0: No item is present in that folder + // 1: Some items are present in that folder, but none of them is worn + // 2: Some items are present in that folder, and some of them are worn + // 3: Some items are present in that folder, and all of them are worn + // + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "|33,Sub Hats|00"), + }; + + Assert.True(await _rlv.ProcessMessage("@getinvworn:Clothing/Hats=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetOutfitQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetOutfitQueryTests.cs new file mode 100644 index 00000000..68054f73 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetOutfitQueryTests.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetOutfitQueryTests : RestrictionsBase + { + #region @getoutfit[:part]= + [Fact] + public async Task GetOutfit_WearingNothing() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List(); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "0000000000000000"), + }; + + Assert.True(await _rlv.ProcessMessage("@getoutfit=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetOutfit_ExternalItems() + { + var actual = _actionCallbacks.RecordReplies(); + + var currentOutfit = new List(); + + var externalWearable = new RlvInventoryItem( + new Guid("12312312-0001-4aaa-8aaa-aaaaaaaaaaaa"), + "External Tattoo", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + null, + null, + RlvWearableType.Tattoo); + var externalAttachable = new RlvInventoryItem( + new Guid("12312312-0002-4aaa-8aaa-aaaaaaaaaaaa"), + "External Jaw Thing", + new Guid("12312312-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + RlvAttachmentPoint.Jaw, + new Guid("12312312-0002-4aaa-8aaa-ffffffffffff"), + null); + + currentOutfit.Add(externalWearable); + currentOutfit.Add(externalAttachable); + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "0000000000000010"), + }; + + Assert.True(await _rlv.ProcessMessage("@getoutfit=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetOutfit_WearingSomeItems() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List() + { + new(new Guid($"c0000000-cccc-4ccc-8ccc-cccccccccccc"), "My Socks", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), null, null, RlvWearableType.Socks), + new(new Guid($"c0000001-cccc-4ccc-8ccc-cccccccccccc"), "My Hair", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), null, null, RlvWearableType.Hair) + }; + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "0000001000010000"), + }; + + Assert.True(await _rlv.ProcessMessage("@getoutfit=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetOutfit_WearingEverything() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List(); + foreach (var item in Enum.GetValues()) + { + if (item == RlvWearableType.Invalid) + { + continue; + } + + currentOutfit.Add(new RlvInventoryItem( + new Guid($"c{(int)item:D7}-cccc-4ccc-8ccc-cccccccccccc"), + $"My {item}", + new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), + null, + null, + item)); + } + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "1111111111111111"), + }; + + Assert.True(await _rlv.ProcessMessage("@getoutfit=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetOutfit_Specific_Exists() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List() + { + new(new Guid($"c0000000-cccc-4ccc-8ccc-cccccccccccc"), "My Socks", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), null, null, RlvWearableType.Socks) + }; + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "1"), + }; + + Assert.True(await _rlv.ProcessMessage("@getoutfit:socks=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetOutfit_Specific_NotExists() + { + var actual = _actionCallbacks.RecordReplies(); + var currentOutfit = new List() + { + new(new Guid($"c0000001-cccc-4ccc-8ccc-cccccccccccc"), "My Hair", new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"), null, null, RlvWearableType.Hair) + }; + + _queryCallbacks.Setup(e => + e.TryGetCurrentOutfitAsync(default) + ).ReturnsAsync((true, currentOutfit)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "0"), + }; + + Assert.True(await _rlv.ProcessMessage("@getoutfit:socks=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetPathNewQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetPathNewQueryTests.cs new file mode 100644 index 00000000..a295140d --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetPathNewQueryTests.cs @@ -0,0 +1,239 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetPathNewQueryTests : RestrictionsBase + { + #region @getpath @getpathnew[: or or ]= + + [Fact] + public async Task GetPathNew_BySender() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat (Worn on spine) + // \-Accessories + // |= Watch + // \= Glasses + // + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Clothing/Hats"), + }; + + Assert.True(await _rlv.ProcessMessage("@getpathnew=1234", sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId.Value, sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetPathNew_ByUUID() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat (Worn on spine) + // \-Accessories + // |= Watch + // \= Glasses + // + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Clothing/Hats"), + }; + + Assert.True(await _rlv.ProcessMessage($"@getpathnew:{sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId.Value}=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetPathNew_ByUUID_Unknown() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = null; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = null; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = null; + sampleTree.Root_Clothing_HappyShirt.AttachedTo = null; + sampleTree.Root_Accessories_Glasses.AttachedTo = null; + sampleTree.Root_Clothing_RetroPants.WornOn = null; + sampleTree.Root_Accessories_Watch.WornOn = null; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, ""), + }; + + Assert.True(await _rlv.ProcessMessage($"@getpathnew:BADBADBA-DBAD-4BAD-8BAD-BADBADBADBAD=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetPathNew_ByAttach() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (attached to 'Pelvis') + // | |= Happy Shirt + // | |= Retro Pants + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (attached to 'Groin') + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses (attached to 'Groin') + // + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Groin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Glasses.AttachedTo = RlvAttachmentPoint.Groin; + sampleTree.Root_Accessories_Glasses.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Accessories,Clothing/Hats"), + }; + + Assert.True(await _rlv.ProcessMessage($"@getpathnew:groin=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetPathNew_ByWorn() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants (worn on 'Tattoo') + // | \-Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (worn on 'Pants') + // | \= Party Hat + // \-Accessories + // |= Watch (Worn on 'pants') + // \= Glasses + // + + + var actual = _actionCallbacks.RecordReplies(); + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Pants; + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + var expected = new List<(int Channel, string Text)> + { + (1234, "Accessories,Clothing/Hats"), + }; + + Assert.True(await _rlv.ProcessMessage($"@getpathnew:pants=1234", _sender.Id, _sender.Name)); + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetSitIdQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetSitIdQueryTests.cs new file mode 100644 index 00000000..bd6dc1f1 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetSitIdQueryTests.cs @@ -0,0 +1,51 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetSitIdQueryTests : RestrictionsBase + { + #region @getsitid= + + private void SetCurrentSitId(Guid objectId) + { + _queryCallbacks.Setup(e => + e.TryGetSitIdAsync(default) + ).ReturnsAsync((objectId != Guid.Empty, objectId)); + } + + [Fact] + public async Task GetSitID() + { + var actual = _actionCallbacks.RecordReplies(); + SetCurrentSitId(Guid.Empty); + + await _rlv.ProcessMessage("@getsitid=1234", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "NULL_KEY"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetSitID_Default() + { + var actual = _actionCallbacks.RecordReplies(); + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + SetCurrentSitId(objectId1); + + await _rlv.ProcessMessage("@getsitid=1234", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, objectId1.ToString()), + }; + + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetStatusAllQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetStatusAllQueryTests.cs new file mode 100644 index 00000000..edaa5b6a --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetStatusAllQueryTests.cs @@ -0,0 +1,30 @@ +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetStatusAllQueryTests : RestrictionsBase + { + #region @getstatusall[:[;]]= + + [Fact] + public async Task GetStatusAll() + { + var actual = _actionCallbacks.RecordReplies(); + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplocal=n", sender2.Id, sender2.Name); + + await _rlv.ProcessMessage("@getstatusall=1234", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/fly/tplure/tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1/tplocal"), + }; + + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/GetStatusQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/GetStatusQueryTests.cs new file mode 100644 index 00000000..8fa014a8 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/GetStatusQueryTests.cs @@ -0,0 +1,73 @@ +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class GetStatusQueryTests : RestrictionsBase + { + + #region @getstatus[:[;]]= + + [Fact] + public async Task GetStatus() + { + var actual = _actionCallbacks.RecordReplies(); + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplocal=n", sender2.Id, sender2.Name); + + await _rlv.ProcessMessage("@getstatus=1234", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/fly/tplure/tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetStatus_filtered() + { + var actual = _actionCallbacks.RecordReplies(); + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplocal=n", sender2.Id, sender2.Name); + + await _rlv.ProcessMessage("@getstatus:tp=1234", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/tplure/tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task GetStatus_customSeparator() + { + var actual = _actionCallbacks.RecordReplies(); + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@tplocal=n", sender2.Id, sender2.Name); + + await _rlv.ProcessMessage("@getstatus:; ! =1234", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, $" ! fly ! tplure ! tplure:3d6181b0-6a4b-97ef-18d8-722652995cf1"), + }; + + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/NotifyQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/NotifyQueryTests.cs new file mode 100644 index 00000000..2bde2180 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/NotifyQueryTests.cs @@ -0,0 +1,424 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class NotifyQueryTests : RestrictionsBase + { + #region @notify:[;word]= + [Fact] + public async Task Notify() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@alwaysrun=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendim:group_name=add", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/notify:1234=n"), + (1234, "/sendim=n"), + (1234, "/alwaysrun=n"), + (1234, "/sendim:group_name=n"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyFiltered() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@notify:1234;run=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@alwaysrun=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendim:group_name=add", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/alwaysrun=n"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyMultiCommand() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendim=n,sendim:group_name=add,alwaysrun=n", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/notify:1234=n"), + (1234, "/sendim=n"), + (1234, "/sendim:group_name=n"), + (1234, "/alwaysrun=n"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyMultiChannels() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@notify:12345=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/notify:1234=n"), + (1234, "/notify:12345=n"), + (12345, "/notify:12345=n"), + (1234, "/sendim=n"), + (12345, "/sendim=n"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyMultiChannelsFiltered() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@notify:12345;im=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/notify:1234=n"), + (1234, "/notify:12345;im=n"), + (1234, "/sendim=n"), + (12345, "/sendim=n"), + }; + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("@camdistmax:123=n", "/camdistmax:123=n")] + [InlineData("@setcam_avdistmax:123=n", "/setcam_avdistmax:123=n")] + [InlineData("@camdistmin:123=n", "/camdistmin:123=n")] + [InlineData("@setcam_avdistmin:123=n", "/setcam_avdistmin:123=n")] + [InlineData("@camunlock=n", "/camunlock=n")] + [InlineData("@setcam_unlock=n", "/setcam_unlock=n")] + [InlineData("@camtextures:1cdbc6a2-ae6b-3130-9348-3d3b1ca84c53=n", "/camtextures:1cdbc6a2-ae6b-3130-9348-3d3b1ca84c53=n")] + [InlineData("@touchfar:5=n", "/touchfar:5=n")] + [InlineData("@fartouch:5=n", "/fartouch:5=n")] + public async Task NotifySynonyms(string command, string expectedReply) + { + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage(command, _sender.Id, _sender.Name); + + _actionCallbacks.Verify(c => c.SendReplyAsync(1234, "/notify:1234=n", It.IsAny()), Times.Once); + _actionCallbacks.Verify(c => c.SendReplyAsync(1234, expectedReply, It.IsAny()), Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + + [Fact] + public async Task NotifyClear_Filtered() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@clear=fly", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/notify:1234=n"), + (1234, "/fly=n"), + // Begin processing clear()... + (1234, "/fly=y"), + (1234, "/clear:fly"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyClear() + { + var actual = _actionCallbacks.RecordReplies(); + + var sender2Id = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@notify:1234=add", sender2Id, "Main"); + await _rlv.ProcessMessage("@fly=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@clear", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/notify:1234=n"), + (1234, "/fly=n"), + // Begin processing clear()... + (1234, "/fly=y"), + (1234, "/clear"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyInventoryOffer() + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportInventoryOfferAccepted("#RLV/~MyCuffs"); + await _rlv.ReportInventoryOfferAccepted("Objects/New Folder (3)"); + await _rlv.ReportInventoryOfferDeclined("#RLV/Foo/Bar"); + + var expected = new List<(int Channel, string Text)> + { + (1234, "/notify:1234=n"), + (1234, "/accepted_in_rlv inv_offer ~MyCuffs"), + (1234, "/accepted_in_inv inv_offer Objects/New Folder (3)"), + (1234, "/declined inv_offer Foo/Bar"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifySitStandLegal() + { + var actual = _actionCallbacks.RecordReplies(); + + var sitTarget = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportSit(sitTarget); + await _rlv.ReportUnsit(sitTarget); + await _rlv.ReportSit(null); + await _rlv.ReportUnsit(null); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/sat object legally {sitTarget}"), + (1234, $"/unsat object legally {sitTarget}"), + (1234, $"/sat ground legally"), + (1234, $"/unsat ground legally"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifySitStandWithRestrictions() + { + var actual = _actionCallbacks.RecordReplies(); + + var sitTarget = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@sit=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@unsit=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + + await _rlv.ReportSit(sitTarget); + await _rlv.ReportUnsit(sitTarget); + await _rlv.ReportSit(null); + await _rlv.ReportUnsit(null); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/sat object illegally {sitTarget}"), + (1234, $"/unsat object illegally {sitTarget}"), + (1234, $"/sat ground illegally"), + (1234, $"/unsat ground illegally"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyWear() + { + var actual = _actionCallbacks.RecordReplies(); + + var itemId1 = new Guid("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"); + var itemId2 = new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportItemWorn(itemId1, false, RlvWearableType.Skin); + await _rlv.ReportItemWorn(itemId2, true, RlvWearableType.Tattoo); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/worn legally skin"), + (1234, $"/worn legally tattoo"), + }; + + Assert.Equal(expected, actual); + } + + + [Fact] + public async Task NotifyWear_Illegal() + { + var actual = _actionCallbacks.RecordReplies(); + + var itemId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@addoutfit:skin=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportItemWorn(itemId1, false, RlvWearableType.Skin); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/worn illegally skin"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyUnWear() + { + var actual = _actionCallbacks.RecordReplies(); + var wornItem = new RlvObject("TargetItem", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var folderId1 = new Guid("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"); + var folderId2 = new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportItemUnworn(wornItem.Id, folderId1, false, RlvWearableType.Skin); + await _rlv.ReportItemUnworn(wornItem.Id, folderId2, true, RlvWearableType.Tattoo); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/unworn legally skin"), + (1234, $"/unworn legally tattoo"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyUnWear_illegal() + { + var actual = _actionCallbacks.RecordReplies(); + var wornItem = new RlvObject("TargetItem", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var itemId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@remoutfit:skin=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + + await _rlv.ReportItemUnworn(wornItem.Id, itemId1, false, RlvWearableType.Skin); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/unworn illegally skin"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyAttached() + { + var actual = _actionCallbacks.RecordReplies(); + + var itemId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + var itemId2 = new Guid("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportItemAttached(itemId1, false, RlvAttachmentPoint.Chest); + await _rlv.ReportItemAttached(itemId2, true, RlvAttachmentPoint.Skull); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/attached legally chest"), + (1234, $"/attached legally skull"), + }; + + Assert.Equal(expected, actual); + } + + + [Fact] + public async Task NotifyAttached_Illegal() + { + var actual = _actionCallbacks.RecordReplies(); + + var itemId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@addattach:chest=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportItemAttached(itemId1, false, RlvAttachmentPoint.Chest); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/attached illegally chest"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyDetached() + { + var actual = _actionCallbacks.RecordReplies(); + var wornItem = new RlvObject("TargetItem", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var wornItemPrimId = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-ffffffffffff"); + + var folderId1 = new Guid("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"); + var folderId2 = new Guid("cccccccc-cccc-4ccc-8ccc-cccccccccccc"); + + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportItemDetached(wornItem.Id, wornItemPrimId, folderId1, false, RlvAttachmentPoint.Chest); + await _rlv.ReportItemDetached(wornItem.Id, wornItemPrimId, folderId2, true, RlvAttachmentPoint.Skull); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/detached legally chest"), + (1234, $"/detached legally skull"), + }; + + Assert.Equal(expected, actual); + } + + [Fact] + public async Task NotifyDetached_Illegal() + { + var actual = _actionCallbacks.RecordReplies(); + var wornItem = new RlvObject("TargetItem", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var wornItemPrimId = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-ffffffffffff"); + + var itemId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@remattach:chest=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@notify:1234=add", _sender.Id, _sender.Name); + await _rlv.ReportItemDetached(wornItem.Id, wornItemPrimId, itemId1, false, RlvAttachmentPoint.Chest); + + var expected = new List<(int Channel, string Text)> + { + (1234, $"/notify:1234=n"), + (1234, $"/detached illegally chest"), + }; + + Assert.Equal(expected, actual); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/VersionNumBlQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/VersionNumBlQueryTests.cs new file mode 100644 index 00000000..2c017e35 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/VersionNumBlQueryTests.cs @@ -0,0 +1,27 @@ +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class VersionNumBlQueryTests : RestrictionsBase + { + + #region @versionnumbl= + + [Theory] + [InlineData("", RlvService.RLVVersionNum)] + [InlineData("sendim,recvim", RlvService.RLVVersionNum + ",recvim,sendim")] + public async Task VersionNumBL(string seed, string expectedResponse) + { + var actual = _actionCallbacks.RecordReplies(); + SeedBlacklist(seed); + + await _rlv.ProcessMessage("@versionnumbl=1234", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, expectedResponse), + }; + + Assert.Equal(expected, actual); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Queries/VersionQueryTests.cs b/LibreMetaverse.RLV.Tests/Queries/VersionQueryTests.cs new file mode 100644 index 00000000..cba26d91 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Queries/VersionQueryTests.cs @@ -0,0 +1,43 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Queries +{ + public class VersionQueryTests : RestrictionsBase + { + #region @version Manual + [Fact] + public async Task ManualVersion() + { + _rlv.EnableInstantMessageProcessing = true; + + await _rlv.ProcessInstantMessage("@version", _sender.Id); + + _actionCallbacks.Verify(c => + c.SendInstantMessageAsync(_sender.Id, RlvService.RLVVersion, It.IsAny()), + Times.Once); + + _actionCallbacks.VerifyNoOtherCalls(); + } + #endregion + + #region @version= + [Theory] + [InlineData("@version", 1234, RlvService.RLVVersion)] + [InlineData("@versionnew", 1234, RlvService.RLVVersion)] + [InlineData("@versionnum", 1234, RlvService.RLVVersionNum)] + public async Task CheckVersions(string command, int channel, string expectedResponse) + { + var actual = _actionCallbacks.RecordReplies(); + + await _rlv.ProcessMessage($"{command}={channel}", _sender.Id, _sender.Name); + + var expected = new List<(int Channel, string Text)> + { + (1234, expectedResponse), + }; + + Assert.Equal(expected, actual); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/AddAttachRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/AddAttachRestrictionTests.cs new file mode 100644 index 00000000..89c9f243 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/AddAttachRestrictionTests.cs @@ -0,0 +1,60 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class AddAttachRestrictionTests : RestrictionsBase + { + #region @addattach[:]= + [Fact] + public async Task AddAttach() + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@addattach=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Fancy Hat + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Happy Shirt + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + } + + [Fact] + public async Task AddAttach_Specific() + { + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.AttachedTo = RlvAttachmentPoint.Groin; + sampleTree.Root_Clothing_HappyShirt.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@addattach:groin=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Fancy Hat + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Happy Shirt + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/AddOutfitRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/AddOutfitRestrictionTests.cs new file mode 100644 index 00000000..2d21f3bf --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/AddOutfitRestrictionTests.cs @@ -0,0 +1,91 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class AddOutfitRestrictionTests : RestrictionsBase + { + #region @addoutfit[:]= + [Fact] + public async Task AddOutfit() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (Worn as shirt) + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Worn as hair) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Skull; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Hair; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@addoutfit=n", sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name)); + + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + } + + [Fact] + public async Task AddOutfit_part() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt (Worn as shirt) + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Worn as hair) + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_HappyShirt.WornOn = RlvWearableType.Shirt; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.WornOn = RlvWearableType.Hair; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@addoutfit:shirt=n", _sender.Id, _sender.Name)); + + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/AllowIdleRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/AllowIdleRestrictionTests.cs new file mode 100644 index 00000000..4d45d6a4 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/AllowIdleRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class AllowIdleRestrictionTests : RestrictionsBase + { + #region @allowidle= + [Fact] + public async Task CanAllowIdle() + { + await CheckSimpleCommand("allowIdle", m => m.CanAllowIdle()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/AlwaysRunRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/AlwaysRunRestrictionTests.cs new file mode 100644 index 00000000..c22f7d39 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/AlwaysRunRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class AlwaysRunRestrictionTests : RestrictionsBase + { + #region @alwaysrun= + [Fact] + public async Task CanAlwaysRun() + { + await CheckSimpleCommand("alwaysRun", m => m.CanAlwaysRun()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/AttachAllThisRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/AttachAllThisRestrictionTests.cs new file mode 100644 index 00000000..6d7b6b46 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/AttachAllThisRestrictionTests.cs @@ -0,0 +1,465 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class AttachAllThisRestrictionTests : RestrictionsBase + { + #region @attachallthis[:||]= + + [Fact] + public async Task AttachAllThis() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Locked] + // | | + // | |- Sub Hats [Locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat (attached chin) + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis=n", sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(2, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachAllThis_Recursive() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [LOCKED] + // | |= Business Pants (attached chin) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [LOCKED] + // | | + // | |- Sub Hats [LOCKED] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachAllThis_Recursive_Path() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Locked] + // | |= Business Pants (attached chin) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Locked] + // | | + // | |- Sub Hats [Locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis:Clothing=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachAllThis_Recursive_Worn() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Locked] + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants (Worn as pants) + // | \- Hats [Locked] + // | | + // | |- Sub Hats [Locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis:pants=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) - Folder locked due to RetroPants being worn as 'pants' + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) - Folder locked due to RetroPants being worn as 'pants' + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachAllThis_Recursive_Attached() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [Locked] + // | |= Business Pants (Attached to chest) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Locked] + // | | + // | |- Sub Hats [Locked] + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis:chest=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) - Folder locked due to HappyShirt attachment of 'chest' + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) - Folder locked due to HappyShirt attachment of 'chest' + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.True(clothingFolderLocked.CanDetach); + Assert.False(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.True(hatsFolderLocked.CanDetach); + Assert.False(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.True(subhatsFolderLocked.CanDetach); + Assert.False(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Single(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Empty(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachAllThis_Recursive_Attached_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (Attached to chest) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachallthis:chest=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + Assert.True(await _rlv.ProcessMessage("@attachallthis:chest=y", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Empty(lockedFolders); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/AttachThisRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/AttachThisRestrictionTests.cs new file mode 100644 index 00000000..bff58024 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/AttachThisRestrictionTests.cs @@ -0,0 +1,409 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class AttachThisRestrictionTests : RestrictionsBase + { + #region @attachthis[:||]= + [Fact] + public async Task AttachThis() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [Locked] + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat (Attached to spine) + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachthis=n", sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachThis_NotRecursive() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [LOCKED, no-attach] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt <-- No Attach + // | |= Retro Pants <-- No Attach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the #RLV/Clothing folder because the Business Pants are issuing the command, which is in the Clothing folder. + // Business Pants cannot be attached, but hats are still attachable. + Assert.True(await _rlv.ProcessMessage("@attachthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachThis_ByPath() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats [LOCKED - NO-ATTACH] + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- Cannot attach + // | \= Party Hat <-- Cannot attach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@attachthis:Clothing/Hats=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachThis_ByRlvAttachmentPoint() + { + // #RLV + // | + // |- .private + // | + // |- Clothing [LOCKED, no-attach] + // | |= Business Pants (Attached to pelvis) + // | |= Happy Shirt <-- No attach + // | |= Retro Pants <-- No attach + // | \- Hats Clothing [LOCKED, no-attach] + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to pelvis) + // | \= Party Hat <-- No attach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the Hats folder, all hats are no longer attachable + Assert.True(await _rlv.ProcessMessage("@attachthis:pelvis=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - folder was locked because PartyHat (groin) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) - folder was locked because BusinessPants (groin) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(2, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Single(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Empty(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Single(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Empty(clothingFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachThis_ByWornLayer() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats Clothing + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories [LOCKED, no-attach] + // |= Watch (Worn as tattoo) + // \= Glasses + // + + // TryGetRlvInventoryTree + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the Hats folder, all hats are no longer attachable + Assert.True(await _rlv.ProcessMessage("@attachthis:tattoo=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses (LOCKED) - folder was locked from Watch (tattoo) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch (LOCKED) + Assert.False(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Accessories_Folder.Id, out var accessoriesFolderLocked)); + + Assert.Equal(sampleTree.Accessories_Folder.Name, accessoriesFolderLocked.Name); + Assert.Equal(sampleTree.Accessories_Folder.Id, accessoriesFolderLocked.Id); + Assert.True(accessoriesFolderLocked.CanDetach); + Assert.False(accessoriesFolderLocked.CanAttach); + Assert.True(accessoriesFolderLocked.IsLocked); + + Assert.Empty(accessoriesFolderLocked.AttachExceptions); + Assert.Single(accessoriesFolderLocked.AttachRestrictions); + Assert.Empty(accessoriesFolderLocked.DetachExceptions); + Assert.Empty(accessoriesFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task AttachThis_ByWornLayer_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as tattoo) + // \= Glasses + // + + // TryGetRlvInventoryTree + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the Hats folder, all hats are no longer attachable + Assert.True(await _rlv.ProcessMessage("@attachthis:tattoo=n", _sender.Id, _sender.Name)); + Assert.True(await _rlv.ProcessMessage("@attachthis:tattoo=y", _sender.Id, _sender.Name)); + + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Clothing_RetroPants, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Glasses, true)); + Assert.True(_rlv.Permissions.CanAttach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Empty(lockedFolders); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ChatNormalTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ChatNormalTests.cs new file mode 100644 index 00000000..cb4b8c5b --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ChatNormalTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ChatNormalTests : RestrictionsBase + { + #region @chatnormal= + [Fact] + public async Task CanChatNormal() + { + await CheckSimpleCommand("chatNormal", m => m.CanChatNormal()); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ChatShoutRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ChatShoutRestrictionTests.cs new file mode 100644 index 00000000..cc974083 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ChatShoutRestrictionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ChatShoutRestrictionTests : RestrictionsBase + { + #region @chatshout= + [Fact] + public async Task CanChatShout() + { + await CheckSimpleCommand("chatShout", m => m.CanChatShout()); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ChatWhisperRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ChatWhisperRestrictionTests.cs new file mode 100644 index 00000000..a29567cb --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ChatWhisperRestrictionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ChatWhisperRestrictionTests : RestrictionsBase + { + #region @chatwhisper= + [Fact] + public async Task CanChatWhisper() + { + await CheckSimpleCommand("chatWhisper", m => m.CanChatWhisper()); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/DefaultWearRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/DefaultWearRestrictionTests.cs new file mode 100644 index 00000000..afca851c --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/DefaultWearRestrictionTests.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class DefaultWearRestrictionTests : RestrictionsBase + { + #region @defaultwear= + + [Fact] + public async Task CanDefaultWear() + { + await CheckSimpleCommand("defaultWear", m => m.CanDefaultWear()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/DetachAllThisRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/DetachAllThisRestrictionTests.cs new file mode 100644 index 00000000..e16a7a32 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/DetachAllThisRestrictionTests.cs @@ -0,0 +1,462 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class DetachAllThisRestrictionTests : RestrictionsBase + { + #region @detachallthis[:||]= + + [Fact] + public async Task DetachAllThis() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats <-- Expected locked, no-detach + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- No detach + // | \= Party Hat (Attached to spine) <-- No detach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis=n", sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(2, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachAllThis_Recursive() + { + // #RLV + // | + // |- .private + // | + // |- Clothing <-- Expected locked, no-detach + // | |= Business Pants (Attached pelvis) <-- No detach + // | |= Happy Shirt <-- No detach + // | |= Retro Pants <-- No detach + // | \- Hats <-- Expected locked, no-detach + // | | + // | |- Sub Hats <-- Expected locked, no-detach + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- No detach + // | \= Party Hat <-- No detach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachAllThis_Recursive_Path() + { + // #RLV + // | + // |- .private + // | + // |- Clothing <-- Expected locked, no-detach + // | |= Business Pants <-- No detach + // | |= Happy Shirt <-- No detach + // | |= Retro Pants <-- No detach + // | \- Hats <-- Expected locked, no-detach + // | | + // | |- Sub Hats <-- Expected locked, no-detach + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- No detach + // | \= Party Hat <-- No detach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis:Clothing=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachAllThis_Recursive_Worn() + { + // #RLV + // | + // |- .private + // | + // |- Clothing <-- Expected locked, no-detach + // | |= Business Pants <-- No detach + // | |= Happy Shirt <-- No detach + // | |= Retro Pants (Worn pants) <-- No detach + // | \- Hats <-- Expected locked, no-detach + // | | + // | |- Sub Hats <-- Expected locked, no-detach + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- No detach + // | \= Party Hat <-- No detach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis:pants=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) - Folder locked due to RetroPants being worn as 'pants' + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) - Folder locked due to RetroPants being worn as 'pants' + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachAllThis_Recursive_Attached() + { + // #RLV + // | + // |- .private + // | + // |- Clothing <-- Expected locked, no-detach + // | |= Business Pants (Attached chest) <-- No detach + // | |= Happy Shirt <-- No detach + // | |= Retro Pants <-- No detach + // | \- Hats <-- Expected locked, no-detach + // | | + // | |- Sub Hats <-- Expected locked, no-detach + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- No detach + // | \= Party Hat <-- No detach + // \-Accessories + // |= Watch + // \= Glasses + // + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis:chest=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - Parent folder locked recursively + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) - Folder locked due to HappyShirt attachment of 'chest' + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) - Folder locked due to HappyShirt attachment of 'chest' + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(3, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_SubHats_Folder.Id, out var subhatsFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Equal(sampleTree.Clothing_Folder.Name, clothingFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Folder.Id, clothingFolderLocked.Id); + Assert.False(clothingFolderLocked.CanDetach); + Assert.True(clothingFolderLocked.CanAttach); + Assert.True(clothingFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_Folder.Name, hatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_Folder.Id, hatsFolderLocked.Id); + Assert.False(hatsFolderLocked.CanDetach); + Assert.True(hatsFolderLocked.CanAttach); + Assert.True(hatsFolderLocked.IsLocked); + + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Name, subhatsFolderLocked.Name); + Assert.Equal(sampleTree.Clothing_Hats_SubHats_Folder.Id, subhatsFolderLocked.Id); + Assert.False(subhatsFolderLocked.CanDetach); + Assert.True(subhatsFolderLocked.CanAttach); + Assert.True(subhatsFolderLocked.IsLocked); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + + Assert.Empty(subhatsFolderLocked.AttachExceptions); + Assert.Empty(subhatsFolderLocked.AttachRestrictions); + Assert.Empty(subhatsFolderLocked.DetachExceptions); + Assert.Single(subhatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachAllThis_Recursive_Attached_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants (Attached to chest) + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Chest; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachallthis:chest=n", _sender.Id, _sender.Name)); + Assert.True(await _rlv.ProcessMessage("@detachallthis:chest=y", _sender.Id, _sender.Name)); + + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Empty(lockedFolders); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/DetachRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/DetachRestrictionTests.cs new file mode 100644 index 00000000..c6aaeebc --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/DetachRestrictionTests.cs @@ -0,0 +1,77 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class DetachRestrictionTests : RestrictionsBase + { + #region @detach= | @detach:= + + [Fact] + public void Detach_Default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectPrimId1 = new Guid("00000000-0000-4000-8000-ffffffffffff"); + + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + var objectPrimId2 = new Guid("11111111-1111-4111-8111-ffffffffffff"); + + + var folderId1 = new Guid("99999999-9999-4999-8999-999999999999"); + + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, false, null, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, objectPrimId1, folderId1, false, RlvAttachmentPoint.Chest, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, false, null, RlvWearableType.Shirt)); + + Assert.True(_rlv.Permissions.CanDetach(objectId2, null, folderId1, true, null, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId2, objectPrimId2, folderId1, true, RlvAttachmentPoint.Chest, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId2, null, folderId1, true, null, RlvWearableType.Shirt)); + } + + [Fact] + public async Task Detach() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectPrimId1 = new Guid("00000000-0000-4000-8000-ffffffffffff"); + + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + var objectPrimId2 = new Guid("11111111-1111-4111-8111-ffffffffffff"); + + var folderId1 = new Guid("99999999-9999-4999-8999-999999999999"); + + Assert.True(await _rlv.ProcessMessage("@detach=n", objectPrimId2, "objectPrimId2")); + + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, false, null, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, objectPrimId1, folderId1, false, RlvAttachmentPoint.Chest, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, false, null, RlvWearableType.Shirt)); + + Assert.True(_rlv.Permissions.CanDetach(objectId2, null, folderId1, true, null, null)); + Assert.False(_rlv.Permissions.CanDetach(objectId2, objectPrimId2, folderId1, true, RlvAttachmentPoint.Chest, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId2, null, folderId1, true, null, RlvWearableType.Shirt)); + } + + [Fact] + public async Task Detach_AttachPoint() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectPrimId1 = new Guid("00000000-0000-4000-8000-ffffffffffff"); + + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + var objectPrimId2 = new Guid("11111111-1111-4111-8111-ffffffffffff"); + + var folderId1 = new Guid("99999999-9999-4999-8999-999999999999"); + + Assert.True(await _rlv.ProcessMessage("@detach:skull=n", _sender.Id, _sender.Name)); + + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, false, null, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, objectPrimId1, folderId1, false, RlvAttachmentPoint.Chest, null)); + Assert.False(_rlv.Permissions.CanDetach(objectId1, objectPrimId1, folderId1, false, RlvAttachmentPoint.Skull, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, false, null, RlvWearableType.Shirt)); + + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, true, null, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, objectPrimId2, folderId1, true, RlvAttachmentPoint.Chest, null)); + Assert.False(_rlv.Permissions.CanDetach(objectId1, objectPrimId2, folderId1, true, RlvAttachmentPoint.Skull, null)); + Assert.True(_rlv.Permissions.CanDetach(objectId1, null, folderId1, true, null, RlvWearableType.Shirt)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/DetachThisRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/DetachThisRestrictionTests.cs new file mode 100644 index 00000000..de5b9e6b --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/DetachThisRestrictionTests.cs @@ -0,0 +1,411 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class DetachThisRestrictionTests : RestrictionsBase + { + #region @detachthis[:||]= + + [Fact] + public async Task DetachThis() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats <-- Expected locked, no-detach + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- No detach + // | \= Party Hat (Attached to spine) <-- No detach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachthis=n", sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId!.Value, sampleTree.Root_Clothing_Hats_PartyHat_Spine.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachThis_NotRecursive() + { + // #RLV + // | + // |- .private + // | + // |- Clothing <-- Expected locked, no-detach + // | |= Business Pants (Attached pelvis) <-- No detach + // | |= Happy Shirt <-- No detach + // | |= Retro Pants <-- No detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + // TryGetRlvInventoryTree + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the #RLV/Clothing folder because the Business Pants are issuing the command, which is in the Clothing folder. + // Business Pants cannot be detached, but hats are still detachable. + Assert.True(await _rlv.ProcessMessage("@detachthis=n", sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId!.Value, sampleTree.Root_Clothing_BusinessPants_Pelvis.Name)); + + // #RLV/Clothing/Hats/Party Hat () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachThis_ByPath() + { + // #RLV + // | + // |- .private + // | + // |- Clothing <-- Expected locked, no-detach + // | |= Business Pants <-- No detach + // | |= Happy Shirt <-- No detach + // | |= Retro Pants <-- No detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the Hats folder, all hats are no longer detachable + Assert.True(await _rlv.ProcessMessage("@detachthis:Clothing=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachThis_ByRlvAttachmentPoint() + { + // #RLV + // | + // |- .private + // | + // |- Clothing <-- Expected locked, no-detach + // | |= Business Pants (Attached pelvis) <-- No detach + // | |= Happy Shirt <-- No detach + // | |= Retro Pants <-- No detach + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat <-- No detach + // | \= Party Hat (Attached pelvis) <-- No detach + // \-Accessories + // |= Watch + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0001-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedTo = RlvAttachmentPoint.Pelvis; + sampleTree.Root_Clothing_BusinessPants_Pelvis.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the Hats folder, all hats are no longer detachable + Assert.True(await _rlv.ProcessMessage("@detachthis:pelvis=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat (LOCKED) - folder was locked because PartyHat + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants (LOCKED) - folder was locked because BusinessPants + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Equal(2, lockedFolders.Count); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Folder.Id, out var clothingFolderLocked)); + Assert.True(lockedFolders.TryGetValue(sampleTree.Clothing_Hats_Folder.Id, out var hatsFolderLocked)); + + Assert.Empty(clothingFolderLocked.AttachExceptions); + Assert.Empty(clothingFolderLocked.AttachRestrictions); + Assert.Empty(clothingFolderLocked.DetachExceptions); + Assert.Single(clothingFolderLocked.DetachRestrictions); + + Assert.Empty(hatsFolderLocked.AttachExceptions); + Assert.Empty(hatsFolderLocked.AttachRestrictions); + Assert.Empty(hatsFolderLocked.DetachExceptions); + Assert.Single(hatsFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachThis_ByWornLayer() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as tattoo) <-- no detach + // \= Glasses <-- No detach + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + // This should lock the Hats folder, all hats are no longer detachable + Assert.True(await _rlv.ProcessMessage("@detachthis:tattoo=n", _sender.Id, _sender.Name)); + + // #RLV/Clothing/Hats/Party Hat () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + + // #RLV/Clothing/Hats/Fancy Hat () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + + // #RLV/Clothing/Business Pants () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + + // #RLV/Clothing/Happy Shirt () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + + // #RLV/Clothing/Retro Pants () + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + + // #RLV/Accessories/Glasses (LOCKED) - folder was locked from Watch (tattoo) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + + // #RLV/Accessories/Watch (LOCKED) + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Single(lockedFolders); + + Assert.True(lockedFolders.TryGetValue(sampleTree.Accessories_Folder.Id, out var accessoriesFolderLocked)); + + Assert.Equal(sampleTree.Accessories_Folder.Name, accessoriesFolderLocked.Name); + Assert.Equal(sampleTree.Accessories_Folder.Id, accessoriesFolderLocked.Id); + Assert.False(accessoriesFolderLocked.CanDetach); + Assert.True(accessoriesFolderLocked.CanAttach); + Assert.True(accessoriesFolderLocked.IsLocked); + + Assert.Empty(accessoriesFolderLocked.AttachExceptions); + Assert.Empty(accessoriesFolderLocked.AttachRestrictions); + Assert.Empty(accessoriesFolderLocked.DetachExceptions); + Assert.Single(accessoriesFolderLocked.DetachRestrictions); + } + + [Fact] + public async Task DetachThis_ByWornLayer_AddRem() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@detachthis:tattoo=n", _sender.Id, _sender.Name)); + Assert.True(await _rlv.ProcessMessage("@detachthis:tattoo=y", _sender.Id, _sender.Name)); + + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_BusinessPants_Pelvis, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_HappyShirt, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Glasses, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + + var lockedFolders = _rlv.Restrictions.GetLockedFolders(); + Assert.Empty(lockedFolders); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/EditAttachRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/EditAttachRestrictionTests.cs new file mode 100644 index 00000000..aea417ac --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/EditAttachRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class EditAttachRestrictionTests : RestrictionsBase + { + #region @editattach= + [Fact] + public async Task CanEdit_Attachment() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage($"@editattach=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, null)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, null)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, null)); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId1)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId1)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/EditRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/EditRestrictionTests.cs new file mode 100644 index 00000000..0c454d9f --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/EditRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class EditRestrictionTests : RestrictionsBase + { + #region @edit= + + [Fact] + public async Task CanEditFolderNameSpecifiesToAddInsteadOfReplace() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@edit=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, null)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, null)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, null)); + + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId1)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/EditWorldRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/EditWorldRestrictionTests.cs new file mode 100644 index 00000000..7555d22f --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/EditWorldRestrictionTests.cs @@ -0,0 +1,23 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class EditWorldRestrictionTests : RestrictionsBase + { + #region @editworld= + [Fact] + public async Task CanEdit_World() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage($"@editworld=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, null)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, null)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, null)); + + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId1)); + Assert.True(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId1)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/FarTouchRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/FarTouchRestrictionTests.cs new file mode 100644 index 00000000..a598025e --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/FarTouchRestrictionTests.cs @@ -0,0 +1,63 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class FarTouchRestrictionTests : RestrictionsBase + { + #region @touchfar @fartouch[:max_distance]= + + [Theory] + [InlineData("fartouch")] + [InlineData("touchfar")] + public async Task CanFarTouch(string command) + { + await _rlv.ProcessMessage($"@{command}:0.9=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetMaxFarTouchDistance(out var distance)); + Assert.Equal(0.9f, distance); + } + + [Theory] + [InlineData("fartouch")] + [InlineData("touchfar")] + public async Task CanFarTouch_Synonym(string command) + { + await _rlv.ProcessMessage($"@{command}:0.9=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetMaxFarTouchDistance(out var distance)); + Assert.Equal(0.9f, distance); + } + + [Theory] + [InlineData("fartouch")] + [InlineData("touchfar")] + public async Task CanFarTouch_Default(string command) + { + await _rlv.ProcessMessage($"@{command}=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetMaxFarTouchDistance(out var distance)); + Assert.Equal(1.5f, distance); + } + + [Theory] + [InlineData("fartouch", "fartouch")] + [InlineData("fartouch", "touchfar")] + [InlineData("touchfar", "touchfar")] + [InlineData("touchfar", "fartouch")] + public async Task CanFarTouch_Multiple_Synonyms(string command1, string command2) + { + await _rlv.ProcessMessage($"@{command1}:12.34=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@{command2}:6.78=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetMaxFarTouchDistance(out var actualDistance2)); + + await _rlv.ProcessMessage($"@{command1}:6.78=y", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.TryGetMaxFarTouchDistance(out var actualDistance1)); + + Assert.Equal(12.34f, actualDistance1, FloatTolerance); + Assert.Equal(6.78f, actualDistance2, FloatTolerance); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/FlyRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/FlyRestrictionTests.cs new file mode 100644 index 00000000..9aece5dc --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/FlyRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class FlyRestrictionTests : RestrictionsBase + { + #region @fly= + [Fact] + public async Task CanFly() + { + await CheckSimpleCommand("fly", m => m.CanFly()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/InteractRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/InteractRestrictionTests.cs new file mode 100644 index 00000000..24089215 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/InteractRestrictionTests.cs @@ -0,0 +1,37 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class InteractRestrictionTests : RestrictionsBase + { + #region @interact= + + [Fact] + public async Task CanInteract() + { + await CheckSimpleCommand("interact", m => m.CanInteract()); + } + + [Fact] + public async Task CanInteract_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage($"@interact=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Attached, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.RezzedInWorld, objectId1)); + Assert.False(_rlv.Permissions.CanEdit(RlvPermissionsService.ObjectLocation.Hud, objectId1)); + + Assert.False(_rlv.Permissions.CanRez()); + + Assert.False(_rlv.Permissions.CanSit()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/JumpRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/JumpRestrictionTests.cs new file mode 100644 index 00000000..1731fefe --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/JumpRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class JumpRestrictionTests : RestrictionsBase + { + #region @jump= (RLVa) + [Fact] + public async Task CanJump() + { + await CheckSimpleCommand("jump", m => m.CanJump()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/PermissiveRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/PermissiveRestrictionTests.cs new file mode 100644 index 00000000..0570cb28 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/PermissiveRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class PermissiveRestrictionTests : RestrictionsBase + { + #region @Permissive + [Fact] + public async Task Permissive_On() + { + await _rlv.ProcessMessage("@permissive=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.IsPermissive()); + } + + [Fact] + public async Task Permissive_Off() + { + await _rlv.ProcessMessage("@permissive=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@permissive=y", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.IsPermissive()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvChatExceptionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvChatExceptionTests.cs new file mode 100644 index 00000000..0af744f2 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvChatExceptionTests.cs @@ -0,0 +1,15 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvChatExceptionTests : RestrictionsBase + { + [Fact] + public async Task CanRecvChat() + { + await _rlv.ProcessMessage("@recvchat=n", _sender.Id, _sender.Name); + var userId = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.False(_rlv.Permissions.CanReceiveChat("Hello world", userId)); + Assert.True(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId)); + } + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvChatFromRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvChatFromRestrictionTests.cs new file mode 100644 index 00000000..725acc4f --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvChatFromRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvChatFromRestrictionTests : RestrictionsBase + { + #region @recvchatfrom:= + + [Fact] + public async Task CanRecvChatFrom() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@recvchatfrom:{userId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanReceiveChat("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId1)); + + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId2)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvChatSecExceptionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvChatSecExceptionTests.cs new file mode 100644 index 00000000..cb2b0d2c --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvChatSecExceptionTests.cs @@ -0,0 +1,23 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvChatSecExceptionTests : RestrictionsBase + { + #region @recvchat_sec= + [Fact] + public async Task CanRecvChat_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@recvchat_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvchat:{userId1}=add", sender2.Id, sender2.Name); + await _rlv.ProcessMessage($"@recvchat:{userId2}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanReceiveChat("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteFromRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteFromRestrictionTests.cs new file mode 100644 index 00000000..9d329e45 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteFromRestrictionTests.cs @@ -0,0 +1,22 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvEmoteFromRestrictionTests : RestrictionsBase + { + #region @recvemotefrom:= + [Fact] + public async Task CanRecvChat_RecvEmoteFrom() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@recvemotefrom:{userId1}=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId2)); + Assert.False(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteRestrictionTests.cs new file mode 100644 index 00000000..97bb63a3 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteRestrictionTests.cs @@ -0,0 +1,18 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvEmoteRestrictionTests : RestrictionsBase + { + #region @recvemote= + [Fact] + public async Task CanRecvChat_RecvEmote() + { + await _rlv.ProcessMessage("@recvemote=n", _sender.Id, _sender.Name); + + var userId = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId)); + Assert.False(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteSecRestrictionTests.cs new file mode 100644 index 00000000..96720ddd --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvEmoteSecRestrictionTests.cs @@ -0,0 +1,26 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvEmoteSecRestrictionTests : RestrictionsBase + { + #region @recvemote_sec= + [Fact] + public async Task CanRecvChat_RecvEmote_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@recvemote_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvemote:{userId1}=add", sender2.Id, sender2.Name); + await _rlv.ProcessMessage($"@recvemote:{userId2}=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("Hello world", userId2)); + Assert.False(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveChat("/me says Hello world", userId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvImFromRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvImFromRestrictionTests.cs new file mode 100644 index 00000000..5ba7aa09 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvImFromRestrictionTests.cs @@ -0,0 +1,43 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvImFromRestrictionTests : RestrictionsBase + { + #region @recvimfrom:= + [Fact] + public async Task CanReceiveIMFrom() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@recvimfrom:{userId1}=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanReceiveIM("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", userId2)); + } + + [Fact] + public async Task CanReceiveIMFrom_Group() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var groupId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@recvimfrom:First Group=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "First Group")); + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", groupId2, "Second Group")); + } + + [Fact] + public async Task CanReceiveIMTo_AllGroups() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var groupId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@recvimfrom:allgroups=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "First Group")); + Assert.False(_rlv.Permissions.CanReceiveIM("Hello world", groupId2, "Second Group")); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvImRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvImRestrictionTests.cs new file mode 100644 index 00000000..7d5e8b55 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvImRestrictionTests.cs @@ -0,0 +1,19 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvImRestrictionTests : RestrictionsBase + { + #region @recvim= + [Fact] + public async Task CanReceiveIM() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@recvim=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanReceiveIM("Hello", userId1)); + Assert.False(_rlv.Permissions.CanReceiveIM("Hello", userId1, "Group Name")); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RecvImSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RecvImSecRestrictionTests.cs new file mode 100644 index 00000000..a2c1664b --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RecvImSecRestrictionTests.cs @@ -0,0 +1,51 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RecvImSecRestrictionTests : RestrictionsBase + { + #region @recvim_sec= + + [Fact] + public async Task CanReceiveIM_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@recvim_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:{userId1}=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:{userId2}=add", sender2.Id, sender2.Name); + + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", userId1)); + Assert.False(_rlv.Permissions.CanReceiveIM("Hello world", userId2)); + } + + [Fact] + public async Task CanReceiveIM_Secure_Group() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@recvim_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:Group Name=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:allgroups=add", sender2.Id, sender2.Name); + + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "Group Name")); + Assert.False(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "Another Group")); + } + + [Fact] + public async Task CanReceiveIM_Secure_AllGroups() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@recvim_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@recvim:allgroups=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "Group Name")); + Assert.True(_rlv.Permissions.CanReceiveIM("Hello world", groupId1, "Another Group")); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RemAttachRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RemAttachRestrictionTests.cs new file mode 100644 index 00000000..89179ae8 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RemAttachRestrictionTests.cs @@ -0,0 +1,96 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RemAttachRestrictionTests : RestrictionsBase + { + #region @remattach[:]= + [Fact] + public async Task RemAttach() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to Chin) + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@remattach=n", _sender.Id, _sender.Name)); + + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + } + + [Fact] + public async Task RemAttach_Specific() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to Chin) + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedTo = RlvAttachmentPoint.Spine; + sampleTree.Root_Clothing_Hats_PartyHat_Spine.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0002-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@remattach:spine=n", _sender.Id, _sender.Name)); + + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_PartyHat_Spine, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RemOutfitRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RemOutfitRestrictionTests.cs new file mode 100644 index 00000000..25a68b73 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RemOutfitRestrictionTests.cs @@ -0,0 +1,95 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RemOutfitRestrictionTests : RestrictionsBase + { + + #region @remoutfit[:]= + [Fact] + public async Task RemOutfit() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to Chin) + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@remoutfit=n", _sender.Id, _sender.Name)); + + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + } + + [Fact] + public async Task RemOutfit_part() + { + // #RLV + // | + // |- .private + // | + // |- Clothing + // | |= Business Pants + // | |= Happy Shirt + // | |= Retro Pants + // | \- Hats + // | | + // | |- Sub Hats + // | | \ (Empty) + // | | + // | |= Fancy Hat (Attached to Chin) + // | \= Party Hat + // \-Accessories + // |= Watch (Worn as Tattoo) + // \= Glasses + // + + var sampleTree = SampleInventoryTree.BuildInventoryTree(); + var sharedFolder = sampleTree.Root; + + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedTo = RlvAttachmentPoint.Chin; + sampleTree.Root_Clothing_Hats_FancyHat_Chin.AttachedPrimId = new Guid("11111111-0003-4aaa-8aaa-ffffffffffff"); + + sampleTree.Root_Accessories_Watch.WornOn = RlvWearableType.Tattoo; + sampleTree.Root_Clothing_RetroPants.WornOn = RlvWearableType.Pants; + + _queryCallbacks.Setup(e => + e.TryGetSharedFolderAsync(default) + ).ReturnsAsync((true, sharedFolder)); + + Assert.True(await _rlv.ProcessMessage("@remoutfit:pants=n", _sender.Id, _sender.Name)); + + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_Hats_FancyHat_Chin, true)); + Assert.False(_rlv.Permissions.CanDetach(sampleTree.Root_Clothing_RetroPants, true)); + Assert.True(_rlv.Permissions.CanDetach(sampleTree.Root_Accessories_Watch, true)); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/RezRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/RezRestrictionTests.cs new file mode 100644 index 00000000..d36e0b8f --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/RezRestrictionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class RezRestrictionTests : RestrictionsBase + { + #region @rez= + [Fact] + public async Task CanRez() + { + await CheckSimpleCommand("rez", m => m.CanRez()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SendChannelRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SendChannelRestrictionTests.cs new file mode 100644 index 00000000..51c64ac1 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SendChannelRestrictionTests.cs @@ -0,0 +1,15 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SendChannelRestrictionTests : RestrictionsBase + { + #region @sendchannel[:]= + [Fact] + public async Task CanSendChannel() + { + await _rlv.ProcessMessage("@sendchannel=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanChat(123, "Hello world")); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SendChannelSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SendChannelSecRestrictionTests.cs new file mode 100644 index 00000000..143a26c2 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SendChannelSecRestrictionTests.cs @@ -0,0 +1,38 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SendChannelSecRestrictionTests : RestrictionsBase + { + #region @sendchannel_sec[:]= + [Fact] + public async Task CanSendChannel_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@sendchannel_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendchannel:123=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendchannel:456=n", sender2.Id, sender2.Name); + + Assert.True(_rlv.Permissions.CanChat(123, "Hello world")); + Assert.False(_rlv.Permissions.CanChat(456, "Hello world")); + } + + [Fact] + public async Task CanSendChannel_Secure_Exception() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@sendchannel_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@sendchannel_sec:123=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanChat(123, "Hello world")); + Assert.False(_rlv.Permissions.CanChat(456, "Hello world")); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SendChatRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SendChatRestrictionTests.cs new file mode 100644 index 00000000..e52e4c41 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SendChatRestrictionTests.cs @@ -0,0 +1,33 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SendChatRestrictionTests : RestrictionsBase + { + #region @sendchat= + [Fact] + public async Task CanSendChat() + { + await CheckSimpleCommand("sendChat", m => m.CanSendChat()); + } + + [Fact] + public async Task CanChat_SendChatRestriction() + { + await _rlv.ProcessMessage("@sendchat=n", _sender.Id, _sender.Name); + + // No public chat allowed unless it starts with '/' + Assert.False(_rlv.Permissions.CanChat(0, "Hello")); + + // Emotes and other messages starting with / are allowed + Assert.True(_rlv.Permissions.CanChat(0, "/me says Hello")); + Assert.True(_rlv.Permissions.CanChat(0, "/ something?")); + + // Messages containing ()"-*=_^ are prohibited + Assert.False(_rlv.Permissions.CanChat(0, "/me says Hello ^_^")); + + // Private channels are not impacted + Assert.True(_rlv.Permissions.CanChat(5, "Hello")); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SendGestureRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SendGestureRestrictionTests.cs new file mode 100644 index 00000000..7e832bba --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SendGestureRestrictionTests.cs @@ -0,0 +1,16 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SendGestureRestrictionTests : RestrictionsBase + { + + #region @sendgesture= + + [Fact] + public async Task CanSendGesture() + { + await CheckSimpleCommand("sendGesture", m => m.CanSendGesture()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SendImRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SendImRestrictionTests.cs new file mode 100644 index 00000000..fa0811b8 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SendImRestrictionTests.cs @@ -0,0 +1,18 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SendImRestrictionTests : RestrictionsBase + { + #region @sendim= + [Fact] + public async Task CanSendIM() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@sendim=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanSendIM("Hello", userId1)); + Assert.False(_rlv.Permissions.CanSendIM("Hello", userId1, "Group Name")); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SendImSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SendImSecRestrictionTests.cs new file mode 100644 index 00000000..6e5c83bf --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SendImSecRestrictionTests.cs @@ -0,0 +1,52 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SendImSecRestrictionTests : RestrictionsBase + { + + #region @sendim_sec= + [Fact] + public async Task CanSendIM_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@sendim_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:{userId1}=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:{userId2}=add", sender2.Id, sender2.Name); + + Assert.True(_rlv.Permissions.CanSendIM("Hello world", userId1)); + Assert.False(_rlv.Permissions.CanSendIM("Hello world", userId2)); + } + + [Fact] + public async Task CanSendIM_Secure_Group() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@sendim_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:Group Name=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:allgroups=add", sender2.Id, sender2.Name); + + Assert.True(_rlv.Permissions.CanSendIM("Hello world", groupId1, "Group Name")); + Assert.False(_rlv.Permissions.CanSendIM("Hello world", groupId1, "Another Group")); + } + + [Fact] + public async Task CanSendIM_Secure_AllGroups() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@sendim_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@sendim:allgroups=add", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanSendIM("Hello world", groupId1, "Group Name")); + Assert.True(_rlv.Permissions.CanSendIM("Hello world", groupId1, "Another Group")); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SendImToRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SendImToRestrictionTests.cs new file mode 100644 index 00000000..a2dfecb3 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SendImToRestrictionTests.cs @@ -0,0 +1,45 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SendImToRestrictionTests : RestrictionsBase + { + #region @sendimto:= + + [Fact] + public async Task CanSendIMTo() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@sendimto:{userId1}=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanSendIM("Hello world", userId1)); + Assert.True(_rlv.Permissions.CanSendIM("Hello world", userId2)); + } + + [Fact] + public async Task CanSendIMTo_Group() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var groupId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@sendimto:First Group=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanSendIM("Hello world", groupId1, "First Group")); + Assert.True(_rlv.Permissions.CanSendIM("Hello world", groupId2, "Second Group")); + } + + [Fact] + public async Task CanSendIMTo_AllGroups() + { + var groupId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var groupId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@sendimto:allgroups=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanSendIM("Hello world", groupId1, "First Group")); + Assert.False(_rlv.Permissions.CanSendIM("Hello world", groupId2, "Second Group")); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SetDebugRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SetDebugRestrictionTests.cs new file mode 100644 index 00000000..1939e32e --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SetDebugRestrictionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SetDebugRestrictionTests : RestrictionsBase + { + + #region @setdebug= + [Fact] + public async Task CanSetDebug() + { + await CheckSimpleCommand("setDebug", m => m.CanSetDebug()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SetEnvRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SetEnvRestrictionTests.cs new file mode 100644 index 00000000..4a61bc28 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SetEnvRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SetEnvRestrictionTests : RestrictionsBase + { + #region @setenv= + [Fact] + public async Task CanSetEnv() + { + await CheckSimpleCommand("setEnv", m => m.CanSetEnv()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SetGroupRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SetGroupRestrictionTests.cs new file mode 100644 index 00000000..7c2572c0 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SetGroupRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SetGroupRestrictionTests : RestrictionsBase + { + #region @setgroup= + [Fact] + public async Task CanSetGroup() + { + await CheckSimpleCommand("setGroup", m => m.CanSetGroup()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShareRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShareRestrictionTests.cs new file mode 100644 index 00000000..d8bbf155 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShareRestrictionTests.cs @@ -0,0 +1,19 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShareRestrictionTests : RestrictionsBase + { + #region @share= + + [Fact] + public async Task CanShare() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@share=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShare(null)); + Assert.False(_rlv.Permissions.CanShare(userId1)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShareSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShareSecRestrictionTests.cs new file mode 100644 index 00000000..3290be96 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShareSecRestrictionTests.cs @@ -0,0 +1,38 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShareSecRestrictionTests : RestrictionsBase + { + #region @share_sec= + [Fact] + public async Task CanShare_Secure_Default() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@share_sec=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShare(null)); + Assert.False(_rlv.Permissions.CanShare(userId1)); + Assert.False(_rlv.Permissions.CanShare(userId2)); + } + + [Fact] + public async Task CanShare_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@share_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@share:{userId1}=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@share:{userId2}=add", sender2.Id, sender2.Name); + + Assert.False(_rlv.Permissions.CanShare(null)); + Assert.True(_rlv.Permissions.CanShare(userId1)); + Assert.False(_rlv.Permissions.CanShare(userId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextAllRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextAllRestrictionTests.cs new file mode 100644 index 00000000..37a55b59 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextAllRestrictionTests.cs @@ -0,0 +1,20 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowHoverTextAllRestrictionTests : RestrictionsBase + { + + #region @showhovertextall= + [Fact] + public async Task CanShowHoverTextAll() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@showhovertextall=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextHudRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextHudRestrictionTests.cs new file mode 100644 index 00000000..4d981cc7 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextHudRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowHoverTextHudRestrictionTests : RestrictionsBase + { + #region @showhovertexthud= + + [Fact] + public async Task CanShowHoverTextHud() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@showhovertexthud=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId2)); + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextRestrictionTests.cs new file mode 100644 index 00000000..3655fce5 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextRestrictionTests.cs @@ -0,0 +1,22 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowHoverTextRestrictionTests : RestrictionsBase + { + #region @showhovertext:= + [Fact] + public async Task CanShowHoverText() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@showhovertext:{objectId1}=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId2)); + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextWorldRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextWorldRestrictionTests.cs new file mode 100644 index 00000000..7e504b06 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowHoverTextWorldRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowHoverTextWorldRestrictionTests : RestrictionsBase + { + #region @showhovertextworld= + + [Fact] + public async Task CanShowHoverTextWorld() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@showhovertextworld=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId1)); + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId1)); + + Assert.False(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.World, objectId2)); + Assert.True(_rlv.Permissions.CanShowHoverText(RlvPermissionsService.HoverTextLocation.Hud, objectId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowInvRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowInvRestrictionTests.cs new file mode 100644 index 00000000..2cd27753 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowInvRestrictionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowInvRestrictionTests : RestrictionsBase + { + #region @showinv= + [Fact] + public async Task CanShowInv() + { + await CheckSimpleCommand("showInv", m => m.CanShowInv()); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowLocRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowLocRestrictionTests.cs new file mode 100644 index 00000000..1642c44a --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowLocRestrictionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowLocRestrictionTests : RestrictionsBase + { + #region @showloc= + [Fact] + public async Task CanShowLoc() + { + await CheckSimpleCommand("showLoc", m => m.CanShowLoc()); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowMiniMapRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowMiniMapRestrictionTests.cs new file mode 100644 index 00000000..6aee48e5 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowMiniMapRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowMiniMapRestrictionTests : RestrictionsBase + { + #region @showminimap= + [Fact] + public async Task CanShowMiniMap() + { + await CheckSimpleCommand("showMiniMap", m => m.CanShowMiniMap()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowNameTagsRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowNameTagsRestrictionTests.cs new file mode 100644 index 00000000..dd55d591 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowNameTagsRestrictionTests.cs @@ -0,0 +1,18 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowNameTagsRestrictionTests : RestrictionsBase + { + #region @shownametags= + [Fact] + public async Task CanShowNameTags() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@shownametags=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowNameTags(null)); + Assert.False(_rlv.Permissions.CanShowNameTags(userId1)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowNamesRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowNamesRestrictionTests.cs new file mode 100644 index 00000000..79f9f0a8 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowNamesRestrictionTests.cs @@ -0,0 +1,18 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowNamesRestrictionTests : RestrictionsBase + { + #region @shownames= + [Fact] + public async Task CanShowNames() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + await _rlv.ProcessMessage("@shownames=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowNames(null)); + Assert.False(_rlv.Permissions.CanShowNames(userId1)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowNamesSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowNamesSecRestrictionTests.cs new file mode 100644 index 00000000..ce56c3fc --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowNamesSecRestrictionTests.cs @@ -0,0 +1,52 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowNamesSecRestrictionTests : RestrictionsBase + { + #region @shownames_sec[:except_uuid]= + [Fact] + public async Task CanShowNames_Secure_Default() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@shownames_sec=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowNames(null)); + Assert.False(_rlv.Permissions.CanShowNames(userId1)); + Assert.False(_rlv.Permissions.CanShowNames(userId2)); + } + + [Fact] + public async Task CanShowNames_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("22222222-2222-4222-8222-222222222222")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@shownames_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@shownames:{userId1}=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@shownames:{userId2}=add", sender2.Id, sender2.Name); + + Assert.False(_rlv.Permissions.CanShowNames(null)); + Assert.True(_rlv.Permissions.CanShowNames(userId1)); + Assert.False(_rlv.Permissions.CanShowNames(userId2)); + } + + [Fact] + public async Task CanShowNames_Secure_Except() + { + var sender2 = new RlvObject("Sender 2", new Guid("22222222-2222-4222-8222-222222222222")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@shownames_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@shownames_sec:{userId1}=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanShowNames(null)); + Assert.True(_rlv.Permissions.CanShowNames(userId1)); + Assert.False(_rlv.Permissions.CanShowNames(userId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowNearbyRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowNearbyRestrictionTests.cs new file mode 100644 index 00000000..f58bff42 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowNearbyRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowNearbyRestrictionTests : RestrictionsBase + { + #region @shownearby= + [Fact] + public async Task CanShowNearby() + { + await CheckSimpleCommand("showNearby", m => m.CanShowNearby()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ShowWorldMapRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ShowWorldMapRestrictionTests.cs new file mode 100644 index 00000000..aeb6a635 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ShowWorldMapRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ShowWorldMapRestrictionTests : RestrictionsBase + { + #region @showworldmap= + [Fact] + public async Task CanShowWorldMap() + { + await CheckSimpleCommand("showWorldMap", m => m.CanShowWorldMap()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SitRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SitRestrictionTests.cs new file mode 100644 index 00000000..92c81d2c --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SitRestrictionTests.cs @@ -0,0 +1,14 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SitRestrictionTests : RestrictionsBase + { + + #region @sit= + [Fact] + public async Task CanSit() + { + await CheckSimpleCommand("sit", m => m.CanSit()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/SitTpRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/SitTpRestrictionTests.cs new file mode 100644 index 00000000..c7c90bec --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/SitTpRestrictionTests.cs @@ -0,0 +1,73 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class SitTpRestrictionTests : RestrictionsBase + { + #region @sittp[:max_distance]= + + [Fact] + public void CanSitTp_Default() + { + Assert.False(_rlv.Permissions.CanSitTp(out var maxDistance)); + Assert.Equal(1.5f, maxDistance); + } + + [Fact] + public async Task CanSitTp_Single() + { + await _rlv.ProcessMessage("@SitTp:2.5=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanSitTp(out var maxDistance)); + Assert.Equal(2.5f, maxDistance); + } + + [Fact] + public async Task CanSitTp_Multiple_SingleSender() + { + await _rlv.ProcessMessage("@SitTp:3.5=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@SitTp:4.5=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@SitTp:2.5=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanSitTp(out var maxDistance)); + Assert.Equal(2.5f, maxDistance); + } + + [Fact] + public async Task CanSitTp_Multiple_SingleSender_WithRemoval() + { + await _rlv.ProcessMessage("@SitTp:3.5=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@SitTp:4.5=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@SitTp:2.5=n", _sender.Id, _sender.Name); + + await _rlv.ProcessMessage("@SitTp:8.5=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@SitTp:8.5=y", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanSitTp(out var maxDistance)); + Assert.Equal(2.5f, maxDistance); + } + + [Fact] + public async Task CanSitTp_Multiple_MultipleSenders() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var sender3 = new RlvObject("Sender 3", new Guid("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb")); + + await _rlv.ProcessMessage("@SitTp:3.5=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@SitTp:4.5=n", sender2.Id, sender2.Name); + await _rlv.ProcessMessage("@SitTp:2.5=n", sender3.Id, sender3.Name); + + Assert.True(_rlv.Permissions.CanSitTp(out var maxDistance)); + Assert.Equal(2.5f, maxDistance); + } + + [Fact] + public async Task CanSitTp_Off() + { + await _rlv.ProcessMessage("@SitTp:2.5=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage("@SitTp:2.5=y", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanSitTp(out var maxDistance)); + Assert.Equal(1.5f, maxDistance); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/StandTpRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/StandTpRestrictionTests.cs new file mode 100644 index 00000000..cd7a4fc2 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/StandTpRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class StandTpRestrictionTests : RestrictionsBase + { + #region @standtp= + [Fact] + public async Task CanStandTp() + { + await CheckSimpleCommand("standTp", m => m.CanStandTp()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/StartImRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/StartImRestrictionTests.cs new file mode 100644 index 00000000..850b2275 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/StartImRestrictionTests.cs @@ -0,0 +1,18 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class StartImRestrictionTests : RestrictionsBase + { + #region @startim= + [Fact] + public async Task CanStartIM() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + + await _rlv.ProcessMessage("@startim=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanStartIM(userId1)); + } + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/StartImToRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/StartImToRestrictionTests.cs new file mode 100644 index 00000000..2edba120 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/StartImToRestrictionTests.cs @@ -0,0 +1,20 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class StartImToRestrictionTests : RestrictionsBase + { + + #region @startimto:= + [Fact] + public async Task CanStartIMTo() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage($"@startimto:{userId2}=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanStartIM(userId1)); + Assert.False(_rlv.Permissions.CanStartIM(userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TempRunRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TempRunRestrictionTests.cs new file mode 100644 index 00000000..dc5e45c0 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TempRunRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TempRunRestrictionTests : RestrictionsBase + { + #region @temprun= + [Fact] + public async Task CanTempRun() + { + await CheckSimpleCommand("tempRun", m => m.CanTempRun()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TouchAllRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TouchAllRestrictionTests.cs new file mode 100644 index 00000000..14574eb1 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TouchAllRestrictionTests.cs @@ -0,0 +1,36 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TouchAllRestrictionTests : RestrictionsBase + { + #region @touchall= + + [Fact] + public void TouchAll() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("11111111-1111-4111-8111-111111111111"); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + + [Fact] + public async Task TouchAll_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@touchall=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachOtherRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachOtherRestrictionTests.cs new file mode 100644 index 00000000..bfa4d846 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachOtherRestrictionTests.cs @@ -0,0 +1,39 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TouchAttachOtherRestrictionTests : RestrictionsBase + { + #region @touchattachother= @touchattachother:= + + [Fact] + public async Task TouchAttachOther_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage("@touchattachother=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + + [Fact] + public async Task TouchAttachOther_Specific() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + var userId2 = new Guid("66666666-6666-4666-8666-666666666666"); + + await _rlv.ProcessMessage($"@touchattachother:{userId2}=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId2, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachRestrictionTests.cs new file mode 100644 index 00000000..d8e9d747 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TouchAttachRestrictionTests : RestrictionsBase + { + #region @touchattach= + + [Fact] + public async Task TouchAttach_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage("@touchattach=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachSelfRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachSelfRestrictionTests.cs new file mode 100644 index 00000000..fad47e72 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TouchAttachSelfRestrictionTests.cs @@ -0,0 +1,24 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TouchAttachSelfRestrictionTests : RestrictionsBase + { + #region @touchattachself= + + [Fact] + public async Task TouchAttachSelf_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage("@touchattachself=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TouchHudRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TouchHudRestrictionTests.cs new file mode 100644 index 00000000..5b335937 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TouchHudRestrictionTests.cs @@ -0,0 +1,40 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TouchHudRestrictionTests : RestrictionsBase + { + #region @touchhud[:]= + + [Fact] + public async Task TouchHud_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage($"@touchhud=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + + [Fact] + public async Task TouchHud_specific() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage($"@touchhud:{objectId2}=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId2, null, null)); + } + + #endregion + + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TouchWorldRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TouchWorldRestrictionTests.cs new file mode 100644 index 00000000..941c789e --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TouchWorldRestrictionTests.cs @@ -0,0 +1,22 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TouchWorldRestrictionTests : RestrictionsBase + { + #region @touchworld= + [Fact] + public async Task TouchWorld_default() + { + var objectId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var objectId2 = new Guid("11111111-1111-4111-8111-111111111111"); + var userId1 = new Guid("55555555-5555-4555-8555-555555555555"); + + await _rlv.ProcessMessage("@touchworld=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedSelf, objectId1, null, null)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.AttachedOther, objectId1, userId1, null)); + Assert.False(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.RezzedInWorld, objectId1, null, 5.0f)); + Assert.True(_rlv.Permissions.CanTouch(RlvPermissionsService.TouchLocation.Hud, objectId1, null, null)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TpLmRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TpLmRestrictionTests.cs new file mode 100644 index 00000000..fd83bd53 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TpLmRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TpLmRestrictionTests : RestrictionsBase + { + #region @tplm= + [Fact] + public async Task CanTpLm() + { + await CheckSimpleCommand("tpLm", m => m.CanTpLm()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TpLocRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TpLocRestrictionTests.cs new file mode 100644 index 00000000..b453e4fd --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TpLocRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TpLocRestrictionTests : RestrictionsBase + { + #region @tploc= + [Fact] + public async Task CanTpLoc() + { + await CheckSimpleCommand("tpLoc", m => m.CanTpLoc()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TpLocalRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TpLocalRestrictionTests.cs new file mode 100644 index 00000000..f051f7ef --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TpLocalRestrictionTests.cs @@ -0,0 +1,25 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TpLocalRestrictionTests : RestrictionsBase + { + #region @tplocal[:max_distance]= + [Fact] + public async Task CanTpLocal_Default() + { + await _rlv.ProcessMessage("@TpLocal=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTpLocal(out var distance)); + Assert.Equal(0.0f, distance, FloatTolerance); + } + + [Fact] + public async Task CanTpLocal() + { + await _rlv.ProcessMessage("@TpLocal:0.9=n", _sender.Id, _sender.Name); + + Assert.True(_rlv.Permissions.CanTpLocal(out var distance)); + Assert.Equal(0.9f, distance, FloatTolerance); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TpLureRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TpLureRestrictionTests.cs new file mode 100644 index 00000000..afe742cb --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TpLureRestrictionTests.cs @@ -0,0 +1,28 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TpLureRestrictionTests : RestrictionsBase + { + #region @tplure= + + [Fact] + public void CanTpLure_Default() + { + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.True(_rlv.Permissions.CanTPLure(null)); + Assert.True(_rlv.Permissions.CanTPLure(userId1)); + } + + [Fact] + public async Task CanTpLure() + { + await _rlv.ProcessMessage("@tplure=n", _sender.Id, _sender.Name); + + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.False(_rlv.Permissions.CanTPLure(null)); + Assert.False(_rlv.Permissions.CanTPLure(userId1)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TpLureSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TpLureSecRestrictionTests.cs new file mode 100644 index 00000000..f4fc761e --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TpLureSecRestrictionTests.cs @@ -0,0 +1,40 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TpLureSecRestrictionTests : RestrictionsBase + { + #region @tplure_sec= + [Fact] + public async Task CanTpLure_Secure_Default() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@tplure_sec=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTPLure(null)); + Assert.False(_rlv.Permissions.CanTPLure(userId1)); + Assert.False(_rlv.Permissions.CanTPLure(userId2)); + } + + [Fact] + public async Task CanTpLure_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@tplure_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@tplure:{userId1}=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@tplure:{userId2}=add", sender2.Id, sender2.Name); + + Assert.False(_rlv.Permissions.CanTPLure(null)); + Assert.True(_rlv.Permissions.CanTPLure(userId1)); + Assert.False(_rlv.Permissions.CanTPLure(userId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TpRequestRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TpRequestRestrictionTests.cs new file mode 100644 index 00000000..cf58e224 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TpRequestRestrictionTests.cs @@ -0,0 +1,33 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TpRequestRestrictionTests : RestrictionsBase + { + #region @tprequest= + + [Fact] + public async Task CanTpRequest() + { + await _rlv.ProcessMessage("@tprequest=n", _sender.Id, _sender.Name); + + var userId1 = new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + + Assert.False(_rlv.Permissions.CanTpRequest(null)); + Assert.False(_rlv.Permissions.CanTpRequest(userId1)); + } + + [Fact] + public async Task CanTpRequest_Except() + { + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@tprequest=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@tprequest:{userId1}=add", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTpRequest(null)); + Assert.True(_rlv.Permissions.CanTpRequest(userId1)); + Assert.False(_rlv.Permissions.CanTpRequest(userId2)); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/TpRequestSecRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/TpRequestSecRestrictionTests.cs new file mode 100644 index 00000000..84dd7ba5 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/TpRequestSecRestrictionTests.cs @@ -0,0 +1,39 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class TpRequestSecRestrictionTests : RestrictionsBase + { + #region @tprequest_sec= + + [Fact] + public async Task CanTpRequest_Secure_Default() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@tprequest_sec=n", _sender.Id, _sender.Name); + + Assert.False(_rlv.Permissions.CanTpRequest(null)); + Assert.False(_rlv.Permissions.CanTpRequest(userId1)); + Assert.False(_rlv.Permissions.CanTpRequest(userId2)); + } + + [Fact] + public async Task CanTpRequest_Secure() + { + var sender2 = new RlvObject("Sender 2", new Guid("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")); + var userId1 = new Guid("00000000-0000-4000-8000-000000000000"); + var userId2 = new Guid("11111111-1111-4111-8111-111111111111"); + + await _rlv.ProcessMessage("@tprequest_sec=n", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@tprequest:{userId1}=add", _sender.Id, _sender.Name); + await _rlv.ProcessMessage($"@tprequest:{userId2}=add", sender2.Id, sender2.Name); + + Assert.False(_rlv.Permissions.CanTpRequest(null)); + Assert.True(_rlv.Permissions.CanTpRequest(userId1)); + Assert.False(_rlv.Permissions.CanTpRequest(userId2)); + } + + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/UnsitRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/UnsitRestrictionTests.cs new file mode 100644 index 00000000..40e301c2 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/UnsitRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class UnsitRestrictionTests : RestrictionsBase + { + #region @unsit= + [Fact] + public async Task CanUnsit() + { + await CheckSimpleCommand("unsit", m => m.CanUnsit()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ViewNoteRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ViewNoteRestrictionTests.cs new file mode 100644 index 00000000..ff142cdd --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ViewNoteRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ViewNoteRestrictionTests : RestrictionsBase + { + #region @viewnote= + [Fact] + public async Task CanViewNote() + { + await CheckSimpleCommand("viewNote", m => m.CanViewNote()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ViewScriptRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ViewScriptRestrictionTests.cs new file mode 100644 index 00000000..7fc91fc0 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ViewScriptRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ViewScriptRestrictionTests : RestrictionsBase + { + #region @viewscript= + [Fact] + public async Task CanViewScript() + { + await CheckSimpleCommand("viewScript", m => m.CanViewScript()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/Restrictions/ViewTextureRestrictionTests.cs b/LibreMetaverse.RLV.Tests/Restrictions/ViewTextureRestrictionTests.cs new file mode 100644 index 00000000..ef88b804 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/Restrictions/ViewTextureRestrictionTests.cs @@ -0,0 +1,13 @@ +namespace LibreMetaverse.RLV.Tests.Restrictions +{ + public class ViewTextureRestrictionTests : RestrictionsBase + { + #region @viewtexture= + [Fact] + public async Task CanViewTexture() + { + await CheckSimpleCommand("viewTexture", m => m.CanViewTexture()); + } + #endregion + } +} diff --git a/LibreMetaverse.RLV.Tests/RestrictionsBase.cs b/LibreMetaverse.RLV.Tests/RestrictionsBase.cs new file mode 100644 index 00000000..24755ed4 --- /dev/null +++ b/LibreMetaverse.RLV.Tests/RestrictionsBase.cs @@ -0,0 +1,98 @@ +using Moq; + +namespace LibreMetaverse.RLV.Tests +{ + public class RestrictionsBase + { + public record RlvObject(string Name, Guid Id); + + protected readonly RlvObject _sender; + protected readonly Mock _queryCallbacks; + protected readonly Mock _actionCallbacks; + protected readonly RlvService _rlv; + + public const float FloatTolerance = 0.00001f; + + public RestrictionsBase() + { + _sender = new RlvObject("Sender 1", new Guid("ffffffff-ffff-4fff-8fff-ffffffffffff")); + _queryCallbacks = new Mock(); + _actionCallbacks = new Mock(); + _rlv = new RlvService(_queryCallbacks.Object, _actionCallbacks.Object, true); + } + + protected async Task CheckSimpleCommand(string cmd, Func canFunc) + { + await _rlv.ProcessMessage($"@{cmd}=n", _sender.Id, _sender.Name); + Assert.False(canFunc(_rlv.Permissions)); + + await _rlv.ProcessMessage($"@{cmd}=y", _sender.Id, _sender.Name); + Assert.True(canFunc(_rlv.Permissions)); + } + + protected void SeedBlacklist(string seed) + { + var blacklistEntries = seed.Split(',', StringSplitOptions.RemoveEmptyEntries); + foreach (var item in blacklistEntries) + { + _rlv.Blacklist.BlacklistBehavior(item.Trim()); + } + } + + // + // RLVA stuff to implement + // + + // @getattachnames[:]= + // @getaddattachnames[:]= + // @getremattachnames[:]= + // @getoutfitnames= + // @getaddoutfitnames= + // @getremoutfitnames= + + // @fly:[true|false]=force + + // @setcam_eyeoffset[:]=force, + // @setcam_eyeoffsetscale[:]=force + // @setcam_focusoffset[:]=force + // @setcam_focus:[;[;]]=force + // @setcam_mode[: