1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

[PM-12489] Extract OrganizationService.DeleteAsync and OrganizationService.InitiateDeleteAsync into commands (#5279)

* Create organization deletion command with logic extracted from OrganizationService

* Add unit tests for OrganizationDeleteCommand

* Register OrganizationDeleteCommand for dependency injection

* Refactor organization deletion logic to use IOrganizationDeleteCommand and remove legacy IOrganizationService.DeleteAsync method

* Add organization deletion initiation command and refactor service usage

* Enhance organization deletion commands with detailed XML documentation

* Refactor organization command registration to include sign-up and deletion methods
This commit is contained in:
Rui Tomé
2025-01-27 10:58:08 +00:00
committed by GitHub
parent f2182c2aae
commit 3908edd08f
13 changed files with 337 additions and 95 deletions

View File

@ -0,0 +1,53 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Repositories;
using Bit.Core.Exceptions;
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 NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
[SutProviderCustomize]
public class OrganizationDeleteCommandTests
{
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task Delete_Success(Organization organization, SutProvider<OrganizationDeleteCommand> sutProvider)
{
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
await sutProvider.Sut.DeleteAsync(organization);
await organizationRepository.Received().DeleteAsync(organization);
await applicationCacheService.Received().DeleteOrganizationAbilityAsync(organization.Id);
}
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task Delete_Fails_KeyConnector(Organization organization, SutProvider<OrganizationDeleteCommand> sutProvider,
SsoConfig ssoConfig)
{
ssoConfig.Enabled = true;
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
var ssoConfigRepository = sutProvider.GetDependency<ISsoConfigRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(ssoConfig);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteAsync(organization));
Assert.Contains("You cannot delete an Organization that is using Key Connector.", exception.Message);
await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default);
await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default);
}
}

View File

@ -0,0 +1,112 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tokens;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
[SutProviderCustomize]
public class OrganizationInitiateDeleteCommandTests
{
[Theory]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task InitiateDeleteAsync_ValidAdminUser_Success(OrganizationUserType organizationUserType,
Organization organization, User orgAdmin, OrganizationUserOrganizationDetails orgAdminUser,
string token, SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
{
orgAdminUser.Type = organizationUserType;
orgAdminUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserRepository>()
.GetByEmailAsync(orgAdmin.Email)
.Returns(orgAdmin);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetDetailsByUserAsync(orgAdmin.Id, organization.Id)
.Returns(orgAdminUser);
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgDeleteTokenable>>()
.Protect(Arg.Any<OrgDeleteTokenable>())
.Returns(token);
await sutProvider.Sut.InitiateDeleteAsync(organization, orgAdmin.Email);
await sutProvider.GetDependency<IMailService>().Received(1)
.SendInitiateDeleteOrganzationEmailAsync(orgAdmin.Email, organization, token);
}
[Theory, BitAutoData]
public async Task InitiateDeleteAsync_UserNotFound_ThrowsBadRequest(
Organization organization, string email, SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
{
sutProvider.GetDependency<IUserRepository>()
.GetByEmailAsync(email)
.Returns((User)null);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InitiateDeleteAsync(organization, email));
Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message);
}
[Theory]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task InitiateDeleteAsync_UserNotOrgAdmin_ThrowsBadRequest(OrganizationUserType organizationUserType,
Organization organization, User user, OrganizationUserOrganizationDetails orgUser,
SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
{
orgUser.Type = organizationUserType;
orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserRepository>()
.GetByEmailAsync(user.Email)
.Returns(user);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetDetailsByUserAsync(user.Id, organization.Id)
.Returns(orgUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InitiateDeleteAsync(organization, user.Email));
Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message);
}
[Theory]
[BitAutoData(OrganizationUserStatusType.Invited)]
[BitAutoData(OrganizationUserStatusType.Revoked)]
[BitAutoData(OrganizationUserStatusType.Accepted)]
public async Task InitiateDeleteAsync_UserNotConfirmed_ThrowsBadRequest(
OrganizationUserStatusType organizationUserStatusType,
Organization organization, User user, OrganizationUserOrganizationDetails orgUser,
SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
{
orgUser.Type = OrganizationUserType.Admin;
orgUser.Status = organizationUserStatusType;
sutProvider.GetDependency<IUserRepository>()
.GetByEmailAsync(user.Email)
.Returns(user);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetDetailsByUserAsync(user.Id, organization.Id)
.Returns(orgUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InitiateDeleteAsync(organization, user.Email));
Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message);
}
}