1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-05 18:12:48 -05:00

Merge branch 'main' into ac/pm-22101/enforce-restrictions-on-default-collection

This commit is contained in:
Rui Tome
2025-06-25 14:17:57 +01:00
226 changed files with 25732 additions and 3696 deletions

View File

@ -1,9 +1,9 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Models.Data.Integrations;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Xunit;
namespace Bit.Core.Test.Models.Data.Integrations;
namespace Bit.Core.Test.Models.Data.EventIntegrations;
public class IntegrationMessageTests
{
@ -45,6 +45,7 @@ public class IntegrationMessageTests
var json = message.ToJson();
var result = IntegrationMessage<WebhookIntegrationConfigurationDetails>.FromJson(json);
Assert.NotNull(result);
Assert.Equal(message.Configuration, result.Configuration);
Assert.Equal(message.MessageId, result.MessageId);
Assert.Equal(message.RenderedTemplate, result.RenderedTemplate);

View File

@ -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.Repositories;
using Bit.Core.Services;
@ -442,4 +443,98 @@ public class ConfirmOrganizationUserCommandTests
await sutProvider.GetDependency<IMailService>().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email, orgUser.AccessSecretsManager);
await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is<List<OrganizationUser>>(users => users.Contains(orgUser) && users.Count == 1));
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganizationDataOwnershipPolicyApplicable_WithValidCollectionName_CreatesDefaultCollection(
Organization organization, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, string collectionName, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
organization.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = organization.Id;
orgUser.UserId = user.Id;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
sutProvider.GetDependency<IUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(user.Id)
.Returns(new OrganizationDataOwnershipPolicyRequirement(
OrganizationDataOwnershipState.Enabled,
[organization.Id]));
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName);
await sutProvider.GetDependency<ICollectionRepository>()
.Received(1)
.CreateAsync(
Arg.Is<Collection>(c => c.Name == collectionName &&
c.OrganizationId == organization.Id &&
c.Type == CollectionType.DefaultUserCollection),
Arg.Is<IEnumerable<CollectionAccessSelection>>(groups => groups == null),
Arg.Is<IEnumerable<CollectionAccessSelection>>(u =>
u.Count() == 1 &&
u.First().Id == orgUser.Id &&
u.First().Manage == true));
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganizationDataOwnershipPolicyApplicable_WithInvalidCollectionName_DoesNotCreateDefaultCollection(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(user.Id)
.Returns(new OrganizationDataOwnershipPolicyRequirement(
OrganizationDataOwnershipState.Enabled,
[org.Id]));
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, "");
await sutProvider.GetDependency<ICollectionRepository>()
.DidNotReceive()
.CreateAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<CollectionAccessSelection>>(), Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganizationDataOwnershipPolicyNotApplicable_DoesNotCreateDefaultCollection(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, string collectionName, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
sutProvider.GetDependency<IUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(user.Id)
.Returns(new OrganizationDataOwnershipPolicyRequirement(
OrganizationDataOwnershipState.Enabled,
[Guid.NewGuid()]));
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName);
await sutProvider.GetDependency<ICollectionRepository>()
.DidNotReceive()
.CreateAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<CollectionAccessSelection>>(), Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
}

View File

@ -1,6 +1,5 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Payments;
using Bit.Core.AdminConsole.Utilities.Validation;

View File

@ -27,8 +27,10 @@ public class UpdateOrganizationUserCommandTests
List<CollectionAccessSelection> collections, List<Guid> groups, SutProvider<UpdateOrganizationUserCommand> sutProvider)
{
user.Id = default(Guid);
var existingUserType = OrganizationUserType.User;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collections, groups));
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collections, groups));
Assert.Contains("invite the user first", exception.Message.ToLowerInvariant());
}
@ -37,9 +39,10 @@ public class UpdateOrganizationUserCommandTests
Guid? savingUserId, SutProvider<UpdateOrganizationUserCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(user.Id).Returns(originalUser);
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, null));
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, null));
}
[Theory, BitAutoData]
@ -55,8 +58,10 @@ public class UpdateOrganizationUserCommandTests
.Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>()
.Select(guid => new Collection { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList());
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null));
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null));
}
[Theory, BitAutoData]
@ -76,9 +81,9 @@ public class UpdateOrganizationUserCommandTests
result.RemoveAt(0);
return result;
});
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null));
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null));
}
[Theory, BitAutoData]
@ -94,8 +99,10 @@ public class UpdateOrganizationUserCommandTests
.Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>()
.Select(guid => new Group { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList());
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess));
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess));
}
[Theory, BitAutoData]
@ -115,9 +122,9 @@ public class UpdateOrganizationUserCommandTests
result.RemoveAt(0);
return result;
});
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess));
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess));
}
[Theory, BitAutoData]
@ -165,7 +172,9 @@ public class UpdateOrganizationUserCommandTests
.GetCountByFreeOrganizationAdminUserAsync(newUserData.Id)
.Returns(0);
await sutProvider.Sut.UpdateUserAsync(newUserData, savingUser.UserId, collections, groups);
var existingUserType = OrganizationUserType.User;
await sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, savingUser.UserId, collections, groups);
var organizationService = sutProvider.GetDependency<IOrganizationService>();
await organizationService.Received(1).ValidateOrganizationUserUpdatePermissions(
@ -184,7 +193,7 @@ public class UpdateOrganizationUserCommandTests
[Theory]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsNotAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
OrganizationUserType userType,
OrganizationUser oldUserData,
OrganizationUser newUserData,
@ -199,10 +208,39 @@ public class UpdateOrganizationUserCommandTests
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value)
.Returns(1);
var existingUserType = OrganizationUserType.User;
// Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpdateUserAsync(newUserData, null, null, null));
() => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null));
Assert.Contains("User can only be an admin of one free organization.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Owner)]
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
OrganizationUserType newUserType,
OrganizationUserType existingUserType,
OrganizationUser oldUserData,
OrganizationUser newUserData,
Organization organization,
SutProvider<UpdateOrganizationUserCommand> sutProvider)
{
organization.PlanType = PlanType.Free;
newUserData.Type = newUserType;
Setup(sutProvider, organization, newUserData, oldUserData);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value)
.Returns(2);
// Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null));
Assert.Contains("User can only be an admin of one free organization.", exception.Message);
}

View File

