2025-08-17 19:55:33 -04:00
using Moq ;
namespace LibreMetaverse.RLV.Tests.Commands
{
public class AttachAllCommandTests : RestrictionsBase
{
#region @attachallover @attachalloverorreplace @attachall : < folder1 / . . . / folderN > = 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 ;
2025-08-21 04:43:06 -04:00
var inventoryMap = new InventoryMap ( sharedFolder , [ ] ) ;
2025-08-17 19:55:33 -04:00
_queryCallbacks . Setup ( e = >
2025-08-21 04:43:06 -04:00
e . TryGetInventoryMapAsync ( default )
) . ReturnsAsync ( ( true , inventoryMap ) ) ;
2025-08-17 19:55:33 -04:00
_actionCallbacks . Setup ( e = >
e . AttachAsync ( It . IsAny < IReadOnlyList < AttachmentRequest > > ( ) , It . IsAny < CancellationToken > ( ) )
) . Returns ( Task . CompletedTask ) ;
// Attach everything inside of of the Clothing folder, and all of its subfolders recursively
var expected = new HashSet < AttachmentRequest > ( )
{
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 < IReadOnlyList < AttachmentRequest > > ( ids = >
ids ! = null & &
ids . Count = = expected . Count & &
expected . SetEquals ( ids )
) ,
It . IsAny < CancellationToken > ( )
) ,
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" ;
2025-08-21 04:43:06 -04:00
var inventoryMap = new InventoryMap ( sharedFolder , [ ] ) ;
2025-08-17 19:55:33 -04:00
_queryCallbacks . Setup ( e = >
2025-08-21 04:43:06 -04:00
e . TryGetInventoryMapAsync ( default )
) . ReturnsAsync ( ( true , inventoryMap ) ) ;
2025-08-17 19:55:33 -04:00
_actionCallbacks . Setup ( e = >
e . AttachAsync ( It . IsAny < IReadOnlyList < AttachmentRequest > > ( ) , It . IsAny < CancellationToken > ( ) )
) . 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 < AttachmentRequest > ( )
{
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 < IReadOnlyList < AttachmentRequest > > ( ids = >
ids ! = null & &
ids . Count = = expected . Count & &
expected . SetEquals ( ids )
) ,
It . IsAny < CancellationToken > ( )
) ,
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" ;
2025-08-21 04:43:06 -04:00
var inventoryMap = new InventoryMap ( sharedFolder , [ ] ) ;
2025-08-17 19:55:33 -04:00
_queryCallbacks . Setup ( e = >
2025-08-21 04:43:06 -04:00
e . TryGetInventoryMapAsync ( default )
) . ReturnsAsync ( ( true , inventoryMap ) ) ;
2025-08-17 19:55:33 -04:00
_actionCallbacks . Setup ( e = >
e . AttachAsync ( It . IsAny < IReadOnlyList < AttachmentRequest > > ( ) , It . IsAny < CancellationToken > ( ) )
) . 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 < AttachmentRequest > ( )
{
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 < IReadOnlyList < AttachmentRequest > > ( ids = >
ids ! = null & &
ids . Count = = expected . Count & &
expected . SetEquals ( ids )
) ,
It . IsAny < CancellationToken > ( )
) ,
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" ;
2025-08-21 04:43:06 -04:00
var inventoryMap = new InventoryMap ( sharedFolder , [ ] ) ;
2025-08-17 19:55:33 -04:00
_queryCallbacks . Setup ( e = >
2025-08-21 04:43:06 -04:00
e . TryGetInventoryMapAsync ( default )
) . ReturnsAsync ( ( true , inventoryMap ) ) ;
2025-08-17 19:55:33 -04:00
_actionCallbacks . Setup ( e = >
e . AttachAsync ( It . IsAny < IReadOnlyList < AttachmentRequest > > ( ) , It . IsAny < CancellationToken > ( ) )
) . 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 < AttachmentRequest > ( )
{
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 < IReadOnlyList < AttachmentRequest > > ( ids = >
ids ! = null & &
ids . Count = = expected . Count & &
expected . SetEquals ( ids )
) ,
It . IsAny < CancellationToken > ( )
) ,
Times . Once
) ;
_actionCallbacks . VerifyNoOtherCalls ( ) ;
}
#endregion
}
}