diff --git a/test/Api.Test/Controllers/CollectionsControllerTests.cs b/test/Api.Test/Controllers/CollectionsControllerTests.cs index 196569acb3..9e3db4741a 100644 --- a/test/Api.Test/Controllers/CollectionsControllerTests.cs +++ b/test/Api.Test/Controllers/CollectionsControllerTests.cs @@ -2,6 +2,7 @@ using Bit.Api.Controllers; using Bit.Api.Models.Request; using Bit.Api.Vault.AuthorizationHandlers.Collections; +using Bit.Core; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Exceptions; @@ -25,6 +26,8 @@ public class CollectionsControllerTests public async Task Post_Success(Guid orgId, CollectionRequestModel collectionRequest, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); + Collection ExpectedCollection() => Arg.Is(c => c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId && c.OrganizationId == orgId); @@ -47,6 +50,7 @@ public class CollectionsControllerTests public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); sutProvider.GetDependency() .ViewAssignedCollections(orgId) .Returns(true); @@ -73,6 +77,7 @@ public class CollectionsControllerTests public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); sutProvider.GetDependency() .EditAssignedCollections(orgId) .Returns(true); @@ -91,6 +96,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_NoManagerPermissions_ThrowsNotFound(Organization organization, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(false); await Assert.ThrowsAsync(() => sutProvider.Sut.GetManyWithDetails(organization.Id)); @@ -101,6 +107,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_AdminPermissions_GetsAllCollections(Organization organization, User user, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); sutProvider.GetDependency().UserId.Returns(user.Id); sutProvider.GetDependency().ViewAllCollections(organization.Id).Returns(true); sutProvider.GetDependency().OrganizationAdmin(organization.Id).Returns(true); @@ -114,6 +121,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_MissingViewAllPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); sutProvider.GetDependency().UserId.Returns(user.Id); sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(true); sutProvider.GetDependency().OrganizationManager(organization.Id).Returns(true); @@ -127,6 +135,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_CustomUserWithManagerPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); sutProvider.GetDependency().UserId.Returns(user.Id); sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(true); sutProvider.GetDependency().EditAssignedCollections(organization.Id).Returns(true); @@ -142,6 +151,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task DeleteMany_Success(Guid orgId, Collection collection1, Collection collection2, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); // Arrange var model = new CollectionBulkDeleteRequestModel { @@ -184,6 +194,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task DeleteMany_PermissionDenied_ThrowsNotFound(Guid orgId, Collection collection1, Collection collection2, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); // Arrange var model = new CollectionBulkDeleteRequestModel { @@ -225,6 +236,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task PostBulkCollectionAccess_Success(User actingUser, ICollection collections, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); // Arrange var userId = Guid.NewGuid(); var groupId = Guid.NewGuid(); @@ -271,6 +283,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task PostBulkCollectionAccess_CollectionsNotFound_Throws(User actingUser, ICollection collections, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); var userId = Guid.NewGuid(); var groupId = Guid.NewGuid(); var model = new BulkCollectionAccessRequestModel @@ -302,6 +315,7 @@ public class CollectionsControllerTests [Theory, BitAutoData] public async Task PostBulkCollectionAccess_AccessDenied_Throws(User actingUser, ICollection collections, SutProvider sutProvider) { + sutProvider.EnableFeatureFlag(FeatureFlagKeys.FlexibleCollections); var userId = Guid.NewGuid(); var groupId = Guid.NewGuid(); var model = new BulkCollectionAccessRequestModel diff --git a/test/Api.Test/Controllers/LegacyCollectionsControllerTests.cs b/test/Api.Test/Controllers/LegacyCollectionsControllerTests.cs new file mode 100644 index 0000000000..68683837d1 --- /dev/null +++ b/test/Api.Test/Controllers/LegacyCollectionsControllerTests.cs @@ -0,0 +1,254 @@ +using Bit.Api.Controllers; +using Bit.Api.Models.Request; +using Bit.Core.Context; +using Bit.Core.Entities; +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; + +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_Success(Guid orgId, SutProvider sutProvider) + { + sutProvider.GetDependency() + .OrganizationManager(orgId) + .Returns(true); + + sutProvider.GetDependency() + .EditAnyCollection(orgId) + .Returns(false); + + var collectionRequest = new CollectionRequestModel + { + Name = "encrypted_string", + ExternalId = "my_external_id" + }; + + _ = await sutProvider.Sut.Post(orgId, collectionRequest); + + await sutProvider.GetDependency() + .Received(1) + .SaveAsync(Arg.Any(), Arg.Any>(), null); + } + + [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) + .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) + .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); + } + + [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); + } + + [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); + } + + [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); + } + + + [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)))); + } + + +}