@ -0,0 +1,185 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations.OrganizationSignUp;
[SutProviderCustomize]
public class ResellerClientOrganizationSignUpCommandTests
{
[Theory]
[BitAutoData]
public async Task SignUpResellerClientAsync_WithValidParameters_CreatesOrganizationSuccessfully(
Organization organization,
string ownerEmail,
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
{
var result = await sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail);
Assert.NotNull(result.Organization);
Assert.False(result.Organization.Enabled);
Assert.Equal(OrganizationStatusType.Pending, result.Organization.Status);
Assert.NotNull(result.OwnerOrganizationUser);
Assert.Equal(ownerEmail, result.OwnerOrganizationUser.Email);
Assert.Equal(OrganizationUserType.Owner, result.OwnerOrganizationUser.Type);
Assert.Equal(OrganizationUserStatusType.Invited, result.OwnerOrganizationUser.Status);
await sutProvider.GetDependency<IOrganizationRepository>()
.Received(1)
.CreateAsync(
Arg.Is<Organization>(o =>
o.Id != default &&
o.Name == organization.Name &&
o.Enabled == false &&
o.Status == OrganizationStatusType.Pending
)
);
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.Received(1)
.CreateAsync(
Arg.Is<OrganizationApiKey>(k =>
k.OrganizationId == result.Organization.Id &&
k.Type == OrganizationApiKeyType.Default &&
!string.IsNullOrEmpty(k.ApiKey)
)
);
await sutProvider.GetDependency<IApplicationCacheService>()
.Received(1)
.UpsertOrganizationAbilityAsync(Arg.Is<Organization>(o => o.Id == result.Organization.Id));
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.CreateAsync(
Arg.Is<OrganizationUser>(u =>
u.OrganizationId == result.Organization.Id &&
u.Email == ownerEmail &&
u.Type == OrganizationUserType.Owner &&
u.Status == OrganizationUserStatusType.Invited &&
u.UserId == null
)
);
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.Received(1)
.SendInvitesAsync(
Arg.Is<SendInvitesRequest>(r =>
r.Users.Count() == 1 &&
r.Users.First().Email == ownerEmail &&
r.Organization.Id == result.Organization.Id &&
r.InitOrganization == true
)
);
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogOrganizationUserEventAsync(
Arg.Is<OrganizationUser>(u => u.Email == ownerEmail),
EventType.OrganizationUser_Invited
);
}
[Theory]
[BitAutoData]
public async Task SignUpResellerClientAsync_WhenOrganizationRepositoryThrows_PerformsCleanup(
Organization organization,
string ownerEmail,
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>()
.When(x => x.CreateAsync(Arg.Any<Organization>()))
.Do(_ => throw new Exception());
await Assert.ThrowsAsync<Exception>(
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
await AssertCleanupIsPerformed(sutProvider);
}
[Theory]
[BitAutoData]
public async Task SignUpResellerClientAsync_WhenOrganizationUserCreationFails_PerformsCleanup(
Organization organization,
string ownerEmail,
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.When(x => x.CreateAsync(Arg.Any<OrganizationUser>()))
.Do(_ => throw new Exception());
await Assert.ThrowsAsync<Exception>(
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
await sutProvider.GetDependency<IOrganizationRepository>()
.Received(1)
.CreateAsync(Arg.Any<Organization>());
await AssertCleanupIsPerformed(sutProvider);
}
[Theory]
[BitAutoData]
public async Task SignUpResellerClientAsync_WhenInvitationSendingFails_PerformsCleanup(
Organization organization,
string ownerEmail,
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
{
sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.When(x => x.SendInvitesAsync(Arg.Any<SendInvitesRequest>()))
.Do(_ => throw new Exception());
await Assert.ThrowsAsync<Exception>(
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
await sutProvider.GetDependency<IOrganizationRepository>()
.Received(1)
.CreateAsync(Arg.Any<Organization>());
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.CreateAsync(Arg.Any<OrganizationUser>());
await AssertCleanupIsPerformed(sutProvider);
}
[Theory]
[BitAutoData]
public async Task SignUpResellerClientAsync_WhenEventLoggingFails_PerformsCleanup(
Organization organization,
string ownerEmail,
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
{
sutProvider.GetDependency<IEventService>()
.When(x => x.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>()))
.Do(_ => throw new Exception());
await Assert.ThrowsAsync<Exception>(
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
await sutProvider.GetDependency<IOrganizationRepository>()
.Received(1)
.CreateAsync(Arg.Any<Organization>());
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.CreateAsync(Arg.Any<OrganizationUser>());
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.Received(1)
.SendInvitesAsync(Arg.Any<SendInvitesRequest>());
await AssertCleanupIsPerformed(sutProvider);
}
private static async Task AssertCleanupIsPerformed(SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
{
await sutProvider.GetDependency<IPaymentService>()
.Received(1)
.CancelAndRecoverChargesAsync(Arg.Any<Organization>());
await sutProvider.GetDependency<IOrganizationRepository>()
.Received(1)
.DeleteAsync(Arg.Any<Organization>());
await sutProvider.GetDependency<IApplicationCacheService>()
.Received(1)
.DeleteOrganizationAbilityAsync(Arg.Any<Guid>());
}
}

View File

@ -0,0 +1,53 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
[SutProviderCustomize]
public class OrganizationDataOwnershipPolicyRequirementFactoryTests
{
[Theory, BitAutoData]
public void State_WithNoPolicies_ReturnsAllowed(SutProvider<OrganizationDataOwnershipPolicyRequirementFactory> sutProvider)
{
var actual = sutProvider.Sut.Create([]);
Assert.Equal(OrganizationDataOwnershipState.Disabled, actual.State);
}
[Theory, BitAutoData]
public void State_WithOrganizationDataOwnershipPolicies_ReturnsRestricted(
[PolicyDetails(PolicyType.OrganizationDataOwnership)] PolicyDetails[] policies,
SutProvider<OrganizationDataOwnershipPolicyRequirementFactory> sutProvider)
{
var actual = sutProvider.Sut.Create(policies);
Assert.Equal(OrganizationDataOwnershipState.Enabled, actual.State);
}
[Theory, BitAutoData]
public void RequiresDefaultCollection_WithNoPolicies_ReturnsFalse(
Guid organizationId,
SutProvider<OrganizationDataOwnershipPolicyRequirementFactory> sutProvider)
{
var actual = sutProvider.Sut.Create([]);
Assert.False(actual.RequiresDefaultCollection(organizationId));
}
[Theory, BitAutoData]
public void RequiresDefaultCollection_WithOrganizationDataOwnershipPolicies_ReturnsCorrectResult(
[PolicyDetails(PolicyType.OrganizationDataOwnership)] PolicyDetails[] policies,
Guid nonPolicyOrganizationId,
SutProvider<OrganizationDataOwnershipPolicyRequirementFactory> sutProvider)
{
var actual = sutProvider.Sut.Create(policies);
Assert.True(actual.RequiresDefaultCollection(policies[0].OrganizationId));
Assert.False(actual.RequiresDefaultCollection(nonPolicyOrganizationId));
}
}

View File

@ -1,31 +0,0 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
[SutProviderCustomize]
public class PersonalOwnershipPolicyRequirementFactoryTests
{
[Theory, BitAutoData]
public void DisablePersonalOwnership_WithNoPolicies_ReturnsFalse(SutProvider<PersonalOwnershipPolicyRequirementFactory> sutProvider)
{
var actual = sutProvider.Sut.Create([]);
Assert.False(actual.DisablePersonalOwnership);
}
[Theory, BitAutoData]
public void DisablePersonalOwnership_WithPersonalOwnershipPolicies_ReturnsTrue(
[PolicyDetails(PolicyType.PersonalOwnership)] PolicyDetails[] policies,
SutProvider<PersonalOwnershipPolicyRequirementFactory> sutProvider)
{
var actual = sutProvider.Sut.Create(policies);
Assert.True(actual.DisablePersonalOwnership);
}
}

View File

@ -1,7 +1,7 @@
#nullable enable
using Azure.Messaging.ServiceBus;
using Bit.Core.AdminConsole.Models.Data.Integrations;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
@ -52,7 +52,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = 0;
var result = new IntegrationHandlerResult(false, message);
@ -71,7 +70,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = _maxRetries;
var result = new IntegrationHandlerResult(false, message);
result.Retryable = true;
@ -90,12 +88,10 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = 0;
var result = new IntegrationHandlerResult(false, message);
result.Retryable = true;
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!;
@ -110,7 +106,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
var result = new IntegrationHandlerResult(true, message);
_handler.HandleAsync(Arg.Any<string>()).Returns(result);

View File

@ -1,6 +1,6 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.Integrations;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;

View File

@ -1,4 +1,4 @@
using Bit.Core.AdminConsole.Models.Data.Integrations;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Services;
using Xunit;

View File

@ -1,9 +1,10 @@
using System.Text;
using Bit.Core.AdminConsole.Models.Data.Integrations;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
@ -18,19 +19,24 @@ public class RabbitMqIntegrationListenerServiceTests
private const string _queueName = "test_queue";
private const string _retryQueueName = "test_queue_retry";
private const string _routingKey = "test_routing_key";
private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>();
private SutProvider<RabbitMqIntegrationListenerService> GetSutProvider()
{
return new SutProvider<RabbitMqIntegrationListenerService>()
var sutProvider = new SutProvider<RabbitMqIntegrationListenerService>()
.SetDependency(_handler)
.SetDependency(_rabbitMqService)
.SetDependency(_queueName, "queueName")
.SetDependency(_retryQueueName, "retryQueueName")
.SetDependency(_routingKey, "routingKey")
.SetDependency(_maxRetries, "maxRetries")
.WithFakeTimeProvider()
.Create();
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(_now);
return sutProvider;
}
[Fact]
@ -55,7 +61,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
message.RetryCount = 0;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
@ -94,7 +100,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
message.RetryCount = _maxRetries;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
@ -132,7 +138,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
message.RetryCount = 0;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
@ -145,7 +151,7 @@ public class RabbitMqIntegrationListenerServiceTests
);
var result = new IntegrationHandlerResult(false, message);
result.Retryable = true;
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
result.DelayUntilDate = _now.AddMinutes(1);
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
@ -173,7 +179,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
deliveryTag: 0,
@ -205,7 +211,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
message.DelayUntilDate = _now.AddMinutes(1);
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
deliveryTag: 0,

View File

@ -1,4 +1,4 @@
using Bit.Core.AdminConsole.Models.Data.Integrations;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;

View File

@ -1,10 +1,11 @@
using System.Net;
using Bit.Core.AdminConsole.Models.Data.Integrations;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using Bit.Test.Common.MockedHttpClient;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
@ -33,6 +34,7 @@ public class WebhookIntegrationHandlerTests
return new SutProvider<WebhookIntegrationHandler>()
.SetDependency(clientFactory)
.WithFakeTimeProvider()
.Create();
}
@ -62,9 +64,13 @@ public class WebhookIntegrationHandlerTests
}
[Theory, BitAutoData]
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
{
var sutProvider = GetSutProvider();
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
var retryAfter = now.AddSeconds(60);
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
_handler.Fallback
@ -78,19 +84,21 @@ public class WebhookIntegrationHandlerTests
Assert.True(result.Retryable);
Assert.Equal(result.Message, message);
Assert.True(result.DelayUntilDate.HasValue);
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61));
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
Assert.Equal("Too Many Requests", result.FailureReason);
}
[Theory, BitAutoData]
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
{
var sutProvider = GetSutProvider();
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
var retryAfter = now.AddSeconds(60);
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
_handler.Fallback
.WithStatusCode(HttpStatusCode.TooManyRequests)
.WithHeader("Retry-After", DateTime.UtcNow.AddSeconds(60).ToString("r")) // "r" is the round-trip format: RFC1123
.WithHeader("Retry-After", retryAfter.ToString("r"))
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
var result = await sutProvider.Sut.HandleAsync(message);
@ -99,7 +107,7 @@ public class WebhookIntegrationHandlerTests
Assert.True(result.Retryable);
Assert.Equal(result.Message, message);
Assert.True(result.DelayUntilDate.HasValue);
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61));
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
Assert.Equal("Too Many Requests", result.FailureReason);
}

