From e042360c00472ece65fe8d9a80ad18c01d45354e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Mon, 12 Dec 2022 09:59:48 +0000 Subject: [PATCH] [EC-654] Create commands for Group Create and Group Update (#2442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [EC-654] Add CreateGroupCommand and UpdateGroupCommand Added new CQRS commands CreateGroupCommand and UpdateGroupCommand
Updated GroupService to use new commands Edited existing GroupServiceTests and added new tests for the new commands * [EC-654] dotnet format * [EC-654] Replace GroupService.SaveAsync with CreateGroup and UpdateGroup commands * [EC-654] Add assertions to check calls on IReferenceEventService * [EC-654] Use AssertHelper.AssertRecent for DateTime properties * [EC-654] Extracted database reads from CreateGroupCommand and UpdateGroupCommand. Added unit tests. * [EC-654] Changed CreateGroupCommand and UpdateGroupCommand Validate method to private --- .../Scim/Controllers/v2/GroupsController.cs | 12 +- .../Groups/Interfaces/IPatchGroupCommand.cs | 5 +- .../Groups/Interfaces/IPostGroupCommand.cs | 2 +- .../Groups/Interfaces/IPutGroupCommand.cs | 2 +- .../src/Scim/Groups/PatchGroupCommand.cs | 13 ++- .../src/Scim/Groups/PostGroupCommand.cs | 19 ++-- .../src/Scim/Groups/PutGroupCommand.cs | 16 +-- .../ScimServiceCollectionExtensions.cs | 5 +- .../Groups/PatchGroupCommandTests.cs | 69 +++++++----- .../Scim.Test/Groups/PostGroupCommandTests.cs | 34 +++--- .../Scim.Test/Groups/PutGroupCommandTests.cs | 28 +++-- .../Scim.Test/Users/PatchUserCommandTests.cs | 4 +- src/Api/Controllers/GroupsController.cs | 21 +++- .../Public/Controllers/GroupsController.cs | 23 ++-- .../Groups/CreateGroupCommand.cs | 72 ++++++++++++ .../Groups/Interfaces/ICreateGroupCommand.cs | 14 +++ .../Groups/Interfaces/IUpdateGroupCommand.cs | 14 +++ .../Groups/UpdateGroupCommand.cs | 66 +++++++++++ ...OrganizationServiceCollectionExtensions.cs | 10 ++ src/Core/Services/IGroupService.cs | 3 - .../Services/Implementations/GroupService.cs | 83 +------------- .../Controllers/GroupsControllerTests.cs | 69 ++++++++++++ .../Controllers/GroupsControllerTests.cs | 67 ++++++++++++ .../Groups/CreateGroupCommandTests.cs | 80 ++++++++++++++ .../Groups/UpdateGroupCommandTests.cs | 71 ++++++++++++ test/Core.Test/Services/GroupServiceTests.cs | 103 ------------------ 26 files changed, 618 insertions(+), 287 deletions(-) create mode 100644 src/Core/OrganizationFeatures/Groups/CreateGroupCommand.cs create mode 100644 src/Core/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs create mode 100644 src/Core/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs create mode 100644 src/Core/OrganizationFeatures/Groups/UpdateGroupCommand.cs create mode 100644 test/Api.Test/Controllers/GroupsControllerTests.cs create mode 100644 test/Api.Test/Public/Controllers/GroupsControllerTests.cs create mode 100644 test/Core.Test/OrganizationFeatures/Groups/CreateGroupCommandTests.cs create mode 100644 test/Core.Test/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs diff --git a/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs b/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs index 33276f8fb..8a3c02f5e 100644 --- a/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs +++ b/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs @@ -16,6 +16,7 @@ namespace Bit.Scim.Controllers.v2; public class GroupsController : Controller { private readonly IGroupRepository _groupRepository; + private readonly IOrganizationRepository _organizationRepository; private readonly IGetGroupsListQuery _getGroupsListQuery; private readonly IDeleteGroupCommand _deleteGroupCommand; private readonly IPatchGroupCommand _patchGroupCommand; @@ -25,6 +26,7 @@ public class GroupsController : Controller public GroupsController( IGroupRepository groupRepository, + IOrganizationRepository organizationRepository, IGetGroupsListQuery getGroupsListQuery, IDeleteGroupCommand deleteGroupCommand, IPatchGroupCommand patchGroupCommand, @@ -33,6 +35,7 @@ public class GroupsController : Controller ILogger logger) { _groupRepository = groupRepository; + _organizationRepository = organizationRepository; _getGroupsListQuery = getGroupsListQuery; _deleteGroupCommand = deleteGroupCommand; _patchGroupCommand = patchGroupCommand; @@ -73,7 +76,8 @@ public class GroupsController : Controller [HttpPost("")] public async Task Post(Guid organizationId, [FromBody] ScimGroupRequestModel model) { - var group = await _postGroupCommand.PostGroupAsync(organizationId, model); + var organization = await _organizationRepository.GetByIdAsync(organizationId); + var group = await _postGroupCommand.PostGroupAsync(organization, model); var scimGroupResponseModel = new ScimGroupResponseModel(group); return new CreatedResult(Url.Action(nameof(Get), new { group.OrganizationId, group.Id }), scimGroupResponseModel); } @@ -81,7 +85,8 @@ public class GroupsController : Controller [HttpPut("{id}")] public async Task Put(Guid organizationId, Guid id, [FromBody] ScimGroupRequestModel model) { - var group = await _putGroupCommand.PutGroupAsync(organizationId, id, model); + var organization = await _organizationRepository.GetByIdAsync(organizationId); + var group = await _putGroupCommand.PutGroupAsync(organization, id, model); var response = new ScimGroupResponseModel(group); return Ok(response); @@ -90,7 +95,8 @@ public class GroupsController : Controller [HttpPatch("{id}")] public async Task Patch(Guid organizationId, Guid id, [FromBody] ScimPatchModel model) { - await _patchGroupCommand.PatchGroupAsync(organizationId, id, model); + var organization = await _organizationRepository.GetByIdAsync(organizationId); + await _patchGroupCommand.PatchGroupAsync(organization, id, model); return new NoContentResult(); } diff --git a/bitwarden_license/src/Scim/Groups/Interfaces/IPatchGroupCommand.cs b/bitwarden_license/src/Scim/Groups/Interfaces/IPatchGroupCommand.cs index 61dc064de..181f0c70a 100644 --- a/bitwarden_license/src/Scim/Groups/Interfaces/IPatchGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/Interfaces/IPatchGroupCommand.cs @@ -1,8 +1,9 @@ -using Bit.Scim.Models; +using Bit.Core.Entities; +using Bit.Scim.Models; namespace Bit.Scim.Groups.Interfaces; public interface IPatchGroupCommand { - Task PatchGroupAsync(Guid organizationId, Guid id, ScimPatchModel model); + Task PatchGroupAsync(Organization organization, Guid id, ScimPatchModel model); } diff --git a/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs b/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs index 6863b999f..76e80f08d 100644 --- a/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs @@ -5,5 +5,5 @@ namespace Bit.Scim.Groups.Interfaces; public interface IPostGroupCommand { - Task PostGroupAsync(Guid organizationId, ScimGroupRequestModel model); + Task PostGroupAsync(Organization organization, ScimGroupRequestModel model); } diff --git a/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs b/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs index 4e37cb5c8..052a4e554 100644 --- a/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs @@ -5,5 +5,5 @@ namespace Bit.Scim.Groups.Interfaces; public interface IPutGroupCommand { - Task PutGroupAsync(Guid organizationId, Guid id, ScimGroupRequestModel model); + Task PutGroupAsync(Organization organization, Guid id, ScimGroupRequestModel model); } diff --git a/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs b/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs index 4789d4977..750dcec46 100644 --- a/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs @@ -1,6 +1,8 @@ using System.Text.Json; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Scim.Groups.Interfaces; @@ -12,22 +14,25 @@ public class PatchGroupCommand : IPatchGroupCommand { private readonly IGroupRepository _groupRepository; private readonly IGroupService _groupService; + private readonly IUpdateGroupCommand _updateGroupCommand; private readonly ILogger _logger; public PatchGroupCommand( IGroupRepository groupRepository, IGroupService groupService, + IUpdateGroupCommand updateGroupCommand, ILogger logger) { _groupRepository = groupRepository; _groupService = groupService; + _updateGroupCommand = updateGroupCommand; _logger = logger; } - public async Task PatchGroupAsync(Guid organizationId, Guid id, ScimPatchModel model) + public async Task PatchGroupAsync(Organization organization, Guid id, ScimPatchModel model) { var group = await _groupRepository.GetByIdAsync(id); - if (group == null || group.OrganizationId != organizationId) + if (group == null || group.OrganizationId != organization.Id) { throw new NotFoundException("Group not found."); } @@ -49,7 +54,7 @@ public class PatchGroupCommand : IPatchGroupCommand else if (operation.Path?.ToLowerInvariant() == "displayname") { group.Name = operation.Value.GetString(); - await _groupService.SaveAsync(group, EventSystemUser.SCIM); + await _updateGroupCommand.UpdateGroupAsync(group, organization, EventSystemUser.SCIM); operationHandled = true; } // Replace group name from value object @@ -57,7 +62,7 @@ public class PatchGroupCommand : IPatchGroupCommand operation.Value.TryGetProperty("displayName", out var displayNameProperty)) { group.Name = displayNameProperty.GetString(); - await _groupService.SaveAsync(group, EventSystemUser.SCIM); + await _updateGroupCommand.UpdateGroupAsync(group, organization, EventSystemUser.SCIM); operationHandled = true; } } diff --git a/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs b/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs index 2ce4e793a..4da336fb7 100644 --- a/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs @@ -1,8 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Scim.Context; using Bit.Scim.Groups.Interfaces; using Bit.Scim.Models; @@ -12,34 +12,35 @@ namespace Bit.Scim.Groups; public class PostGroupCommand : IPostGroupCommand { private readonly IGroupRepository _groupRepository; - private readonly IGroupService _groupService; private readonly IScimContext _scimContext; + private readonly ICreateGroupCommand _createGroupCommand; public PostGroupCommand( IGroupRepository groupRepository, - IGroupService groupService, - IScimContext scimContext) + IOrganizationRepository organizationRepository, + IScimContext scimContext, + ICreateGroupCommand createGroupCommand) { _groupRepository = groupRepository; - _groupService = groupService; _scimContext = scimContext; + _createGroupCommand = createGroupCommand; } - public async Task PostGroupAsync(Guid organizationId, ScimGroupRequestModel model) + public async Task PostGroupAsync(Organization organization, ScimGroupRequestModel model) { if (string.IsNullOrWhiteSpace(model.DisplayName)) { throw new BadRequestException(); } - var groups = await _groupRepository.GetManyByOrganizationIdAsync(organizationId); + var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id); if (!string.IsNullOrWhiteSpace(model.ExternalId) && groups.Any(g => g.ExternalId == model.ExternalId)) { throw new ConflictException(); } - var group = model.ToGroup(organizationId); - await _groupService.SaveAsync(group, EventSystemUser.SCIM, null); + var group = model.ToGroup(organization.Id); + await _createGroupCommand.CreateGroupAsync(group, organization, EventSystemUser.SCIM, collections: null); await UpdateGroupMembersAsync(group, model); return group; diff --git a/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs b/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs index 6bf78a0ac..ec2cd170d 100644 --- a/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs @@ -1,8 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Scim.Context; using Bit.Scim.Groups.Interfaces; using Bit.Scim.Models; @@ -12,29 +12,29 @@ namespace Bit.Scim.Groups; public class PutGroupCommand : IPutGroupCommand { private readonly IGroupRepository _groupRepository; - private readonly IGroupService _groupService; private readonly IScimContext _scimContext; + private readonly IUpdateGroupCommand _updateGroupCommand; public PutGroupCommand( IGroupRepository groupRepository, - IGroupService groupService, - IScimContext scimContext) + IScimContext scimContext, + IUpdateGroupCommand updateGroupCommand) { _groupRepository = groupRepository; - _groupService = groupService; _scimContext = scimContext; + _updateGroupCommand = updateGroupCommand; } - public async Task PutGroupAsync(Guid organizationId, Guid id, ScimGroupRequestModel model) + public async Task PutGroupAsync(Organization organization, Guid id, ScimGroupRequestModel model) { var group = await _groupRepository.GetByIdAsync(id); - if (group == null || group.OrganizationId != organizationId) + if (group == null || group.OrganizationId != organization.Id) { throw new NotFoundException("Group not found."); } group.Name = model.DisplayName; - await _groupService.SaveAsync(group, EventSystemUser.SCIM); + await _updateGroupCommand.UpdateGroupAsync(group, organization, EventSystemUser.SCIM); await UpdateGroupMembersAsync(group, model); return group; diff --git a/bitwarden_license/src/Scim/Utilities/ScimServiceCollectionExtensions.cs b/bitwarden_license/src/Scim/Utilities/ScimServiceCollectionExtensions.cs index 56761ac93..fff70760d 100644 --- a/bitwarden_license/src/Scim/Utilities/ScimServiceCollectionExtensions.cs +++ b/bitwarden_license/src/Scim/Utilities/ScimServiceCollectionExtensions.cs @@ -1,6 +1,4 @@ -using Bit.Core.OrganizationFeatures.Groups; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.OrganizationFeatures.OrganizationUsers; +using Bit.Core.OrganizationFeatures.OrganizationUsers; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Scim.Groups; using Bit.Scim.Groups.Interfaces; @@ -13,7 +11,6 @@ public static class ScimServiceCollectionExtensions { public static void AddScimGroupCommands(this IServiceCollection services) { - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs b/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs index c0f4b1221..505a67f57 100644 --- a/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs @@ -2,6 +2,7 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Scim.Groups; @@ -19,8 +20,10 @@ public class PatchGroupCommandTests { [Theory] [BitAutoData] - public async Task PatchGroup_ReplaceListMembers_Success(SutProvider sutProvider, Group group, IEnumerable userIds) + public async Task PatchGroup_ReplaceListMembers_Success(SutProvider sutProvider, Organization organization, Group group, IEnumerable userIds) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -39,15 +42,17 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); await sutProvider.GetDependency().Received(1).UpdateUsersAsync(group.Id, Arg.Is>(arg => arg.All(id => userIds.Contains(id)))); } [Theory] [BitAutoData] - public async Task PatchGroup_ReplaceDisplayNameFromPath_Success(SutProvider sutProvider, Group group, string displayName) + public async Task PatchGroup_ReplaceDisplayNameFromPath_Success(SutProvider sutProvider, Organization organization, Group group, string displayName) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -66,16 +71,18 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); - await sutProvider.GetDependency().Received(1).SaveAsync(group, EventSystemUser.SCIM); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync(group, organization, EventSystemUser.SCIM); Assert.Equal(displayName, group.Name); } [Theory] [BitAutoData] - public async Task PatchGroup_ReplaceDisplayNameFromValueObject_Success(SutProvider sutProvider, Group group, string displayName) + public async Task PatchGroup_ReplaceDisplayNameFromValueObject_Success(SutProvider sutProvider, Organization organization, Group group, string displayName) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -93,16 +100,18 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); - await sutProvider.GetDependency().Received(1).SaveAsync(group, EventSystemUser.SCIM); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync(group, organization, EventSystemUser.SCIM); Assert.Equal(displayName, group.Name); } [Theory] [BitAutoData] - public async Task PatchGroup_AddSingleMember_Success(SutProvider sutProvider, Group group, ICollection existingMembers, Guid userId) + public async Task PatchGroup_AddSingleMember_Success(SutProvider sutProvider, Organization organization, Group group, ICollection existingMembers, Guid userId) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -124,15 +133,17 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); await sutProvider.GetDependency().Received(1).UpdateUsersAsync(group.Id, Arg.Is>(arg => arg.All(id => existingMembers.Append(userId).Contains(id)))); } [Theory] [BitAutoData] - public async Task PatchGroup_AddListMembers_Success(SutProvider sutProvider, Group group, ICollection existingMembers, ICollection userIds) + public async Task PatchGroup_AddListMembers_Success(SutProvider sutProvider, Organization organization, Group group, ICollection existingMembers, ICollection userIds) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -155,15 +166,17 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); await sutProvider.GetDependency().Received(1).UpdateUsersAsync(group.Id, Arg.Is>(arg => arg.All(id => existingMembers.Concat(userIds).Contains(id)))); } [Theory] [BitAutoData] - public async Task PatchGroup_RemoveSingleMember_Success(SutProvider sutProvider, Group group, Guid userId) + public async Task PatchGroup_RemoveSingleMember_Success(SutProvider sutProvider, Organization organization, Group group, Guid userId) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -181,15 +194,17 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); await sutProvider.GetDependency().Received(1).DeleteUserAsync(group, userId, EventSystemUser.SCIM); } [Theory] [BitAutoData] - public async Task PatchGroup_RemoveListMembers_Success(SutProvider sutProvider, Group group, ICollection existingMembers) + public async Task PatchGroup_RemoveListMembers_Success(SutProvider sutProvider, Organization organization, Group group, ICollection existingMembers) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -212,15 +227,17 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); await sutProvider.GetDependency().Received(1).UpdateUsersAsync(group.Id, Arg.Is>(arg => arg.All(id => existingMembers.Contains(id)))); } [Theory] [BitAutoData] - public async Task PatchGroup_NoAction_Success(SutProvider sutProvider, Group group) + public async Task PatchGroup_NoAction_Success(SutProvider sutProvider, Organization organization, Group group) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -231,17 +248,17 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel); + await sutProvider.Sut.PatchGroupAsync(organization, group.Id, scimPatchModel); - await sutProvider.GetDependency().Received(0).UpdateUsersAsync(group.Id, Arg.Any>()); - await sutProvider.GetDependency().Received(0).GetManyUserIdsByIdAsync(group.Id); - await sutProvider.GetDependency().Received(0).SaveAsync(group); - await sutProvider.GetDependency().Received(0).DeleteUserAsync(group, Arg.Any()); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().UpdateUsersAsync(default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyUserIdsByIdAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().UpdateGroupAsync(default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default); } [Theory] [BitAutoData] - public async Task PatchGroup_NotFound_Throws(SutProvider sutProvider, Guid organizationId, Guid groupId) + public async Task PatchGroup_NotFound_Throws(SutProvider sutProvider, Organization organization, Guid groupId) { var scimPatchModel = new Models.ScimPatchModel { @@ -249,12 +266,12 @@ public class PatchGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await Assert.ThrowsAsync(async () => await sutProvider.Sut.PatchGroupAsync(organizationId, groupId, scimPatchModel)); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.PatchGroupAsync(organization, groupId, scimPatchModel)); } [Theory] [BitAutoData] - public async Task PatchGroup_MismatchingOrganizationId_Throws(SutProvider sutProvider, Guid organizationId, Guid groupId) + public async Task PatchGroup_MismatchingOrganizationId_Throws(SutProvider sutProvider, Organization organization, Guid groupId) { var scimPatchModel = new Models.ScimPatchModel { @@ -270,6 +287,6 @@ public class PatchGroupCommandTests OrganizationId = Guid.NewGuid() }); - await Assert.ThrowsAsync(async () => await sutProvider.Sut.PatchGroupAsync(organizationId, groupId, scimPatchModel)); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.PatchGroupAsync(organization, groupId, scimPatchModel)); } } diff --git a/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs b/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs index 24f629219..f7efb022d 100644 --- a/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs @@ -1,8 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Scim.Context; using Bit.Scim.Groups; using Bit.Scim.Models; @@ -20,7 +20,7 @@ public class PostGroupCommandTests { [Theory] [BitAutoData] - public async Task PostGroup_Success(SutProvider sutProvider, string displayName, string externalId, Guid organizationId, ICollection groups) + public async Task PostGroup_Success(SutProvider sutProvider, string displayName, string externalId, Organization organization, ICollection groups) { var scimGroupRequestModel = new ScimGroupRequestModel { @@ -32,26 +32,26 @@ public class PostGroupCommandTests var expectedResult = new Group { - OrganizationId = organizationId, + OrganizationId = organization.Id, Name = displayName, ExternalId = externalId, }; sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(organizationId) + .GetManyByOrganizationIdAsync(organization.Id) .Returns(groups); - var group = await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel); + var group = await sutProvider.Sut.PostGroupAsync(organization, scimGroupRequestModel); - await sutProvider.GetDependency().Received(1).SaveAsync(group, EventSystemUser.SCIM, null); - await sutProvider.GetDependency().Received(0).UpdateUsersAsync(Arg.Any(), Arg.Any>()); + await sutProvider.GetDependency().Received(1).CreateGroupAsync(group, organization, EventSystemUser.SCIM, null); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().UpdateUsersAsync(default, default); AssertHelper.AssertPropertyEqual(expectedResult, group, "Id", "CreationDate", "RevisionDate"); } [Theory] [BitAutoData] - public async Task PostGroup_WithMembers_Success(SutProvider sutProvider, string displayName, string externalId, Guid organizationId, ICollection groups, IEnumerable membersUserIds) + public async Task PostGroup_WithMembers_Success(SutProvider sutProvider, string displayName, string externalId, Organization organization, ICollection groups, IEnumerable membersUserIds) { var scimGroupRequestModel = new ScimGroupRequestModel { @@ -63,22 +63,22 @@ public class PostGroupCommandTests var expectedResult = new Group { - OrganizationId = organizationId, + OrganizationId = organization.Id, Name = displayName, ExternalId = externalId }; sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(organizationId) + .GetManyByOrganizationIdAsync(organization.Id) .Returns(groups); sutProvider.GetDependency() .RequestScimProvider .Returns(Core.Enums.ScimProviderType.Okta); - var group = await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel); + var group = await sutProvider.Sut.PostGroupAsync(organization, scimGroupRequestModel); - await sutProvider.GetDependency().Received(1).SaveAsync(group, EventSystemUser.SCIM, null); + await sutProvider.GetDependency().Received(1).CreateGroupAsync(group, organization, EventSystemUser.SCIM, null); await sutProvider.GetDependency().Received(1).UpdateUsersAsync(Arg.Any(), Arg.Is>(arg => arg.All(id => membersUserIds.Contains(id)))); AssertHelper.AssertPropertyEqual(expectedResult, group, "Id", "CreationDate", "RevisionDate"); @@ -88,7 +88,7 @@ public class PostGroupCommandTests [BitAutoData((string)null)] [BitAutoData("")] [BitAutoData(" ")] - public async Task PostGroup_NullDisplayName_Throws(string displayName, SutProvider sutProvider, Guid organizationId) + public async Task PostGroup_NullDisplayName_Throws(string displayName, SutProvider sutProvider, Organization organization) { var scimGroupRequestModel = new ScimGroupRequestModel { @@ -98,12 +98,12 @@ public class PostGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await Assert.ThrowsAsync(async () => await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel)); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.PostGroupAsync(organization, scimGroupRequestModel)); } [Theory] [BitAutoData] - public async Task PostGroup_ExistingExternalId_Throws(string displayName, SutProvider sutProvider, Guid organizationId, ICollection groups) + public async Task PostGroup_ExistingExternalId_Throws(string displayName, SutProvider sutProvider, Organization organization, ICollection groups) { var scimGroupRequestModel = new ScimGroupRequestModel { @@ -114,9 +114,9 @@ public class PostGroupCommandTests }; sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(organizationId) + .GetManyByOrganizationIdAsync(organization.Id) .Returns(groups); - await Assert.ThrowsAsync(async () => await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel)); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.PostGroupAsync(organization, scimGroupRequestModel)); } } diff --git a/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs b/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs index c40fcf99c..4bcc3c0c8 100644 --- a/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs @@ -1,8 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Scim.Context; using Bit.Scim.Groups; using Bit.Scim.Models; @@ -20,8 +20,10 @@ public class PutGroupCommandTests { [Theory] [BitAutoData] - public async Task PutGroup_Success(SutProvider sutProvider, Group group, string displayName) + public async Task PutGroup_Success(SutProvider sutProvider, Organization organization, Group group, string displayName) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -41,19 +43,21 @@ public class PutGroupCommandTests OrganizationId = group.OrganizationId }; - var result = await sutProvider.Sut.PutGroupAsync(group.OrganizationId, group.Id, inputModel); + var result = await sutProvider.Sut.PutGroupAsync(organization, group.Id, inputModel); AssertHelper.AssertPropertyEqual(expectedResult, result, "CreationDate", "RevisionDate"); Assert.Equal(displayName, group.Name); - await sutProvider.GetDependency().Received(1).SaveAsync(group, EventSystemUser.SCIM); - await sutProvider.GetDependency().Received(0).UpdateUsersAsync(group.Id, Arg.Any>()); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync(group, organization, EventSystemUser.SCIM); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().UpdateUsersAsync(default, default); } [Theory] [BitAutoData] - public async Task PutGroup_ChangeMembers_Success(SutProvider sutProvider, Group group, string displayName, IEnumerable membersUserIds) + public async Task PutGroup_ChangeMembers_Success(SutProvider sutProvider, Organization organization, Group group, string displayName, IEnumerable membersUserIds) { + group.OrganizationId = organization.Id; + sutProvider.GetDependency() .GetByIdAsync(group.Id) .Returns(group); @@ -78,18 +82,18 @@ public class PutGroupCommandTests OrganizationId = group.OrganizationId }; - var result = await sutProvider.Sut.PutGroupAsync(group.OrganizationId, group.Id, inputModel); + var result = await sutProvider.Sut.PutGroupAsync(organization, group.Id, inputModel); AssertHelper.AssertPropertyEqual(expectedResult, result, "CreationDate", "RevisionDate"); Assert.Equal(displayName, group.Name); - await sutProvider.GetDependency().Received(1).SaveAsync(group, EventSystemUser.SCIM); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync(group, organization, EventSystemUser.SCIM); await sutProvider.GetDependency().Received(1).UpdateUsersAsync(group.Id, Arg.Is>(arg => arg.All(id => membersUserIds.Contains(id)))); } [Theory] [BitAutoData] - public async Task PutGroup_NotFound_Throws(SutProvider sutProvider, Guid organizationId, Guid groupId, string displayName) + public async Task PutGroup_NotFound_Throws(SutProvider sutProvider, Organization organization, Guid groupId, string displayName) { var scimGroupRequestModel = new ScimGroupRequestModel { @@ -97,12 +101,12 @@ public class PutGroupCommandTests Schemas = new List { ScimConstants.Scim2SchemaUser } }; - await Assert.ThrowsAsync(async () => await sutProvider.Sut.PutGroupAsync(organizationId, groupId, scimGroupRequestModel)); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.PutGroupAsync(organization, groupId, scimGroupRequestModel)); } [Theory] [BitAutoData] - public async Task PutGroup_MismatchingOrganizationId_Throws(SutProvider sutProvider, Guid organizationId, Guid groupId, string displayName) + public async Task PutGroup_MismatchingOrganizationId_Throws(SutProvider sutProvider, Organization organization, Guid groupId, string displayName) { var scimGroupRequestModel = new ScimGroupRequestModel { @@ -118,6 +122,6 @@ public class PutGroupCommandTests OrganizationId = Guid.NewGuid() }); - await Assert.ThrowsAsync(async () => await sutProvider.Sut.PutGroupAsync(organizationId, groupId, scimGroupRequestModel)); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.PutGroupAsync(organization, groupId, scimGroupRequestModel)); } } diff --git a/bitwarden_license/test/Scim.Test/Users/PatchUserCommandTests.cs b/bitwarden_license/test/Scim.Test/Users/PatchUserCommandTests.cs index a917ce4c1..977011b35 100644 --- a/bitwarden_license/test/Scim.Test/Users/PatchUserCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Users/PatchUserCommandTests.cs @@ -147,8 +147,8 @@ public class PatchUserCommandTests await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel); - await sutProvider.GetDependency().Received(0).RestoreUserAsync(organizationUser, EventSystemUser.SCIM, Arg.Any()); - await sutProvider.GetDependency().Received(0).RevokeUserAsync(organizationUser, EventSystemUser.SCIM); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RestoreUserAsync(default, EventSystemUser.SCIM, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RevokeUserAsync(default, EventSystemUser.SCIM); } [Theory] diff --git a/src/Api/Controllers/GroupsController.cs b/src/Api/Controllers/GroupsController.cs index d38ba03bc..5aae7ad04 100644 --- a/src/Api/Controllers/GroupsController.cs +++ b/src/Api/Controllers/GroupsController.cs @@ -2,6 +2,7 @@ using Bit.Api.Models.Response; using Bit.Core.Context; using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; @@ -15,16 +16,25 @@ public class GroupsController : Controller { private readonly IGroupRepository _groupRepository; private readonly IGroupService _groupService; + private readonly IOrganizationRepository _organizationRepository; private readonly ICurrentContext _currentContext; + private readonly ICreateGroupCommand _createGroupCommand; + private readonly IUpdateGroupCommand _updateGroupCommand; public GroupsController( IGroupRepository groupRepository, IGroupService groupService, - ICurrentContext currentContext) + IOrganizationRepository organizationRepository, + ICurrentContext currentContext, + ICreateGroupCommand createGroupCommand, + IUpdateGroupCommand updateGroupCommand) { _groupRepository = groupRepository; _groupService = groupService; + _organizationRepository = organizationRepository; _currentContext = currentContext; + _createGroupCommand = createGroupCommand; + _updateGroupCommand = updateGroupCommand; } [HttpGet("{id}")] @@ -93,8 +103,10 @@ public class GroupsController : Controller throw new NotFoundException(); } + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); var group = model.ToGroup(orgIdGuid); - await _groupService.SaveAsync(group, model.Collections?.Select(c => c.ToSelectionReadOnly())); + await _createGroupCommand.CreateGroupAsync(group, organization, model.Collections?.Select(c => c.ToSelectionReadOnly())); + return new GroupResponseModel(group); } @@ -108,7 +120,10 @@ public class GroupsController : Controller throw new NotFoundException(); } - await _groupService.SaveAsync(model.ToGroup(group), model.Collections?.Select(c => c.ToSelectionReadOnly())); + var orgIdGuid = new Guid(orgId); + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + + await _updateGroupCommand.UpdateGroupAsync(model.ToGroup(group), organization, model.Collections?.Select(c => c.ToSelectionReadOnly())); return new GroupResponseModel(group); } diff --git a/src/Api/Public/Controllers/GroupsController.cs b/src/Api/Public/Controllers/GroupsController.cs index f65f7b9fe..17e9a96dd 100644 --- a/src/Api/Public/Controllers/GroupsController.cs +++ b/src/Api/Public/Controllers/GroupsController.cs @@ -2,8 +2,8 @@ using Bit.Api.Models.Public.Request; using Bit.Api.Models.Public.Response; using Bit.Core.Context; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; -using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -14,17 +14,23 @@ namespace Bit.Api.Public.Controllers; public class GroupsController : Controller { private readonly IGroupRepository _groupRepository; - private readonly IGroupService _groupService; + private readonly IOrganizationRepository _organizationRepository; private readonly ICurrentContext _currentContext; + private readonly ICreateGroupCommand _createGroupCommand; + private readonly IUpdateGroupCommand _updateGroupCommand; public GroupsController( IGroupRepository groupRepository, - IGroupService groupService, - ICurrentContext currentContext) + IOrganizationRepository organizationRepository, + ICurrentContext currentContext, + ICreateGroupCommand createGroupCommand, + IUpdateGroupCommand updateGroupCommand) { _groupRepository = groupRepository; - _groupService = groupService; + _organizationRepository = organizationRepository; _currentContext = currentContext; + _createGroupCommand = createGroupCommand; + _updateGroupCommand = updateGroupCommand; } /// @@ -104,7 +110,8 @@ public class GroupsController : Controller { var group = model.ToGroup(_currentContext.OrganizationId.Value); var associations = model.Collections?.Select(c => c.ToSelectionReadOnly()); - await _groupService.SaveAsync(group, associations); + var organization = await _organizationRepository.GetByIdAsync(_currentContext.OrganizationId.Value); + await _createGroupCommand.CreateGroupAsync(group, organization, associations); var response = new GroupResponseModel(group, associations); return new JsonResult(response); } @@ -129,9 +136,11 @@ public class GroupsController : Controller { return new NotFoundResult(); } + var updatedGroup = model.ToGroup(existingGroup); var associations = model.Collections?.Select(c => c.ToSelectionReadOnly()); - await _groupService.SaveAsync(updatedGroup, associations); + var organization = await _organizationRepository.GetByIdAsync(_currentContext.OrganizationId.Value); + await _updateGroupCommand.UpdateGroupAsync(updatedGroup, organization, associations); var response = new GroupResponseModel(updatedGroup, associations); return new JsonResult(response); } diff --git a/src/Core/OrganizationFeatures/Groups/CreateGroupCommand.cs b/src/Core/OrganizationFeatures/Groups/CreateGroupCommand.cs new file mode 100644 index 000000000..37a862ced --- /dev/null +++ b/src/Core/OrganizationFeatures/Groups/CreateGroupCommand.cs @@ -0,0 +1,72 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Business; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.OrganizationFeatures.Groups; + +public class CreateGroupCommand : ICreateGroupCommand +{ + private readonly IEventService _eventService; + private readonly IGroupRepository _groupRepository; + private readonly IReferenceEventService _referenceEventService; + + public CreateGroupCommand( + IEventService eventService, + IGroupRepository groupRepository, + IReferenceEventService referenceEventService) + { + _eventService = eventService; + _groupRepository = groupRepository; + _referenceEventService = referenceEventService; + } + + public async Task CreateGroupAsync(Group group, Organization organization, + IEnumerable collections = null) + { + Validate(organization); + await GroupRepositoryCreateGroupAsync(group, organization, collections); + await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created); + } + + public async Task CreateGroupAsync(Group group, Organization organization, EventSystemUser systemUser, + IEnumerable collections = null) + { + Validate(organization); + await GroupRepositoryCreateGroupAsync(group, organization, collections); + await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created, systemUser); + } + + private async Task GroupRepositoryCreateGroupAsync(Group group, Organization organization, IEnumerable collections = null) + { + group.CreationDate = group.RevisionDate = DateTime.UtcNow; + + if (collections == null) + { + await _groupRepository.CreateAsync(group); + } + else + { + await _groupRepository.CreateAsync(group, collections); + } + + await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.GroupCreated, organization)); + } + + private static void Validate(Organization organization) + { + if (organization == null) + { + throw new BadRequestException("Organization not found"); + } + + if (!organization.UseGroups) + { + throw new BadRequestException("This organization cannot use groups."); + } + } +} diff --git a/src/Core/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs b/src/Core/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs new file mode 100644 index 000000000..bdd1d1d79 --- /dev/null +++ b/src/Core/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs @@ -0,0 +1,14 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; + +namespace Bit.Core.OrganizationFeatures.Groups.Interfaces; + +public interface ICreateGroupCommand +{ + Task CreateGroupAsync(Group group, Organization organization, + IEnumerable collections = null); + + Task CreateGroupAsync(Group group, Organization organization, EventSystemUser systemUser, + IEnumerable collections = null); +} diff --git a/src/Core/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs b/src/Core/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs new file mode 100644 index 000000000..d0b213475 --- /dev/null +++ b/src/Core/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs @@ -0,0 +1,14 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; + +namespace Bit.Core.OrganizationFeatures.Groups.Interfaces; + +public interface IUpdateGroupCommand +{ + Task UpdateGroupAsync(Group group, Organization organization, + IEnumerable collections = null); + + Task UpdateGroupAsync(Group group, Organization organization, EventSystemUser systemUser, + IEnumerable collections = null); +} diff --git a/src/Core/OrganizationFeatures/Groups/UpdateGroupCommand.cs b/src/Core/OrganizationFeatures/Groups/UpdateGroupCommand.cs new file mode 100644 index 000000000..50422f04e --- /dev/null +++ b/src/Core/OrganizationFeatures/Groups/UpdateGroupCommand.cs @@ -0,0 +1,66 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.OrganizationFeatures.Groups; + +public class UpdateGroupCommand : IUpdateGroupCommand +{ + private readonly IEventService _eventService; + private readonly IGroupRepository _groupRepository; + + public UpdateGroupCommand( + IEventService eventService, + IGroupRepository groupRepository) + { + _eventService = eventService; + _groupRepository = groupRepository; + } + + public async Task UpdateGroupAsync(Group group, Organization organization, + IEnumerable collections = null) + { + Validate(organization); + await GroupRepositoryUpdateGroupAsync(group, collections); + await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated); + } + + public async Task UpdateGroupAsync(Group group, Organization organization, EventSystemUser systemUser, + IEnumerable collections = null) + { + Validate(organization); + await GroupRepositoryUpdateGroupAsync(group, collections); + await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated, systemUser); + } + + private async Task GroupRepositoryUpdateGroupAsync(Group group, IEnumerable collections = null) + { + group.RevisionDate = DateTime.UtcNow; + + if (collections == null) + { + await _groupRepository.ReplaceAsync(group); + } + else + { + await _groupRepository.ReplaceAsync(group, collections); + } + } + + private static void Validate(Organization organization) + { + if (organization == null) + { + throw new BadRequestException("Organization not found"); + } + + if (!organization.UseGroups) + { + throw new BadRequestException("This organization cannot use groups."); + } + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index f83f17e8c..07e39a964 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using Bit.Core.Models.Business.Tokenables; +using Bit.Core.OrganizationFeatures.Groups; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationApiKeys; using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationConnections; @@ -22,11 +24,19 @@ public static class OrganizationServiceCollectionExtensions { services.AddScoped(); services.AddTokenizers(); + services.AddOrganizationGroupCommands(); services.AddOrganizationConnectionCommands(); services.AddOrganizationSponsorshipCommands(globalSettings); services.AddOrganizationApiKeyCommandsQueries(); } + private static void AddOrganizationGroupCommands(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + } + private static void AddOrganizationConnectionCommands(this IServiceCollection services) { services.AddScoped(); diff --git a/src/Core/Services/IGroupService.cs b/src/Core/Services/IGroupService.cs index 1753122d5..14967b6e8 100644 --- a/src/Core/Services/IGroupService.cs +++ b/src/Core/Services/IGroupService.cs @@ -1,13 +1,10 @@ using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.Models.Data; namespace Bit.Core.Services; public interface IGroupService { - Task SaveAsync(Group group, IEnumerable collections = null); - Task SaveAsync(Group group, EventSystemUser systemUser, IEnumerable collections = null); [Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")] Task DeleteAsync(Group group); [Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")] diff --git a/src/Core/Services/Implementations/GroupService.cs b/src/Core/Services/Implementations/GroupService.cs index fb55a5915..c878c321d 100644 --- a/src/Core/Services/Implementations/GroupService.cs +++ b/src/Core/Services/Implementations/GroupService.cs @@ -1,8 +1,6 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models.Business; -using Bit.Core.Models.Data; using Bit.Core.Repositories; namespace Bit.Core.Services; @@ -10,96 +8,17 @@ namespace Bit.Core.Services; public class GroupService : IGroupService { private readonly IEventService _eventService; - private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IGroupRepository _groupRepository; - private readonly IReferenceEventService _referenceEventService; public GroupService( IEventService eventService, - IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, - IGroupRepository groupRepository, - IReferenceEventService referenceEventService) + IGroupRepository groupRepository) { _eventService = eventService; - _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _groupRepository = groupRepository; - _referenceEventService = referenceEventService; - } - - public async Task SaveAsync(Group group, - IEnumerable collections = null) - { - await GroupRepositorySaveAsync(group, systemUser: null, collections); - } - - public async Task SaveAsync(Group group, EventSystemUser systemUser, - IEnumerable collections = null) - { - await GroupRepositorySaveAsync(group, systemUser, collections); - } - - private async Task GroupRepositorySaveAsync(Group group, EventSystemUser? systemUser, IEnumerable collections = null) - { - var org = await _organizationRepository.GetByIdAsync(group.OrganizationId); - if (org == null) - { - throw new BadRequestException("Organization not found"); - } - - if (!org.UseGroups) - { - throw new BadRequestException("This organization cannot use groups."); - } - - if (group.Id == default(Guid)) - { - group.CreationDate = group.RevisionDate = DateTime.UtcNow; - - if (collections == null) - { - await _groupRepository.CreateAsync(group); - } - else - { - await _groupRepository.CreateAsync(group, collections); - } - - if (systemUser.HasValue) - { - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created, systemUser.Value); - } - else - { - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created); - } - - await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.GroupCreated, org)); - } - else - { - group.RevisionDate = DateTime.UtcNow; - - if (collections == null) - { - await _groupRepository.ReplaceAsync(group); - } - else - { - await _groupRepository.ReplaceAsync(group, collections); - } - - if (systemUser.HasValue) - { - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated, systemUser.Value); - } - else - { - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated); - } - } } [Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")] diff --git a/test/Api.Test/Controllers/GroupsControllerTests.cs b/test/Api.Test/Controllers/GroupsControllerTests.cs new file mode 100644 index 000000000..8554a9ca9 --- /dev/null +++ b/test/Api.Test/Controllers/GroupsControllerTests.cs @@ -0,0 +1,69 @@ +using Bit.Api.Controllers; +using Bit.Api.Models.Request; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Controllers; + +[ControllerCustomize(typeof(GroupsController))] +[SutProviderCustomize] +public class GroupsControllerTests +{ + [Theory] + [BitAutoData] + public async Task Post_Success(Organization organization, GroupRequestModel groupRequestModel, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); + + var response = await sutProvider.Sut.Post(organization.Id.ToString(), groupRequestModel); + + await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); + await sutProvider.GetDependency().Received(1).CreateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name && + g.AccessAll == groupRequestModel.AccessAll && g.ExternalId == groupRequestModel.ExternalId), + organization, + Arg.Any>()); + + Assert.NotNull(response.Id); + Assert.Equal(groupRequestModel.Name, response.Name); + Assert.Equal(organization.Id.ToString(), response.OrganizationId); + Assert.Equal(groupRequestModel.AccessAll, response.AccessAll); + Assert.Equal(groupRequestModel.ExternalId, response.ExternalId); + } + + [Theory] + [BitAutoData] + public async Task Put_Success(Organization organization, Group group, GroupRequestModel groupRequestModel, SutProvider sutProvider) + { + group.OrganizationId = organization.Id; + + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency().GetByIdAsync(group.Id).Returns(group); + sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); + + var response = await sutProvider.Sut.Put(organization.Id.ToString(), group.Id.ToString(), groupRequestModel); + + await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name && + g.AccessAll == groupRequestModel.AccessAll && g.ExternalId == groupRequestModel.ExternalId), + Arg.Is(o => o.Id == organization.Id), + Arg.Any>()); + + Assert.NotNull(response.Id); + Assert.Equal(groupRequestModel.Name, response.Name); + Assert.Equal(organization.Id.ToString(), response.OrganizationId); + Assert.Equal(groupRequestModel.AccessAll, response.AccessAll); + Assert.Equal(groupRequestModel.ExternalId, response.ExternalId); + } +} diff --git a/test/Api.Test/Public/Controllers/GroupsControllerTests.cs b/test/Api.Test/Public/Controllers/GroupsControllerTests.cs new file mode 100644 index 000000000..fd6a60933 --- /dev/null +++ b/test/Api.Test/Public/Controllers/GroupsControllerTests.cs @@ -0,0 +1,67 @@ +using Bit.Api.Models.Public.Request; +using Bit.Api.Models.Public.Response; +using Bit.Api.Public.Controllers; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Mvc; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Public.Controllers; + +[ControllerCustomize(typeof(GroupsController))] +[SutProviderCustomize] +public class GroupsControllerTests +{ + [Theory] + [BitAutoData] + public async Task Post_Success(Organization organization, GroupCreateUpdateRequestModel groupRequestModel, SutProvider sutProvider) + { + sutProvider.GetDependency().OrganizationId.Returns(organization.Id); + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + + var response = await sutProvider.Sut.Post(groupRequestModel) as JsonResult; + var responseValue = response.Value as GroupResponseModel; + + await sutProvider.GetDependency().Received(1).CreateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name && + g.AccessAll == groupRequestModel.AccessAll && g.ExternalId == groupRequestModel.ExternalId), + organization, + Arg.Any>()); + + Assert.Equal(groupRequestModel.Name, responseValue.Name); + Assert.Equal(groupRequestModel.AccessAll, responseValue.AccessAll); + Assert.Equal(groupRequestModel.ExternalId, responseValue.ExternalId); + } + + [Theory] + [BitAutoData] + public async Task Put_Success(Organization organization, Group group, GroupCreateUpdateRequestModel groupRequestModel, SutProvider sutProvider) + { + group.OrganizationId = organization.Id; + + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency().GetByIdAsync(group.Id).Returns(group); + sutProvider.GetDependency().OrganizationId.Returns(organization.Id); + + var response = await sutProvider.Sut.Put(group.Id, groupRequestModel) as JsonResult; + var responseValue = response.Value as GroupResponseModel; + + await sutProvider.GetDependency().Received(1).UpdateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name && + g.AccessAll == groupRequestModel.AccessAll && g.ExternalId == groupRequestModel.ExternalId), + Arg.Is(o => o.Id == organization.Id), + Arg.Any>()); + + Assert.Equal(groupRequestModel.Name, responseValue.Name); + Assert.Equal(groupRequestModel.AccessAll, responseValue.AccessAll); + Assert.Equal(groupRequestModel.ExternalId, responseValue.ExternalId); + } +} diff --git a/test/Core.Test/OrganizationFeatures/Groups/CreateGroupCommandTests.cs b/test/Core.Test/OrganizationFeatures/Groups/CreateGroupCommandTests.cs new file mode 100644 index 000000000..9d18da466 --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/Groups/CreateGroupCommandTests.cs @@ -0,0 +1,80 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Business; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.Groups; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.Groups; + +[SutProviderCustomize] +public class CreateGroupCommandTests +{ + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task CreateGroup_Success(SutProvider sutProvider, Organization organization, Group group) + { + await sutProvider.Sut.CreateGroupAsync(group, organization); + + await sutProvider.GetDependency().Received(1).CreateAsync(group); + await sutProvider.GetDependency().Received(1).LogGroupEventAsync(group, Enums.EventType.Group_Created); + await sutProvider.GetDependency().Received(1).RaiseEventAsync(Arg.Is(r => r.Type == ReferenceEventType.GroupCreated && r.Id == organization.Id && r.Source == ReferenceEventSource.Organization)); + AssertHelper.AssertRecent(group.CreationDate); + AssertHelper.AssertRecent(group.RevisionDate); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task CreateGroup_WithCollections_Success(SutProvider sutProvider, Organization organization, Group group, List collections) + { + await sutProvider.Sut.CreateGroupAsync(group, organization, collections); + + await sutProvider.GetDependency().Received(1).CreateAsync(group, collections); + await sutProvider.GetDependency().Received(1).LogGroupEventAsync(group, Enums.EventType.Group_Created); + await sutProvider.GetDependency().Received(1).RaiseEventAsync(Arg.Is(r => r.Type == ReferenceEventType.GroupCreated && r.Id == organization.Id && r.Source == ReferenceEventSource.Organization)); + AssertHelper.AssertRecent(group.CreationDate); + AssertHelper.AssertRecent(group.RevisionDate); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task CreateGroup_WithEventSystemUser_Success(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) + { + await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser); + + await sutProvider.GetDependency().Received(1).CreateAsync(group); + await sutProvider.GetDependency().Received(1).LogGroupEventAsync(group, Enums.EventType.Group_Created, eventSystemUser); + await sutProvider.GetDependency().Received(1).RaiseEventAsync(Arg.Is(r => r.Type == ReferenceEventType.GroupCreated && r.Id == organization.Id && r.Source == ReferenceEventSource.Organization)); + AssertHelper.AssertRecent(group.CreationDate); + AssertHelper.AssertRecent(group.RevisionDate); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task CreateGroup_WithNullOrganization_Throws(SutProvider sutProvider, Group group, EventSystemUser eventSystemUser) + { + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, null, eventSystemUser)); + + Assert.Contains("Organization not found", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); + } + + [Theory, OrganizationCustomize(UseGroups = false), BitAutoData] + public async Task CreateGroup_WithUseGroupsAsFalse_Throws(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) + { + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser)); + + Assert.Contains("This organization cannot use groups", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); + } +} diff --git a/test/Core.Test/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs b/test/Core.Test/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs new file mode 100644 index 000000000..6f765736c --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs @@ -0,0 +1,71 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.Groups; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.Groups; + +[SutProviderCustomize] +public class UpdateGroupCommandTests +{ + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_Success(SutProvider sutProvider, Group group, Organization organization) + { + await sutProvider.Sut.UpdateGroupAsync(group, organization); + + await sutProvider.GetDependency().Received(1).ReplaceAsync(group); + await sutProvider.GetDependency().Received(1).LogGroupEventAsync(group, Enums.EventType.Group_Updated); + AssertHelper.AssertRecent(group.RevisionDate); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithCollections_Success(SutProvider sutProvider, Group group, Organization organization, List collections) + { + await sutProvider.Sut.UpdateGroupAsync(group, organization, collections); + + await sutProvider.GetDependency().Received(1).ReplaceAsync(group, collections); + await sutProvider.GetDependency().Received(1).LogGroupEventAsync(group, Enums.EventType.Group_Updated); + AssertHelper.AssertRecent(group.RevisionDate); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithEventSystemUser_Success(SutProvider sutProvider, Group group, Organization organization, EventSystemUser eventSystemUser) + { + await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser); + + await sutProvider.GetDependency().Received(1).ReplaceAsync(group); + await sutProvider.GetDependency().Received(1).LogGroupEventAsync(group, Enums.EventType.Group_Updated, eventSystemUser); + AssertHelper.AssertRecent(group.RevisionDate); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithNullOrganization_Throws(SutProvider sutProvider, Group group, EventSystemUser eventSystemUser) + { + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, null, eventSystemUser)); + + Assert.Contains("Organization not found", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); + } + + [Theory, OrganizationCustomize(UseGroups = false), BitAutoData] + public async Task UpdateGroup_WithUseGroupsAsFalse_Throws(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) + { + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser)); + + Assert.Contains("This organization cannot use groups", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); + } +} diff --git a/test/Core.Test/Services/GroupServiceTests.cs b/test/Core.Test/Services/GroupServiceTests.cs index 7a4bf728c..e904ea7a7 100644 --- a/test/Core.Test/Services/GroupServiceTests.cs +++ b/test/Core.Test/Services/GroupServiceTests.cs @@ -1,7 +1,6 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.OrganizationFixtures; @@ -16,105 +15,6 @@ namespace Bit.Core.Test.Services; [OrganizationCustomize(UseGroups = true)] public class GroupServiceTests { - [Theory, BitAutoData] - public async Task SaveAsync_DefaultGroupId_CreatesGroupInRepository(Group group, Organization organization, SutProvider sutProvider) - { - group.Id = default(Guid); - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - organization.UseGroups = true; - var utcNow = DateTime.UtcNow; - - await sutProvider.Sut.SaveAsync(group); - - await sutProvider.GetDependency().Received().CreateAsync(group); - await sutProvider.GetDependency().Received().LogGroupEventAsync(group, EventType.Group_Created); - Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1)); - Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_DefaultGroupId_WithEventSystemUser_CreatesGroupInRepository(Group group, Organization organization, EventSystemUser eventSystemUser, SutProvider sutProvider) - { - group.Id = default(Guid); - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - organization.UseGroups = true; - var utcNow = DateTime.UtcNow; - - await sutProvider.Sut.SaveAsync(group, eventSystemUser); - - await sutProvider.GetDependency().Received().CreateAsync(group); - await sutProvider.GetDependency().Received().LogGroupEventAsync(group, EventType.Group_Created, eventSystemUser); - Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1)); - Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_DefaultGroupIdAndCollections_CreatesGroupInRepository(Group group, Organization organization, List collections, SutProvider sutProvider) - { - group.Id = default(Guid); - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - organization.UseGroups = true; - var utcNow = DateTime.UtcNow; - - await sutProvider.Sut.SaveAsync(group, collections); - - await sutProvider.GetDependency().Received().CreateAsync(group, collections); - await sutProvider.GetDependency().Received().LogGroupEventAsync(group, EventType.Group_Created); - Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1)); - Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_NonDefaultGroupId_ReplaceGroupInRepository(Group group, Organization organization, List collections, SutProvider sutProvider) - { - organization.UseGroups = true; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - - await sutProvider.Sut.SaveAsync(group, collections); - - await sutProvider.GetDependency().Received().ReplaceAsync(group, collections); - await sutProvider.GetDependency().Received().LogGroupEventAsync(group, EventType.Group_Updated); - Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_NonDefaultGroupId_ReplaceGroupInRepository_NoCollections(Group group, Organization organization, SutProvider sutProvider) - { - organization.UseGroups = true; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - - await sutProvider.Sut.SaveAsync(group, null); - - await sutProvider.GetDependency().Received().ReplaceAsync(group); - await sutProvider.GetDependency().Received().LogGroupEventAsync(group, EventType.Group_Updated); - Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_NonExistingOrganizationId_ThrowsBadRequest(Group group, SutProvider sutProvider) - { - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(group)); - Assert.Contains("Organization not found", exception.Message); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); - } - - [Theory, OrganizationCustomize(UseGroups = false), BitAutoData] - public async Task SaveAsync_OrganizationDoesNotUseGroups_ThrowsBadRequest(Group group, Organization organization, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(group)); - - Assert.Contains("This organization cannot use groups", exception.Message); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); - } - [Theory, BitAutoData] public async Task DeleteAsync_ValidData_DeletesGroup(Group group, SutProvider sutProvider) { @@ -138,7 +38,6 @@ public class GroupServiceTests { group.OrganizationId = organization.Id; organization.UseGroups = true; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); organizationUser.OrganizationId = organization.Id; sutProvider.GetDependency().GetByIdAsync(organizationUser.Id) .Returns(organizationUser); @@ -155,7 +54,6 @@ public class GroupServiceTests { group.OrganizationId = organization.Id; organization.UseGroups = true; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); organizationUser.OrganizationId = organization.Id; sutProvider.GetDependency().GetByIdAsync(organizationUser.Id) .Returns(organizationUser); @@ -172,7 +70,6 @@ public class GroupServiceTests { group.OrganizationId = organization.Id; organization.UseGroups = true; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); // organizationUser.OrganizationId = organization.Id; sutProvider.GetDependency().GetByIdAsync(organizationUser.Id) .Returns(organizationUser);