diff --git a/src/Api/Controllers/CollectionsController.cs b/src/Api/Controllers/CollectionsController.cs index ba270ceb8..b4b1681dd 100644 --- a/src/Api/Controllers/CollectionsController.cs +++ b/src/Api/Controllers/CollectionsController.cs @@ -4,7 +4,6 @@ using Bit.Api.Utilities; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Core.Context; using Bit.Core.Entities; -using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; @@ -26,8 +25,6 @@ public class CollectionsController : Controller private readonly IAuthorizationService _authorizationService; private readonly ICurrentContext _currentContext; private readonly IBulkAddCollectionAccessCommand _bulkAddCollectionAccessCommand; - private readonly IOrganizationUserRepository _organizationUserRepository; - private readonly IApplicationCacheService _applicationCacheService; public CollectionsController( ICollectionRepository collectionRepository, @@ -36,511 +33,21 @@ public class CollectionsController : Controller IUserService userService, IAuthorizationService authorizationService, ICurrentContext currentContext, - IBulkAddCollectionAccessCommand bulkAddCollectionAccessCommand, - IOrganizationUserRepository organizationUserRepository, - IApplicationCacheService applicationCacheService) + IBulkAddCollectionAccessCommand bulkAddCollectionAccessCommand) { _collectionRepository = collectionRepository; - _organizationUserRepository = organizationUserRepository; _collectionService = collectionService; _deleteCollectionCommand = deleteCollectionCommand; _userService = userService; _authorizationService = authorizationService; _currentContext = currentContext; _bulkAddCollectionAccessCommand = bulkAddCollectionAccessCommand; - _organizationUserRepository = organizationUserRepository; - _applicationCacheService = applicationCacheService; } [HttpGet("{id}")] public async Task Get(Guid orgId, Guid id) { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - return await Get_vNext(id); - } - - // Old pre-flexible collections logic follows - if (!await CanViewCollectionAsync(orgId, id)) - { - throw new NotFoundException(); - } - - var collection = await GetCollectionAsync(id, orgId); - - return new CollectionResponseModel(collection); - } - - [HttpGet("{id}/details")] - public async Task GetDetails(Guid orgId, Guid id) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - return await GetDetails_vNext(id); - } - - // Old pre-flexible collections logic follows - if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId)) - { - throw new NotFoundException(); - } - - if (await _currentContext.ViewAllCollections(orgId)) - { - (var collection, var access) = await _collectionRepository.GetByIdWithAccessAsync(id); - if (collection == null || collection.OrganizationId != orgId) - { - throw new NotFoundException(); - } - - return new CollectionAccessDetailsResponseModel(collection, access.Groups, access.Users); - } - else - { - (var collection, var access) = await _collectionRepository.GetByIdWithAccessAsync(id, - _currentContext.UserId.Value, false); - if (collection == null || collection.OrganizationId != orgId) - { - throw new NotFoundException(); - } - - return new CollectionAccessDetailsResponseModel(collection, access.Groups, access.Users); - } - } - - [HttpGet("details")] - public async Task> GetManyWithDetails(Guid orgId) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - return await GetManyWithDetails_vNext(orgId); - } - - // Old pre-flexible collections logic follows - if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId) && !await _currentContext.ManageGroups(orgId)) - { - throw new NotFoundException(); - } - - // We always need to know which collections the current user is assigned to - var assignedOrgCollections = - await _collectionRepository.GetManyByUserIdWithAccessAsync(_currentContext.UserId.Value, orgId, - false); - - if (await _currentContext.ViewAllCollections(orgId) || await _currentContext.ManageUsers(orgId)) - { - // The user can view all collections, but they may not always be assigned to all of them - var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(orgId); - - return new ListResponseModel(allOrgCollections.Select(c => - new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users) - { - // Manually determine which collections they're assigned to - Assigned = assignedOrgCollections.Any(ac => ac.Item1.Id == c.Item1.Id) - }) - ); - } - - return new ListResponseModel(assignedOrgCollections.Select(c => - new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users) - { - Assigned = true // Mapping from assignedOrgCollections implies they're all assigned - }) - ); - } - - [HttpGet("")] - public async Task> Get(Guid orgId) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - return await GetByOrgId_vNext(orgId); - } - - // Old pre-flexible collections logic follows - IEnumerable orgCollections = null; - if (await _currentContext.ManageGroups(orgId)) - { - // ManageGroups users need to see all collections to manage other users' collection access. - // This is not added to collectionService.GetOrganizationCollectionsAsync as that may have - // unintended consequences on other logic that also uses that method. - // This is a quick fix but it will be properly fixed by permission changes in Flexible Collections. - - // Get all collections for organization - orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(orgId); - } - else - { - // Returns all collections or collections the user is assigned to, depending on permissions - orgCollections = await _collectionService.GetOrganizationCollectionsAsync(orgId); - } - - var responses = orgCollections.Select(c => new CollectionResponseModel(c)); - return new ListResponseModel(responses); - } - - [HttpGet("~/collections")] - public async Task> GetUser() - { - var collections = await _collectionRepository.GetManyByUserIdAsync( - _userService.GetProperUserId(User).Value, false); - var responses = collections.Select(c => new CollectionDetailsResponseModel(c)); - return new ListResponseModel(responses); - } - - [HttpGet("{id}/users")] - public async Task> GetUsers(Guid orgId, Guid id) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - return await GetUsers_vNext(id); - } - - // Old pre-flexible collections logic follows - var collection = await GetCollectionAsync(id, orgId); - var collectionUsers = await _collectionRepository.GetManyUsersByIdAsync(collection.Id); - var responses = collectionUsers.Select(cu => new SelectionReadOnlyResponseModel(cu)); - return responses; - } - - [HttpPost("")] - public async Task Post(Guid orgId, [FromBody] CollectionRequestModel model) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - return await Post_vNext(orgId, model); - } - - var collection = model.ToCollection(orgId); - - var authorized = await CanCreateCollection(orgId, collection.Id) || await CanEditCollectionAsync(orgId, collection.Id); - if (!authorized) - { - throw new NotFoundException(); - } - - var groups = model.Groups?.Select(g => g.ToSelectionReadOnly()); - var users = model.Users?.Select(g => g.ToSelectionReadOnly()).ToList() ?? new List(); - - // Pre-flexible collections logic assigned Managers to collections they create - var assignUserToCollection = - !await _currentContext.EditAnyCollection(orgId) && - await _currentContext.EditAssignedCollections(orgId); - var isNewCollection = collection.Id == default; - - if (assignUserToCollection && isNewCollection && _currentContext.UserId.HasValue) - { - var orgUser = await _organizationUserRepository.GetByOrganizationAsync(orgId, _currentContext.UserId.Value); - // don't add duplicate access if the user has already specified it themselves - var existingAccess = users.Any(u => u.Id == orgUser.Id); - if (orgUser is { Status: OrganizationUserStatusType.Confirmed } && !existingAccess) - { - users.Add(new CollectionAccessSelection - { - Id = orgUser.Id, - ReadOnly = false - }); - } - } - - await _collectionService.SaveAsync(collection, groups, users); - - if (!_currentContext.UserId.HasValue || await _currentContext.ProviderUserForOrgAsync(orgId)) - { - return new CollectionResponseModel(collection); - } - - // If we have a user, fetch the collection to get the latest permission details - var userCollectionDetails = await _collectionRepository.GetByIdAsync(collection.Id, - _currentContext.UserId.Value, await FlexibleCollectionsIsEnabledAsync(collection.OrganizationId)); - - return userCollectionDetails == null - ? new CollectionResponseModel(collection) - : new CollectionDetailsResponseModel(userCollectionDetails); - } - - [HttpPut("{id}")] - [HttpPost("{id}")] - public async Task Put(Guid orgId, Guid id, [FromBody] CollectionRequestModel model) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - return await Put_vNext(id, model); - } - - // Old pre-flexible collections logic follows - if (!await CanEditCollectionAsync(orgId, id)) - { - throw new NotFoundException(); - } - - var collection = await GetCollectionAsync(id, orgId); - var groups = model.Groups?.Select(g => g.ToSelectionReadOnly()); - var users = model.Users?.Select(g => g.ToSelectionReadOnly()); - await _collectionService.SaveAsync(model.ToCollection(collection), groups, users); - - if (!_currentContext.UserId.HasValue || await _currentContext.ProviderUserForOrgAsync(collection.OrganizationId)) - { - return new CollectionResponseModel(collection); - } - - // If we have a user, fetch the collection details to get the latest permission details for the user - var updatedCollectionDetails = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value, await FlexibleCollectionsIsEnabledAsync(collection.OrganizationId)); - - return updatedCollectionDetails == null - ? new CollectionResponseModel(collection) - : new CollectionDetailsResponseModel(updatedCollectionDetails); - } - - [HttpPut("{id}/users")] - public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable model) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - await PutUsers_vNext(id, model); - return; - } - - // Old pre-flexible collections logic follows - if (!await CanEditCollectionAsync(orgId, id)) - { - throw new NotFoundException(); - } - - var collection = await GetCollectionAsync(id, orgId); - await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly())); - } - - [HttpPost("bulk-access")] - public async Task PostBulkCollectionAccess(Guid orgId, [FromBody] BulkCollectionAccessRequestModel model) - { - // Authorization logic assumes flexible collections is enabled - // Remove after all organizations have been migrated - if (!await FlexibleCollectionsIsEnabledAsync(orgId)) - { - throw new NotFoundException("Feature disabled."); - } - - var collections = await _collectionRepository.GetManyByManyIdsAsync(model.CollectionIds); - if (collections.Count(c => c.OrganizationId == orgId) != model.CollectionIds.Count()) - { - throw new NotFoundException("One or more collections not found."); - } - - var result = await _authorizationService.AuthorizeAsync(User, collections, - new[] { BulkCollectionOperations.ModifyUserAccess, BulkCollectionOperations.ModifyGroupAccess }); - - if (!result.Succeeded) - { - throw new NotFoundException(); - } - - await _bulkAddCollectionAccessCommand.AddAccessAsync( - collections, - model.Users?.Select(u => u.ToSelectionReadOnly()).ToList(), - model.Groups?.Select(g => g.ToSelectionReadOnly()).ToList()); - } - - [HttpDelete("{id}")] - [HttpPost("{id}/delete")] - public async Task Delete(Guid orgId, Guid id) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - await Delete_vNext(id); - return; - } - - // Old pre-flexible collections logic follows - if (!await CanDeleteCollectionAsync(orgId, id)) - { - throw new NotFoundException(); - } - - var collection = await GetCollectionAsync(id, orgId); - await _deleteCollectionCommand.DeleteAsync(collection); - } - - [HttpDelete("")] - [HttpPost("delete")] - public async Task DeleteMany(Guid orgId, [FromBody] CollectionBulkDeleteRequestModel model) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids); - var result = await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.Delete); - if (!result.Succeeded) - { - throw new NotFoundException(); - } - - await _deleteCollectionCommand.DeleteManyAsync(collections); - return; - } - - // Old pre-flexible collections logic follows - if (!await _currentContext.DeleteAssignedCollections(orgId) && !await DeleteAnyCollection(orgId)) - { - throw new NotFoundException(); - } - - var userCollections = await _collectionService.GetOrganizationCollectionsAsync(orgId); - var filteredCollections = userCollections - .Where(c => model.Ids.Contains(c.Id) && c.OrganizationId == orgId); - - if (!filteredCollections.Any()) - { - throw new BadRequestException("No collections found."); - } - - await _deleteCollectionCommand.DeleteManyAsync(filteredCollections); - } - - [HttpDelete("{id}/user/{orgUserId}")] - [HttpPost("{id}/delete-user/{orgUserId}")] - public async Task DeleteUser(Guid orgId, Guid id, Guid orgUserId) - { - if (await FlexibleCollectionsIsEnabledAsync(orgId)) - { - // New flexible collections logic - await DeleteUser_vNext(id, orgUserId); - return; - } - - // Old pre-flexible collections logic follows - var collection = await GetCollectionAsync(id, orgId); - await _collectionService.DeleteUserAsync(collection, orgUserId); - } - - [Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")] - private async Task GetCollectionAsync(Guid id, Guid orgId) - { - Collection collection = default; - if (await _currentContext.ViewAllCollections(orgId)) - { - collection = await _collectionRepository.GetByIdAsync(id); - } - else if (await _currentContext.ViewAssignedCollections(orgId)) - { - collection = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value, false); - } - - if (collection == null || collection.OrganizationId != orgId) - { - throw new NotFoundException(); - } - - return collection; - } - - [Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")] - private async Task CanCreateCollection(Guid orgId, Guid collectionId) - { - if (collectionId != default) - { - return false; - } - - return await _currentContext.OrganizationManager(orgId) || (_currentContext.Organizations?.Any(o => o.Id == orgId && - (o.Permissions?.CreateNewCollections ?? false)) ?? false); - } - - [Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")] - private async Task CanEditCollectionAsync(Guid orgId, Guid collectionId) - { - if (collectionId == default) - { - return false; - } - - if (await _currentContext.EditAnyCollection(orgId)) - { - return true; - } - - if (await _currentContext.EditAssignedCollections(orgId)) - { - var collectionDetails = - await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value, false); - return collectionDetails != null; - } - - return false; - } - - [Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")] - private async Task CanDeleteCollectionAsync(Guid orgId, Guid collectionId) - { - if (collectionId == default) - { - return false; - } - - if (await DeleteAnyCollection(orgId)) - { - return true; - } - - if (await _currentContext.DeleteAssignedCollections(orgId)) - { - var collectionDetails = - await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value, false); - return collectionDetails != null; - } - - return false; - } - - [Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")] - private async Task DeleteAnyCollection(Guid orgId) - { - return await _currentContext.OrganizationAdmin(orgId) || - (_currentContext.Organizations?.Any(o => o.Id == orgId - && (o.Permissions?.DeleteAnyCollection ?? false)) ?? false); - } - - [Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")] - private async Task CanViewCollectionAsync(Guid orgId, Guid collectionId) - { - if (collectionId == default) - { - return false; - } - - if (await _currentContext.ViewAllCollections(orgId)) - { - return true; - } - - if (await _currentContext.ViewAssignedCollections(orgId)) - { - var collectionDetails = - await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value, false); - return collectionDetails != null; - } - - return false; - } - - [Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")] - private async Task ViewAtLeastOneCollectionAsync(Guid orgId) - { - return await _currentContext.ViewAllCollections(orgId) || await _currentContext.ViewAssignedCollections(orgId); - } - - private async Task Get_vNext(Guid collectionId) - { - var collection = await _collectionRepository.GetByIdAsync(collectionId); + var collection = await _collectionRepository.GetByIdAsync(id); var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Read)).Succeeded; if (!authorized) { @@ -550,9 +57,9 @@ public class CollectionsController : Controller return new CollectionResponseModel(collection); } - private async Task GetDetails_vNext(Guid id) + [HttpGet("{id}/details")] + public async Task GetDetails(Guid orgId, Guid id) { - // New flexible collections logic var collectionAdminDetails = await _collectionRepository.GetByIdWithPermissionsAsync(id, _currentContext.UserId, true); @@ -565,7 +72,8 @@ public class CollectionsController : Controller return new CollectionAccessDetailsResponseModel(collectionAdminDetails); } - private async Task> GetManyWithDetails_vNext(Guid orgId) + [HttpGet("details")] + public async Task> GetManyWithDetails(Guid orgId) { var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithPermissionsAsync( orgId, _currentContext.UserId.Value, true); @@ -587,7 +95,8 @@ public class CollectionsController : Controller )); } - private async Task> GetByOrgId_vNext(Guid orgId) + [HttpGet("")] + public async Task> Get(Guid orgId) { IEnumerable orgCollections; @@ -606,7 +115,17 @@ public class CollectionsController : Controller return new ListResponseModel(responses); } - private async Task> GetUsers_vNext(Guid id) + [HttpGet("~/collections")] + public async Task> GetUser() + { + var collections = await _collectionRepository.GetManyByUserIdAsync( + _userService.GetProperUserId(User).Value, false); + var responses = collections.Select(c => new CollectionDetailsResponseModel(c)); + return new ListResponseModel(responses); + } + + [HttpGet("{id}/users")] + public async Task> GetUsers(Guid orgId, Guid id) { var collection = await _collectionRepository.GetByIdAsync(id); var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ReadAccess)).Succeeded; @@ -620,7 +139,8 @@ public class CollectionsController : Controller return responses; } - private async Task Post_vNext(Guid orgId, [FromBody] CollectionRequestModel model) + [HttpPost("")] + public async Task Post(Guid orgId, [FromBody] CollectionRequestModel model) { var collection = model.ToCollection(orgId); @@ -646,7 +166,9 @@ public class CollectionsController : Controller return new CollectionAccessDetailsResponseModel(collectionWithPermissions); } - private async Task Put_vNext(Guid id, CollectionRequestModel model) + [HttpPut("{id}")] + [HttpPost("{id}")] + public async Task Put(Guid orgId, Guid id, [FromBody] CollectionRequestModel model) { var collection = await _collectionRepository.GetByIdAsync(id); var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Update)).Succeeded; @@ -670,7 +192,8 @@ public class CollectionsController : Controller return new CollectionAccessDetailsResponseModel(collectionWithPermissions); } - private async Task PutUsers_vNext(Guid id, IEnumerable model) + [HttpPut("{id}/users")] + public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable model) { var collection = await _collectionRepository.GetByIdAsync(id); var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded; @@ -682,7 +205,32 @@ public class CollectionsController : Controller await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly())); } - private async Task Delete_vNext(Guid id) + [HttpPost("bulk-access")] + public async Task PostBulkCollectionAccess(Guid orgId, [FromBody] BulkCollectionAccessRequestModel model) + { + var collections = await _collectionRepository.GetManyByManyIdsAsync(model.CollectionIds); + if (collections.Count(c => c.OrganizationId == orgId) != model.CollectionIds.Count()) + { + throw new NotFoundException("One or more collections not found."); + } + + var result = await _authorizationService.AuthorizeAsync(User, collections, + new[] { BulkCollectionOperations.ModifyUserAccess, BulkCollectionOperations.ModifyGroupAccess }); + + if (!result.Succeeded) + { + throw new NotFoundException(); + } + + await _bulkAddCollectionAccessCommand.AddAccessAsync( + collections, + model.Users?.Select(u => u.ToSelectionReadOnly()).ToList(), + model.Groups?.Select(g => g.ToSelectionReadOnly()).ToList()); + } + + [HttpDelete("{id}")] + [HttpPost("{id}/delete")] + public async Task Delete(Guid orgId, Guid id) { var collection = await _collectionRepository.GetByIdAsync(id); var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Delete)).Succeeded; @@ -694,7 +242,23 @@ public class CollectionsController : Controller await _deleteCollectionCommand.DeleteAsync(collection); } - private async Task DeleteUser_vNext(Guid id, Guid orgUserId) + [HttpDelete("")] + [HttpPost("delete")] + public async Task DeleteMany(Guid orgId, [FromBody] CollectionBulkDeleteRequestModel model) + { + var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids); + var result = await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.Delete); + if (!result.Succeeded) + { + throw new NotFoundException(); + } + + await _deleteCollectionCommand.DeleteManyAsync(collections); + } + + [HttpDelete("{id}/user/{orgUserId}")] + [HttpPost("{id}/delete-user/{orgUserId}")] + public async Task DeleteUser(Guid orgId, Guid id, Guid orgUserId) { var collection = await _collectionRepository.GetByIdAsync(id); var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded; @@ -705,10 +269,4 @@ public class CollectionsController : Controller await _collectionService.DeleteUserAsync(collection, orgUserId); } - - private async Task FlexibleCollectionsIsEnabledAsync(Guid organizationId) - { - var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId); - return organizationAbility?.FlexibleCollections ?? false; - } } diff --git a/test/Api.Test/Controllers/CollectionsControllerTests.cs b/test/Api.Test/Controllers/CollectionsControllerTests.cs index 52062cb89..3a59edffe 100644 --- a/test/Api.Test/Controllers/CollectionsControllerTests.cs +++ b/test/Api.Test/Controllers/CollectionsControllerTests.cs @@ -2,11 +2,11 @@ using Bit.Api.Controllers; using Bit.Api.Models.Request; using Bit.Api.Vault.AuthorizationHandlers.Collections; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.Models.Data; -using Bit.Core.Models.Data.Organizations; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; @@ -23,14 +23,12 @@ namespace Bit.Api.Test.Controllers; public class CollectionsControllerTests { [Theory, BitAutoData] - public async Task Post_Success(OrganizationAbility organizationAbility, CollectionRequestModel collectionRequest, + public async Task Post_Success(Organization organization, CollectionRequestModel collectionRequest, SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - Collection ExpectedCollection() => Arg.Is(c => c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId && - c.OrganizationId == organizationAbility.Id); + c.OrganizationId == organization.Id); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), @@ -38,7 +36,7 @@ public class CollectionsControllerTests Arg.Is>(r => r.Contains(BulkCollectionOperations.Create))) .Returns(AuthorizationResult.Success()); - _ = await sutProvider.Sut.Post(organizationAbility.Id, collectionRequest); + _ = await sutProvider.Sut.Post(organization.Id, collectionRequest); await sutProvider.GetDependency() .Received(1) @@ -48,11 +46,8 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task Put_Success(Collection collection, CollectionRequestModel collectionRequest, - SutProvider sutProvider, OrganizationAbility organizationAbility) + SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collection.OrganizationId = organizationAbility.Id; - Collection ExpectedCollection() => Arg.Is(c => c.Id == collection.Id && c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId && c.OrganizationId == collection.OrganizationId); @@ -77,11 +72,8 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task Put_WithNoCollectionPermission_ThrowsNotFound(Collection collection, CollectionRequestModel collectionRequest, - SutProvider sutProvider, OrganizationAbility organizationAbility) + SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collection.OrganizationId = organizationAbility.Id; - sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), collection, @@ -96,11 +88,9 @@ public class CollectionsControllerTests } [Theory, BitAutoData] - public async Task GetOrganizationCollectionsWithGroups_WithReadAllPermissions_GetsAllCollections(OrganizationAbility organizationAbility, + public async Task GetOrganizationCollectionsWithGroups_WithReadAllPermissions_GetsAllCollections(Organization organization, Guid userId, SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - sutProvider.GetDependency().UserId.Returns(userId); sutProvider.GetDependency() @@ -110,20 +100,20 @@ public class CollectionsControllerTests Arg.Is>(requirements => requirements.Cast().All(operation => operation.Name == nameof(CollectionOperations.ReadAllWithAccess) - && operation.OrganizationId == organizationAbility.Id))) + && operation.OrganizationId == organization.Id))) .Returns(AuthorizationResult.Success()); - await sutProvider.Sut.GetManyWithDetails(organizationAbility.Id); + await sutProvider.Sut.GetManyWithDetails(organization.Id); - await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organizationAbility.Id, userId, true); + await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organization.Id, userId, true); } [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_MissingReadAllPermissions_GetsAssignedCollections( - OrganizationAbility organizationAbility, Guid userId, SutProvider sutProvider, List collections) + Organization organization, Guid userId, SutProvider sutProvider, + List collections) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collections.ForEach(c => c.OrganizationId = organizationAbility.Id); + collections.ForEach(c => c.OrganizationId = organization.Id); collections.ForEach(c => c.Manage = false); var managedCollection = collections.First(); @@ -138,7 +128,7 @@ public class CollectionsControllerTests Arg.Is>(requirements => requirements.Cast().All(operation => operation.Name == nameof(CollectionOperations.ReadAllWithAccess) - && operation.OrganizationId == organizationAbility.Id))) + && operation.OrganizationId == organization.Id))) .Returns(AuthorizationResult.Failed()); sutProvider.GetDependency() @@ -151,23 +141,23 @@ public class CollectionsControllerTests .Returns(AuthorizationResult.Success()); sutProvider.GetDependency() - .GetManyByOrganizationIdWithPermissionsAsync(organizationAbility.Id, userId, true) + .GetManyByOrganizationIdWithPermissionsAsync(organization.Id, userId, true) .Returns(collections); - var response = await sutProvider.Sut.GetManyWithDetails(organizationAbility.Id); + var response = await sutProvider.Sut.GetManyWithDetails(organization.Id); - await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organizationAbility.Id, userId, true); + await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organization.Id, userId, true); Assert.Single(response.Data); - Assert.All(response.Data, c => Assert.Equal(organizationAbility.Id, c.OrganizationId)); + Assert.All(response.Data, c => Assert.Equal(organization.Id, c.OrganizationId)); Assert.All(response.Data, c => Assert.Equal(managedCollection.Id, c.Id)); } [Theory, BitAutoData] public async Task GetOrganizationCollections_WithReadAllPermissions_GetsAllCollections( - OrganizationAbility organizationAbility, List collections, Guid userId, SutProvider sutProvider) + Organization organization, List collections, Guid userId, + SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collections.ForEach(c => c.OrganizationId = organizationAbility.Id); + collections.ForEach(c => c.OrganizationId = organization.Id); sutProvider.GetDependency().UserId.Returns(userId); @@ -178,26 +168,25 @@ public class CollectionsControllerTests Arg.Is>(requirements => requirements.Cast().All(operation => operation.Name == nameof(CollectionOperations.ReadAll) - && operation.OrganizationId == organizationAbility.Id))) + && operation.OrganizationId == organization.Id))) .Returns(AuthorizationResult.Success()); sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(organizationAbility.Id) + .GetManyByOrganizationIdAsync(organization.Id) .Returns(collections); - var response = await sutProvider.Sut.Get(organizationAbility.Id); + var response = await sutProvider.Sut.Get(organization.Id); - await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdAsync(organizationAbility.Id); + await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdAsync(organization.Id); Assert.Equal(collections.Count, response.Data.Count()); } [Theory, BitAutoData] public async Task GetOrganizationCollections_MissingReadAllPermissions_GetsManageableCollections( - OrganizationAbility organizationAbility, List collections, Guid userId, SutProvider sutProvider) + Organization organization, List collections, Guid userId, SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collections.ForEach(c => c.OrganizationId = organizationAbility.Id); + collections.ForEach(c => c.OrganizationId = organization.Id); collections.ForEach(c => c.Manage = false); var managedCollection = collections.First(); @@ -212,7 +201,7 @@ public class CollectionsControllerTests Arg.Is>(requirements => requirements.Cast().All(operation => operation.Name == nameof(CollectionOperations.ReadAll) - && operation.OrganizationId == organizationAbility.Id))) + && operation.OrganizationId == organization.Id))) .Returns(AuthorizationResult.Failed()); sutProvider.GetDependency() @@ -228,24 +217,22 @@ public class CollectionsControllerTests .GetManyByUserIdAsync(userId, false) .Returns(collections); - var result = await sutProvider.Sut.Get(organizationAbility.Id); + var result = await sutProvider.Sut.Get(organization.Id); - await sutProvider.GetDependency().DidNotReceive().GetManyByOrganizationIdAsync(organizationAbility.Id); + await sutProvider.GetDependency().DidNotReceive().GetManyByOrganizationIdAsync(organization.Id); await sutProvider.GetDependency().Received(1).GetManyByUserIdAsync(userId, false); Assert.Single(result.Data); - Assert.All(result.Data, c => Assert.Equal(organizationAbility.Id, c.OrganizationId)); + Assert.All(result.Data, c => Assert.Equal(organization.Id, c.OrganizationId)); Assert.All(result.Data, c => Assert.Equal(managedCollection.Id, c.Id)); } [Theory, BitAutoData] - public async Task DeleteMany_Success(OrganizationAbility organizationAbility, Collection collection1, Collection collection2, + public async Task DeleteMany_Success(Organization organization, Collection collection1, Collection collection2, SutProvider sutProvider) { // Arrange - var orgId = organizationAbility.Id; - ArrangeOrganizationAbility(sutProvider, organizationAbility); - + var orgId = organization.Id; var model = new CollectionBulkDeleteRequestModel { Ids = new[] { collection1.Id, collection2.Id } @@ -285,13 +272,11 @@ public class CollectionsControllerTests } [Theory, BitAutoData] - public async Task DeleteMany_PermissionDenied_ThrowsNotFound(OrganizationAbility organizationAbility, Collection collection1, + public async Task DeleteMany_PermissionDenied_ThrowsNotFound(Organization organization, Collection collection1, Collection collection2, SutProvider sutProvider) { // Arrange - var orgId = organizationAbility.Id; - ArrangeOrganizationAbility(sutProvider, organizationAbility); - + var orgId = organization.Id; var model = new CollectionBulkDeleteRequestModel { Ids = new[] { collection1.Id, collection2.Id } @@ -331,12 +316,10 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task PostBulkCollectionAccess_Success(User actingUser, List collections, - OrganizationAbility organizationAbility, SutProvider sutProvider) + Organization organization, SutProvider sutProvider) { // Arrange - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collections.ForEach(c => c.OrganizationId = organizationAbility.Id); - + collections.ForEach(c => c.OrganizationId = organization.Id); var userId = Guid.NewGuid(); var groupId = Guid.NewGuid(); var model = new BulkCollectionAccessRequestModel @@ -365,7 +348,7 @@ public class CollectionsControllerTests IEnumerable ExpectedCollectionAccess() => Arg.Is>(cols => cols.SequenceEqual(collections)); // Act - await sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model); + await sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model); // Assert await sutProvider.GetDependency().Received().AuthorizeAsync( @@ -384,11 +367,10 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task PostBulkCollectionAccess_CollectionsNotFound_Throws(User actingUser, - OrganizationAbility organizationAbility, List collections, + Organization organization, List collections, SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collections.ForEach(c => c.OrganizationId = organizationAbility.Id); + collections.ForEach(c => c.OrganizationId = organization.Id); var userId = Guid.NewGuid(); var groupId = Guid.NewGuid(); @@ -407,7 +389,7 @@ public class CollectionsControllerTests .Returns(collections.Skip(1).ToList()); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model)); + () => sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model)); Assert.Equal("One or more collections not found.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().AuthorizeAsync( @@ -421,13 +403,11 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task PostBulkCollectionAccess_CollectionsBelongToDifferentOrganizations_Throws(User actingUser, - OrganizationAbility organizationAbility, List collections, + Organization organization, List collections, SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - // First collection has a different orgId - collections.Skip(1).ToList().ForEach(c => c.OrganizationId = organizationAbility.Id); + collections.Skip(1).ToList().ForEach(c => c.OrganizationId = organization.Id); var userId = Guid.NewGuid(); var groupId = Guid.NewGuid(); @@ -446,7 +426,7 @@ public class CollectionsControllerTests .Returns(collections); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model)); + () => sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model)); Assert.Equal("One or more collections not found.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().AuthorizeAsync( @@ -458,42 +438,11 @@ public class CollectionsControllerTests .AddAccessAsync(default, default, default); } - [Theory, BitAutoData] - public async Task PostBulkCollectionAccess_FlexibleCollectionsDisabled_Throws(OrganizationAbility organizationAbility, List collections, - SutProvider sutProvider) - { - organizationAbility.FlexibleCollections = false; - sutProvider.GetDependency().GetOrganizationAbilityAsync(organizationAbility.Id) - .Returns(organizationAbility); - - var userId = Guid.NewGuid(); - var groupId = Guid.NewGuid(); - var model = new BulkCollectionAccessRequestModel - { - CollectionIds = collections.Select(c => c.Id), - Users = new[] { new SelectionReadOnlyRequestModel { Id = userId, Manage = true } }, - Groups = new[] { new SelectionReadOnlyRequestModel { Id = groupId, ReadOnly = true } }, - }; - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model)); - - Assert.Equal("Feature disabled.", exception.Message); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().AuthorizeAsync( - Arg.Any(), - Arg.Any>(), - Arg.Any>() - ); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .AddAccessAsync(default, default, default); - } - [Theory, BitAutoData] public async Task PostBulkCollectionAccess_AccessDenied_Throws(User actingUser, List collections, - OrganizationAbility organizationAbility, SutProvider sutProvider) + Organization organization, SutProvider sutProvider) { - ArrangeOrganizationAbility(sutProvider, organizationAbility); - collections.ForEach(c => c.OrganizationId = organizationAbility.Id); + collections.ForEach(c => c.OrganizationId = organization.Id); var userId = Guid.NewGuid(); var groupId = Guid.NewGuid(); @@ -522,7 +471,7 @@ public class CollectionsControllerTests IEnumerable ExpectedCollectionAccess() => Arg.Is>(cols => cols.SequenceEqual(collections)); - await Assert.ThrowsAsync(() => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model)); + await Assert.ThrowsAsync(() => sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model)); await sutProvider.GetDependency().Received().AuthorizeAsync( Arg.Any(), ExpectedCollectionAccess(), @@ -534,12 +483,4 @@ public class CollectionsControllerTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .AddAccessAsync(default, default, default); } - - private void ArrangeOrganizationAbility(SutProvider sutProvider, OrganizationAbility organizationAbility) - { - organizationAbility.FlexibleCollections = true; - - sutProvider.GetDependency().GetOrganizationAbilityAsync(organizationAbility.Id) - .Returns(organizationAbility); - } } diff --git a/test/Api.Test/Controllers/LegacyCollectionsControllerTests.cs b/test/Api.Test/Controllers/LegacyCollectionsControllerTests.cs deleted file mode 100644 index 0d2ec824f..000000000 --- a/test/Api.Test/Controllers/LegacyCollectionsControllerTests.cs +++ /dev/null @@ -1,318 +0,0 @@ -using Bit.Api.Controllers; -using Bit.Api.Models.Request; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Context; -using Bit.Core.Entities; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.Models.Data; -using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; -using Bit.Core.Repositories; -using Bit.Core.Services; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using NSubstitute; -using Xunit; -using Collection = Bit.Core.Entities.Collection; -using User = Bit.Core.Entities.User; - -namespace Bit.Api.Test.Controllers; - -/// -/// CollectionsController tests that use pre-Flexible Collections logic. To be removed when the feature flag is removed. -/// Note the feature flag defaults to OFF so it is not explicitly set in these tests. -/// -[ControllerCustomize(typeof(CollectionsController))] -[SutProviderCustomize] -public class LegacyCollectionsControllerTests -{ - [Theory, BitAutoData] - public async Task Post_Manager_AssignsToCollection_Success(Guid orgId, OrganizationUser orgUser, SutProvider sutProvider) - { - orgUser.Type = OrganizationUserType.Manager; - orgUser.Status = OrganizationUserStatusType.Confirmed; - - sutProvider.GetDependency() - .OrganizationManager(orgId) - .Returns(true); - - sutProvider.GetDependency() - .EditAnyCollection(orgId) - .Returns(false); - - sutProvider.GetDependency() - .EditAssignedCollections(orgId) - .Returns(true); - - sutProvider.GetDependency().UserId = orgUser.UserId; - - sutProvider.GetDependency().GetByOrganizationAsync(orgId, orgUser.UserId.Value) - .Returns(orgUser); - - sutProvider.GetDependency() - .GetByIdAsync(Arg.Any(), orgUser.UserId.Value, Arg.Any()) - .Returns(new CollectionDetails()); - - var collectionRequest = new CollectionRequestModel - { - Name = "encrypted_string", - ExternalId = "my_external_id" - }; - - _ = await sutProvider.Sut.Post(orgId, collectionRequest); - - var test = sutProvider.GetDependency().ReceivedCalls(); - await sutProvider.GetDependency() - .Received(1) - .SaveAsync(Arg.Any(), Arg.Any>(), - Arg.Is>(users => users.Any(u => u.Id == orgUser.Id && !u.ReadOnly && !u.HidePasswords && !u.Manage))); - } - - [Theory, BitAutoData] - public async Task Post_Owner_DoesNotAssignToCollection_Success(Guid orgId, OrganizationUser orgUser, SutProvider sutProvider) - { - orgUser.Type = OrganizationUserType.Owner; - orgUser.Status = OrganizationUserStatusType.Confirmed; - - sutProvider.GetDependency() - .OrganizationManager(orgId) - .Returns(true); - - sutProvider.GetDependency() - .EditAnyCollection(orgId) - .Returns(true); - - sutProvider.GetDependency() - .EditAssignedCollections(orgId) - .Returns(true); - - sutProvider.GetDependency().UserId = orgUser.UserId; - - sutProvider.GetDependency().GetByOrganizationAsync(orgId, orgUser.UserId.Value) - .Returns(orgUser); - - sutProvider.GetDependency() - .GetByIdAsync(Arg.Any(), orgUser.UserId.Value, Arg.Any()) - .Returns(new CollectionDetails()); - - var collectionRequest = new CollectionRequestModel - { - Name = "encrypted_string", - ExternalId = "my_external_id" - }; - - _ = await sutProvider.Sut.Post(orgId, collectionRequest); - - var test = sutProvider.GetDependency().ReceivedCalls(); - await sutProvider.GetDependency() - .Received(1) - .SaveAsync(Arg.Any(), Arg.Any>(), - Arg.Is>(users => !users.Any())); - } - - [Theory, BitAutoData] - public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest, - SutProvider sutProvider) - { - sutProvider.GetDependency() - .ViewAssignedCollections(orgId) - .Returns(true); - - sutProvider.GetDependency() - .EditAssignedCollections(orgId) - .Returns(true); - - sutProvider.GetDependency() - .UserId - .Returns(userId); - - sutProvider.GetDependency() - .GetByIdAsync(collectionId, userId, Arg.Any()) - .Returns(new CollectionDetails - { - OrganizationId = orgId, - }); - - _ = await sutProvider.Sut.Put(orgId, collectionId, collectionRequest); - } - - [Theory, BitAutoData] - public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest, - SutProvider sutProvider) - { - sutProvider.GetDependency() - .EditAssignedCollections(orgId) - .Returns(true); - - sutProvider.GetDependency() - .UserId - .Returns(userId); - - sutProvider.GetDependency() - .GetByIdAsync(collectionId, userId, Arg.Any()) - .Returns(Task.FromResult(null)); - - _ = await Assert.ThrowsAsync(async () => await sutProvider.Sut.Put(orgId, collectionId, collectionRequest)); - } - - [Theory, BitAutoData] - public async Task GetOrganizationCollectionsWithGroups_NoManagerPermissions_ThrowsNotFound(Organization organization, SutProvider sutProvider) - { - sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(false); - - await Assert.ThrowsAsync(() => sutProvider.Sut.GetManyWithDetails(organization.Id)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdWithAccessAsync(default, default, default); - } - - [Theory, BitAutoData] - public async Task GetOrganizationCollectionsWithGroups_AdminPermissions_GetsAllCollections(Organization organization, User user, SutProvider sutProvider) - { - sutProvider.GetDependency().UserId.Returns(user.Id); - sutProvider.GetDependency().ViewAllCollections(organization.Id).Returns(true); - sutProvider.GetDependency().OrganizationAdmin(organization.Id).Returns(true); - - await sutProvider.Sut.GetManyWithDetails(organization.Id); - - await sutProvider.GetDependency().Received().GetManyByOrganizationIdWithAccessAsync(organization.Id); - await sutProvider.GetDependency().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id, Arg.Any()); - } - - [Theory, BitAutoData] - public async Task GetOrganizationCollectionsWithGroups_MissingViewAllPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider sutProvider) - { - sutProvider.GetDependency().UserId.Returns(user.Id); - sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(true); - sutProvider.GetDependency().OrganizationManager(organization.Id).Returns(true); - - await sutProvider.Sut.GetManyWithDetails(organization.Id); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default); - await sutProvider.GetDependency().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id, Arg.Any()); - } - - [Theory, BitAutoData] - public async Task GetOrganizationCollectionsWithGroups_CustomUserWithManagerPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider sutProvider) - { - sutProvider.GetDependency().UserId.Returns(user.Id); - sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(true); - sutProvider.GetDependency().EditAssignedCollections(organization.Id).Returns(true); - - - await sutProvider.Sut.GetManyWithDetails(organization.Id); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default); - await sutProvider.GetDependency().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id, Arg.Any()); - } - - - [Theory, BitAutoData] - public async Task DeleteMany_Success(Guid orgId, User user, Collection collection1, Collection collection2, SutProvider sutProvider) - { - // Arrange - var model = new CollectionBulkDeleteRequestModel - { - Ids = new[] { collection1.Id, collection2.Id }, - }; - - var collections = new List - { - new CollectionDetails - { - Id = collection1.Id, - OrganizationId = orgId, - }, - new CollectionDetails - { - Id = collection2.Id, - OrganizationId = orgId, - }, - }; - - sutProvider.GetDependency() - .DeleteAssignedCollections(orgId) - .Returns(true); - - sutProvider.GetDependency() - .UserId - .Returns(user.Id); - - sutProvider.GetDependency() - .GetOrganizationCollectionsAsync(orgId) - .Returns(collections); - - // Act - await sutProvider.Sut.DeleteMany(orgId, model); - - // Assert - await sutProvider.GetDependency() - .Received(1) - .DeleteManyAsync(Arg.Is>(coll => coll.Select(c => c.Id).SequenceEqual(collections.Select(c => c.Id)))); - - } - - [Theory, BitAutoData] - public async Task DeleteMany_CanNotDeleteAssignedCollection_ThrowsNotFound(Guid orgId, Collection collection1, Collection collection2, SutProvider sutProvider) - { - // Arrange - var model = new CollectionBulkDeleteRequestModel - { - Ids = new[] { collection1.Id, collection2.Id }, - }; - - sutProvider.GetDependency() - .DeleteAssignedCollections(orgId) - .Returns(false); - - // Assert - await Assert.ThrowsAsync(() => - sutProvider.Sut.DeleteMany(orgId, model)); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .DeleteManyAsync((IEnumerable)default); - - } - - - [Theory, BitAutoData] - public async Task DeleteMany_UserCanNotAccessCollections_FiltersOutInvalid(Guid orgId, User user, Collection collection1, Collection collection2, SutProvider sutProvider) - { - // Arrange - var model = new CollectionBulkDeleteRequestModel - { - Ids = new[] { collection1.Id, collection2.Id }, - }; - - var collections = new List - { - new CollectionDetails - { - Id = collection2.Id, - OrganizationId = orgId, - }, - }; - - sutProvider.GetDependency() - .DeleteAssignedCollections(orgId) - .Returns(true); - - sutProvider.GetDependency() - .UserId - .Returns(user.Id); - - sutProvider.GetDependency() - .GetOrganizationCollectionsAsync(orgId) - .Returns(collections); - - // Act - await sutProvider.Sut.DeleteMany(orgId, model); - - // Assert - await sutProvider.GetDependency() - .Received(1) - .DeleteManyAsync(Arg.Is>(coll => coll.Select(c => c.Id).SequenceEqual(collections.Select(c => c.Id)))); - } - - -}