View File

@ -0,0 +1,133 @@

using AutoFixture;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Dirt.ReportFeatures;
[SutProviderCustomize]
public class AddOrganizationReportCommandTests
{
[Theory]
[BitAutoData]
public async Task AddOrganizationReportAsync_ShouldReturnOrganizationReport(
SutProvider<AddOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Create<AddOrganizationReportRequest>();
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(fixture.Create<Organization>());
sutProvider.GetDependency<IOrganizationReportRepository>()
.CreateAsync(Arg.Any<OrganizationReport>())
.Returns(c => c.Arg<OrganizationReport>());
// Act
var result = await sutProvider.Sut.AddOrganizationReportAsync(request);
// Assert
Assert.NotNull(result);
}
[Theory]
[BitAutoData]
public async Task AddOrganizationReportAsync_WithInvalidOrganizationId_ShouldThrowError(
SutProvider<AddOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Create<AddOrganizationReportRequest>();
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(null as Organization);
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddOrganizationReportAsync(request));
Assert.Equal("Invalid Organization", exception.Message);
}
[Theory]
[BitAutoData]
public async Task AddOrganizationReportAsync_WithInvalidUrl_ShouldThrowError(
SutProvider<AddOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Build<AddOrganizationReportRequest>()
.Without(_ => _.ReportData)
.Create();
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(fixture.Create<Organization>());
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddOrganizationReportAsync(request));
Assert.Equal("Report Data is required", exception.Message);
}
[Theory]
[BitAutoData]
public async Task AddOrganizationReportAsync_Multiples_WithInvalidOrganizationId_ShouldThrowError(
SutProvider<AddOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Create<AddOrganizationReportRequest>();
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(null as Organization);
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddOrganizationReportAsync(request));
Assert.Equal("Invalid Organization", exception.Message);
}
[Theory]
[BitAutoData]
public async Task AddOrganizationReportAsync_Multiples_WithInvalidUrl_ShouldThrowError(
SutProvider<AddOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Build<AddOrganizationReportRequest>()
.Without(_ => _.ReportData)
.Create();
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(fixture.Create<Organization>());
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddOrganizationReportAsync(request));
Assert.Equal("Report Data is required", exception.Message);
}
[Theory]
[BitAutoData]
public async Task AddOrganizationReportAsync_WithNullOrganizationId_ShouldThrowError(
SutProvider<AddOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Build<AddOrganizationReportRequest>()
.With(x => x.OrganizationId, default(Guid))
.Create();
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddOrganizationReportAsync(request));
Assert.Equal("Invalid Organization", exception.Message);
}
}

View File

@ -1,9 +1,9 @@
using AutoFixture;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Reports.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
using Bit.Core.Dirt.Reports.Repositories;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;

View File

@ -0,0 +1,194 @@
using AutoFixture;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Dirt.ReportFeatures;
[SutProviderCustomize]
public class DeleteOrganizationReportCommandTests
{
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withValidRequest_Success(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var OrganizationReports = fixture.CreateMany<OrganizationReport>(2).ToList();
// only take one id from the list - we only want to drop one record
var request = fixture.Build<DropOrganizationReportRequest>()
.With(x => x.OrganizationReportIds,
OrganizationReports.Select(x => x.Id).Take(1).ToList())
.Create();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(OrganizationReports);
// Act
await sutProvider.Sut.DropOrganizationReportAsync(request);
// Assert
await sutProvider.GetDependency<IOrganizationReportRepository>()
.Received(1)
.GetByOrganizationIdAsync(request.OrganizationId);
await sutProvider.GetDependency<IOrganizationReportRepository>()
.Received(1)
.DeleteAsync(Arg.Is<OrganizationReport>(_ =>
request.OrganizationReportIds.Contains(_.Id)));
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withValidRequest_nothingToDrop(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var OrganizationReports = fixture.CreateMany<OrganizationReport>(2).ToList();
// we are passing invalid data
var request = fixture.Build<DropOrganizationReportRequest>()
.With(x => x.OrganizationReportIds, new List<Guid> { Guid.NewGuid() })
.Create();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(OrganizationReports);
// Act
await sutProvider.Sut.DropOrganizationReportAsync(request);
// Assert
await sutProvider.GetDependency<IOrganizationReportRepository>()
.Received(1)
.GetByOrganizationIdAsync(request.OrganizationId);
await sutProvider.GetDependency<IOrganizationReportRepository>()
.Received(0)
.DeleteAsync(Arg.Any<OrganizationReport>());
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withNodata_fails(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
// we are passing invalid data
var request = fixture.Build<DropOrganizationReportRequest>()
.Create();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(null as List<OrganizationReport>);
// Act
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.DropOrganizationReportAsync(request));
// Assert
await sutProvider.GetDependency<IOrganizationReportRepository>()
.Received(1)
.GetByOrganizationIdAsync(request.OrganizationId);
await sutProvider.GetDependency<IOrganizationReportRepository>()
.Received(0)
.DeleteAsync(Arg.Any<OrganizationReport>());
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withInvalidOrganizationId_ShouldThrowError(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Create<DropOrganizationReportRequest>();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(null as List<OrganizationReport>);
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.DropOrganizationReportAsync(request));
Assert.Equal("No data found.", exception.Message);
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withInvalidOrganizationReportId_ShouldThrowError(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Create<DropOrganizationReportRequest>();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(new List<OrganizationReport>());
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.DropOrganizationReportAsync(request));
Assert.Equal("No data found.", exception.Message);
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withNullOrganizationId_ShouldThrowError(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Build<DropOrganizationReportRequest>()
.With(x => x.OrganizationId, default(Guid))
.Create();
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.DropOrganizationReportAsync(request));
Assert.Equal("No data found.", exception.Message);
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withNullOrganizationReportIds_ShouldThrowError(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Build<DropOrganizationReportRequest>()
.With(x => x.OrganizationReportIds, default(List<Guid>))
.Create();
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.DropOrganizationReportAsync(request));
Assert.Equal("No data found.", exception.Message);
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withEmptyOrganizationReportIds_ShouldThrowError(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var request = fixture.Build<DropOrganizationReportRequest>()
.With(x => x.OrganizationReportIds, new List<Guid>())
.Create();
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.DropOrganizationReportAsync(request));
Assert.Equal("No data found.", exception.Message);
}
[Theory, BitAutoData]
public async Task DropOrganizationReportAsync_withEmptyRequest_ShouldThrowError(
SutProvider<DropOrganizationReportCommand> sutProvider)
{
// Arrange
var request = new DropOrganizationReportRequest();
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.DropOrganizationReportAsync(request));
Assert.Equal("No data found.", exception.Message);
}
}

