mirror of
https://github.com/bitwarden/server
synced 2026-01-19 00:43:47 +00:00
Merge branch 'main' into tools/pm-21918/send-authentication-commands
This commit is contained in:
@@ -10,6 +10,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
@@ -471,18 +472,32 @@ public class ConfirmOrganizationUserCommandTests
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
|
||||
|
||||
var policyDetails = new PolicyDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
OrganizationUserId = orgUser.Id,
|
||||
IsProvider = false,
|
||||
OrganizationUserStatus = orgUser.Status,
|
||||
OrganizationUserType = orgUser.Type,
|
||||
PolicyType = PolicyType.OrganizationDataOwnership
|
||||
};
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetManyByOrganizationIdAsync<OrganizationDataOwnershipPolicyRequirement>(organization.Id)
|
||||
.Returns(new List<Guid> { orgUser.Id });
|
||||
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(orgUser.UserId!.Value)
|
||||
.Returns(new OrganizationDataOwnershipPolicyRequirement(OrganizationDataOwnershipState.Enabled, [policyDetails]));
|
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName);
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.Received(1)
|
||||
.UpsertDefaultCollectionsAsync(
|
||||
organization.Id,
|
||||
Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(orgUser.Id)),
|
||||
collectionName);
|
||||
.CreateAsync(
|
||||
Arg.Is<Collection>(c =>
|
||||
c.Name == collectionName &&
|
||||
c.OrganizationId == organization.Id &&
|
||||
c.Type == CollectionType.DefaultUserCollection),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cu =>
|
||||
cu.Single().Id == orgUser.Id &&
|
||||
cu.Single().Manage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -511,7 +526,7 @@ public class ConfirmOrganizationUserCommandTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganizationDataOwnershipPolicyNotApplicable_DoesNotCreateDefaultCollection(
|
||||
Organization org, OrganizationUser confirmingUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Accepted, OrganizationUserType.Owner)] OrganizationUser orgUser, User user,
|
||||
string key, string collectionName, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = PlanType.EnterpriseAnnually;
|
||||
@@ -523,9 +538,18 @@ public class ConfirmOrganizationUserCommandTests
|
||||
sutProvider.GetDependency<IUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
|
||||
|
||||
var policyDetails = new PolicyDetails
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
OrganizationUserId = orgUser.Id,
|
||||
IsProvider = false,
|
||||
OrganizationUserStatus = orgUser.Status,
|
||||
OrganizationUserType = orgUser.Type,
|
||||
PolicyType = PolicyType.OrganizationDataOwnership
|
||||
};
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetManyByOrganizationIdAsync<OrganizationDataOwnershipPolicyRequirement>(org.Id)
|
||||
.Returns(new List<Guid> { orgUser.UserId!.Value });
|
||||
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(orgUser.UserId!.Value)
|
||||
.Returns(new OrganizationDataOwnershipPolicyRequirement(OrganizationDataOwnershipState.Disabled, [policyDetails]));
|
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccountvNext;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@@ -17,12 +17,12 @@ using Xunit;
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccountvNext;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
public class DeleteClaimedOrganizationUserAccountCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_WithValidSingleUser_CallsDeleteManyUsersAsync(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -65,7 +65,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WithEmptyUserIds_ReturnsEmptyResults(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId)
|
||||
{
|
||||
@@ -77,7 +77,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WithValidUsers_DeletesUsersAndLogsEvents(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
User user1,
|
||||
User user2,
|
||||
Guid organizationId,
|
||||
@@ -135,7 +135,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WithValidationErrors_ReturnsErrorResults(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid orgUserId1,
|
||||
Guid orgUserId2,
|
||||
@@ -183,7 +183,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WithMixedValidationResults_HandlesPartialSuccessCorrectly(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
User validUser,
|
||||
Guid organizationId,
|
||||
Guid validOrgUserId,
|
||||
@@ -243,7 +243,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_CancelPremiumsAsync_HandlesGatewayExceptionAndLogsWarning(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -285,7 +285,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).CancelPremiumAsync(user);
|
||||
await AssertSuccessfulUserOperations(sutProvider, [user], [orgUser]);
|
||||
|
||||
sutProvider.GetDependency<ILogger<DeleteClaimedOrganizationUserAccountCommandvNext>>()
|
||||
sutProvider.GetDependency<ILogger<DeleteClaimedOrganizationUserAccountCommand>>()
|
||||
.Received(1)
|
||||
.Log(
|
||||
LogLevel.Warning,
|
||||
@@ -299,7 +299,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateInternalRequests_CreatesCorrectRequestsForAllUsers(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
User user1,
|
||||
User user2,
|
||||
Guid organizationId,
|
||||
@@ -326,7 +326,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
.GetUsersOrganizationClaimedStatusAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(claimedStatuses);
|
||||
|
||||
sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidatorvNext>()
|
||||
sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidator>()
|
||||
.ValidateAsync(Arg.Any<IEnumerable<DeleteUserValidationRequest>>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
@@ -338,7 +338,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
await sutProvider.Sut.DeleteManyUsersAsync(organizationId, orgUserIds, deletingUserId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidatorvNext>()
|
||||
await sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidator>()
|
||||
.Received(1)
|
||||
.ValidateAsync(Arg.Is<IEnumerable<DeleteUserValidationRequest>>(requests =>
|
||||
requests.Count() == 2 &&
|
||||
@@ -359,7 +359,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetUsersAsync_WithNullUserIds_ReturnsEmptyCollection(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
[OrganizationUser] OrganizationUser orgUserWithoutUserId)
|
||||
@@ -374,7 +374,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(ids => !ids.Any()))
|
||||
.Returns([]);
|
||||
|
||||
sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidatorvNext>()
|
||||
sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidator>()
|
||||
.ValidateAsync(Arg.Any<IEnumerable<DeleteUserValidationRequest>>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
@@ -386,7 +386,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
await sutProvider.Sut.DeleteManyUsersAsync(organizationId, [orgUserWithoutUserId.Id], deletingUserId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidatorvNext>()
|
||||
await sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidator>()
|
||||
.Received(1)
|
||||
.ValidateAsync(Arg.Is<IEnumerable<DeleteUserValidationRequest>>(requests =>
|
||||
requests.Count() == 1 &&
|
||||
@@ -406,7 +406,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
ValidationResultHelpers.Invalid(request, error);
|
||||
|
||||
private static void SetupRepositoryMocks(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
ICollection<OrganizationUser> orgUsers,
|
||||
IEnumerable<User> users,
|
||||
Guid organizationId,
|
||||
@@ -426,16 +426,16 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
}
|
||||
|
||||
private static void SetupValidatorMock(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
IEnumerable<ValidationResult<DeleteUserValidationRequest>> validationResults)
|
||||
{
|
||||
sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidatorvNext>()
|
||||
sutProvider.GetDependency<IDeleteClaimedOrganizationUserAccountValidator>()
|
||||
.ValidateAsync(Arg.Any<IEnumerable<DeleteUserValidationRequest>>())
|
||||
.Returns(validationResults);
|
||||
}
|
||||
|
||||
private static async Task AssertSuccessfulUserOperations(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
IEnumerable<User> expectedUsers,
|
||||
IEnumerable<OrganizationUser> expectedOrgUsers)
|
||||
{
|
||||
@@ -457,7 +457,7 @@ public class DeleteClaimedOrganizationUserAccountCommandvNextTests
|
||||
events.Any(e => e.Item1.Id == expectedOrgUser.Id && e.Item2 == EventType.OrganizationUser_Deleted))));
|
||||
}
|
||||
|
||||
private static async Task AssertNoUserOperations(SutProvider<DeleteClaimedOrganizationUserAccountCommandvNext> sutProvider)
|
||||
private static async Task AssertNoUserOperations(SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IUserRepository>().DidNotReceiveWithAnyArgs().DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushLogOutAsync(default);
|
||||
@@ -1,4 +1,4 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccountvNext;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
@@ -13,12 +13,12 @@ using Xunit;
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccountvNext;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
public class DeleteClaimedOrganizationUserAccountValidatorTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithValidSingleRequest_ReturnsValidResult(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -50,7 +50,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithMultipleValidRequests_ReturnsAllValidResults(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user1,
|
||||
User user2,
|
||||
Guid organizationId,
|
||||
@@ -97,7 +97,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithNullUser_ReturnsUserNotFoundError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
[OrganizationUser] OrganizationUser organizationUser)
|
||||
@@ -123,7 +123,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithNullOrganizationUser_ReturnsUserNotFoundError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId)
|
||||
@@ -149,7 +149,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithInvitedUser_ReturnsInvalidUserStatusError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -178,7 +178,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WhenDeletingYourself_ReturnsCannotDeleteYourselfError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
[OrganizationUser] OrganizationUser organizationUser)
|
||||
@@ -206,7 +206,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithUnclaimedUser_ReturnsUserNotClaimedError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -235,7 +235,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_DeletingOwnerWhenCurrentUserIsNotOwner_ReturnsCannotDeleteOwnersError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -266,7 +266,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_DeletingOwnerWhenCurrentUserIsOwner_ReturnsValidResult(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -296,7 +296,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithSoleOwnerOfOrganization_ReturnsSoleOwnerError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -331,7 +331,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithSoleProviderOwner_ReturnsSoleProviderError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -366,7 +366,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_CustomUserDeletingAdmin_ReturnsCannotDeleteAdminsError(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -397,7 +397,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_AdminDeletingAdmin_ReturnsValidResult(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User user,
|
||||
Guid organizationId,
|
||||
Guid deletingUserId,
|
||||
@@ -427,7 +427,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithMixedValidAndInvalidRequests_ReturnsCorrespondingResults(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
User validUser,
|
||||
User invalidUser,
|
||||
Guid organizationId,
|
||||
@@ -475,7 +475,7 @@ public class DeleteClaimedOrganizationUserAccountValidatorvNextTests
|
||||
}
|
||||
|
||||
private static void SetupMocks(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidatorvNext> sutProvider,
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid userId,
|
||||
OrganizationUserType currentUserType = OrganizationUserType.Owner)
|
||||
@@ -1,526 +0,0 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteClaimedOrganizationUserAccountCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_WithValidUser_DeletesUserAndLogsEvent(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user, Guid deletingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUser.Id)
|
||||
.Returns(organizationUser);
|
||||
|
||||
sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
|
||||
.GetUsersOrganizationClaimedStatusAsync(
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(organizationUser.Id)))
|
||||
.Returns(new Dictionary<Guid, bool> { { organizationUser.Id, true } });
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(organizationUser.Id)),
|
||||
includeProvider: Arg.Any<bool>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DeleteUserAsync(organizationUser.OrganizationId, organizationUser.Id, deletingUserId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user);
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Deleted);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_WithUserNotFound_ThrowsException(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
Guid organizationId, Guid organizationUserId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUserId)
|
||||
.Returns((OrganizationUser?)null);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.DeleteUserAsync(organizationId, organizationUserId, null));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Member not found.", exception.Message);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_DeletingYourself_ThrowsException(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||
Guid deletingUserId)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = user.Id = deletingUserId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUser.Id)
|
||||
.Returns(organizationUser);
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DeleteUserAsync(organizationUser.OrganizationId, organizationUser.Id, deletingUserId));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("You cannot delete yourself.", exception.Message);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_WhenUserIsInvited_ThrowsException(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
[OrganizationUser(OrganizationUserStatusType.Invited, OrganizationUserType.User)] OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUser.Id)
|
||||
.Returns(organizationUser);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DeleteUserAsync(organizationUser.OrganizationId, organizationUser.Id, null));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("You cannot delete a member with Invited status.", exception.Message);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_WhenCustomUserDeletesAdmin_ThrowsException(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Admin)] OrganizationUser organizationUser,
|
||||
Guid deletingUserId)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUser.Id)
|
||||
.Returns(organizationUser);
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationCustom(organizationUser.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DeleteUserAsync(organizationUser.OrganizationId, organizationUser.Id, deletingUserId));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Custom users can not delete admins.", exception.Message);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_DeletingOwnerWhenNotOwner_ThrowsException(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
||||
Guid deletingUserId)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUser.Id)
|
||||
.Returns(organizationUser);
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationUser.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DeleteUserAsync(organizationUser.OrganizationId, organizationUser.Id, deletingUserId));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Only owners can delete other owners.", exception.Message);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_DeletingLastConfirmedOwner_ThrowsException(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
||||
Guid deletingUserId)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUser.Id)
|
||||
.Returns(organizationUser);
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationUser.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(organizationUser.Id)),
|
||||
includeProvider: Arg.Any<bool>())
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DeleteUserAsync(organizationUser.OrganizationId, organizationUser.Id, deletingUserId));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Organization must have at least one confirmed owner.", exception.Message);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteUserAsync_WithUserNotManaged_ThrowsException(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUser.Id)
|
||||
.Returns(organizationUser);
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
|
||||
sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
|
||||
.GetUsersOrganizationClaimedStatusAsync(organizationUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, bool> { { organizationUser.Id, false } });
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DeleteUserAsync(organizationUser.OrganizationId, organizationUser.Id, null));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Member is not claimed by the organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WithValidUsers_DeletesUsersAndLogsEvents(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user1, User user2, Guid organizationId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser1,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser2)
|
||||
{
|
||||
// Arrange
|
||||
orgUser1.OrganizationId = orgUser2.OrganizationId = organizationId;
|
||||
orgUser1.UserId = user1.Id;
|
||||
orgUser2.UserId = user2.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser> { orgUser1, orgUser2 });
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user1.Id) && ids.Contains(user2.Id)))
|
||||
.Returns(new[] { user1, user2 });
|
||||
|
||||
sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
|
||||
.GetUsersOrganizationClaimedStatusAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, true }, { orgUser2.Id, true } });
|
||||
|
||||
// Act
|
||||
var userIds = new[] { orgUser1.Id, orgUser2.Id };
|
||||
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, userIds, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count());
|
||||
Assert.All(results, r => Assert.Empty(r.Item2));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyAsync(userIds);
|
||||
await sutProvider.GetDependency<IUserRepository>().Received(1).DeleteManyAsync(Arg.Is<IEnumerable<User>>(users => users.Any(u => u.Id == user1.Id) && users.Any(u => u.Id == user2.Id)));
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
|
||||
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1
|
||||
&& events.Count(e => e.Item1.Id == orgUser2.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WhenUserNotFound_ReturnsErrorMessage(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid orgUserId)
|
||||
{
|
||||
// Act
|
||||
var result = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, new[] { orgUserId }, null);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUserId, result.First().Item1);
|
||||
Assert.Contains("Member not found.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WhenDeletingYourself_ReturnsErrorMessage(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
User user, [OrganizationUser] OrganizationUser orgUser, Guid deletingUserId)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.UserId = user.Id = deletingUserId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser> { orgUser });
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user.Id)))
|
||||
.Returns(new[] { user });
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.DeleteManyUsersAsync(orgUser.OrganizationId, new[] { orgUser.Id }, deletingUserId);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUser.Id, result.First().Item1);
|
||||
Assert.Contains("You cannot delete yourself.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WhenUserIsInvited_ReturnsErrorMessage(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider,
|
||||
[OrganizationUser(OrganizationUserStatusType.Invited, OrganizationUserType.User)] OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.UserId = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser> { orgUser });
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.DeleteManyUsersAsync(orgUser.OrganizationId, new[] { orgUser.Id }, null);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUser.Id, result.First().Item1);
|
||||
Assert.Contains("You cannot delete a member with Invited status.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WhenDeletingOwnerAsNonOwner_ReturnsErrorMessage(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
|
||||
Guid deletingUserId)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser> { orgUser });
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(user.Id)))
|
||||
.Returns(new[] { user });
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(orgUser.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.DeleteManyUsersAsync(orgUser.OrganizationId, new[] { orgUser.Id }, deletingUserId);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUser.Id, result.First().Item1);
|
||||
Assert.Contains("Only owners can delete other owners.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WhenDeletingLastOwner_ReturnsErrorMessage(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
|
||||
Guid deletingUserId)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser> { orgUser });
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(user.Id)))
|
||||
.Returns(new[] { user });
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(orgUser.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(orgUser.OrganizationId, Arg.Any<IEnumerable<Guid>>(), Arg.Any<bool>())
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.DeleteManyUsersAsync(orgUser.OrganizationId, new[] { orgUser.Id }, deletingUserId);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUser.Id, result.First().Item1);
|
||||
Assert.Contains("Organization must have at least one confirmed owner.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_WhenUserNotManaged_ReturnsErrorMessage(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser> { orgUser });
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(orgUser.UserId.Value)))
|
||||
.Returns(new[] { user });
|
||||
|
||||
sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
|
||||
.GetUsersOrganizationClaimedStatusAsync(Arg.Any<Guid>(), Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, bool> { { orgUser.Id, false } });
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.DeleteManyUsersAsync(orgUser.OrganizationId, new[] { orgUser.Id }, null);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUser.Id, result.First().Item1);
|
||||
Assert.Contains("Member is not claimed by the organization.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteManyUsersAsync_MixedValidAndInvalidUsers_ReturnsAppropriateResults(
|
||||
SutProvider<DeleteClaimedOrganizationUserAccountCommand> sutProvider, User user1, User user3,
|
||||
Guid organizationId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser1,
|
||||
[OrganizationUser(OrganizationUserStatusType.Invited, OrganizationUserType.User)] OrganizationUser orgUser2,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser3)
|
||||
{
|
||||
// Arrange
|
||||
orgUser1.UserId = user1.Id;
|
||||
orgUser2.UserId = null;
|
||||
orgUser3.UserId = user3.Id;
|
||||
orgUser1.OrganizationId = orgUser2.OrganizationId = orgUser3.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new List<OrganizationUser> { orgUser1, orgUser2, orgUser3 });
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user1.Id) && ids.Contains(user3.Id)))
|
||||
.Returns(new[] { user1, user3 });
|
||||
|
||||
sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
|
||||
.GetUsersOrganizationClaimedStatusAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, true }, { orgUser3.Id, false } });
|
||||
|
||||
// Act
|
||||
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, new[] { orgUser1.Id, orgUser2.Id, orgUser3.Id }, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, results.Count());
|
||||
Assert.Empty(results.First(r => r.Item1 == orgUser1.Id).Item2);
|
||||
Assert.Equal("You cannot delete a member with Invited status.", results.First(r => r.Item1 == orgUser2.Id).Item2);
|
||||
Assert.Equal("Member is not claimed by the organization.", results.First(r => r.Item1 == orgUser3.Id).Item2);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
|
||||
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Organizations.Queries;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
@@ -13,11 +14,14 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Stripe;
|
||||
using Stripe.Tax;
|
||||
using Stripe.TestHelpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Organizations.Queries;
|
||||
|
||||
using static StripeConstants;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GetOrganizationWarningsQueryTests
|
||||
{
|
||||
@@ -57,7 +61,7 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Status = StripeConstants.SubscriptionStatus.Trialing,
|
||||
Status = SubscriptionStatus.Trialing,
|
||||
TrialEnd = now.AddDays(7),
|
||||
Customer = new Customer
|
||||
{
|
||||
@@ -95,7 +99,7 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Status = StripeConstants.SubscriptionStatus.Trialing,
|
||||
Status = SubscriptionStatus.Trialing,
|
||||
TrialEnd = now.AddDays(7),
|
||||
Customer = new Customer
|
||||
{
|
||||
@@ -142,7 +146,7 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Status = StripeConstants.SubscriptionStatus.Unpaid,
|
||||
Status = SubscriptionStatus.Unpaid,
|
||||
Customer = new Customer
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
@@ -170,7 +174,8 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Status = StripeConstants.SubscriptionStatus.Unpaid
|
||||
Customer = new Customer(),
|
||||
Status = SubscriptionStatus.Unpaid
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id)
|
||||
@@ -197,7 +202,8 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Status = StripeConstants.SubscriptionStatus.Unpaid
|
||||
Customer = new Customer(),
|
||||
Status = SubscriptionStatus.Unpaid
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
|
||||
@@ -223,7 +229,8 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Status = StripeConstants.SubscriptionStatus.Canceled
|
||||
Customer = new Customer(),
|
||||
Status = SubscriptionStatus.Canceled
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
|
||||
@@ -249,7 +256,8 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Status = StripeConstants.SubscriptionStatus.Unpaid
|
||||
Customer = new Customer(),
|
||||
Status = SubscriptionStatus.Unpaid
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(false);
|
||||
@@ -275,8 +283,9 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
CollectionMethod = StripeConstants.CollectionMethod.SendInvoice,
|
||||
Status = StripeConstants.SubscriptionStatus.Active,
|
||||
CollectionMethod = CollectionMethod.SendInvoice,
|
||||
Customer = new Customer(),
|
||||
Status = SubscriptionStatus.Active,
|
||||
CurrentPeriodEnd = now.AddDays(10),
|
||||
TestClock = new TestClock
|
||||
{
|
||||
@@ -313,11 +322,12 @@ public class GetOrganizationWarningsQueryTests
|
||||
))
|
||||
.Returns(new Subscription
|
||||
{
|
||||
CollectionMethod = StripeConstants.CollectionMethod.SendInvoice,
|
||||
Status = StripeConstants.SubscriptionStatus.Active,
|
||||
CollectionMethod = CollectionMethod.SendInvoice,
|
||||
Customer = new Customer(),
|
||||
Status = SubscriptionStatus.Active,
|
||||
LatestInvoice = new Invoice
|
||||
{
|
||||
Status = StripeConstants.InvoiceStatus.Open,
|
||||
Status = InvoiceStatus.Open,
|
||||
DueDate = now.AddDays(30),
|
||||
Created = now
|
||||
},
|
||||
@@ -360,8 +370,9 @@ public class GetOrganizationWarningsQueryTests
|
||||
.Returns(new Subscription
|
||||
{
|
||||
Id = subscriptionId,
|
||||
CollectionMethod = StripeConstants.CollectionMethod.SendInvoice,
|
||||
Status = StripeConstants.SubscriptionStatus.PastDue,
|
||||
CollectionMethod = CollectionMethod.SendInvoice,
|
||||
Customer = new Customer(),
|
||||
Status = SubscriptionStatus.PastDue,
|
||||
TestClock = new TestClock
|
||||
{
|
||||
FrozenTime = now
|
||||
@@ -390,4 +401,406 @@ public class GetOrganizationWarningsQueryTests
|
||||
|
||||
Assert.Equal(dueDate.AddDays(30), response.ResellerRenewal.PastDue!.SuspensionDate);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_USCustomer_NoTaxIdWarning(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "US" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId>() },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.Null(response.TaxId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_FreeCustomer_NoTaxIdWarning(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.Free;
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId>() },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.Null(response.TaxId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_NotOwner_NoTaxIdWarning(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId>() },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(false);
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.Null(response.TaxId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_HasProvider_NoTaxIdWarning(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId>() },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>()
|
||||
.GetByOrganizationIdAsync(organization.Id)
|
||||
.Returns(new Provider());
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.Null(response.TaxId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_NoRegistrationInCountry_NoTaxIdWarning(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId>() },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = new List<Registration>
|
||||
{
|
||||
new() { Country = "GB" }
|
||||
}
|
||||
});
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.Null(response.TaxId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_Has_TaxIdWarning_Missing(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId>() },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = new List<Registration>
|
||||
{
|
||||
new() { Country = "CA" }
|
||||
}
|
||||
});
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.True(response is
|
||||
{
|
||||
TaxId.Type: "tax_id_missing"
|
||||
});
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_Has_TaxIdWarning_PendingVerification(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
|
||||
var taxId = new TaxId
|
||||
{
|
||||
Verification = new TaxIdVerification
|
||||
{
|
||||
Status = TaxIdVerificationStatus.Pending
|
||||
}
|
||||
};
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId> { taxId } },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = new List<Registration>
|
||||
{
|
||||
new() { Country = "CA" }
|
||||
}
|
||||
});
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.True(response is
|
||||
{
|
||||
TaxId.Type: "tax_id_pending_verification"
|
||||
});
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_Has_TaxIdWarning_FailedVerification(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
|
||||
var taxId = new TaxId
|
||||
{
|
||||
Verification = new TaxIdVerification
|
||||
{
|
||||
Status = TaxIdVerificationStatus.Unverified
|
||||
}
|
||||
};
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId> { taxId } },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = new List<Registration>
|
||||
{
|
||||
new() { Country = "CA" }
|
||||
}
|
||||
});
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.True(response is
|
||||
{
|
||||
TaxId.Type: "tax_id_failed_verification"
|
||||
});
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_VerifiedTaxId_NoTaxIdWarning(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
|
||||
var taxId = new TaxId
|
||||
{
|
||||
Verification = new TaxIdVerification
|
||||
{
|
||||
Status = TaxIdVerificationStatus.Verified
|
||||
}
|
||||
};
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId> { taxId } },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = new List<Registration>
|
||||
{
|
||||
new() { Country = "CA" }
|
||||
}
|
||||
});
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.Null(response.TaxId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Run_NullVerification_NoTaxIdWarning(
|
||||
Organization organization,
|
||||
SutProvider<GetOrganizationWarningsQuery> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
|
||||
var taxId = new TaxId
|
||||
{
|
||||
Verification = null
|
||||
};
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Customer = new Customer
|
||||
{
|
||||
Address = new Address { Country = "CA" },
|
||||
TaxIds = new StripeList<TaxId> { Data = new List<TaxId> { taxId } },
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>()
|
||||
}
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISubscriberService>()
|
||||
.GetSubscription(organization, Arg.Any<SubscriptionGetOptions>())
|
||||
.Returns(subscription);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = new List<Registration>
|
||||
{
|
||||
new() { Country = "CA" }
|
||||
}
|
||||
});
|
||||
|
||||
var response = await sutProvider.Sut.Run(organization);
|
||||
|
||||
Assert.Null(response.TaxId);
|
||||
}
|
||||
}
|
||||
|
||||
322
test/Core.Test/KeyManagement/Kdf/ChangeKdfCommandTests.cs
Normal file
322
test/Core.Test/KeyManagement/Kdf/ChangeKdfCommandTests.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Kdf.Implementations;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.KeyManagement.Kdf;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class ChangeKdfCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_ChangesKdfAsync(SutProvider<ChangeKdfCommand> sutProvider, User user)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
sutProvider.GetDependency<IUserService>().UpdatePasswordHash(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(IdentityResult.Success));
|
||||
|
||||
var kdf = new KdfSettings
|
||||
{
|
||||
KdfType = Enums.KdfType.Argon2id,
|
||||
Iterations = 4,
|
||||
Memory = 512,
|
||||
Parallelism = 4
|
||||
};
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterPasswordAuthenticationHash = "newMasterPassword",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterKeyWrappedUserKey = "masterKeyWrappedUserKey",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
|
||||
await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData);
|
||||
|
||||
await sutProvider.GetDependency<IUserRepository>().Received(1).ReplaceAsync(Arg.Is<User>(u =>
|
||||
u.Id == user.Id
|
||||
&& u.Kdf == Enums.KdfType.Argon2id
|
||||
&& u.KdfIterations == 4
|
||||
&& u.KdfMemory == 512
|
||||
&& u.KdfParallelism == 4
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_UserIsNull_ThrowsArgumentNullException(SutProvider<ChangeKdfCommand> sutProvider)
|
||||
{
|
||||
var kdf = new KdfSettings
|
||||
{
|
||||
KdfType = Enums.KdfType.Argon2id,
|
||||
Iterations = 4,
|
||||
Memory = 512,
|
||||
Parallelism = 4
|
||||
};
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterPasswordAuthenticationHash = "newMasterPassword",
|
||||
Salt = "salt"
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterKeyWrappedUserKey = "masterKeyWrappedUserKey",
|
||||
Salt = "salt"
|
||||
};
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
|
||||
await sutProvider.Sut.ChangeKdfAsync(null!, "masterPassword", authenticationData, unlockData));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_WrongPassword_ReturnsPasswordMismatch(SutProvider<ChangeKdfCommand> sutProvider, User user)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(false));
|
||||
|
||||
var kdf = new KdfSettings
|
||||
{
|
||||
KdfType = Enums.KdfType.Argon2id,
|
||||
Iterations = 4,
|
||||
Memory = 512,
|
||||
Parallelism = 4
|
||||
};
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterPasswordAuthenticationHash = "newMasterPassword",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterKeyWrappedUserKey = "masterKeyWrappedUserKey",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData);
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Contains(result.Errors, e => e.Code == "PasswordMismatch");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_WithAuthenticationAndUnlockData_UpdatesUserCorrectly(SutProvider<ChangeKdfCommand> sutProvider, User user)
|
||||
{
|
||||
var constantKdf = new KdfSettings
|
||||
{
|
||||
KdfType = Enums.KdfType.Argon2id,
|
||||
Iterations = 5,
|
||||
Memory = 1024,
|
||||
Parallelism = 4
|
||||
};
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = constantKdf,
|
||||
MasterPasswordAuthenticationHash = "new-auth-hash",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = constantKdf,
|
||||
MasterKeyWrappedUserKey = "new-wrapped-key",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
sutProvider.GetDependency<IUserService>().UpdatePasswordHash(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(IdentityResult.Success));
|
||||
|
||||
await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData);
|
||||
|
||||
await sutProvider.GetDependency<IUserRepository>().Received(1).ReplaceAsync(Arg.Is<User>(u =>
|
||||
u.Id == user.Id
|
||||
&& u.Kdf == constantKdf.KdfType
|
||||
&& u.KdfIterations == constantKdf.Iterations
|
||||
&& u.KdfMemory == constantKdf.Memory
|
||||
&& u.KdfParallelism == constantKdf.Parallelism
|
||||
&& u.Key == "new-wrapped-key"
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_KdfNotEqualBetweenAuthAndUnlock_ThrowsBadRequestException(SutProvider<ChangeKdfCommand> sutProvider, User user)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = new KdfSettings { KdfType = Enums.KdfType.Argon2id, Iterations = 4, Memory = 512, Parallelism = 4 },
|
||||
MasterPasswordAuthenticationHash = "new-auth-hash",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = new KdfSettings { KdfType = Enums.KdfType.PBKDF2_SHA256, Iterations = 100000 },
|
||||
MasterKeyWrappedUserKey = "new-wrapped-key",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||
await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_AuthDataSaltMismatch_Throws(SutProvider<ChangeKdfCommand> sutProvider, User user, KdfSettings kdf)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterPasswordAuthenticationHash = "new-auth-hash",
|
||||
Salt = "different-salt"
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterKeyWrappedUserKey = "new-wrapped-key",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_UnlockDataSaltMismatch_Throws(SutProvider<ChangeKdfCommand> sutProvider, User user, KdfSettings kdf)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterPasswordAuthenticationHash = "new-auth-hash",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterKeyWrappedUserKey = "new-wrapped-key",
|
||||
Salt = "different-salt"
|
||||
};
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_UpdatePasswordHashFails_ReturnsFailure(SutProvider<ChangeKdfCommand> sutProvider, User user)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
var failedResult = IdentityResult.Failed(new IdentityError { Code = "TestFail", Description = "Test fail" });
|
||||
sutProvider.GetDependency<IUserService>().UpdatePasswordHash(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(failedResult));
|
||||
|
||||
var kdf = new KdfSettings
|
||||
{
|
||||
KdfType = Enums.KdfType.Argon2id,
|
||||
Iterations = 4,
|
||||
Memory = 512,
|
||||
Parallelism = 4
|
||||
};
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterPasswordAuthenticationHash = "newMasterPassword",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = kdf,
|
||||
MasterKeyWrappedUserKey = "masterKeyWrappedUserKey",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData);
|
||||
|
||||
Assert.False(result.Succeeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_InvalidKdfSettings_ThrowsBadRequestException(SutProvider<ChangeKdfCommand> sutProvider, User user)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
|
||||
// Create invalid KDF settings (iterations too low for PBKDF2)
|
||||
var invalidKdf = new KdfSettings
|
||||
{
|
||||
KdfType = Enums.KdfType.PBKDF2_SHA256,
|
||||
Iterations = 1000, // This is below the minimum of 600,000
|
||||
Memory = null,
|
||||
Parallelism = null
|
||||
};
|
||||
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = invalidKdf,
|
||||
MasterPasswordAuthenticationHash = "new-auth-hash",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = invalidKdf,
|
||||
MasterKeyWrappedUserKey = "new-wrapped-key",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||
await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData));
|
||||
|
||||
Assert.Equal("KDF settings are invalid.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ChangeKdfAsync_InvalidArgon2Settings_ThrowsBadRequestException(SutProvider<ChangeKdfCommand> sutProvider, User user)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(Arg.Any<User>(), Arg.Any<string>()).Returns(Task.FromResult(true));
|
||||
|
||||
// Create invalid Argon2 KDF settings (memory too high)
|
||||
var invalidKdf = new KdfSettings
|
||||
{
|
||||
KdfType = Enums.KdfType.Argon2id,
|
||||
Iterations = 3, // Valid
|
||||
Memory = 2048, // This is above the maximum of 1024
|
||||
Parallelism = 4 // Valid
|
||||
};
|
||||
|
||||
var authenticationData = new MasterPasswordAuthenticationData
|
||||
{
|
||||
Kdf = invalidKdf,
|
||||
MasterPasswordAuthenticationHash = "new-auth-hash",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
var unlockData = new MasterPasswordUnlockData
|
||||
{
|
||||
Kdf = invalidKdf,
|
||||
MasterKeyWrappedUserKey = "new-wrapped-key",
|
||||
Salt = user.GetMasterPasswordSalt()
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||
await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData));
|
||||
|
||||
Assert.Equal("KDF settings are invalid.", exception.Message);
|
||||
}
|
||||
|
||||
}
|
||||
230
test/Core.Test/Utilities/EnumerationProtectionHelpersTests.cs
Normal file
230
test/Core.Test/Utilities/EnumerationProtectionHelpersTests.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Bit.Core.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Utilities;
|
||||
|
||||
public class EnumerationProtectionHelpersTests
|
||||
{
|
||||
#region GetIndexForInputHash Tests
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_NullHmacKey_ReturnsZero()
|
||||
{
|
||||
// Arrange
|
||||
byte[] hmacKey = null;
|
||||
var salt = "test@example.com";
|
||||
var range = 10;
|
||||
|
||||
// Act
|
||||
var result = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_ZeroRange_ReturnsZero()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = "test@example.com";
|
||||
var range = 0;
|
||||
|
||||
// Act
|
||||
var result = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_NegativeRange_ReturnsZero()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = "test@example.com";
|
||||
var range = -5;
|
||||
|
||||
// Act
|
||||
var result = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_ValidInputs_ReturnsConsistentResult()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = Encoding.UTF8.GetBytes("test-key-12345678901234567890123456789012");
|
||||
var salt = "test@example.com";
|
||||
var range = 10;
|
||||
|
||||
// Act
|
||||
var result1 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
var result2 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
Assert.InRange(result1, 0, range - 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_SameInputSameKey_AlwaysReturnsSameResult()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = "consistent@example.com";
|
||||
var range = 100;
|
||||
|
||||
// Act - Call multiple times
|
||||
var results = new int[10];
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
results[i] = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
}
|
||||
|
||||
// Assert - All results should be identical
|
||||
Assert.All(results, result => Assert.Equal(results[0], result));
|
||||
Assert.All(results, result => Assert.InRange(result, 0, range - 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_DifferentInputsSameKey_ReturnsDifferentResults()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt1 = "user1@example.com";
|
||||
var salt2 = "user2@example.com";
|
||||
var range = 100;
|
||||
|
||||
// Act
|
||||
var result1 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt1, range);
|
||||
var result2 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt2, range);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(result1, result2);
|
||||
Assert.InRange(result1, 0, range - 1);
|
||||
Assert.InRange(result2, 0, range - 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_DifferentKeysSameInput_ReturnsDifferentResults()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey1 = RandomNumberGenerator.GetBytes(32);
|
||||
var hmacKey2 = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = "test@example.com";
|
||||
var range = 100;
|
||||
|
||||
// Act
|
||||
var result1 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey1, salt, range);
|
||||
var result2 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey2, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(result1, result2);
|
||||
Assert.InRange(result1, 0, range - 1);
|
||||
Assert.InRange(result2, 0, range - 1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(5)]
|
||||
[InlineData(10)]
|
||||
[InlineData(100)]
|
||||
[InlineData(1000)]
|
||||
public void GetIndexForInputHash_VariousRanges_ReturnsValidIndex(int range)
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = "test@example.com";
|
||||
|
||||
// Act
|
||||
var result = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.InRange(result, 0, range - 1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void GetIndexForInputHash_EmptyString_HandlesGracefully(string salt)
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
|
||||
// Act
|
||||
var result = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, 10);
|
||||
|
||||
// Assert
|
||||
Assert.InRange(result, 0, 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_NullInput_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
string salt = null;
|
||||
var range = 10;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<NullReferenceException>(() =>
|
||||
EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_SpecialCharacters_HandlesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = "test+user@example.com!@#$%^&*()";
|
||||
var range = 50;
|
||||
|
||||
// Act
|
||||
var result1 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
var result2 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
Assert.InRange(result1, 0, range - 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_UnicodeCharacters_HandlesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = "tëst@éxämplé.cöm";
|
||||
var range = 25;
|
||||
|
||||
// Act
|
||||
var result1 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
var result2 = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
Assert.InRange(result1, 0, range - 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetIndexForInputHash_LongInput_HandlesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var hmacKey = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = new string('a', 1000) + "@example.com";
|
||||
var range = 30;
|
||||
|
||||
// Act
|
||||
var result = EnumerationProtectionHelpers.GetIndexForInputHash(hmacKey, salt, range);
|
||||
|
||||
// Assert
|
||||
Assert.InRange(result, 0, range - 1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user