View File

@ -1,8 +1,8 @@
using AutoFixture;
using Bit.Core.Dirt.Reports.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
using Bit.Core.Dirt.Reports.Repositories;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;

View File

@ -0,0 +1,188 @@
using AutoFixture;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Dirt.ReportFeatures;
[SutProviderCustomize]
public class GetOrganizationReportQueryTests
{
[Theory]
[BitAutoData]
public async Task GetOrganizationReportAsync_WithValidOrganizationId_ShouldReturnOrganizationReport(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = fixture.Create<Guid>();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(fixture.CreateMany<OrganizationReport>(2).ToList());
// Act
var result = await sutProvider.Sut.GetOrganizationReportAsync(organizationId);
// Assert
Assert.NotNull(result);
Assert.True(result.Count() == 2);
}
[Theory]
[BitAutoData]
public async Task GetOrganizationReportAsync_WithInvalidOrganizationId_ShouldFail(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Is<Guid>(x => x == Guid.Empty))
.Returns(new List<OrganizationReport>());
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetOrganizationReportAsync(Guid.Empty));
// Assert
Assert.Equal("OrganizationId is required.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetLatestOrganizationReportAsync_WithValidOrganizationId_ShouldReturnOrganizationReport(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = fixture.Create<Guid>();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetLatestByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(fixture.Create<OrganizationReport>());
// Act
var result = await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId);
// Assert
Assert.NotNull(result);
}
[Theory]
[BitAutoData]
public async Task GetLatestOrganizationReportAsync_WithInvalidOrganizationId_ShouldFail(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetLatestByOrganizationIdAsync(Arg.Is<Guid>(x => x == Guid.Empty))
.Returns(default(OrganizationReport));
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetOrganizationReportAsync(Guid.Empty));
// Assert
Assert.Equal("OrganizationId is required.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetOrganizationReportAsync_WithNoReports_ShouldReturnEmptyList(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = fixture.Create<Guid>();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(new List<OrganizationReport>());
// Act
var result = await sutProvider.Sut.GetOrganizationReportAsync(organizationId);
// Assert
Assert.NotNull(result);
Assert.Empty(result);
}
[Theory]
[BitAutoData]
public async Task GetLatestOrganizationReportAsync_WithNoReports_ShouldReturnNull(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = fixture.Create<Guid>();
sutProvider.GetDependency<IOrganizationReportRepository>()
.GetLatestByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(default(OrganizationReport));
// Act
var result = await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId);
// Assert
Assert.Null(result);
}
[Theory]
[BitAutoData]
public async Task GetOrganizationReportAsync_WithNullOrganizationId_ShouldThrowException(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = default(Guid);
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetOrganizationReportAsync(organizationId));
// Assert
Assert.Equal("OrganizationId is required.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetLatestOrganizationReportAsync_WithNullOrganizationId_ShouldThrowException(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = default(Guid);
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId));
// Assert
Assert.Equal("OrganizationId is required.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetOrganizationReportAsync_WithInvalidOrganizationId_ShouldThrowException(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = Guid.Empty;
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetOrganizationReportAsync(organizationId));
// Assert
Assert.Equal("OrganizationId is required.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetLatestOrganizationReportAsync_WithInvalidOrganizationId_ShouldThrowException(
SutProvider<GetOrganizationReportQuery> sutProvider)
{
// Arrange
var fixture = new Fixture();
var organizationId = Guid.Empty;
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId));
// Assert
Assert.Equal("OrganizationId is required.", exception.Message);
}
}

View File

@ -1,7 +1,7 @@
using AutoFixture;
using Bit.Core.Dirt.Reports.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Dirt.Reports.Repositories;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;

View File

@ -1,86 +0,0 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Repositories;
using Bit.Core.Entities;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.UserKey.Implementations;
using Bit.Core.Platform.Push;
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.UserKey;
[SutProviderCustomize]
public class RotateUserKeyCommandTests
{
[Theory, BitAutoData]
public async Task RotateUserKeyAsync_Success(SutProvider<RotateUserKeyCommand> sutProvider, User user,
RotateUserKeyData model)
{
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(user, model.MasterPasswordHash)
.Returns(true);
foreach (var webauthnCred in model.WebAuthnKeys)
{
var dbWebauthnCred = new WebAuthnCredential
{
EncryptedPublicKey = "encryptedPublicKey",
EncryptedUserKey = "encryptedUserKey"
};
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetByIdAsync(webauthnCred.Id, user.Id)
.Returns(dbWebauthnCred);
}
var result = await sutProvider.Sut.RotateUserKeyAsync(user, model);
Assert.Equal(IdentityResult.Success, result);
}
[Theory, BitAutoData]
public async Task RotateUserKeyAsync_InvalidMasterPasswordHash_ReturnsFailedIdentityResult(
SutProvider<RotateUserKeyCommand> sutProvider, User user, RotateUserKeyData model)
{
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(user, model.MasterPasswordHash)
.Returns(false);
foreach (var webauthnCred in model.WebAuthnKeys)
{
var dbWebauthnCred = new WebAuthnCredential
{
EncryptedPublicKey = "encryptedPublicKey",
EncryptedUserKey = "encryptedUserKey"
};
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetByIdAsync(webauthnCred.Id, user.Id)
.Returns(dbWebauthnCred);
}
var result = await sutProvider.Sut.RotateUserKeyAsync(user, model);
Assert.False(result.Succeeded);
}
[Theory, BitAutoData]
public async Task RotateUserKeyAsync_LogsOutUser(
SutProvider<RotateUserKeyCommand> sutProvider, User user, RotateUserKeyData model)
{
sutProvider.GetDependency<IUserService>().CheckPasswordAsync(user, model.MasterPasswordHash)
.Returns(true);
foreach (var webauthnCred in model.WebAuthnKeys)
{
var dbWebauthnCred = new WebAuthnCredential
{
EncryptedPublicKey = "encryptedPublicKey",
EncryptedUserKey = "encryptedUserKey"
};
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetByIdAsync(webauthnCred.Id, user.Id)
.Returns(dbWebauthnCred);
}
await sutProvider.Sut.RotateUserKeyAsync(user, model);
await sutProvider.GetDependency<IPushNotificationService>().ReceivedWithAnyArgs()
.PushLogOutAsync(default, default);
}
}

View File

@ -11,16 +11,14 @@ namespace Bit.Core.Test.Models.Api.Request;
public class PushSendRequestModelTests
{
[Theory]
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "], [null, "", " "])]
public void Validate_UserIdOrganizationIdInstallationIdNullOrEmpty_Invalid(string? userId, string? organizationId,
string? installationId)
[Fact]
public void Validate_UserIdOrganizationIdInstallationIdNull_Invalid()
{
var model = new PushSendRequestModel
var model = new PushSendRequestModel<string>
{
UserId = userId,
OrganizationId = organizationId,
InstallationId = installationId,
UserId = null,
OrganizationId = null,
InstallationId = null,
Type = PushType.SyncCiphers,
Payload = "test"
};
@ -32,16 +30,14 @@ public class PushSendRequestModelTests
result => result.ErrorMessage == "UserId or OrganizationId or InstallationId is required.");
}
[Theory]
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])]
public void Validate_UserIdProvidedOrganizationIdInstallationIdNullOrEmpty_Valid(string? organizationId,
string? installationId)
[Fact]
public void Validate_UserIdProvidedOrganizationIdInstallationIdNull_Valid()
{
var model = new PushSendRequestModel
var model = new PushSendRequestModel<string>
{
UserId = Guid.NewGuid().ToString(),
OrganizationId = organizationId,
InstallationId = installationId,
UserId = Guid.NewGuid(),
OrganizationId = null,
InstallationId = null,
Type = PushType.SyncCiphers,
Payload = "test"
};
@ -51,16 +47,14 @@ public class PushSendRequestModelTests
Assert.Empty(results);
}
[Theory]
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])]
public void Validate_OrganizationIdProvidedUserIdInstallationIdNullOrEmpty_Valid(string? userId,
string? installationId)
[Fact]
public void Validate_OrganizationIdProvidedUserIdInstallationIdNull_Valid()
{
var model = new PushSendRequestModel
var model = new PushSendRequestModel<string>
{
UserId = userId,
OrganizationId = Guid.NewGuid().ToString(),
InstallationId = installationId,
UserId = null,
OrganizationId = Guid.NewGuid(),
InstallationId = null,
Type = PushType.SyncCiphers,
Payload = "test"
};
@ -70,16 +64,14 @@ public class PushSendRequestModelTests
Assert.Empty(results);
}
[Theory]
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])]
public void Validate_InstallationIdProvidedUserIdOrganizationIdNullOrEmpty_Valid(string? userId,
string? organizationId)
[Fact]
public void Validate_InstallationIdProvidedUserIdOrganizationIdNull_Valid()
{
var model = new PushSendRequestModel
var model = new PushSendRequestModel<string>
{
UserId = userId,
OrganizationId = organizationId,
InstallationId = Guid.NewGuid().ToString(),
UserId = null,
OrganizationId = null,
InstallationId = Guid.NewGuid(),
Type = PushType.SyncCiphers,
Payload = "test"
};
@ -94,10 +86,10 @@ public class PushSendRequestModelTests
[BitAutoData("Type")]
public void Validate_RequiredFieldNotProvided_Invalid(string requiredField)
{
var model = new PushSendRequestModel
var model = new PushSendRequestModel<string>
{
UserId = Guid.NewGuid().ToString(),
OrganizationId = Guid.NewGuid().ToString(),
UserId = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
Type = PushType.SyncCiphers,
Payload = "test"
};
@ -115,7 +107,7 @@ public class PushSendRequestModelTests
var serialized = JsonSerializer.Serialize(dictionary, JsonHelpers.IgnoreWritingNull);
var jsonException =
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<PushSendRequestModel>(serialized));
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<PushSendRequestModel<string>>(serialized));
Assert.Contains($"missing required properties, including the following: {requiredField}",
jsonException.Message);
}
@ -123,15 +115,15 @@ public class PushSendRequestModelTests
[Fact]
public void Validate_AllFieldsPresent_Valid()
{
var model = new PushSendRequestModel
var model = new PushSendRequestModel<string>
{
UserId = Guid.NewGuid().ToString(),
OrganizationId = Guid.NewGuid().ToString(),
UserId = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
Type = PushType.SyncCiphers,
Payload = "test payload",
Identifier = Guid.NewGuid().ToString(),
ClientType = ClientType.All,
DeviceId = Guid.NewGuid().ToString()
DeviceId = Guid.NewGuid()
};
var results = Validate(model);
@ -139,7 +131,7 @@ public class PushSendRequestModelTests
Assert.Empty(results);
}
private static List<ValidationResult> Validate(PushSendRequestModel model)
private static List<ValidationResult> Validate<T>(PushSendRequestModel<T> model)
{
var results = new List<ValidationResult>();
Validator.TryValidateObject(model, new ValidationContext(model), results, true);

View File

@ -5,12 +5,11 @@ using Bit.Core.Auth.Entities;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Models.Data;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Enums;
using Bit.Core.NotificationHub;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
@ -33,483 +32,6 @@ public class NotificationHubPushNotificationServiceTests
private static readonly DateTime _now = DateTime.UtcNow;
private static readonly Guid _installationId = Guid.Parse("da73177b-513f-4444-b582-595c890e1022");
[Theory]
[BitAutoData]
[NotificationCustomize]
public async Task PushNotificationAsync_GlobalInstallationIdDefault_NotSent(
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification)
{
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = default;
await sutProvider.Sut.PushNotificationAsync(notification);
await sutProvider.GetDependency<INotificationHubPool>()
.Received(0)
.AllClients
.Received(0)
.SendTemplateNotificationAsync(Arg.Any<IDictionary<string, string>>(), Arg.Any<string>());
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData]
[NotificationCustomize]
public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId(
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification, Guid installationId)
{
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
notification.ClientType = ClientType.All;
var expectedNotification = ToNotificationPushNotification(notification, null, installationId);
await sutProvider.Sut.PushNotificationAsync(notification);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
expectedNotification,
$"(template:payload && installationId:{installationId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize]
public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, Guid installationId)
{
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, null, installationId);
await sutProvider.Sut.PushNotificationAsync(notification);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
expectedNotification,
$"(template:payload && installationId:{installationId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(false)]
public async Task PushNotificationAsync_UserIdProvidedClientTypeAll_SentToUser(
bool organizationIdNull, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification)
{
if (organizationIdNull)
{
notification.OrganizationId = null;
}
notification.ClientType = ClientType.All;
var expectedNotification = ToNotificationPushNotification(notification, null, null);
await sutProvider.Sut.PushNotificationAsync(notification);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
expectedNotification,
$"(template:payload_userId:{notification.UserId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task PushNotificationAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification)
{
notification.OrganizationId = null;
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, null, null);
await sutProvider.Sut.PushNotificationAsync(notification);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
expectedNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task PushNotificationAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification)
{
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, null, null);
await sutProvider.Sut.PushNotificationAsync(notification);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
expectedNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData]
[NotificationCustomize(false)]
public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization(
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification)
{
notification.UserId = null;
notification.ClientType = ClientType.All;
var expectedNotification = ToNotificationPushNotification(notification, null, null);
await sutProvider.Sut.PushNotificationAsync(notification);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
expectedNotification,
$"(template:payload && organizationId:{notification.OrganizationId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification)
{
notification.UserId = null;
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, null, null);
await sutProvider.Sut.PushNotificationAsync(notification);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
expectedNotification,
$"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData]
[NotificationCustomize]
public async Task PushNotificationStatusAsync_GlobalInstallationIdDefault_NotSent(
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = default;
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await sutProvider.GetDependency<INotificationHubPool>()
.Received(0)
.AllClients
.Received(0)
.SendTemplateNotificationAsync(Arg.Any<IDictionary<string, string>>(), Arg.Any<string>());
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData]
[NotificationCustomize]
public async Task PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId(
SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus, Guid installationId)
{
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
notification.ClientType = ClientType.All;
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId);
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
expectedNotification,
$"(template:payload && installationId:{installationId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize]
public async Task
PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus, Guid installationId)
{
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId);
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
expectedNotification,
$"(template:payload && installationId:{installationId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(false)]
public async Task PushNotificationStatusAsync_UserIdProvidedClientTypeAll_SentToUser(
bool organizationIdNull, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
if (organizationIdNull)
{
notification.OrganizationId = null;
}
notification.ClientType = ClientType.All;
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
expectedNotification,
$"(template:payload_userId:{notification.UserId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
notification.OrganizationId = null;
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
expectedNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
expectedNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData]
[NotificationCustomize(false)]
public async Task PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization(
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{
notification.UserId = null;
notification.ClientType = ClientType.All;
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
expectedNotification,
$"(template:payload && organizationId:{notification.OrganizationId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Web)]
[BitAutoData(ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task
PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
notification.UserId = null;
notification.ClientType = clientType;
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
expectedNotification,
$"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData([null])]
[BitAutoData(ClientType.All)]
public async Task SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload,
string identifier)
{
await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null,
clientType);
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{identifier})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Mobile)]
[BitAutoData(ClientType.Web)]
public async Task SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload,
string identifier)
{
await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null,
clientType);
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{identifier} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData([null])]
[BitAutoData(ClientType.All)]
public async Task SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId, PushType pushType,
string payload, string identifier)
{
await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier,
null, clientType);
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
$"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Mobile)]
[BitAutoData(ClientType.Web)]
public async Task SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId,
PushType pushType, string payload, string identifier)
{
await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier,
null, clientType);
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
$"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData([null])]
[BitAutoData(ClientType.All)]
public async Task SendPayloadToInstallationAsync_ClientTypeNullOrAll_SentToInstallation(ClientType? clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid installationId, PushType pushType,
string payload, string identifier)
{
await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier,
null, clientType);
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
$"(template:payload && installationId:{installationId} && !deviceIdentifier:{identifier})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(ClientType.Browser)]
[BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Mobile)]
[BitAutoData(ClientType.Web)]
public async Task SendPayloadToInstallationAsync_ClientTypeExplicit_SentToInstallationAndClientType(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid installationId,
PushType pushType, string payload, string identifier)
{
await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier,
null, clientType);
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
$"(template:payload && installationId:{installationId} && !deviceIdentifier:{identifier} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Fact]
public async Task PushSyncCipherCreateAsync_SendExpectedData()
{
@ -1066,7 +588,7 @@ public class NotificationHubPushNotificationServiceTests
);
}
private async Task VerifyNotificationAsync(Func<NotificationHubPushNotificationService, Task> test,
private async Task VerifyNotificationAsync(Func<IPushNotificationService, Task> test,
PushType type, JsonNode expectedPayload, string tag)
{
var installationDeviceRepository = Substitute.For<IInstallationDeviceRepository>();
@ -1104,12 +626,11 @@ public class NotificationHubPushNotificationServiceTests
notificationHubPool,
httpContextAccessor,
NullLogger<NotificationHubPushNotificationService>.Instance,
globalSettings,
fakeTimeProvider
globalSettings
);
// Act
await test(sut);
await test(new EngineWrapper(sut, fakeTimeProvider, _installationId));
// Assert
var calls = notificationHubProxy.ReceivedCalls();

View File

@ -9,14 +9,11 @@ using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Enums;
using Bit.Core.Platform.Push;
using Bit.Core.Platform.Push.Internal;
using Bit.Core.Settings;
using Bit.Core.Test.AutoFixture;
using Bit.Core.Test.AutoFixture.CurrentContextFixtures;
using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@ -42,96 +39,6 @@ public class AzureQueuePushNotificationServiceTests
_fakeTimeProvider.SetUtcNow(DateTime.UtcNow);
}
[Theory]
[BitAutoData]
[NotificationCustomize]
[CurrentContextCustomize]
public async Task PushNotificationAsync_NotificationGlobal_Sent(
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
ICurrentContext currentContext, Guid installationId)
{
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
.GetService(Arg.Any<Type>()).Returns(currentContext);
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
await sutProvider.Sut.PushNotificationAsync(notification);
await sutProvider.GetDependency<QueueClient>().Received(1)
.SendMessageAsync(Arg.Is<string>(message =>
MatchMessage(PushType.Notification, message,
new NotificationPushNotificationEquals(notification, null, installationId),
deviceIdentifier.ToString())));
}
[Theory]
[BitAutoData]
[NotificationCustomize(false)]
[CurrentContextCustomize]
public async Task PushNotificationAsync_NotificationNotGlobal_Sent(
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
ICurrentContext currentContext, Guid installationId)
{
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
.GetService(Arg.Any<Type>()).Returns(currentContext);
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
await sutProvider.Sut.PushNotificationAsync(notification);
await sutProvider.GetDependency<QueueClient>().Received(1)
.SendMessageAsync(Arg.Is<string>(message =>
MatchMessage(PushType.Notification, message,
new NotificationPushNotificationEquals(notification, null, null),
deviceIdentifier.ToString())));
}
[Theory]
[BitAutoData]
[NotificationCustomize]
[NotificationStatusCustomize]
[CurrentContextCustomize]
public async Task PushNotificationStatusAsync_NotificationGlobal_Sent(
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId)
{
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
.GetService(Arg.Any<Type>()).Returns(currentContext);
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await sutProvider.GetDependency<QueueClient>().Received(1)
.SendMessageAsync(Arg.Is<string>(message =>
MatchMessage(PushType.NotificationStatus, message,
new NotificationPushNotificationEquals(notification, notificationStatus, installationId),
deviceIdentifier.ToString())));
}
[Theory]
[BitAutoData]
[NotificationCustomize(false)]
[NotificationStatusCustomize]
[CurrentContextCustomize]
public async Task PushNotificationStatusAsync_NotificationNotGlobal_Sent(
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId)
{
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
.GetService(Arg.Any<Type>()).Returns(currentContext);
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await sutProvider.GetDependency<QueueClient>().Received(1)
.SendMessageAsync(Arg.Is<string>(message =>
MatchMessage(PushType.NotificationStatus, message,
new NotificationPushNotificationEquals(notification, notificationStatus, null),
deviceIdentifier.ToString())));
}
[Theory]
[InlineData("6a5bbe1b-cf16-49a6-965f-5c2eac56a531", null)]
[InlineData(null, "b9a3fcb4-2447-45c1-aad2-24de43c88c44")]
@ -844,7 +751,7 @@ public class AzureQueuePushNotificationServiceTests
// );
// }
private async Task VerifyNotificationAsync(Func<AzureQueuePushNotificationService, Task> test, JsonNode expectedMessage)
private async Task VerifyNotificationAsync(Func<IPushNotificationService, Task> test, JsonNode expectedMessage)
{
var queueClient = Substitute.For<QueueClient>();
@ -872,7 +779,7 @@ public class AzureQueuePushNotificationServiceTests
_fakeTimeProvider
);
await test(sut);
await test(new EngineWrapper(sut, _fakeTimeProvider, _globalSettings.Installation.Id));
// Hoist equality checker outside the expression so that we
// can more easily place a breakpoint

View File

@ -1,98 +1,8 @@
#nullable enable
using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Platform.Push;
using Bit.Core.Platform.Push.Internal;
using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Platform.Push.Services;
[SutProviderCustomize]
public class MultiServicePushNotificationServiceTests
{
[Theory]
[BitAutoData]
[NotificationCustomize]
public async Task PushNotificationAsync_Notification_Sent(
SutProvider<MultiServicePushNotificationService> sutProvider, Notification notification)
{
await sutProvider.Sut.PushNotificationAsync(notification);
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
.First()
.Received(1)
.PushNotificationAsync(notification);
}
[Theory]
[BitAutoData]
[NotificationCustomize]
[NotificationStatusCustomize]
public async Task PushNotificationStatusAsync_Notification_Sent(
SutProvider<MultiServicePushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
.First()
.Received(1)
.PushNotificationStatusAsync(notification, notificationStatus);
}
[Theory]
[BitAutoData([null, null])]
[BitAutoData(ClientType.All, null)]
[BitAutoData([null, "test device id"])]
[BitAutoData(ClientType.All, "test device id")]
public async Task SendPayloadToUserAsync_Message_Sent(ClientType? clientType, string? deviceId, string userId,
PushType type, object payload, string identifier, SutProvider<MultiServicePushNotificationService> sutProvider)
{
await sutProvider.Sut.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType);
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
.First()
.Received(1)
.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType);
}
[Theory]
[BitAutoData([null, null])]
[BitAutoData(ClientType.All, null)]
[BitAutoData([null, "test device id"])]
[BitAutoData(ClientType.All, "test device id")]
public async Task SendPayloadToOrganizationAsync_Message_Sent(ClientType? clientType, string? deviceId,
string organizationId, PushType type, object payload, string identifier,
SutProvider<MultiServicePushNotificationService> sutProvider)
{
await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId,
clientType);
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
.First()
.Received(1)
.SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, clientType);
}
[Theory]
[BitAutoData([null, null])]
[BitAutoData(ClientType.All, null)]
[BitAutoData([null, "test device id"])]
[BitAutoData(ClientType.All, "test device id")]
public async Task SendPayloadToInstallationAsync_Message_Sent(ClientType? clientType, string? deviceId,
string installationId, PushType type, object payload, string identifier,
SutProvider<MultiServicePushNotificationService> sutProvider)
{
await sutProvider.Sut.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId,
clientType);
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
.First()
.Received(1)
.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, clientType);
}
// TODO: Can add a couple tests here
}

View File

@ -19,14 +19,13 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
protected override string ExpectedClientUrl() => "https://localhost:7777/send";
protected override IPushNotificationService CreateService()
protected override IPushEngine CreateService()
{
return new NotificationsApiPushNotificationService(
HttpClientFactory,
GlobalSettings,
HttpContextAccessor,
NullLogger<NotificationsApiPushNotificationService>.Instance,
FakeTimeProvider
NullLogger<NotificationsApiPushNotificationService>.Instance
);
}
@ -221,7 +220,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
["UserId"] = send.UserId,
["RevisionDate"] = send.RevisionDate,
},
["ContextId"] = null,
["ContextId"] = DeviceIdentifier,
};
}
@ -236,7 +235,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
["UserId"] = send.UserId,
["RevisionDate"] = send.RevisionDate,
},
["ContextId"] = null,
["ContextId"] = DeviceIdentifier,
};
}
@ -251,7 +250,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
["UserId"] = send.UserId,
["RevisionDate"] = send.RevisionDate,
},
["ContextId"] = null,
["ContextId"] = DeviceIdentifier,
};
}

View File

@ -15,11 +15,28 @@ using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using RichardSzalay.MockHttp;
using Xunit;
public class EngineWrapper(IPushEngine pushEngine, FakeTimeProvider fakeTimeProvider, Guid installationId) : IPushNotificationService
{
public Guid InstallationId { get; } = installationId;
public TimeProvider TimeProvider { get; } = fakeTimeProvider;
public ILogger Logger => NullLogger<EngineWrapper>.Instance;
public Task PushAsync<T>(PushNotification<T> pushNotification) where T : class
=> pushEngine.PushAsync(pushNotification);
public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds)
=> pushEngine.PushCipherAsync(cipher, pushType, collectionIds);
}
public abstract class PushTestBase
{
protected static readonly string DeviceIdentifier = "test_device_identifier";
@ -51,7 +68,7 @@ public abstract class PushTestBase
FakeTimeProvider.SetUtcNow(DateTimeOffset.UtcNow);
}
protected abstract IPushNotificationService CreateService();
protected abstract IPushEngine CreateService();
protected abstract string ExpectedClientUrl();
@ -480,7 +497,7 @@ public abstract class PushTestBase
})
.Respond(HttpStatusCode.OK);
await test(CreateService());
await test(new EngineWrapper(CreateService(), FakeTimeProvider, GlobalSettings.Installation.Id));
Assert.NotNull(actualNode);

View File

@ -4,8 +4,8 @@ using System.Text.Json.Nodes;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Platform.Push;
using Bit.Core.Platform.Push.Internal;
using Bit.Core.Repositories;
using Bit.Core.Settings;
@ -14,7 +14,6 @@ using Bit.Core.Vault.Entities;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Platform.Push.Services;
@ -38,47 +37,19 @@ public class RelayPushNotificationServiceTests : PushTestBase
GlobalSettings.Installation.IdentityUri = "https://localhost:8888";
}
protected override RelayPushNotificationService CreateService()
protected override IPushEngine CreateService()
{
return new RelayPushNotificationService(
HttpClientFactory,
_deviceRepository,
GlobalSettings,
HttpContextAccessor,
NullLogger<RelayPushNotificationService>.Instance,
FakeTimeProvider
NullLogger<RelayPushNotificationService>.Instance
);
}
protected override string ExpectedClientUrl() => "https://localhost:7777/push/send";
[Fact]
public async Task SendPayloadToInstallationAsync_ThrowsNotImplementedException()
{
var sut = CreateService();
await Assert.ThrowsAsync<NotImplementedException>(
async () => await sut.SendPayloadToInstallationAsync("installation_id", PushType.AuthRequest, new { }, null)
);
}
[Fact]
public async Task SendPayloadToUserAsync_ThrowsNotImplementedException()
{
var sut = CreateService();
await Assert.ThrowsAsync<NotImplementedException>(
async () => await sut.SendPayloadToUserAsync("user_id", PushType.AuthRequest, new { }, null)
);
}
[Fact]
public async Task SendPayloadToOrganizationAsync_ThrowsNotImplementedException()
{
var sut = CreateService();
await Assert.ThrowsAsync<NotImplementedException>(
async () => await sut.SendPayloadToOrganizationAsync("organization_id", PushType.AuthRequest, new { }, null)
);
}
protected override JsonNode GetPushSyncCipherCreatePayload(Cipher cipher, Guid collectionIds)
{
return new JsonObject

View File

@ -25,7 +25,8 @@ public class SendGridMailDeliveryServiceTests : IDisposable
{
Mail =
{
SendGridApiKey = "SendGridApiKey"
SendGridApiKey = "SendGridApiKey",
SendGridApiHost = "https://api.sendgrid.com"
}
};

View File

@ -31,7 +31,7 @@ public class ImportCiphersAsyncCommandTests
SutProvider<ImportCiphersCommand> sutProvider)
{
sutProvider.GetDependency<IPolicyService>()
.AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.PersonalOwnership)
.AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.OrganizationDataOwnership)
.Returns(false);
sutProvider.GetDependency<IFolderRepository>()
@ -51,7 +51,7 @@ public class ImportCiphersAsyncCommandTests
}
[Theory, BitAutoData]
public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyDisabled_Success(
public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyDisabled_Success(
Guid importingUserId,
List<CipherDetails> ciphers,
SutProvider<ImportCiphersCommand> sutProvider)
@ -61,8 +61,10 @@ public class ImportCiphersAsyncCommandTests
.Returns(true);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<PersonalOwnershipPolicyRequirement>(importingUserId)
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = false });
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(importingUserId)
.Returns(new OrganizationDataOwnershipPolicyRequirement(
OrganizationDataOwnershipState.Disabled,
[]));
sutProvider.GetDependency<IFolderRepository>()
.GetManyByUserIdAsync(importingUserId)
@ -89,7 +91,7 @@ public class ImportCiphersAsyncCommandTests
ciphers.ForEach(c => c.UserId = userId);
sutProvider.GetDependency<IPolicyService>()
.AnyPoliciesApplicableToUserAsync(userId, PolicyType.PersonalOwnership)
.AnyPoliciesApplicableToUserAsync(userId, PolicyType.OrganizationDataOwnership)
.Returns(true);
var folderRelationships = new List<KeyValuePair<int, int>>();
@ -101,7 +103,7 @@ public class ImportCiphersAsyncCommandTests
}
[Theory, BitAutoData]
public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyEnabled_ThrowsBadRequestException(
public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyEnabled_ThrowsBadRequestException(
List<Folder> folders,
List<CipherDetails> ciphers,
SutProvider<ImportCiphersCommand> sutProvider)
@ -115,8 +117,10 @@ public class ImportCiphersAsyncCommandTests
.Returns(true);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<PersonalOwnershipPolicyRequirement>(userId)
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = true });
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(userId)
.Returns(new OrganizationDataOwnershipPolicyRequirement(
OrganizationDataOwnershipState.Enabled,
[Guid.NewGuid()]));
var folderRelationships = new List<KeyValuePair<int, int>>();

View File

@ -10,10 +10,10 @@ using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.SendFeatures;
using Bit.Core.Tools.SendFeatures.Commands;
using Bit.Core.Tools.Services;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
@ -35,6 +35,8 @@ public class NonAnonymousSendCommandTests
private readonly ISendCoreHelperService _sendCoreHelperService;
private readonly NonAnonymousSendCommand _nonAnonymousSendCommand;
private readonly ILogger<NonAnonymousSendCommand> _logger;
public NonAnonymousSendCommandTests()
{
_sendRepository = Substitute.For<ISendRepository>();
@ -45,6 +47,7 @@ public class NonAnonymousSendCommandTests
_sendValidationService = Substitute.For<ISendValidationService>();
_currentContext = Substitute.For<ICurrentContext>();
_sendCoreHelperService = Substitute.For<ISendCoreHelperService>();
_logger = Substitute.For<ILogger<NonAnonymousSendCommand>>();
_nonAnonymousSendCommand = new NonAnonymousSendCommand(
_sendRepository,
@ -52,7 +55,8 @@ public class NonAnonymousSendCommandTests
_pushNotificationService,
_sendAuthorizationService,
_sendValidationService,
_sendCoreHelperService
_sendCoreHelperService,
_logger
);
}
@ -652,11 +656,11 @@ public class NonAnonymousSendCommandTests
UserId = userId
};
var fileData = new SendFileData();
var fileLength = 15L * 1024L * 1024L * 1024L; // 15GB
var fileLength = 15L * 1024L * 1024L; // 15 MB
// Configure validation service to return large but insufficient storage (10GB for self-hosted non-premium)
// Configure validation service to return insufficient storage
_sendValidationService.StorageRemainingForSendAsync(send)
.Returns(10L * 1024L * 1024L * 1024L); // 10GB remaining (self-hosted default)
.Returns(10L * 1024L * 1024L); // 10 MB remaining
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
@ -687,11 +691,40 @@ public class NonAnonymousSendCommandTests
UserId = userId
};
var fileData = new SendFileData();
var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB
var fileLength = 2L * 1024L * 1024L * 1024L; // 2MB
// Configure validation service to return 1GB storage (cloud non-premium default)
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
_nonAnonymousSendCommand.SaveFileSendAsync(send, fileData, fileLength));
Assert.Contains("Max file size is ", exception.Message);
// Verify no further methods were called
await _sendValidationService.DidNotReceive().StorageRemainingForSendAsync(Arg.Any<Send>());
await _sendRepository.DidNotReceive().CreateAsync(Arg.Any<Send>());
await _sendRepository.DidNotReceive().UpsertAsync(Arg.Any<Send>());
await _sendFileStorageService.DidNotReceive().GetSendFileUploadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
await _pushNotificationService.DidNotReceive().PushSyncSendCreateAsync(Arg.Any<Send>());
await _pushNotificationService.DidNotReceive().PushSyncSendUpdateAsync(Arg.Any<Send>());
}
[Fact]
public async Task SaveFileSendAsync_UserCanAccessPremium_IsNotPremium_IsNotSelfHosted_NotEnoughSpace_ThrowsBadRequest()
{
// Arrange
var userId = Guid.NewGuid();
var send = new Send
{
Id = Guid.NewGuid(),
Type = SendType.File,
UserId = userId
};
var fileData = new SendFileData();
var fileLength = 2L * 1024L * 1024L; // 2MB
// Configure validation service to return 1 MB storage remaining
_sendValidationService.StorageRemainingForSendAsync(send)
.Returns(1L * 1024L * 1024L * 1024L); // 1GB remaining (cloud default)
.Returns(1L * 1024L * 1024L);
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
@ -756,7 +789,7 @@ public class NonAnonymousSendCommandTests
UserId = null
};
var fileData = new SendFileData();
var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB
var fileLength = 2L * 1024L * 1024L; // 2 MB
// Configure validation service to throw BadRequest when checking storage for org without storage
_sendValidationService.StorageRemainingForSendAsync(send)
@ -792,11 +825,10 @@ public class NonAnonymousSendCommandTests
UserId = null
};
var fileData = new SendFileData();
var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB
var fileLength = 2L * 1024L * 1024L; // 2 MB
// Configure validation service to return 1GB storage (org's max storage limit)
_sendValidationService.StorageRemainingForSendAsync(send)
.Returns(1L * 1024L * 1024L * 1024L); // 1GB remaining
.Returns(1L * 1024L * 1024L); // 1 MB remaining
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
@ -980,7 +1012,7 @@ public class NonAnonymousSendCommandTests
};
// Setup validation to succeed
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, sendFileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY).Returns((true, sendFileData.Size));
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, Arg.Any<long>(), Arg.Any<long>()).Returns((true, sendFileData.Size));
// Act
await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send);
@ -1014,7 +1046,7 @@ public class NonAnonymousSendCommandTests
Data = JsonSerializer.Serialize(sendFileData)
};
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, sendFileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY).Returns((true, sendFileData.Size));
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, Arg.Any<long>(), Arg.Any<long>()).Returns((true, sendFileData.Size));
// Act
await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send);

View File

@ -114,7 +114,7 @@ public class CipherServiceTests
[Theory]
[BitAutoData]
public async Task SaveDetailsAsync_PersonalVault_WithDisablePersonalOwnershipPolicyEnabled_Throws(
public async Task SaveDetailsAsync_PersonalVault_WithOrganizationDataOwnershipPolicyEnabled_Throws(
SutProvider<CipherService> sutProvider,
CipherDetails cipher,
Guid savingUserId)
@ -124,7 +124,7 @@ public class CipherServiceTests
cipher.OrganizationId = null;
sutProvider.GetDependency<IPolicyService>()
.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership)
.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.OrganizationDataOwnership)
.Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
@ -134,7 +134,7 @@ public class CipherServiceTests
[Theory]
[BitAutoData]
public async Task SaveDetailsAsync_PersonalVault_WithDisablePersonalOwnershipPolicyDisabled_Succeeds(
public async Task SaveDetailsAsync_PersonalVault_WithOrganizationDataOwnershipPolicyDisabled_Succeeds(
SutProvider<CipherService> sutProvider,
CipherDetails cipher,
Guid savingUserId)
@ -144,7 +144,7 @@ public class CipherServiceTests
cipher.OrganizationId = null;
sutProvider.GetDependency<IPolicyService>()
.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership)
.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.OrganizationDataOwnership)
.Returns(false);
await sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null);
@ -156,7 +156,7 @@ public class CipherServiceTests
[Theory]
[BitAutoData]
public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyEnabled_Throws(
public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyEnabled_Throws(
SutProvider<CipherService> sutProvider,
CipherDetails cipher,
Guid savingUserId)
@ -170,8 +170,10 @@ public class CipherServiceTests
.Returns(true);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<PersonalOwnershipPolicyRequirement>(savingUserId)
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = true });
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(savingUserId)
.Returns(new OrganizationDataOwnershipPolicyRequirement(
OrganizationDataOwnershipState.Enabled,
[Guid.NewGuid()]));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null));
@ -180,7 +182,7 @@ public class CipherServiceTests
[Theory]
[BitAutoData]
public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyDisabled_Succeeds(
public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyDisabled_Succeeds(
SutProvider<CipherService> sutProvider,
CipherDetails cipher,
Guid savingUserId)
@ -194,8 +196,10 @@ public class CipherServiceTests
.Returns(true);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<PersonalOwnershipPolicyRequirement>(savingUserId)
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = false });
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(savingUserId)
.Returns(new OrganizationDataOwnershipPolicyRequirement(
OrganizationDataOwnershipState.Disabled,
[]));
await sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null);