mirror of
https://github.com/bitwarden/server
synced 2026-01-06 18:43:36 +00:00
Merge remote-tracking branch 'origin/main' into xunit-v3-full-upgrade
This commit is contained in:
@@ -11,7 +11,7 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
@@ -39,7 +39,7 @@ public class OrganizationCustomization : ICustomization
|
||||
{
|
||||
var organizationId = Guid.NewGuid();
|
||||
var maxCollections = (short)new Random().Next(10, short.MaxValue);
|
||||
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == PlanType);
|
||||
var plan = MockPlans.Plans.FirstOrDefault(p => p.Type == PlanType);
|
||||
var seats = (short)new Random().Next(plan.PasswordManager.BaseSeats, plan.PasswordManager.MaxSeats ?? short.MaxValue);
|
||||
var smSeats = plan.SupportsSecretsManager
|
||||
? (short?)new Random().Next(plan.SecretsManager.BaseSeats, plan.SecretsManager.MaxSeats ?? short.MaxValue)
|
||||
@@ -92,7 +92,7 @@ internal class PaidOrganization : ICustomization
|
||||
public PlanType CheckedPlanType { get; set; }
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList();
|
||||
var validUpgradePlans = MockPlans.Plans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList();
|
||||
var lowestActivePaidPlan = validUpgradePlans.First();
|
||||
CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType;
|
||||
validUpgradePlans.Remove(lowestActivePaidPlan);
|
||||
@@ -120,7 +120,7 @@ internal class FreeOrganizationUpgrade : ICustomization
|
||||
.With(o => o.PlanType, PlanType.Free));
|
||||
|
||||
var plansToIgnore = new List<PlanType> { PlanType.Free, PlanType.Custom };
|
||||
var selectedPlan = StaticStore.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
|
||||
var selectedPlan = MockPlans.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
|
||||
|
||||
fixture.Customize<OrganizationUpgrade>(composer => composer
|
||||
.With(ou => ou.Plan, selectedPlan.Type)
|
||||
@@ -168,7 +168,7 @@ public class SecretsManagerOrganizationCustomization : ICustomization
|
||||
.With(o => o.Id, organizationId)
|
||||
.With(o => o.UseSecretsManager, true)
|
||||
.With(o => o.PlanType, planType)
|
||||
.With(o => o.Plan, StaticStore.GetPlan(planType).Name)
|
||||
.With(o => o.Plan, MockPlans.Get(planType).Name)
|
||||
.With(o => o.MaxAutoscaleSmSeats, (int?)null)
|
||||
.With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using NSubstitute;
|
||||
using StackExchange.Redis;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations;
|
||||
|
||||
public class EventIntegrationServiceCollectionExtensionsTests
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public EventIntegrationServiceCollectionExtensionsTests()
|
||||
{
|
||||
_services = new ServiceCollection();
|
||||
_globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
// Add required infrastructure services
|
||||
_services.TryAddSingleton(_globalSettings);
|
||||
_services.TryAddSingleton<IGlobalSettings>(_globalSettings);
|
||||
_services.AddLogging();
|
||||
|
||||
// Mock Redis connection for cache
|
||||
_services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
|
||||
// Mock required repository dependencies for commands
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationRepository>());
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationRepository>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_RegistersAllServices()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName);
|
||||
Assert.NotNull(cache);
|
||||
|
||||
var validator = provider.GetRequiredService<IOrganizationIntegrationConfigurationValidator>();
|
||||
Assert.NotNull(validator);
|
||||
|
||||
using var scope = provider.CreateScope();
|
||||
var sp = scope.ServiceProvider;
|
||||
|
||||
Assert.NotNull(sp.GetService<ICreateOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IGetOrganizationIntegrationsQuery>());
|
||||
|
||||
Assert.NotNull(sp.GetService<ICreateOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IGetOrganizationIntegrationConfigurationsQuery>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_CommandsQueries_AreRegisteredAsScoped()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var createIntegrationDescriptor = _services.First(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
||||
var createConfigDescriptor = _services.First(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand));
|
||||
|
||||
Assert.Equal(ServiceLifetime.Scoped, createIntegrationDescriptor.Lifetime);
|
||||
Assert.Equal(ServiceLifetime.Scoped, createConfigDescriptor.Lifetime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_CommandsQueries_DifferentInstancesPerScope()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var provider = _services.BuildServiceProvider();
|
||||
|
||||
ICreateOrganizationIntegrationCommand? instance1, instance2, instance3;
|
||||
using (var scope1 = provider.CreateScope())
|
||||
{
|
||||
instance1 = scope1.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
}
|
||||
using (var scope2 = provider.CreateScope())
|
||||
{
|
||||
instance2 = scope2.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
}
|
||||
using (var scope3 = provider.CreateScope())
|
||||
{
|
||||
instance3 = scope3.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
}
|
||||
|
||||
Assert.NotNull(instance1);
|
||||
Assert.NotNull(instance2);
|
||||
Assert.NotNull(instance3);
|
||||
Assert.NotSame(instance1, instance2);
|
||||
Assert.NotSame(instance2, instance3);
|
||||
Assert.NotSame(instance1, instance3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_CommandsQueries__SameInstanceWithinScope()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
var provider = _services.BuildServiceProvider();
|
||||
|
||||
using var scope = provider.CreateScope();
|
||||
var instance1 = scope.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
var instance2 = scope.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
|
||||
Assert.NotNull(instance1);
|
||||
Assert.NotNull(instance2);
|
||||
Assert.Same(instance1, instance2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_MultipleCalls_IsIdempotent()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var createConfigCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand)).ToList();
|
||||
Assert.Single(createConfigCmdDescriptors);
|
||||
|
||||
var updateIntegrationCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(IUpdateOrganizationIntegrationCommand)).ToList();
|
||||
Assert.Single(updateIntegrationCmdDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationCommandsQueries_RegistersAllIntegrationServices()
|
||||
{
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IUpdateOrganizationIntegrationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IDeleteOrganizationIntegrationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IGetOrganizationIntegrationsQuery));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationCommandsQueries_MultipleCalls_IsIdempotent()
|
||||
{
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
|
||||
var createCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand)).ToList();
|
||||
Assert.Single(createCmdDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationConfigurationCommandsQueries_RegistersAllConfigurationServices()
|
||||
{
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IUpdateOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IDeleteOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IGetOrganizationIntegrationConfigurationsQuery));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationConfigurationCommandsQueries_MultipleCalls_IsIdempotent()
|
||||
{
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
var createCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand)).ToList();
|
||||
Assert.Single(createCmdDescriptors);
|
||||
}
|
||||
|
||||
private static GlobalSettings CreateGlobalSettings(Dictionary<string, string?> data)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(data)
|
||||
.Build();
|
||||
|
||||
var settings = new GlobalSettings();
|
||||
config.GetSection("GlobalSettings").Bind(settings);
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CreateOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_Success_CreatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(configuration)
|
||||
.Returns(configuration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
configuration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(configuration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_WildcardSuccess_CreatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(configuration)
|
||||
.Returns(configuration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
Assert.Equal(configuration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_ValidationFails_ThrowsBadRequest(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(false);
|
||||
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.Template = "template";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_Success_DeletesConfigurationAndInvalidatesCache(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
configuration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_WildcardSuccess_DeletesConfigurationAndInvalidatesCache(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_ConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns((OrganizationIntegrationConfiguration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_ConfigurationDoesNotBelongToIntegration_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = Guid.NewGuid(); // Different integration
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
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.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GetOrganizationIntegrationConfigurationsQueryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_Success_ReturnsConfigurations(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
List<OrganizationIntegrationConfiguration> configurations)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(integrationId)
|
||||
.Returns(configurations);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetManyByIntegrationAsync(integrationId);
|
||||
Assert.Equal(configurations.Count, result.Count);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_NoConfigurations_ReturnsEmptyList(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(integrationId)
|
||||
.Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UpdateOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_Success_UpdatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
existingConfiguration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WildcardSuccess_UpdatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = null;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedEventType_UpdatesConfigurationAndInvalidatesCacheForBothTypes(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.EventType = EventType.Cipher_Created;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
existingConfiguration.EventType.Value));
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
updatedConfiguration.EventType.Value));
|
||||
// Verify RemoveByTagAsync was NOT called since both are specific event types
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns((OrganizationIntegrationConfiguration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ConfigurationDoesNotBelongToIntegration_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = Guid.NewGuid(); // Different integration
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ValidationFails_ThrowsBadRequest(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(false);
|
||||
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.Template = "template";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedFromWildcardToSpecific_InvalidatesAllCaches(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration,
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider)
|
||||
{
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = null; // Wildcard
|
||||
updatedConfiguration.EventType = EventType.User_LoggedIn; // Specific
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId).Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(existingConfiguration.Id).Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, existingConfiguration.Id, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedFromSpecificToWildcard_InvalidatesAllCaches(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration,
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider)
|
||||
{
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn; // Specific
|
||||
updatedConfiguration.EventType = null; // Wildcard
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId).Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(existingConfiguration.Id).Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, existingConfiguration.Id, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CreateOrganizationIntegrationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_Success_CreatesIntegrationAndInvalidatesCache(
|
||||
SutProvider<CreateOrganizationIntegrationCommand> sutProvider,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetManyByOrganizationAsync(integration.OrganizationId)
|
||||
.Returns([]);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.CreateAsync(integration)
|
||||
.Returns(integration);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(integration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetManyByOrganizationAsync(integration.OrganizationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.CreateAsync(integration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
integration.OrganizationId,
|
||||
integration.Type));
|
||||
Assert.Equal(integration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_DuplicateType_ThrowsBadRequest(
|
||||
SutProvider<CreateOrganizationIntegrationCommand> sutProvider,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegration existingIntegration)
|
||||
{
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingIntegration.Type = IntegrationType.Webhook;
|
||||
existingIntegration.OrganizationId = integration.OrganizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetManyByOrganizationAsync(integration.OrganizationId)
|
||||
.Returns([existingIntegration]);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.CreateAsync(integration));
|
||||
|
||||
Assert.Contains("An integration of this type already exists", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_DifferentType_Success(
|
||||
SutProvider<CreateOrganizationIntegrationCommand> sutProvider,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegration existingIntegration)
|
||||
{
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingIntegration.Type = IntegrationType.Slack;
|
||||
existingIntegration.OrganizationId = integration.OrganizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetManyByOrganizationAsync(integration.OrganizationId)
|
||||
.Returns([existingIntegration]);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.CreateAsync(integration)
|
||||
.Returns(integration);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(integration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.CreateAsync(integration);
|
||||
Assert.Equal(integration, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteOrganizationIntegrationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_Success_DeletesIntegrationAndInvalidatesCache(
|
||||
SutProvider<DeleteOrganizationIntegrationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.DeleteAsync(integration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GetOrganizationIntegrationsQueryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByOrganizationAsync_CallsRepository(
|
||||
SutProvider<GetOrganizationIntegrationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
List<OrganizationIntegration> integrations)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetManyByOrganizationAsync(organizationId)
|
||||
.Returns(integrations);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByOrganizationAsync(organizationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetManyByOrganizationAsync(organizationId);
|
||||
Assert.Equal(integrations.Count, result.Count);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByOrganizationAsync_NoIntegrations_ReturnsEmptyList(
|
||||
SutProvider<GetOrganizationIntegrationsQuery> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetManyByOrganizationAsync(organizationId)
|
||||
.Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByOrganizationAsync(organizationId);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UpdateOrganizationIntegrationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_Success_UpdatesIntegrationAndInvalidatesCache(
|
||||
SutProvider<UpdateOrganizationIntegrationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration existingIntegration,
|
||||
OrganizationIntegration updatedIntegration)
|
||||
{
|
||||
existingIntegration.Id = integrationId;
|
||||
existingIntegration.OrganizationId = organizationId;
|
||||
existingIntegration.Type = IntegrationType.Webhook;
|
||||
updatedIntegration.Id = integrationId;
|
||||
updatedIntegration.OrganizationId = organizationId;
|
||||
updatedIntegration.Type = IntegrationType.Webhook;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(existingIntegration);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, updatedIntegration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedIntegration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
existingIntegration.Type));
|
||||
Assert.Equal(updatedIntegration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration updatedIntegration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, updatedIntegration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration existingIntegration,
|
||||
OrganizationIntegration updatedIntegration)
|
||||
{
|
||||
existingIntegration.Id = integrationId;
|
||||
existingIntegration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(existingIntegration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, updatedIntegration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationIsDifferentType_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration existingIntegration,
|
||||
OrganizationIntegration updatedIntegration)
|
||||
{
|
||||
existingIntegration.Id = integrationId;
|
||||
existingIntegration.OrganizationId = organizationId;
|
||||
existingIntegration.Type = IntegrationType.Webhook;
|
||||
updatedIntegration.Id = integrationId;
|
||||
updatedIntegration.OrganizationId = organizationId;
|
||||
updatedIntegration.Type = IntegrationType.Hec; // Different Type
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(existingIntegration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, updatedIntegration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
@@ -35,7 +35,7 @@ public class IntegrationTemplateContextTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserName_WhenUserIsSet_ReturnsName(EventMessage eventMessage, User user)
|
||||
public void UserName_WhenUserIsSet_ReturnsName(EventMessage eventMessage, OrganizationUserUserDetails user)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
|
||||
|
||||
@@ -51,7 +51,7 @@ public class IntegrationTemplateContextTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserEmail_WhenUserIsSet_ReturnsEmail(EventMessage eventMessage, User user)
|
||||
public void UserEmail_WhenUserIsSet_ReturnsEmail(EventMessage eventMessage, OrganizationUserUserDetails user)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
|
||||
|
||||
@@ -67,7 +67,23 @@ public class IntegrationTemplateContextTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserName_WhenActingUserIsSet_ReturnsName(EventMessage eventMessage, User actingUser)
|
||||
public void UserType_WhenUserIsSet_ReturnsType(EventMessage eventMessage, OrganizationUserUserDetails user)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
|
||||
|
||||
Assert.Equal(user.Type, sut.UserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserType_WhenUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = null };
|
||||
|
||||
Assert.Null(sut.UserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserName_WhenActingUserIsSet_ReturnsName(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
|
||||
|
||||
@@ -83,7 +99,7 @@ public class IntegrationTemplateContextTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserEmail_WhenActingUserIsSet_ReturnsEmail(EventMessage eventMessage, User actingUser)
|
||||
public void ActingUserEmail_WhenActingUserIsSet_ReturnsEmail(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
|
||||
|
||||
@@ -98,6 +114,22 @@ public class IntegrationTemplateContextTests
|
||||
Assert.Null(sut.ActingUserEmail);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserType_WhenActingUserIsSet_ReturnsType(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
|
||||
|
||||
Assert.Equal(actingUser.Type, sut.ActingUserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserType_WhenActingUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = null };
|
||||
|
||||
Assert.Null(sut.ActingUserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void OrganizationName_WhenOrganizationIsSet_ReturnsDisplayName(EventMessage eventMessage, Organization organization)
|
||||
{
|
||||
@@ -113,4 +145,20 @@ public class IntegrationTemplateContextTests
|
||||
|
||||
Assert.Null(sut.OrganizationName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void GroupName_WhenGroupIsSet_ReturnsName(EventMessage eventMessage, Group group)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { Group = group };
|
||||
|
||||
Assert.Equal(group.Name, sut.GroupName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void GroupName_WhenGroupIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { Group = null };
|
||||
|
||||
Assert.Null(sut.GroupName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
@@ -183,17 +182,17 @@ public class VerifyOrganizationDomainCommandTests
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
|
||||
.Received(1)
|
||||
.SaveAsync(Arg.Is<PolicyUpdate>(x => x.Type == PolicyType.SingleOrg &&
|
||||
x.OrganizationId == domain.OrganizationId &&
|
||||
x.Enabled &&
|
||||
.SaveAsync(Arg.Is<SavePolicyModel>(x => x.PolicyUpdate.Type == PolicyType.SingleOrg &&
|
||||
x.PolicyUpdate.OrganizationId == domain.OrganizationId &&
|
||||
x.PolicyUpdate.Enabled &&
|
||||
x.PerformedBy is StandardUser &&
|
||||
x.PerformedBy.UserId == userId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserVerifyOrganizationDomainAsync_WhenPolicyValidatorsRefactorFlagEnabled_UsesVNextSavePolicyCommand(
|
||||
public async Task UserVerifyOrganizationDomainAsync_UsesVNextSavePolicyCommand(
|
||||
OrganizationDomain domain, Guid userId, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
@@ -207,10 +206,6 @@ public class VerifyOrganizationDomainCommandTests
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(userId);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
|
||||
.Returns(true);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
|
||||
@@ -240,9 +235,9 @@ public class VerifyOrganizationDomainCommandTests
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
|
||||
.DidNotReceive()
|
||||
.SaveAsync(Arg.Any<PolicyUpdate>());
|
||||
.SaveAsync(Arg.Any<SavePolicyModel>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
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.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class BulkResendOrganizationInvitesCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task BulkResendInvitesAsync_ValidatesUsersAndSendsBatchInvite(
|
||||
Organization organization,
|
||||
OrganizationUser validUser1,
|
||||
OrganizationUser validUser2,
|
||||
OrganizationUser acceptedUser,
|
||||
OrganizationUser wrongOrgUser,
|
||||
SutProvider<BulkResendOrganizationInvitesCommand> sutProvider)
|
||||
{
|
||||
validUser1.OrganizationId = organization.Id;
|
||||
validUser1.Status = OrganizationUserStatusType.Invited;
|
||||
validUser2.OrganizationId = organization.Id;
|
||||
validUser2.Status = OrganizationUserStatusType.Invited;
|
||||
acceptedUser.OrganizationId = organization.Id;
|
||||
acceptedUser.Status = OrganizationUserStatusType.Accepted;
|
||||
wrongOrgUser.OrganizationId = Guid.NewGuid();
|
||||
wrongOrgUser.Status = OrganizationUserStatusType.Invited;
|
||||
|
||||
var users = new List<OrganizationUser> { validUser1, validUser2, acceptedUser, wrongOrgUser };
|
||||
var userIds = users.Select(u => u.Id).ToList();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(userIds).Returns(users);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
var result = (await sutProvider.Sut.BulkResendInvitesAsync(organization.Id, null, userIds)).ToList();
|
||||
|
||||
Assert.Equal(4, result.Count);
|
||||
Assert.Equal(2, result.Count(r => string.IsNullOrEmpty(r.Item2)));
|
||||
Assert.Equal(2, result.Count(r => r.Item2 == "User invalid."));
|
||||
|
||||
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
|
||||
.Received(1)
|
||||
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(req =>
|
||||
req.Organization == organization &&
|
||||
req.Users.Length == 2 &&
|
||||
req.InitOrganization == false));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task BulkResendInvitesAsync_AllInvalidUsers_DoesNotSendInvites(
|
||||
Organization organization,
|
||||
List<OrganizationUser> organizationUsers,
|
||||
SutProvider<BulkResendOrganizationInvitesCommand> sutProvider)
|
||||
{
|
||||
foreach (var user in organizationUsers)
|
||||
{
|
||||
user.OrganizationId = organization.Id;
|
||||
user.Status = OrganizationUserStatusType.Confirmed;
|
||||
}
|
||||
|
||||
var userIds = organizationUsers.Select(u => u.Id).ToList();
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(userIds).Returns(organizationUsers);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
var result = (await sutProvider.Sut.BulkResendInvitesAsync(organization.Id, null, userIds)).ToList();
|
||||
|
||||
Assert.Equal(organizationUsers.Count, result.Count);
|
||||
Assert.All(result, r => Assert.Equal("User invalid.", r.Item2));
|
||||
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>().DidNotReceive()
|
||||
.SendInvitesAsync(Arg.Any<SendInvitesRequest>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task BulkResendInvitesAsync_OrganizationNotFound_ThrowsNotFoundException(
|
||||
Guid organizationId,
|
||||
List<Guid> userIds,
|
||||
List<OrganizationUser> organizationUsers,
|
||||
SutProvider<BulkResendOrganizationInvitesCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(userIds).Returns(organizationUsers);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns((Organization?)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.BulkResendInvitesAsync(organizationId, null, userIds));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task BulkResendInvitesAsync_EmptyUserList_ReturnsEmpty(
|
||||
Organization organization,
|
||||
SutProvider<BulkResendOrganizationInvitesCommand> sutProvider)
|
||||
{
|
||||
var emptyUserIds = new List<Guid>();
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(emptyUserIds).Returns(new List<OrganizationUser>());
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
var result = await sutProvider.Sut.BulkResendInvitesAsync(organization.Id, null, emptyUserIds);
|
||||
|
||||
Assert.Empty(result);
|
||||
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>().DidNotReceive()
|
||||
.SendInvitesAsync(Arg.Any<SendInvitesRequest>());
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Utilities.Commands;
|
||||
using Bit.Core.AdminConsole.Utilities.Errors;
|
||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
@@ -22,6 +21,7 @@ using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
@@ -29,6 +29,7 @@ using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteUserOrganizationValidationRequestHelpers;
|
||||
using Enterprise2019Plan = Bit.Core.Test.Billing.Mocks.Plans.Enterprise2019Plan;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
|
||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
@@ -5,7 +5,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.V
|
||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
|
||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RevokeUser.v1;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RevokeUser.v2;
|
||||
using Bit.Core.AdminConsole.Utilities.v2.Validation;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.RevokeUser.v2;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RevokeOrganizationUserCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeUsersAsync_WithValidUsers_RevokesUsersAndLogsEvents(
|
||||
SutProvider<RevokeOrganizationUserCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser1,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser2)
|
||||
{
|
||||
// Arrange
|
||||
orgUser1.OrganizationId = orgUser2.OrganizationId = organizationId;
|
||||
orgUser1.UserId = Guid.NewGuid();
|
||||
orgUser2.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
var request = new RevokeOrganizationUsersRequest(
|
||||
organizationId,
|
||||
[orgUser1.Id, orgUser2.Id],
|
||||
actingUser);
|
||||
|
||||
SetupRepositoryMocks(sutProvider, [orgUser1, orgUser2]);
|
||||
SetupValidatorMock(sutProvider, [
|
||||
ValidationResultHelpers.Valid(orgUser1),
|
||||
ValidationResultHelpers.Valid(orgUser2)
|
||||
]);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.RevokeUsersAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count);
|
||||
Assert.All(results, r => Assert.True(r.Result.IsSuccess));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.RevokeManyByIdAsync(Arg.Is<IEnumerable<Guid>>(ids =>
|
||||
ids.Contains(orgUser1.Id) && ids.Contains(orgUser2.Id)));
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(
|
||||
events => events.Count() == 2));
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.Received(1)
|
||||
.PushSyncOrgKeysAsync(orgUser1.UserId!.Value);
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.Received(1)
|
||||
.PushSyncOrgKeysAsync(orgUser2.UserId!.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeUsersAsync_WithSystemUser_LogsEventsWithSystemUserType(
|
||||
SutProvider<RevokeOrganizationUserCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.OrganizationId = organizationId;
|
||||
orgUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(null, false, EventSystemUser.SCIM);
|
||||
|
||||
var request = new RevokeOrganizationUsersRequest(
|
||||
organizationId,
|
||||
[orgUser.Id],
|
||||
actingUser);
|
||||
|
||||
SetupRepositoryMocks(sutProvider, [orgUser]);
|
||||
SetupValidatorMock(sutProvider, [ValidationResultHelpers.Valid(orgUser)]);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.RevokeUsersAsync(request);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)>>(
|
||||
events => events.All(e => e.Item3 == EventSystemUser.SCIM)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeUsersAsync_WithValidationErrors_ReturnsErrorResults(
|
||||
SutProvider<RevokeOrganizationUserCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Revoked, OrganizationUserType.User)] OrganizationUser orgUser1,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser2)
|
||||
{
|
||||
// Arrange
|
||||
orgUser1.OrganizationId = orgUser2.OrganizationId = organizationId;
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
|
||||
var request = new RevokeOrganizationUsersRequest(
|
||||
organizationId,
|
||||
[orgUser1.Id, orgUser2.Id],
|
||||
actingUser);
|
||||
|
||||
SetupRepositoryMocks(sutProvider, [orgUser1, orgUser2]);
|
||||
SetupValidatorMock(sutProvider, [
|
||||
ValidationResultHelpers.Invalid(orgUser1, new UserAlreadyRevoked()),
|
||||
ValidationResultHelpers.Valid(orgUser2)
|
||||
]);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.RevokeUsersAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count);
|
||||
var result1 = results.Single(r => r.Id == orgUser1.Id);
|
||||
var result2 = results.Single(r => r.Id == orgUser2.Id);
|
||||
|
||||
Assert.True(result1.Result.IsError);
|
||||
Assert.True(result2.Result.IsSuccess);
|
||||
|
||||
// Only the valid user should be revoked
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.RevokeManyByIdAsync(Arg.Is<IEnumerable<Guid>>(ids =>
|
||||
ids.Count() == 1 && ids.Contains(orgUser2.Id)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeUsersAsync_WhenPushNotificationFails_ContinuesProcessing(
|
||||
SutProvider<RevokeOrganizationUserCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.OrganizationId = organizationId;
|
||||
orgUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
|
||||
var request = new RevokeOrganizationUsersRequest(
|
||||
organizationId,
|
||||
[orgUser.Id],
|
||||
actingUser);
|
||||
|
||||
SetupRepositoryMocks(sutProvider, [orgUser]);
|
||||
SetupValidatorMock(sutProvider, [ValidationResultHelpers.Valid(orgUser)]);
|
||||
|
||||
sutProvider.GetDependency<IPushNotificationService>()
|
||||
.PushSyncOrgKeysAsync(orgUser.UserId!.Value)
|
||||
.Returns(Task.FromException(new Exception("Push notification failed")));
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.RevokeUsersAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(results);
|
||||
Assert.True(results[0].Result.IsSuccess);
|
||||
|
||||
// Should log warning but continue
|
||||
sutProvider.GetDependency<ILogger<RevokeOrganizationUserCommand>>()
|
||||
.Received()
|
||||
.Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
private static IActingUser CreateActingUser(Guid? userId, bool isOwnerOrProvider, EventSystemUser? systemUserType) =>
|
||||
(userId, systemUserType) switch
|
||||
{
|
||||
({ } id, _) => new StandardUser(id, isOwnerOrProvider),
|
||||
(null, { } type) => new SystemUser(type)
|
||||
};
|
||||
|
||||
private static void SetupRepositoryMocks(
|
||||
SutProvider<RevokeOrganizationUserCommand> sutProvider,
|
||||
ICollection<OrganizationUser> organizationUsers)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(organizationUsers);
|
||||
}
|
||||
|
||||
private static void SetupValidatorMock(
|
||||
SutProvider<RevokeOrganizationUserCommand> sutProvider,
|
||||
ICollection<ValidationResult<OrganizationUser>> validationResults)
|
||||
{
|
||||
sutProvider.GetDependency<IRevokeOrganizationUserValidator>()
|
||||
.ValidateAsync(Arg.Any<RevokeOrganizationUsersValidationRequest>())
|
||||
.Returns(validationResults);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RevokeUser.v2;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.RevokeUser.v2;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RevokeOrganizationUsersValidatorTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithValidUsers_ReturnsSuccess(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser1,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser2)
|
||||
{
|
||||
// Arrange
|
||||
orgUser1.OrganizationId = orgUser2.OrganizationId = organizationId;
|
||||
orgUser1.UserId = Guid.NewGuid();
|
||||
orgUser2.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[orgUser1, orgUser2],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count);
|
||||
Assert.All(results, r => Assert.True(r.IsValid));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithRevokedUser_ReturnsErrorForThatUser(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Revoked, OrganizationUserType.User)] OrganizationUser revokedUser)
|
||||
{
|
||||
// Arrange
|
||||
revokedUser.OrganizationId = organizationId;
|
||||
revokedUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[revokedUser],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(results);
|
||||
Assert.True(results.First().IsError);
|
||||
Assert.IsType<UserAlreadyRevoked>(results.First().AsError);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WhenRevokingSelf_ReturnsErrorForThatUser(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.OrganizationId = organizationId;
|
||||
orgUser.UserId = actingUserId;
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[orgUser],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(results);
|
||||
Assert.True(results.First().IsError);
|
||||
Assert.IsType<CannotRevokeYourself>(results.First().AsError);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WhenNonOwnerRevokesOwner_ReturnsErrorForThatUser(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser ownerUser)
|
||||
{
|
||||
// Arrange
|
||||
ownerUser.OrganizationId = organizationId;
|
||||
ownerUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[ownerUser],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(results);
|
||||
Assert.True(results.First().IsError);
|
||||
Assert.IsType<OnlyOwnersCanRevokeOwners>(results.First().AsError);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WhenOwnerRevokesOwner_ReturnsSuccess(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser ownerUser)
|
||||
{
|
||||
// Arrange
|
||||
ownerUser.OrganizationId = organizationId;
|
||||
ownerUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, true, null);
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[ownerUser],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(results);
|
||||
Assert.True(results.First().IsValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithMultipleUsers_SomeValid_ReturnsMixedResults(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser validUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Revoked, OrganizationUserType.User)] OrganizationUser revokedUser)
|
||||
{
|
||||
// Arrange
|
||||
validUser.OrganizationId = revokedUser.OrganizationId = organizationId;
|
||||
validUser.UserId = Guid.NewGuid();
|
||||
revokedUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null);
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[validUser, revokedUser],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count);
|
||||
|
||||
var validResult = results.Single(r => r.Request.Id == validUser.Id);
|
||||
var errorResult = results.Single(r => r.Request.Id == revokedUser.Id);
|
||||
|
||||
Assert.True(validResult.IsValid);
|
||||
Assert.True(errorResult.IsError);
|
||||
Assert.IsType<UserAlreadyRevoked>(errorResult.AsError);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithSystemUser_DoesNotRequireActingUserId(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.OrganizationId = organizationId;
|
||||
orgUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(null, false, EventSystemUser.SCIM);
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[orgUser],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(results);
|
||||
Assert.True(results.First().IsValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WhenRevokingLastOwner_ReturnsErrorForThatUser(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser lastOwner)
|
||||
{
|
||||
// Arrange
|
||||
lastOwner.OrganizationId = organizationId;
|
||||
lastOwner.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, true, null); // Is an owner
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[lastOwner],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(results);
|
||||
Assert.True(results.First().IsError);
|
||||
Assert.IsType<MustHaveConfirmedOwner>(results.First().AsError);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_WithMultipleValidationErrors_ReturnsAllErrors(
|
||||
SutProvider<RevokeOrganizationUsersValidator> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid actingUserId,
|
||||
[OrganizationUser(OrganizationUserStatusType.Revoked, OrganizationUserType.User)] OrganizationUser revokedUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser ownerUser)
|
||||
{
|
||||
// Arrange
|
||||
revokedUser.OrganizationId = ownerUser.OrganizationId = organizationId;
|
||||
revokedUser.UserId = Guid.NewGuid();
|
||||
ownerUser.UserId = Guid.NewGuid();
|
||||
|
||||
var actingUser = CreateActingUser(actingUserId, false, null); // Not an owner
|
||||
var request = CreateValidationRequest(
|
||||
organizationId,
|
||||
[revokedUser, ownerUser],
|
||||
actingUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var results = (await sutProvider.Sut.ValidateAsync(request)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count);
|
||||
Assert.All(results, r => Assert.True(r.IsError));
|
||||
|
||||
Assert.Contains(results, r => r.AsError is UserAlreadyRevoked);
|
||||
Assert.Contains(results, r => r.AsError is OnlyOwnersCanRevokeOwners);
|
||||
}
|
||||
|
||||
private static IActingUser CreateActingUser(Guid? userId, bool isOwnerOrProvider, EventSystemUser? systemUserType) =>
|
||||
(userId, systemUserType) switch
|
||||
{
|
||||
({ } id, _) => new StandardUser(id, isOwnerOrProvider),
|
||||
(null, { } type) => new SystemUser(type)
|
||||
};
|
||||
|
||||
|
||||
private static RevokeOrganizationUsersValidationRequest CreateValidationRequest(
|
||||
Guid organizationId,
|
||||
ICollection<OrganizationUser> organizationUsers,
|
||||
IActingUser actingUser)
|
||||
{
|
||||
return new RevokeOrganizationUsersValidationRequest(
|
||||
organizationId,
|
||||
organizationUsers.Select(u => u.Id).ToList(),
|
||||
actingUser,
|
||||
organizationUsers
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
|
||||
@@ -10,7 +10,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -28,7 +28,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
var plan = MockPlans.Get(signup.Plan);
|
||||
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
@@ -37,7 +37,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
signup.IsFromProvider = false;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
@@ -77,7 +77,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
signup.UseSecretsManager = false;
|
||||
signup.IsFromProvider = false;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
// Extract orgUserId when created
|
||||
Guid? orgUserId = null;
|
||||
@@ -112,7 +112,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
var plan = MockPlans.Get(signup.Plan);
|
||||
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
@@ -123,7 +123,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
signup.IsFromProvider = false;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
@@ -164,7 +164,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromProvider = true;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
|
||||
@@ -184,7 +184,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
signup.AdditionalStorageGb = 0;
|
||||
signup.IsFromProvider = false;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
@@ -204,7 +204,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
signup.IsFromProvider = false;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
@@ -224,7 +224,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
signup.AdditionalServiceAccounts = -10;
|
||||
signup.IsFromProvider = false;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
@@ -244,7 +244,7 @@ public class CloudICloudOrganizationSignUpCommandTests
|
||||
Owner = new User { Id = Guid.NewGuid() }
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan));
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id)
|
||||
|
||||
@@ -10,7 +10,7 @@ using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -36,7 +36,7 @@ public class ProviderClientOrganizationSignUpCommandTests
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.CollectionName = collectionName;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
var plan = MockPlans.Get(signup.Plan);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(signup.Plan)
|
||||
.Returns(plan);
|
||||
@@ -112,7 +112,7 @@ public class ProviderClientOrganizationSignUpCommandTests
|
||||
signup.Plan = PlanType.TeamsMonthly;
|
||||
signup.AdditionalSeats = -5;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
var plan = MockPlans.Get(signup.Plan);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(signup.Plan)
|
||||
.Returns(plan);
|
||||
@@ -132,7 +132,7 @@ public class ProviderClientOrganizationSignUpCommandTests
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
var plan = MockPlans.Get(signup.Plan);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(signup.Plan)
|
||||
.Returns(plan);
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;
|
||||
using Bit.Core.Billing.Organizations.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationUpdateCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WhenValidOrganization_UpdatesOrganization(
|
||||
Guid organizationId,
|
||||
string name,
|
||||
string billingEmail,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
var organizationBillingService = sutProvider.GetDependency<IOrganizationBillingService>();
|
||||
|
||||
organization.Id = organizationId;
|
||||
organization.GatewayCustomerId = null; // No Stripe customer, so no billing update
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = name,
|
||||
BillingEmail = billingEmail
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationId, result.Id);
|
||||
Assert.Equal(name, result.Name);
|
||||
Assert.Equal(billingEmail.ToLowerInvariant().Trim(), result.BillingEmail);
|
||||
|
||||
await organizationRepository
|
||||
.Received(1)
|
||||
.GetByIdAsync(Arg.Is<Guid>(id => id == organizationId));
|
||||
await organizationService
|
||||
.Received(1)
|
||||
.ReplaceAndUpdateCacheAsync(
|
||||
result,
|
||||
EventType.Organization_Updated);
|
||||
await organizationBillingService
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateOrganizationNameAndEmail(Arg.Any<Organization>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WhenOrganizationNotFound_ThrowsNotFoundException(
|
||||
Guid organizationId,
|
||||
string name,
|
||||
string billingEmail,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns((Organization)null);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = name,
|
||||
BillingEmail = billingEmail
|
||||
};
|
||||
|
||||
// Act/Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(request));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("")]
|
||||
[BitAutoData((string)null)]
|
||||
public async Task UpdateAsync_WhenGatewayCustomerIdIsNullOrEmpty_SkipsBillingUpdate(
|
||||
string gatewayCustomerId,
|
||||
Guid organizationId,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
var organizationBillingService = sutProvider.GetDependency<IOrganizationBillingService>();
|
||||
|
||||
organization.Id = organizationId;
|
||||
organization.Name = "Old Name";
|
||||
organization.GatewayCustomerId = gatewayCustomerId;
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = "New Name",
|
||||
BillingEmail = organization.BillingEmail
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationId, result.Id);
|
||||
Assert.Equal("New Name", result.Name);
|
||||
|
||||
await organizationService
|
||||
.Received(1)
|
||||
.ReplaceAndUpdateCacheAsync(
|
||||
result,
|
||||
EventType.Organization_Updated);
|
||||
await organizationBillingService
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateOrganizationNameAndEmail(Arg.Any<Organization>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WhenKeysProvided_AndNotAlreadySet_SetsKeys(
|
||||
Guid organizationId,
|
||||
string publicKey,
|
||||
string encryptedPrivateKey,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
|
||||
organization.Id = organizationId;
|
||||
organization.PublicKey = null;
|
||||
organization.PrivateKey = null;
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = organization.Name,
|
||||
BillingEmail = organization.BillingEmail,
|
||||
PublicKey = publicKey,
|
||||
EncryptedPrivateKey = encryptedPrivateKey
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationId, result.Id);
|
||||
Assert.Equal(publicKey, result.PublicKey);
|
||||
Assert.Equal(encryptedPrivateKey, result.PrivateKey);
|
||||
|
||||
await organizationService
|
||||
.Received(1)
|
||||
.ReplaceAndUpdateCacheAsync(
|
||||
result,
|
||||
EventType.Organization_Updated);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WhenKeysProvided_AndAlreadySet_DoesNotOverwriteKeys(
|
||||
Guid organizationId,
|
||||
string newPublicKey,
|
||||
string newEncryptedPrivateKey,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
|
||||
organization.Id = organizationId;
|
||||
var existingPublicKey = organization.PublicKey;
|
||||
var existingPrivateKey = organization.PrivateKey;
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = organization.Name,
|
||||
BillingEmail = organization.BillingEmail,
|
||||
PublicKey = newPublicKey,
|
||||
EncryptedPrivateKey = newEncryptedPrivateKey
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationId, result.Id);
|
||||
Assert.Equal(existingPublicKey, result.PublicKey);
|
||||
Assert.Equal(existingPrivateKey, result.PrivateKey);
|
||||
|
||||
await organizationService
|
||||
.Received(1)
|
||||
.ReplaceAndUpdateCacheAsync(
|
||||
result,
|
||||
EventType.Organization_Updated);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_UpdatingNameOnly_UpdatesNameAndNotBillingEmail(
|
||||
Guid organizationId,
|
||||
string newName,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
var organizationBillingService = sutProvider.GetDependency<IOrganizationBillingService>();
|
||||
|
||||
organization.Id = organizationId;
|
||||
organization.Name = "Old Name";
|
||||
var originalBillingEmail = organization.BillingEmail;
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = newName,
|
||||
BillingEmail = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationId, result.Id);
|
||||
Assert.Equal(newName, result.Name);
|
||||
Assert.Equal(originalBillingEmail, result.BillingEmail);
|
||||
|
||||
await organizationService
|
||||
.Received(1)
|
||||
.ReplaceAndUpdateCacheAsync(
|
||||
result,
|
||||
EventType.Organization_Updated);
|
||||
await organizationBillingService
|
||||
.Received(1)
|
||||
.UpdateOrganizationNameAndEmail(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_UpdatingBillingEmailOnly_UpdatesBillingEmailAndNotName(
|
||||
Guid organizationId,
|
||||
string newBillingEmail,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
var organizationBillingService = sutProvider.GetDependency<IOrganizationBillingService>();
|
||||
|
||||
organization.Id = organizationId;
|
||||
organization.BillingEmail = "old@example.com";
|
||||
var originalName = organization.Name;
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = null,
|
||||
BillingEmail = newBillingEmail
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationId, result.Id);
|
||||
Assert.Equal(originalName, result.Name);
|
||||
Assert.Equal(newBillingEmail.ToLowerInvariant().Trim(), result.BillingEmail);
|
||||
|
||||
await organizationService
|
||||
.Received(1)
|
||||
.ReplaceAndUpdateCacheAsync(
|
||||
result,
|
||||
EventType.Organization_Updated);
|
||||
await organizationBillingService
|
||||
.Received(1)
|
||||
.UpdateOrganizationNameAndEmail(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WhenNoChanges_PreservesBothFields(
|
||||
Guid organizationId,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
var organizationBillingService = sutProvider.GetDependency<IOrganizationBillingService>();
|
||||
|
||||
organization.Id = organizationId;
|
||||
var originalName = organization.Name;
|
||||
var originalBillingEmail = organization.BillingEmail;
|
||||
|
||||
organizationRepository
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = null,
|
||||
BillingEmail = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationId, result.Id);
|
||||
Assert.Equal(originalName, result.Name);
|
||||
Assert.Equal(originalBillingEmail, result.BillingEmail);
|
||||
|
||||
await organizationService
|
||||
.Received(1)
|
||||
.ReplaceAndUpdateCacheAsync(
|
||||
result,
|
||||
EventType.Organization_Updated);
|
||||
await organizationBillingService
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateOrganizationNameAndEmail(Arg.Any<Organization>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_SelfHosted_OnlyUpdatesKeysNotOrganizationDetails(
|
||||
Guid organizationId,
|
||||
string newName,
|
||||
string newBillingEmail,
|
||||
string publicKey,
|
||||
string encryptedPrivateKey,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationUpdateCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationBillingService = sutProvider.GetDependency<IOrganizationBillingService>();
|
||||
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
|
||||
globalSettings.SelfHosted.Returns(true);
|
||||
|
||||
organization.Id = organizationId;
|
||||
organization.Name = "Original Name";
|
||||
organization.BillingEmail = "original@example.com";
|
||||
organization.PublicKey = null;
|
||||
organization.PrivateKey = null;
|
||||
|
||||
organizationRepository.GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var request = new OrganizationUpdateRequest
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = newName, // Should be ignored
|
||||
BillingEmail = newBillingEmail, // Should be ignored
|
||||
PublicKey = publicKey,
|
||||
EncryptedPrivateKey = encryptedPrivateKey
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Original Name", result.Name); // Not changed
|
||||
Assert.Equal("original@example.com", result.BillingEmail); // Not changed
|
||||
Assert.Equal(publicKey, result.PublicKey); // Changed
|
||||
Assert.Equal(encryptedPrivateKey, result.PrivateKey); // Changed
|
||||
|
||||
await organizationBillingService
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateOrganizationNameAndEmail(Arg.Any<Organization>());
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
|
||||
@@ -21,52 +21,23 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidat
|
||||
public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_SingleOrgNotEnabled_ReturnsError(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
public void RequiredPolicies_IncludesSingleOrg(
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns((Policy?)null);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
var requiredPolicies = sutProvider.Sut.RequiredPolicies;
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Single organization policy must be enabled", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_SingleOrgPolicyDisabled_ReturnsError(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy singleOrgPolicy,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Single organization policy must be enabled", result, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains(PolicyType.SingleOrg, requiredPolicies);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_UsersNotCompliantWithSingleOrg_ReturnsError(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
Guid nonCompliantUserId,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var orgUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -85,10 +56,6 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUser]);
|
||||
@@ -107,13 +74,10 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_UserWithInvitedStatusInOtherOrg_ValidationPasses(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
Guid userId,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var orgUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -121,7 +85,6 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = userId,
|
||||
Email = "test@email.com"
|
||||
};
|
||||
|
||||
var otherOrgUser = new OrganizationUser
|
||||
@@ -133,10 +96,6 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Email = orgUser.Email
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUser]);
|
||||
@@ -146,7 +105,7 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
.Returns([otherOrgUser]);
|
||||
|
||||
sutProvider.GetDependency<IProviderUserRepository>()
|
||||
.GetManyByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns([]);
|
||||
|
||||
// Act
|
||||
@@ -159,30 +118,37 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_ProviderUsersExist_ReturnsError(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
Guid userId,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
var orgUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = policyUpdate.OrganizationId,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = userId
|
||||
};
|
||||
|
||||
var providerUser = new ProviderUser
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ProviderId = Guid.NewGuid(),
|
||||
UserId = Guid.NewGuid(),
|
||||
UserId = userId,
|
||||
Status = ProviderUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUser]);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns([]);
|
||||
|
||||
sutProvider.GetDependency<IProviderUserRepository>()
|
||||
.GetManyByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns([providerUser]);
|
||||
|
||||
// Act
|
||||
@@ -196,26 +162,18 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_AllValidationsPassed_ReturnsEmptyString(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var orgUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = policyUpdate.OrganizationId,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = Guid.NewGuid(),
|
||||
Email = "user@example.com"
|
||||
UserId = Guid.NewGuid()
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUser]);
|
||||
@@ -225,7 +183,7 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
.Returns([]);
|
||||
|
||||
sutProvider.GetDependency<IProviderUserRepository>()
|
||||
.GetManyByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns([]);
|
||||
|
||||
// Act
|
||||
@@ -249,9 +207,10 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceive()
|
||||
.GetByOrganizationIdTypeAsync(Arg.Any<Guid>(), Arg.Any<PolicyType>());
|
||||
.GetManyDetailsByOrganizationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -268,21 +227,18 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceive()
|
||||
.GetByOrganizationIdTypeAsync(Arg.Any<Guid>(), Arg.Any<PolicyType>());
|
||||
.GetManyDetailsByOrganizationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_IncludesOwnersAndAdmins_InComplianceCheck(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
Guid nonCompliantOwnerId,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ownerUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -290,7 +246,6 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Type = OrganizationUserType.Owner,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = nonCompliantOwnerId,
|
||||
Email = "owner@example.com"
|
||||
};
|
||||
|
||||
var otherOrgUser = new OrganizationUser
|
||||
@@ -301,10 +256,6 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([ownerUser]);
|
||||
@@ -323,12 +274,9 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_InvitedUsersExcluded_FromComplianceCheck(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var invitedUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -339,16 +287,12 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Email = "invited@example.com"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([invitedUser]);
|
||||
|
||||
sutProvider.GetDependency<IProviderUserRepository>()
|
||||
.GetManyByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns([]);
|
||||
|
||||
// Act
|
||||
@@ -359,14 +303,11 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_RevokedUsersExcluded_FromComplianceCheck(
|
||||
public async Task ValidateAsync_EnablingPolicy_RevokedUsersIncluded_InComplianceCheck(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var revokedUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -374,38 +315,44 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Revoked,
|
||||
UserId = Guid.NewGuid(),
|
||||
Email = "revoked@example.com"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
var additionalOrgUser = new OrganizationUser
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Revoked,
|
||||
UserId = revokedUser.UserId,
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
var orgUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
orgUserRepository
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([revokedUser]);
|
||||
|
||||
orgUserRepository.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns([additionalOrgUser]);
|
||||
|
||||
sutProvider.GetDependency<IProviderUserRepository>()
|
||||
.GetManyByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns([]);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
Assert.Contains("compliant with the Single organization policy", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_AcceptedUsersIncluded_InComplianceCheck(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
Guid nonCompliantUserId,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var acceptedUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -413,7 +360,6 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
UserId = nonCompliantUserId,
|
||||
Email = "accepted@example.com"
|
||||
};
|
||||
|
||||
var otherOrgUser = new OrganizationUser
|
||||
@@ -424,10 +370,6 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([acceptedUser]);
|
||||
@@ -443,186 +385,22 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
|
||||
Assert.Contains("compliant with the Single organization policy", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_EmptyOrganization_ReturnsEmptyString(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([]);
|
||||
|
||||
sutProvider.GetDependency<IProviderUserRepository>()
|
||||
.GetManyByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([]);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_WithSavePolicyModel_CallsValidateWithPolicyUpdate(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
singleOrgPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(singleOrgPolicy);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([]);
|
||||
|
||||
sutProvider.GetDependency<IProviderUserRepository>()
|
||||
.GetManyByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([]);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_EnablingPolicy_SetsUseAutomaticUserConfirmationToTrue(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
Organization organization,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Id = policyUpdate.OrganizationId;
|
||||
organization.UseAutomaticUserConfirmation = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.UpsertAsync(Arg.Is<Organization>(o =>
|
||||
o.Id == organization.Id &&
|
||||
o.UseAutomaticUserConfirmation == true &&
|
||||
o.RevisionDate > DateTime.MinValue));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_DisablingPolicy_SetsUseAutomaticUserConfirmationToFalse(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation, false)] PolicyUpdate policyUpdate,
|
||||
Organization organization,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Id = policyUpdate.OrganizationId;
|
||||
organization.UseAutomaticUserConfirmation = true;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.UpsertAsync(Arg.Is<Organization>(o =>
|
||||
o.Id == organization.Id &&
|
||||
o.UseAutomaticUserConfirmation == false &&
|
||||
o.RevisionDate > DateTime.MinValue));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_OrganizationNotFound_DoesNotThrowException(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns((Organization?)null);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceive()
|
||||
.UpsertAsync(Arg.Any<Organization>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ExecutePreUpsertSideEffectAsync_CallsOnSaveSideEffectsAsync(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.AutomaticUserConfirmation)] Policy currentPolicy,
|
||||
Organization organization,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Id = policyUpdate.OrganizationId;
|
||||
currentPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, currentPolicy);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.UpsertAsync(Arg.Is<Organization>(o =>
|
||||
o.Id == organization.Id &&
|
||||
o.UseAutomaticUserConfirmation == policyUpdate.Enabled));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_UpdatesRevisionDate(
|
||||
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
|
||||
Organization organization,
|
||||
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Id = policyUpdate.OrganizationId;
|
||||
var originalRevisionDate = DateTime.UtcNow.AddDays(-1);
|
||||
organization.RevisionDate = originalRevisionDate;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.UpsertAsync(Arg.Is<Organization>(o =>
|
||||
o.Id == organization.Id &&
|
||||
o.RevisionDate > originalRevisionDate));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class BlockClaimedDomainAccountCreationPolicyValidatorTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_NoVerifiedDomains_ValidationError(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("You must claim at least one domain to turn on this policy", result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_HasVerifiedDomains_Success(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_NoValidation(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, false)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
await sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.DidNotReceive()
|
||||
.HasVerifiedDomainsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_NoVerifiedDomains_ValidationError(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("You must claim at least one domain to turn on this policy", result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_HasVerifiedDomains_Success(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_NoValidation(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, false)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
await sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.DidNotReceive()
|
||||
.HasVerifiedDomainsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_FeatureFlagDisabled_ReturnsError(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("This feature is not enabled", result);
|
||||
await sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.DidNotReceive()
|
||||
.HasVerifiedDomainsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Type_ReturnsBlockClaimedDomainAccountCreation()
|
||||
{
|
||||
// Arrange
|
||||
var validator = new BlockClaimedDomainAccountCreationPolicyValidator(null, null);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(PolicyType.BlockClaimedDomainAccountCreation, validator.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredPolicies_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var validator = new BlockClaimedDomainAccountCreationPolicyValidator(null, null);
|
||||
|
||||
// Act
|
||||
var requiredPolicies = validator.RequiredPolicies.ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(requiredPolicies);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,11 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
@@ -95,7 +98,8 @@ public class SavePolicyCommandTests
|
||||
Substitute.For<IPolicyRepository>(),
|
||||
[new FakeSingleOrgPolicyValidator(), new FakeSingleOrgPolicyValidator()],
|
||||
Substitute.For<TimeProvider>(),
|
||||
Substitute.For<IPostSavePolicySideEffect>()));
|
||||
Substitute.For<IPostSavePolicySideEffect>(),
|
||||
Substitute.For<IPushNotificationService>()));
|
||||
Assert.Contains("Duplicate PolicyValidator for SingleOrg policy", exception.Message);
|
||||
}
|
||||
|
||||
@@ -360,6 +364,103 @@ public class SavePolicyCommandTests
|
||||
.ExecuteSideEffectsAsync(default!, default!, default!);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VNextSaveAsync_SendsPushNotification(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy currentPolicy)
|
||||
{
|
||||
// Arrange
|
||||
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate);
|
||||
|
||||
currentPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type)
|
||||
.Returns(currentPolicy);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([currentPolicy]);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.VNextSaveAsync(savePolicyModel);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||
.PushAsync(Arg.Is<PushNotification<SyncPolicyPushNotification>>(p =>
|
||||
p.Type == PushType.PolicyChanged &&
|
||||
p.Target == NotificationTarget.Organization &&
|
||||
p.TargetId == policyUpdate.OrganizationId &&
|
||||
p.ExcludeCurrentContext == false &&
|
||||
p.Payload.OrganizationId == policyUpdate.OrganizationId &&
|
||||
p.Payload.Policy.Id == result.Id &&
|
||||
p.Payload.Policy.Type == policyUpdate.Type &&
|
||||
p.Payload.Policy.Enabled == policyUpdate.Enabled &&
|
||||
p.Payload.Policy.Data == policyUpdate.Data));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_SendsPushNotification([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||
.PushAsync(Arg.Is<PushNotification<SyncPolicyPushNotification>>(p =>
|
||||
p.Type == PushType.PolicyChanged &&
|
||||
p.Target == NotificationTarget.Organization &&
|
||||
p.TargetId == policyUpdate.OrganizationId &&
|
||||
p.ExcludeCurrentContext == false &&
|
||||
p.Payload.OrganizationId == policyUpdate.OrganizationId &&
|
||||
p.Payload.Policy.Id == result.Id &&
|
||||
p.Payload.Policy.Type == policyUpdate.Type &&
|
||||
p.Payload.Policy.Enabled == policyUpdate.Enabled &&
|
||||
p.Payload.Policy.Data == policyUpdate.Data));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ExistingPolicy_SendsPushNotificationWithUpdatedPolicy(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy currentPolicy)
|
||||
{
|
||||
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||
|
||||
currentPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type)
|
||||
.Returns(currentPolicy);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([currentPolicy]);
|
||||
|
||||
var result = await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||
.PushAsync(Arg.Is<PushNotification<SyncPolicyPushNotification>>(p =>
|
||||
p.Type == PushType.PolicyChanged &&
|
||||
p.Target == NotificationTarget.Organization &&
|
||||
p.TargetId == policyUpdate.OrganizationId &&
|
||||
p.ExcludeCurrentContext == false &&
|
||||
p.Payload.OrganizationId == policyUpdate.OrganizationId &&
|
||||
p.Payload.Policy.Id == result.Id &&
|
||||
p.Payload.Policy.Type == policyUpdate.Type &&
|
||||
p.Payload.Policy.Enabled == policyUpdate.Enabled &&
|
||||
p.Payload.Policy.Data == policyUpdate.Data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new SutProvider with the PolicyValidators registered in the Sut.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
using System.Text.Json;
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
@@ -20,9 +25,10 @@ namespace Bit.Core.Test.Services;
|
||||
public class EventIntegrationHandlerTests
|
||||
{
|
||||
private const string _templateBase = "Date: #Date#, Type: #Type#, UserId: #UserId#";
|
||||
private const string _templateWithGroup = "Group: #GroupName#";
|
||||
private const string _templateWithOrganization = "Org: #OrganizationName#";
|
||||
private const string _templateWithUser = "#UserName#, #UserEmail#";
|
||||
private const string _templateWithActingUser = "#ActingUserName#, #ActingUserEmail#";
|
||||
private const string _templateWithUser = "#UserName#, #UserEmail#, #UserType#";
|
||||
private const string _templateWithActingUser = "#ActingUserName#, #ActingUserEmail#, #ActingUserType#";
|
||||
private static readonly Guid _organizationId = Guid.NewGuid();
|
||||
private static readonly Uri _uri = new Uri("https://localhost");
|
||||
private static readonly Uri _uri2 = new Uri("https://example.com");
|
||||
@@ -33,19 +39,23 @@ public class EventIntegrationHandlerTests
|
||||
private SutProvider<EventIntegrationHandler<WebhookIntegrationConfigurationDetails>> GetSutProvider(
|
||||
List<OrganizationIntegrationConfigurationDetails> configurations)
|
||||
{
|
||||
var configurationCache = Substitute.For<IIntegrationConfigurationDetailsCache>();
|
||||
configurationCache.GetConfigurationDetails(Arg.Any<Guid>(),
|
||||
IntegrationType.Webhook, Arg.Any<EventType>()).Returns(configurations);
|
||||
var cache = Substitute.For<IFusionCache>();
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<object, CancellationToken, Task<List<OrganizationIntegrationConfigurationDetails>>>>(),
|
||||
options: Arg.Any<FusionCacheEntryOptions>(),
|
||||
tags: Arg.Any<IEnumerable<string>>()
|
||||
).Returns(configurations);
|
||||
|
||||
return new SutProvider<EventIntegrationHandler<WebhookIntegrationConfigurationDetails>>()
|
||||
.SetDependency(configurationCache)
|
||||
.SetDependency(cache)
|
||||
.SetDependency(_eventIntegrationPublisher)
|
||||
.SetDependency(IntegrationType.Webhook)
|
||||
.SetDependency(_logger)
|
||||
.Create();
|
||||
}
|
||||
|
||||
private static IntegrationMessage<WebhookIntegrationConfigurationDetails> expectedMessage(string template)
|
||||
private static IntegrationMessage<WebhookIntegrationConfigurationDetails> ExpectedMessage(string template)
|
||||
{
|
||||
return new IntegrationMessage<WebhookIntegrationConfigurationDetails>()
|
||||
{
|
||||
@@ -105,16 +115,363 @@ public class EventIntegrationHandlerTests
|
||||
config.Configuration = null;
|
||||
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Uri = _uri });
|
||||
config.Template = _templateBase;
|
||||
config.Filters = JsonSerializer.Serialize(new IntegrationFilterGroup() { });
|
||||
config.Filters = JsonSerializer.Serialize(new IntegrationFilterGroup());
|
||||
|
||||
return [config];
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserIdPresent_UsesCache(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
).Returns(actingUser);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Equal(actingUser, context.ActingUser);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.ActingUserId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
Assert.Null(context.ActingUser);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserOrganizationIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId = null;
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
Assert.Null(context.ActingUser);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserFactory_CallsOrganizationUserRepository(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
organizationUserRepository.GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.ActingUserId.Value).Returns(actingUser);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>(f => capturedFactory = f)
|
||||
).Returns(actingUser);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await organizationUserRepository.Received(1).GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.ActingUserId.Value);
|
||||
Assert.Equal(actingUser, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_GroupIdPresent_UsesCache(EventMessage eventMessage, Group group)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithGroup));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.GroupId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
).Returns(group);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithGroup);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
);
|
||||
Assert.Equal(group, context.Group);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_GroupIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithGroup));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
eventMessage.GroupId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithGroup);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
);
|
||||
Assert.Null(context.Group);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_GroupFactory_CallsGroupRepository(EventMessage eventMessage, Group group)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithGroup));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var groupRepository = sutProvider.GetDependency<IGroupRepository>();
|
||||
|
||||
eventMessage.GroupId ??= Guid.NewGuid();
|
||||
groupRepository.GetByIdAsync(eventMessage.GroupId.Value).Returns(group);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>(f => capturedFactory = f)
|
||||
).Returns(group);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithGroup);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await groupRepository.Received(1).GetByIdAsync(eventMessage.GroupId.Value);
|
||||
Assert.Equal(group, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationIdPresent_UsesCache(EventMessage eventMessage, Organization organization)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithOrganization));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
).Returns(organization);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithOrganization);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
);
|
||||
Assert.Equal(organization, context.Organization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithOrganization));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithOrganization);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
);
|
||||
Assert.Null(context.Organization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationFactory_CallsOrganizationRepository(EventMessage eventMessage, Organization organization)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithOrganization));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
organizationRepository.GetByIdAsync(eventMessage.OrganizationId.Value).Returns(organization);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>(f => capturedFactory = f)
|
||||
).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithOrganization);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await organizationRepository.Received(1).GetByIdAsync(eventMessage.OrganizationId.Value);
|
||||
Assert.Equal(organization, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_UserIdPresent_UsesCache(EventMessage eventMessage, OrganizationUserUserDetails userDetails)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
).Returns(userDetails);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Equal(userDetails, context.User);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_UserIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId = null;
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Null(context.User);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationUserIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Null(context.User);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_UserFactory_CallsOrganizationUserRepository(EventMessage eventMessage, OrganizationUserUserDetails userDetails)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
organizationUserRepository.GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.UserId.Value).Returns(userDetails);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>(f => capturedFactory = f)
|
||||
).Returns(userDetails);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await organizationUserRepository.Received(1).GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.UserId.Value);
|
||||
Assert.Equal(userDetails, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_NoSpecialTokens_DoesNotCallAnyCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
eventMessage.GroupId ??= Guid.NewGuid();
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateBase);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
);
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
);
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_BaseTemplateNoConfigurations_DoesNothing(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(NoConfigurations());
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
cache.GetOrSetAsync<List<OrganizationIntegrationConfigurationDetails>>(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<Func<object, CancellationToken, Task<List<OrganizationIntegrationConfigurationDetails>>>>(),
|
||||
Arg.Any<FusionCacheEntryOptions>()
|
||||
).Returns(NoConfigurations());
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
Assert.Empty(_eventIntegrationPublisher.ReceivedCalls());
|
||||
@@ -133,31 +490,32 @@ public class EventIntegrationHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_BaseTemplateOneConfiguration_PublishesIntegrationMessage(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateBase));
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateBase));
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
|
||||
var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
|
||||
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IUserRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_BaseTemplateTwoConfigurations_PublishesIntegrationMessages(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(TwoConfigurations(_templateBase));
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(TwoConfigurations(_templateBase));
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
|
||||
var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
@@ -167,77 +525,15 @@ public class EventIntegrationHandlerTests
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IUserRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_ActingUserTemplate_LoadsUserFromRepository(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var user = Substitute.For<User>();
|
||||
user.Email = "test@example.com";
|
||||
user.Name = "Test";
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(user);
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage($"{user.Name}, {user.Email}");
|
||||
|
||||
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IUserRepository>().Received(1).GetByIdAsync(eventMessage.ActingUserId ?? Guid.Empty);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_OrganizationTemplate_LoadsOrganizationFromRepository(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithOrganization));
|
||||
var organization = Substitute.For<Organization>();
|
||||
organization.Name = "Test";
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(organization);
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage($"Org: {organization.Name}");
|
||||
|
||||
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).GetByIdAsync(eventMessage.OrganizationId ?? Guid.Empty);
|
||||
await sutProvider.GetDependency<IUserRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_UserTemplate_LoadsUserFromRepository(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var user = Substitute.For<User>();
|
||||
user.Email = "test@example.com";
|
||||
user.Name = "Test";
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(user);
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage($"{user.Name}, {user.Email}");
|
||||
|
||||
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IUserRepository>().Received(1).GetByIdAsync(eventMessage.UserId ?? Guid.Empty);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_FilterReturnsFalse_DoesNothing(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(ValidFilterConfiguration());
|
||||
sutProvider.GetDependency<IIntegrationFilterService>().EvaluateFilterGroup(
|
||||
Arg.Any<IntegrationFilterGroup>(), Arg.Any<EventMessage>()).Returns(false);
|
||||
@@ -249,14 +545,14 @@ public class EventIntegrationHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_FilterReturnsTrue_PublishesIntegrationMessage(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(ValidFilterConfiguration());
|
||||
sutProvider.GetDependency<IIntegrationFilterService>().EvaluateFilterGroup(
|
||||
Arg.Any<IntegrationFilterGroup>(), Arg.Any<EventMessage>()).Returns(true);
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
|
||||
var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
|
||||
@@ -268,6 +564,7 @@ public class EventIntegrationHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_InvalidFilter_LogsErrorDoesNothing(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(InvalidFilterConfiguration());
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
@@ -277,12 +574,13 @@ public class EventIntegrationHandlerTests
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
Arg.Any<Func<object, Exception, string>>());
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleManyEventsAsync_BaseTemplateNoConfigurations_DoesNothing(List<EventMessage> eventMessages)
|
||||
{
|
||||
eventMessages.ForEach(e => e.OrganizationId = _organizationId);
|
||||
var sutProvider = GetSutProvider(NoConfigurations());
|
||||
|
||||
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||
@@ -292,13 +590,14 @@ public class EventIntegrationHandlerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleManyEventsAsync_BaseTemplateOneConfiguration_PublishesIntegrationMessages(List<EventMessage> eventMessages)
|
||||
{
|
||||
eventMessages.ForEach(e => e.OrganizationId = _organizationId);
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateBase));
|
||||
|
||||
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||
|
||||
foreach (var eventMessage in eventMessages)
|
||||
{
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
|
||||
var expectedMessage = ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
@@ -310,13 +609,14 @@ public class EventIntegrationHandlerTests
|
||||
public async Task HandleManyEventsAsync_BaseTemplateTwoConfigurations_PublishesIntegrationMessages(
|
||||
List<EventMessage> eventMessages)
|
||||
{
|
||||
eventMessages.ForEach(e => e.OrganizationId = _organizationId);
|
||||
var sutProvider = GetSutProvider(TwoConfigurations(_templateBase));
|
||||
|
||||
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||
|
||||
foreach (var eventMessage in eventMessages)
|
||||
{
|
||||
var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
|
||||
var expectedMessage = ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(AssertHelper.AssertPropertyEqual(
|
||||
@@ -327,4 +627,84 @@ public class EventIntegrationHandlerTests
|
||||
expectedMessage, new[] { "MessageId", "OrganizationId" })));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_CapturedFactories_CallConfigurationRepository(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(NoConfigurations());
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var configurationRepository = sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>();
|
||||
|
||||
var configs = OneConfiguration(_templateBase);
|
||||
|
||||
configurationRepository.GetManyByEventTypeOrganizationIdIntegrationType(eventType: eventMessage.Type, organizationId: _organizationId, integrationType: IntegrationType.Webhook).Returns(configs);
|
||||
|
||||
// Capture the factory function - there will be 1 call that returns both specific and wildcard matches
|
||||
Func<FusionCacheFactoryExecutionContext<List<OrganizationIntegrationConfigurationDetails>>, CancellationToken, Task<List<OrganizationIntegrationConfigurationDetails>>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<List<OrganizationIntegrationConfigurationDetails>>, CancellationToken, Task<List<OrganizationIntegrationConfigurationDetails>>>>(f
|
||||
=> capturedFactory = f),
|
||||
options: Arg.Any<FusionCacheEntryOptions>(),
|
||||
tags: Arg.Any<IEnumerable<string>>()
|
||||
).Returns(new List<OrganizationIntegrationConfigurationDetails>());
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
// Verify factory was captured
|
||||
Assert.NotNull(capturedFactory);
|
||||
|
||||
// Execute the captured factory to trigger repository call
|
||||
await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await configurationRepository.Received(1).GetManyByEventTypeOrganizationIdIntegrationType(eventType: eventMessage.Type, organizationId: _organizationId, integrationType: IntegrationType.Webhook);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_ConfigurationCacheOptions_SetsDurationToConstant(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(NoConfigurations());
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
FusionCacheEntryOptions? capturedOption = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<List<OrganizationIntegrationConfigurationDetails>>, CancellationToken, Task<List<OrganizationIntegrationConfigurationDetails>>>>(),
|
||||
options: Arg.Do<FusionCacheEntryOptions>(opt => capturedOption = opt),
|
||||
tags: Arg.Any<IEnumerable<string>?>()
|
||||
).Returns(new List<OrganizationIntegrationConfigurationDetails>());
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
Assert.NotNull(capturedOption);
|
||||
Assert.Equal(EventIntegrationsCacheConstants.DurationForOrganizationIntegrationConfigurationDetails,
|
||||
capturedOption.Duration);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_ConfigurationCache_AddsOrganizationIntegrationTag(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(NoConfigurations());
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
IEnumerable<string>? capturedTags = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<List<OrganizationIntegrationConfigurationDetails>>, CancellationToken, Task<List<OrganizationIntegrationConfigurationDetails>>>>(),
|
||||
options: Arg.Any<FusionCacheEntryOptions>(),
|
||||
tags: Arg.Do<IEnumerable<string>>(t => capturedTags = t)
|
||||
).Returns(new List<OrganizationIntegrationConfigurationDetails>());
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedTag = EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
_organizationId,
|
||||
IntegrationType.Webhook
|
||||
);
|
||||
Assert.NotNull(capturedTags);
|
||||
Assert.Contains(expectedTag, capturedTags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class IntegrationConfigurationDetailsCacheServiceTests
|
||||
{
|
||||
private SutProvider<IntegrationConfigurationDetailsCacheService> GetSutProvider(
|
||||
List<OrganizationIntegrationConfigurationDetails> configurations)
|
||||
{
|
||||
var configurationRepository = Substitute.For<IOrganizationIntegrationConfigurationRepository>();
|
||||
configurationRepository.GetAllConfigurationDetailsAsync().Returns(configurations);
|
||||
|
||||
return new SutProvider<IntegrationConfigurationDetailsCacheService>()
|
||||
.SetDependency(configurationRepository)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetConfigurationDetails_SpecificKeyExists_ReturnsExpectedList(OrganizationIntegrationConfigurationDetails config)
|
||||
{
|
||||
config.EventType = EventType.Cipher_Created;
|
||||
var sutProvider = GetSutProvider([config]);
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
var result = sutProvider.Sut.GetConfigurationDetails(
|
||||
config.OrganizationId,
|
||||
config.IntegrationType,
|
||||
EventType.Cipher_Created);
|
||||
Assert.Single(result);
|
||||
Assert.Same(config, result[0]);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetConfigurationDetails_AllEventsKeyExists_ReturnsExpectedList(OrganizationIntegrationConfigurationDetails config)
|
||||
{
|
||||
config.EventType = null;
|
||||
var sutProvider = GetSutProvider([config]);
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
var result = sutProvider.Sut.GetConfigurationDetails(
|
||||
config.OrganizationId,
|
||||
config.IntegrationType,
|
||||
EventType.Cipher_Created);
|
||||
Assert.Single(result);
|
||||
Assert.Same(config, result[0]);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetConfigurationDetails_BothSpecificAndAllEventsKeyExists_ReturnsExpectedList(
|
||||
OrganizationIntegrationConfigurationDetails specificConfig,
|
||||
OrganizationIntegrationConfigurationDetails allKeysConfig
|
||||
)
|
||||
{
|
||||
specificConfig.EventType = EventType.Cipher_Created;
|
||||
allKeysConfig.EventType = null;
|
||||
allKeysConfig.OrganizationId = specificConfig.OrganizationId;
|
||||
allKeysConfig.IntegrationType = specificConfig.IntegrationType;
|
||||
|
||||
var sutProvider = GetSutProvider([specificConfig, allKeysConfig]);
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
var result = sutProvider.Sut.GetConfigurationDetails(
|
||||
specificConfig.OrganizationId,
|
||||
specificConfig.IntegrationType,
|
||||
EventType.Cipher_Created);
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Template == specificConfig.Template);
|
||||
Assert.Contains(result, r => r.Template == allKeysConfig.Template);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetConfigurationDetails_KeyMissing_ReturnsEmptyList(OrganizationIntegrationConfigurationDetails config)
|
||||
{
|
||||
var sutProvider = GetSutProvider([config]);
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
var result = sutProvider.Sut.GetConfigurationDetails(
|
||||
Guid.NewGuid(),
|
||||
config.IntegrationType,
|
||||
config.EventType ?? EventType.Cipher_Created);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetConfigurationDetails_ReturnsCachedValue_EvenIfRepositoryChanges(OrganizationIntegrationConfigurationDetails config)
|
||||
{
|
||||
var sutProvider = GetSutProvider([config]);
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
|
||||
var newConfig = JsonSerializer.Deserialize<OrganizationIntegrationConfigurationDetails>(JsonSerializer.Serialize(config));
|
||||
Assert.NotNull(newConfig);
|
||||
newConfig.Template = "Changed";
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().GetAllConfigurationDetailsAsync()
|
||||
.Returns([newConfig]);
|
||||
|
||||
var result = sutProvider.Sut.GetConfigurationDetails(
|
||||
config.OrganizationId,
|
||||
config.IntegrationType,
|
||||
config.EventType ?? EventType.Cipher_Created);
|
||||
Assert.Single(result);
|
||||
Assert.NotEqual("Changed", result[0].Template); // should not yet pick up change from repository
|
||||
|
||||
await sutProvider.Sut.RefreshAsync(); // Pick up changes
|
||||
|
||||
result = sutProvider.Sut.GetConfigurationDetails(
|
||||
config.OrganizationId,
|
||||
config.IntegrationType,
|
||||
config.EventType ?? EventType.Cipher_Created);
|
||||
Assert.Single(result);
|
||||
Assert.Equal("Changed", result[0].Template); // Should have the new value
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RefreshAsync_GroupsByCompositeKey(OrganizationIntegrationConfigurationDetails config1)
|
||||
{
|
||||
var config2 = JsonSerializer.Deserialize<OrganizationIntegrationConfigurationDetails>(
|
||||
JsonSerializer.Serialize(config1))!;
|
||||
config2.Template = "Another";
|
||||
|
||||
var sutProvider = GetSutProvider([config1, config2]);
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
|
||||
var results = sutProvider.Sut.GetConfigurationDetails(
|
||||
config1.OrganizationId,
|
||||
config1.IntegrationType,
|
||||
config1.EventType ?? EventType.Cipher_Created);
|
||||
|
||||
Assert.Equal(2, results.Count);
|
||||
Assert.Contains(results, r => r.Template == config1.Template);
|
||||
Assert.Contains(results, r => r.Template == config2.Template);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RefreshAsync_LogsInformationOnSuccess(OrganizationIntegrationConfigurationDetails config)
|
||||
{
|
||||
var sutProvider = GetSutProvider([config]);
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
|
||||
sutProvider.GetDependency<ILogger<IntegrationConfigurationDetailsCacheService>>().Received().Log(
|
||||
LogLevel.Information,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => o.ToString()!.Contains("Refreshed successfully")),
|
||||
null,
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshAsync_OnException_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider([]);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().GetAllConfigurationDetailsAsync()
|
||||
.Throws(new Exception("Database failure"));
|
||||
await sutProvider.Sut.RefreshAsync();
|
||||
|
||||
sutProvider.GetDependency<ILogger<IntegrationConfigurationDetailsCacheService>>().Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => o.ToString()!.Contains("Refresh failed")),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Services;
|
||||
|
||||
public class OrganizationIntegrationConfigurationValidatorTests
|
||||
{
|
||||
private readonly OrganizationIntegrationConfigurationValidator _sut = new();
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_CloudBillingSyncIntegration_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.CloudBillingSync, configuration));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void ValidateConfiguration_EmptyTemplate_ReturnsFalse(string? template)
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration(ChannelId: "C12345")),
|
||||
Template = template
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Slack, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://example.com"))),
|
||||
Template = template
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, config2));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void ValidateConfiguration_EmptyNonNullConfiguration_ReturnsFalse(string? config)
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Hec, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Datadog, config2));
|
||||
|
||||
var config3 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Teams, config3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_NullConfiguration_ReturnsTrue()
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Hec, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Datadog, config2));
|
||||
|
||||
var config3 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Teams, config3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_InvalidJsonConfiguration_ReturnsFalse()
|
||||
{
|
||||
var config = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{not valid json}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Slack, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Hec, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Datadog, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Teams, config));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_InvalidJsonFilters_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://example.com"))),
|
||||
Template = "template",
|
||||
Filters = "{Not valid json}"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ScimIntegration_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Scim, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidSlackConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration(ChannelId: "C12345")),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Slack, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidSlackConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration("C12345")),
|
||||
Template = "template",
|
||||
Filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
})
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Slack, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidNoAuthWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"))),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://localhost"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN")),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidWebhookConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://example.com"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN")),
|
||||
Template = "template",
|
||||
Filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
})
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_UnknownIntegrationType_ReturnsFalse()
|
||||
{
|
||||
var unknownType = (IntegrationType)999;
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(unknownType, configuration));
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Fakes;
|
||||
@@ -618,7 +618,7 @@ public class OrganizationServiceTests
|
||||
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
||||
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites);
|
||||
|
||||
@@ -666,7 +666,7 @@ public class OrganizationServiceTests
|
||||
.SendInvitesAsync(Arg.Any<SendInvitesRequest>()).ThrowsAsync<Exception>();
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
await Assert.ThrowsAsync<AggregateException>(async () =>
|
||||
await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites));
|
||||
@@ -732,7 +732,7 @@ public class OrganizationServiceTests
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscription(organization.Id,
|
||||
seatAdjustment, maxAutoscaleSeats));
|
||||
@@ -757,7 +757,7 @@ public class OrganizationServiceTests
|
||||
organization.SmSeats = 100;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
||||
{
|
||||
@@ -837,7 +837,7 @@ public class OrganizationServiceTests
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
public void ValidateSecretsManagerPlan_ThrowsException_WhenNoSecretsManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var signup = new OrganizationUpgrade
|
||||
{
|
||||
UseSecretsManager = true,
|
||||
@@ -854,7 +854,7 @@ public class OrganizationServiceTests
|
||||
[BitAutoData(PlanType.Free)]
|
||||
public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var signup = new OrganizationUpgrade
|
||||
{
|
||||
UseSecretsManager = true,
|
||||
@@ -871,7 +871,7 @@ public class OrganizationServiceTests
|
||||
PlanType planType,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var signup = new OrganizationUpgrade
|
||||
{
|
||||
UseSecretsManager = true,
|
||||
@@ -890,7 +890,7 @@ public class OrganizationServiceTests
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
public void ValidateSecretsManagerPlan_ThrowsException_WhenMoreSeatsThanPasswordManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var signup = new OrganizationUpgrade
|
||||
{
|
||||
UseSecretsManager = true,
|
||||
@@ -912,7 +912,7 @@ public class OrganizationServiceTests
|
||||
PlanType planType,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var signup = new OrganizationUpgrade
|
||||
{
|
||||
UseSecretsManager = true,
|
||||
@@ -930,7 +930,7 @@ public class OrganizationServiceTests
|
||||
PlanType planType,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var signup = new OrganizationUpgrade
|
||||
{
|
||||
UseSecretsManager = true,
|
||||
@@ -952,7 +952,7 @@ public class OrganizationServiceTests
|
||||
PlanType planType,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var signup = new OrganizationUpgrade
|
||||
{
|
||||
UseSecretsManager = true,
|
||||
|
||||
@@ -83,6 +83,7 @@ public class IntegrationTemplateProcessorTests
|
||||
[Theory]
|
||||
[InlineData("User name is #UserName#")]
|
||||
[InlineData("Email: #UserEmail#")]
|
||||
[InlineData("User type = #UserType#")]
|
||||
public void TemplateRequiresUser_ContainingKeys_ReturnsTrue(string template)
|
||||
{
|
||||
var result = IntegrationTemplateProcessor.TemplateRequiresUser(template);
|
||||
@@ -102,6 +103,7 @@ public class IntegrationTemplateProcessorTests
|
||||
[Theory]
|
||||
[InlineData("Acting user is #ActingUserName#")]
|
||||
[InlineData("Acting user's email is #ActingUserEmail#")]
|
||||
[InlineData("Acting user's type is #ActingUserType#")]
|
||||
public void TemplateRequiresActingUser_ContainingKeys_ReturnsTrue(string template)
|
||||
{
|
||||
var result = IntegrationTemplateProcessor.TemplateRequiresActingUser(template);
|
||||
@@ -118,6 +120,25 @@ public class IntegrationTemplateProcessorTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Group name is #GroupName#!")]
|
||||
[InlineData("Group: #GroupName#")]
|
||||
public void TemplateRequiresGroup_ContainingKeys_ReturnsTrue(string template)
|
||||
{
|
||||
var result = IntegrationTemplateProcessor.TemplateRequiresGroup(template);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("#GroupId#")] // This is on the base class, not fetched, so should be false
|
||||
[InlineData("No Group Tokens")]
|
||||
[InlineData("")]
|
||||
public void TemplateRequiresGroup_EmptyInputOrNoMatchingKeys_ReturnsFalse(string template)
|
||||
{
|
||||
var result = IntegrationTemplateProcessor.TemplateRequiresGroup(template);
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Organization: #OrganizationName#")]
|
||||
[InlineData("Welcome to #OrganizationName#")]
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using Bit.Core.Auth.Attributes;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Attributes;
|
||||
|
||||
public class MarketingInitiativeValidationAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsValid_NullValue_ReturnsTrue()
|
||||
{
|
||||
var sut = new MarketingInitiativeValidationAttribute();
|
||||
|
||||
var actual = sut.IsValid(null);
|
||||
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(MarketingInitiativeConstants.Premium)]
|
||||
public void IsValid_AcceptedValue_ReturnsTrue(string value)
|
||||
{
|
||||
var sut = new MarketingInitiativeValidationAttribute();
|
||||
|
||||
var actual = sut.IsValid(value);
|
||||
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid")]
|
||||
[InlineData("")]
|
||||
[InlineData("Premium")] // case sensitive - capitalized
|
||||
[InlineData("PREMIUM")] // case sensitive - uppercase
|
||||
[InlineData("premium ")] // trailing space
|
||||
[InlineData(" premium")] // leading space
|
||||
public void IsValid_InvalidStringValue_ReturnsFalse(string value)
|
||||
{
|
||||
var sut = new MarketingInitiativeValidationAttribute();
|
||||
|
||||
var actual = sut.IsValid(value);
|
||||
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(123)] // integer
|
||||
[InlineData(true)] // boolean
|
||||
[InlineData(45.67)] // double
|
||||
public void IsValid_NonStringValue_ReturnsFalse(object value)
|
||||
{
|
||||
var sut = new MarketingInitiativeValidationAttribute();
|
||||
|
||||
var actual = sut.IsValid(value);
|
||||
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ErrorMessage_ContainsAcceptedValues()
|
||||
{
|
||||
var sut = new MarketingInitiativeValidationAttribute();
|
||||
|
||||
var errorMessage = sut.ErrorMessage;
|
||||
|
||||
Assert.NotNull(errorMessage);
|
||||
Assert.Contains("premium", errorMessage);
|
||||
Assert.Contains("Marketing initiative type must be one of:", errorMessage);
|
||||
}
|
||||
}
|
||||
224
test/Core.Test/Auth/Entities/AuthRequestTests.cs
Normal file
224
test/Core.Test/Auth/Entities/AuthRequestTests.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Entities;
|
||||
|
||||
public class AuthRequestTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithValidRequest_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = true,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = null,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithWrongUserId_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var differentUserId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = true,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = null,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(differentUserId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.False(result, "Auth request should not validate for a different user");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithWrongAccessCode_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = true,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = null,
|
||||
AccessCode = "correct-code"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, "wrong-code");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithoutResponseDate_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = null, // Not responded to
|
||||
Approved = true,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = null,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.False(result, "Unanswered auth requests should not be valid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithApprovedFalse_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = false, // Denied
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = null,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.False(result, "Denied auth requests should not be valid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithApprovedNull_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = null, // Pending
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = null,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.False(result, "Pending auth requests should not be valid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithExpiredRequest_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = true,
|
||||
CreationDate = DateTime.UtcNow.AddMinutes(-20), // Expired (15 min timeout)
|
||||
AuthenticationDate = null,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.False(result, "Expired auth requests should not be valid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithWrongType_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.Unlock, // Wrong type
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = true,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = null,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.False(result, "Only AuthenticateAndUnlock type should be valid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForAuthentication_WithAlreadyUsed_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var accessCode = "test-access-code";
|
||||
var authRequest = new AuthRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = AuthRequestType.AuthenticateAndUnlock,
|
||||
ResponseDate = DateTime.UtcNow,
|
||||
Approved = true,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
AuthenticationDate = DateTime.UtcNow, // Already used
|
||||
AccessCode = accessCode
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = authRequest.IsValidForAuthentication(userId, accessCode);
|
||||
|
||||
// Assert
|
||||
Assert.False(result, "Auth requests should only be valid for one-time use");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Models.Api.Request.Accounts;
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot tests to ensure the string constants in <see cref="MarketingInitiativeConstants"/> do not change unintentionally.
|
||||
/// If you intentionally change any of these values, please update the tests to reflect the new expected values.
|
||||
/// </summary>
|
||||
public class MarketingInitiativeConstantsSnapshotTests
|
||||
{
|
||||
[Fact]
|
||||
public void MarketingInitiativeConstants_HaveCorrectValues()
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal("premium", MarketingInitiativeConstants.Premium);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
@@ -14,7 +13,6 @@ using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -342,26 +340,26 @@ public class SsoConfigServiceTests
|
||||
|
||||
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
await sutProvider.GetDependency<IVNextSavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.SingleOrg &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
Arg.Is<SavePolicyModel>(t => t.PolicyUpdate.Type == PolicyType.SingleOrg &&
|
||||
t.PolicyUpdate.OrganizationId == organization.Id &&
|
||||
t.PolicyUpdate.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
await sutProvider.GetDependency<IVNextSavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.ResetPassword &&
|
||||
t.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
Arg.Is<SavePolicyModel>(t => t.PolicyUpdate.Type == PolicyType.ResetPassword &&
|
||||
t.PolicyUpdate.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled &&
|
||||
t.PolicyUpdate.OrganizationId == organization.Id &&
|
||||
t.PolicyUpdate.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
await sutProvider.GetDependency<IVNextSavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.RequireSso &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
Arg.Is<SavePolicyModel>(t => t.PolicyUpdate.Type == PolicyType.RequireSso &&
|
||||
t.PolicyUpdate.OrganizationId == organization.Id &&
|
||||
t.PolicyUpdate.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
|
||||
@@ -369,7 +367,7 @@ public class SsoConfigServiceTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_Tde_WhenPolicyValidatorsRefactorEnabled_UsesVNextSavePolicyCommand(
|
||||
public async Task SaveAsync_Tde_UsesVNextSavePolicyCommand(
|
||||
SutProvider<SsoConfigService> sutProvider, Organization organization)
|
||||
{
|
||||
var ssoConfig = new SsoConfig
|
||||
@@ -383,10 +381,6 @@ public class SsoConfigServiceTests
|
||||
OrganizationId = organization.Id,
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||
|
||||
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
|
||||
|
||||
@@ -38,6 +38,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUser_Succeeds(SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user)
|
||||
.Returns(IdentityResult.Success);
|
||||
@@ -62,6 +68,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUser_WhenCreateUserFails_ReturnsIdentityResultFailed(SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user)
|
||||
.Returns(IdentityResult.Failed());
|
||||
@@ -416,6 +428,138 @@ public class RegisterUserCommandTests
|
||||
Assert.Equal(expectedErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationInviteToken_BlockedDomainFromDifferentOrg_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, OrganizationUser orgUser, string orgInviteToken, Guid orgUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.Id = orgUserId;
|
||||
var blockingOrganizationId = Guid.NewGuid(); // Different org that has the domain blocked
|
||||
orgUser.OrganizationId = Guid.NewGuid(); // The org they're trying to join
|
||||
|
||||
var orgInviteTokenable = new OrgUserInviteTokenable(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>()
|
||||
.TryUnprotect(orgInviteToken, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = orgInviteTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Mock the new overload that excludes the organization - it should return true (domain IS blocked by another org)
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com", orgUser.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaOrganizationInviteToken(user, masterPasswordHash, orgInviteToken, orgUserId));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationInviteToken_BlockedDomainFromSameOrg_Succeeds(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, OrganizationUser orgUser, string orgInviteToken, Guid orgUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@company-domain.com";
|
||||
user.ReferenceData = null;
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.Id = orgUserId;
|
||||
// The organization owns the domain and is trying to invite the user
|
||||
orgUser.OrganizationId = Guid.NewGuid();
|
||||
|
||||
var orgInviteTokenable = new OrgUserInviteTokenable(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>()
|
||||
.TryUnprotect(orgInviteToken, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = orgInviteTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Mock the new overload - it should return false (domain is NOT blocked by OTHER orgs)
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("company-domain.com", orgUser.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user, masterPasswordHash)
|
||||
.Returns(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.RegisterUserViaOrganizationInviteToken(user, masterPasswordHash, orgInviteToken, orgUserId);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Succeeded);
|
||||
await sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.Received(1)
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("company-domain.com", orgUser.OrganizationId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationInviteToken_WithValidTokenButNullOrgUser_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, OrganizationUser orgUser, string orgInviteToken, Guid orgUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@example.com";
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.Id = orgUserId;
|
||||
|
||||
var orgInviteTokenable = new OrgUserInviteTokenable(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>()
|
||||
.TryUnprotect(orgInviteToken, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = orgInviteTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Mock GetByIdAsync to return null - simulating a deleted or non-existent organization user
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns((OrganizationUser)null);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaOrganizationInviteToken(user, masterPasswordHash, orgInviteToken, orgUserId));
|
||||
Assert.Equal("Invalid organization user invitation.", exception.Message);
|
||||
|
||||
// Verify that GetByIdAsync was called
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetByIdAsync(orgUserId);
|
||||
|
||||
// Verify that user creation was never attempted
|
||||
await sutProvider.GetDependency<IUserService>()
|
||||
.DidNotReceive()
|
||||
.CreateUserAsync(Arg.Any<User>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// RegisterUserViaEmailVerificationToken tests
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
@@ -425,6 +569,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUserViaEmailVerificationToken_Succeeds(SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -457,6 +607,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUserViaEmailVerificationToken_InvalidToken_ThrowsBadRequestException(SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -495,6 +651,12 @@ public class RegisterUserCommandTests
|
||||
string orgSponsoredFreeFamilyPlanInviteToken)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
|
||||
.ValidateRedemptionTokenAsync(orgSponsoredFreeFamilyPlanInviteToken, user.Email)
|
||||
.Returns((true, new OrganizationSponsorship()));
|
||||
@@ -524,6 +686,12 @@ public class RegisterUserCommandTests
|
||||
string masterPasswordHash, string orgSponsoredFreeFamilyPlanInviteToken)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
|
||||
.ValidateRedemptionTokenAsync(orgSponsoredFreeFamilyPlanInviteToken, user.Email)
|
||||
.Returns((false, new OrganizationSponsorship()));
|
||||
@@ -561,9 +729,14 @@ public class RegisterUserCommandTests
|
||||
EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
emergencyAccess.Email = user.Email;
|
||||
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -597,9 +770,14 @@ public class RegisterUserCommandTests
|
||||
string masterPasswordHash, EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
emergencyAccess.Email = "wrong@email.com";
|
||||
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -640,6 +818,8 @@ public class RegisterUserCommandTests
|
||||
User user, string masterPasswordHash, Guid providerUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
// Start with plaintext
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
var decryptedProviderInviteToken = $"ProviderUserInvite {providerUserId} {user.Email} {nowMillis}";
|
||||
@@ -662,6 +842,10 @@ public class RegisterUserCommandTests
|
||||
sutProvider.GetDependency<IGlobalSettings>()
|
||||
.OrganizationInviteExpirationHours.Returns(120); // 5 days
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user, masterPasswordHash)
|
||||
.Returns(IdentityResult.Success);
|
||||
@@ -691,6 +875,8 @@ public class RegisterUserCommandTests
|
||||
User user, string masterPasswordHash, Guid providerUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
// Start with plaintext
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
var decryptedProviderInviteToken = $"ProviderUserInvite {providerUserId} {user.Email} {nowMillis}";
|
||||
@@ -713,6 +899,10 @@ public class RegisterUserCommandTests
|
||||
sutProvider.GetDependency<IGlobalSettings>()
|
||||
.OrganizationInviteExpirationHours.Returns(120); // 5 days
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
// Using sutProvider in the parameters of the function means that the constructor has already run for the
|
||||
// command so we have to recreate it in order for our mock overrides to be used.
|
||||
sutProvider.Create();
|
||||
@@ -762,11 +952,72 @@ public class RegisterUserCommandTests
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Domain blocking tests (BlockClaimedDomainAccountCreation policy)
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUser_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUser(user));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
|
||||
// Verify user creation was never attempted
|
||||
await sutProvider.GetDependency<IUserService>()
|
||||
.DidNotReceive()
|
||||
.CreateUserAsync(Arg.Any<User>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUser_AllowedDomain_Succeeds(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@allowed-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("allowed-domain.com")
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user)
|
||||
.Returns(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.RegisterUser(user);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Succeeded);
|
||||
await sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.Received(1)
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("allowed-domain.com");
|
||||
}
|
||||
|
||||
// SendWelcomeEmail tests
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
[BitAutoData(PlanType.FamiliesAnnually2019)]
|
||||
[BitAutoData(PlanType.FamiliesAnnually2025)]
|
||||
[BitAutoData(PlanType.Free)]
|
||||
public async Task SendWelcomeEmail_FamilyOrg_SendsFamilyWelcomeEmail(
|
||||
PlanType planType,
|
||||
@@ -799,6 +1050,194 @@ public class RegisterUserCommandTests
|
||||
.SendFreeOrgOrFamilyOrgUserWelcomeEmailAsync(user, organization.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaEmailVerificationToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new RegistrationEmailVerificationTokenable(user.Email, user.Name, receiveMarketingMaterials);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaEmailVerificationToken(user, masterPasswordHash, emailVerificationToken));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
string orgSponsoredFreeFamilyPlanInviteToken)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
|
||||
.ValidateRedemptionTokenAsync(orgSponsoredFreeFamilyPlanInviteToken, user.Email)
|
||||
.Returns((true, new OrganizationSponsorship()));
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(user, masterPasswordHash, orgSponsoredFreeFamilyPlanInviteToken));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
emergencyAccess.Email = user.Email;
|
||||
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 10);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaAcceptEmergencyAccessInviteToken(user, masterPasswordHash, acceptEmergencyAccessInviteToken, acceptEmergencyAccessId));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaProviderInviteToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, Guid providerUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
// Start with plaintext
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
var decryptedProviderInviteToken = $"ProviderUserInvite {providerUserId} {user.Email} {nowMillis}";
|
||||
|
||||
// Get the byte array of the plaintext
|
||||
var decryptedProviderInviteTokenByteArray = Encoding.UTF8.GetBytes(decryptedProviderInviteToken);
|
||||
|
||||
// Base64 encode the byte array (this is passed to protector.protect(bytes))
|
||||
var base64EncodedProviderInvToken = WebEncoders.Base64UrlEncode(decryptedProviderInviteTokenByteArray);
|
||||
|
||||
var mockDataProtector = Substitute.For<IDataProtector>();
|
||||
|
||||
// Given any byte array, just return the decryptedProviderInviteTokenByteArray (sidestepping any actual encryption)
|
||||
mockDataProtector.Unprotect(Arg.Any<byte[]>()).Returns(decryptedProviderInviteTokenByteArray);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectionProvider>()
|
||||
.CreateProtector("ProviderServiceDataProtector")
|
||||
.Returns(mockDataProtector);
|
||||
|
||||
sutProvider.GetDependency<IGlobalSettings>()
|
||||
.OrganizationInviteExpirationHours.Returns(120); // 5 days
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
// Using sutProvider in the parameters of the function means that the constructor has already run for the
|
||||
// command so we have to recreate it in order for our mock overrides to be used.
|
||||
sutProvider.Create();
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaProviderInviteToken(user, masterPasswordHash, base64EncodedProviderInvToken, providerUserId));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Invalid email format tests
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUser_InvalidEmailFormat_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "invalid-email-format";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUser(user));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaEmailVerificationToken_InvalidEmailFormat_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "invalid-email-format";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new RegistrationEmailVerificationTokenable(user.Email, user.Name, receiveMarketingMaterials);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaEmailVerificationToken(user, masterPasswordHash, emailVerificationToken));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendWelcomeEmail_OrganizationNull_SendsIndividualWelcomeEmail(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.UserFeatures.Registration.Implementations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
@@ -21,6 +22,43 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsNewUserAndEnableEmailVerificationTrue_SendsEmailAndReturnsNull(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.ReturnsNull();
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.EnableEmailVerification = true;
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(mockedToken);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails, null);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendRegistrationVerificationEmailAsync(email, mockedToken, null);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenFromMarketingIsPremium_SendsEmailWithMarketingParameterAndReturnsNull(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
@@ -34,31 +72,35 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IMailService>()
|
||||
.SendRegistrationVerificationEmailAsync(email, Arg.Any<string>())
|
||||
.Returns(Task.CompletedTask);
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(mockedToken);
|
||||
|
||||
var fromMarketing = MarketingInitiativeConstants.Premium;
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails);
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails, fromMarketing);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendRegistrationVerificationEmailAsync(email, mockedToken);
|
||||
.SendRegistrationVerificationEmailAsync(email, mockedToken, fromMarketing);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsExistingUserAndEnableEmailVerificationTrue_ReturnsNull(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(new User());
|
||||
@@ -69,27 +111,33 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(mockedToken);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails);
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails, null);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendRegistrationVerificationEmailAsync(email, mockedToken);
|
||||
.SendRegistrationVerificationEmailAsync(email, mockedToken, null);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsNewUserAndEnableEmailVerificationFalse_ReturnsToken(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.ReturnsNull();
|
||||
@@ -100,13 +148,17 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(mockedToken);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails);
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(mockedToken, result);
|
||||
@@ -122,15 +174,17 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
.DisableUserRegistration = true;
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails));
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails, null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsExistingUserAndEnableEmailVerificationFalse_ThrowsBadRequestException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(new User());
|
||||
@@ -138,8 +192,15 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.EnableEmailVerification = false;
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails));
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails, null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -150,7 +211,7 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run(null, name, receiveMarketingEmails));
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run(null, name, receiveMarketingEmails, null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -160,6 +221,90 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
{
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run("", name, receiveMarketingEmails));
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run("", name, receiveMarketingEmails, null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenBlockedDomain_ThrowsBadRequestException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@blockedcompany.com";
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blockedcompany.com")
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails, null));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenAllowedDomain_Succeeds(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@allowedcompany.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.ReturnsNull();
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.EnableEmailVerification = false;
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("allowedcompany.com")
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(mockedToken);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(mockedToken, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_InvalidEmailFormat_ThrowsBadRequestException(
|
||||
SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = "invalid-email-format";
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.Run(email, name, receiveMarketingEmails, null));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Sso;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.UserFeatures.Sso;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UserSsoOrganizationIdentifierQueryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_UserHasSingleConfirmedOrganization_ReturnsIdentifier(
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId,
|
||||
Organization organization,
|
||||
OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = userId;
|
||||
organizationUser.OrganizationId = organization.Id;
|
||||
organizationUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
organization.Identifier = "test-org-identifier";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns([organizationUser]);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-org-identifier", result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.GetByIdAsync(organization.Id);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_UserHasNoOrganizations_ReturnsNull(
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns(Array.Empty<OrganizationUser>());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_UserHasMultipleConfirmedOrganizations_ReturnsNull(
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId,
|
||||
OrganizationUser organizationUser1,
|
||||
OrganizationUser organizationUser2)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser1.UserId = userId;
|
||||
organizationUser1.Status = OrganizationUserStatusType.Confirmed;
|
||||
organizationUser2.UserId = userId;
|
||||
organizationUser2.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns([organizationUser1, organizationUser2]);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserStatusType.Invited)]
|
||||
[BitAutoData(OrganizationUserStatusType.Accepted)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked)]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_UserHasOnlyInvitedOrganization_ReturnsNull(
|
||||
OrganizationUserStatusType status,
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId,
|
||||
OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = userId;
|
||||
organizationUser.Status = status;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns([organizationUser]);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_UserHasMixedStatusOrganizations_OnlyOneConfirmed_ReturnsIdentifier(
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId,
|
||||
Organization organization,
|
||||
OrganizationUser confirmedOrgUser,
|
||||
OrganizationUser invitedOrgUser,
|
||||
OrganizationUser revokedOrgUser)
|
||||
{
|
||||
// Arrange
|
||||
confirmedOrgUser.UserId = userId;
|
||||
confirmedOrgUser.OrganizationId = organization.Id;
|
||||
confirmedOrgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
invitedOrgUser.UserId = userId;
|
||||
invitedOrgUser.Status = OrganizationUserStatusType.Invited;
|
||||
|
||||
revokedOrgUser.UserId = userId;
|
||||
revokedOrgUser.Status = OrganizationUserStatusType.Revoked;
|
||||
|
||||
organization.Identifier = "mixed-status-org";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns(new[] { confirmedOrgUser, invitedOrgUser, revokedOrgUser });
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("mixed-status-org", result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.GetByIdAsync(organization.Id);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_OrganizationNotFound_ReturnsNull(
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId,
|
||||
OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = userId;
|
||||
organizationUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns([organizationUser]);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organizationUser.OrganizationId)
|
||||
.Returns((Organization)null);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.GetByIdAsync(organizationUser.OrganizationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_OrganizationIdentifierIsNull_ReturnsNull(
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId,
|
||||
Organization organization,
|
||||
OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = userId;
|
||||
organizationUser.OrganizationId = organization.Id;
|
||||
organizationUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
organization.Identifier = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns(new[] { organizationUser });
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.GetByIdAsync(organization.Id);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSsoOrganizationIdentifierAsync_OrganizationIdentifierIsEmpty_ReturnsEmpty(
|
||||
SutProvider<UserSsoOrganizationIdentifierQuery> sutProvider,
|
||||
Guid userId,
|
||||
Organization organization,
|
||||
OrganizationUser organizationUser)
|
||||
{
|
||||
// Arrange
|
||||
organizationUser.UserId = userId;
|
||||
organizationUser.OrganizationId = organization.Id;
|
||||
organizationUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
organization.Identifier = string.Empty;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
.Returns(new[] { organizationUser });
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetSsoOrganizationIdentifierAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(string.Empty, result);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetManyByUserAsync(userId);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.GetByIdAsync(organization.Id);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using System.Globalization;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
|
||||
@@ -356,9 +357,18 @@ public class InvoiceExtensionsTests
|
||||
[Fact]
|
||||
public void FormatForProvider_ComplexScenario_HandlesAllLineTypes()
|
||||
{
|
||||
// Arrange
|
||||
var lineItems = new StripeList<InvoiceLineItem>();
|
||||
lineItems.Data = new List<InvoiceLineItem>
|
||||
// Set culture to en-US to ensure consistent decimal formatting in tests
|
||||
// This ensures tests pass on all machines regardless of system locale
|
||||
var originalCulture = Thread.CurrentThread.CurrentCulture;
|
||||
var originalUICulture = Thread.CurrentThread.CurrentUICulture;
|
||||
try
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
|
||||
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
|
||||
|
||||
// Arrange
|
||||
var lineItems = new StripeList<InvoiceLineItem>();
|
||||
lineItems.Data = new List<InvoiceLineItem>
|
||||
{
|
||||
new InvoiceLineItem
|
||||
{
|
||||
@@ -372,23 +382,29 @@ public class InvoiceExtensionsTests
|
||||
new InvoiceLineItem { Description = "Custom Service", Quantity = 2, Amount = 2000 }
|
||||
};
|
||||
|
||||
var invoice = new Invoice
|
||||
var invoice = new Invoice
|
||||
{
|
||||
Lines = lineItems,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 200 }] // Additional $2.00 tax
|
||||
};
|
||||
var subscription = new Subscription();
|
||||
|
||||
// Act
|
||||
var result = invoice.FormatForProvider(subscription);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(5, result.Count);
|
||||
Assert.Equal("5 × Manage service provider (at $6.00 / month)", result[0]);
|
||||
Assert.Equal("10 × Manage service provider (at $4.00 / month)", result[1]);
|
||||
Assert.Equal("1 × Tax (at $8.00 / month)", result[2]);
|
||||
Assert.Equal("Custom Service", result[3]);
|
||||
Assert.Equal("1 × Tax (at $2.00 / month)", result[4]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lines = lineItems,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 200 }] // Additional $2.00 tax
|
||||
};
|
||||
var subscription = new Subscription();
|
||||
|
||||
// Act
|
||||
var result = invoice.FormatForProvider(subscription);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(5, result.Count);
|
||||
Assert.Equal("5 × Manage service provider (at $6.00 / month)", result[0]);
|
||||
Assert.Equal("10 × Manage service provider (at $4.00 / month)", result[1]);
|
||||
Assert.Equal("1 × Tax (at $8.00 / month)", result[2]);
|
||||
Assert.Equal("Custom Service", result[3]);
|
||||
Assert.Equal("1 × Tax (at $2.00 / month)", result[4]);
|
||||
Thread.CurrentThread.CurrentCulture = originalCulture;
|
||||
Thread.CurrentThread.CurrentUICulture = originalUICulture;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
37
test/Core.Test/Billing/Mocks/MockPlans.cs
Normal file
37
test/Core.Test/Billing/Mocks/MockPlans.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks;
|
||||
|
||||
public class MockPlans
|
||||
{
|
||||
public static List<Plan> Plans =>
|
||||
[
|
||||
new CustomPlan(),
|
||||
new Enterprise2019Plan(false),
|
||||
new Enterprise2019Plan(true),
|
||||
new Enterprise2020Plan(false),
|
||||
new Enterprise2020Plan(true),
|
||||
new Enterprise2023Plan(false),
|
||||
new Enterprise2023Plan(true),
|
||||
new EnterprisePlan(false),
|
||||
new EnterprisePlan(true),
|
||||
new Families2019Plan(),
|
||||
new Families2025Plan(),
|
||||
new FamiliesPlan(),
|
||||
new FreePlan(),
|
||||
new Teams2019Plan(false),
|
||||
new Teams2019Plan(true),
|
||||
new Teams2020Plan(false),
|
||||
new Teams2020Plan(true),
|
||||
new Teams2023Plan(false),
|
||||
new Teams2023Plan(true),
|
||||
new TeamsPlan(false),
|
||||
new TeamsPlan(true),
|
||||
new TeamsStarterPlan(),
|
||||
new TeamsStarterPlan2023()
|
||||
];
|
||||
|
||||
public static Plan Get(PlanType planType) => Plans.SingleOrDefault(p => p.Type == planType)!;
|
||||
}
|
||||
21
test/Core.Test/Billing/Mocks/Plans/CustomPlan.cs
Normal file
21
test/Core.Test/Billing/Mocks/Plans/CustomPlan.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record CustomPlan : Plan
|
||||
{
|
||||
public CustomPlan()
|
||||
{
|
||||
Type = PlanType.Custom;
|
||||
PasswordManager = new CustomPasswordManagerFeatures();
|
||||
}
|
||||
|
||||
private record CustomPasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public CustomPasswordManagerFeatures()
|
||||
{
|
||||
AllowSeatAutoscale = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
test/Core.Test/Billing/Mocks/Plans/Enterprise2019Plan.cs
Normal file
103
test/Core.Test/Billing/Mocks/Plans/Enterprise2019Plan.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Enterprise2019Plan : Plan
|
||||
{
|
||||
public Enterprise2019Plan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.EnterpriseAnnually2019 : PlanType.EnterpriseMonthly2019;
|
||||
ProductTier = ProductTierType.Enterprise;
|
||||
Name = isAnnual ? "Enterprise (Annually) 2019" : "Enterprise (Monthly) 2019";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameEnterprise";
|
||||
DescriptionLocalizationKey = "planDescEnterprise";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasPolicies = true;
|
||||
HasSelfHost = true;
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
HasSso = true;
|
||||
HasOrganizationDomains = true;
|
||||
HasKeyConnector = true;
|
||||
HasScim = true;
|
||||
HasResetPassword = true;
|
||||
UsersGetPremium = true;
|
||||
HasCustomPermissions = true;
|
||||
|
||||
UpgradeSortOrder = 4;
|
||||
DisplaySortOrder = 4;
|
||||
LegacyYear = 2020;
|
||||
|
||||
SecretsManager = new Enterprise2019SecretsManagerFeatures(isAnnual);
|
||||
PasswordManager = new Enterprise2019PasswordManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record Enterprise2019SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public Enterprise2019SecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 200;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||
SeatPrice = 144;
|
||||
AdditionalPricePerServiceAccount = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||
SeatPrice = 13;
|
||||
AdditionalPricePerServiceAccount = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Enterprise2019PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Enterprise2019PasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BaseStorageGb = 1;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "enterprise-org-seat-annually";
|
||||
SeatPrice = 36;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "enterprise-org-seat-monthly";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
SeatPrice = 4M;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
test/Core.Test/Billing/Mocks/Plans/Enterprise2020Plan.cs
Normal file
103
test/Core.Test/Billing/Mocks/Plans/Enterprise2020Plan.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Enterprise2020Plan : Plan
|
||||
{
|
||||
public Enterprise2020Plan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.EnterpriseAnnually2020 : PlanType.EnterpriseMonthly2020;
|
||||
ProductTier = ProductTierType.Enterprise;
|
||||
Name = isAnnual ? "Enterprise (Annually) 2020" : "Enterprise (Monthly) 2020";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameEnterprise";
|
||||
DescriptionLocalizationKey = "planDescEnterprise";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasPolicies = true;
|
||||
HasSelfHost = true;
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
HasSso = true;
|
||||
HasOrganizationDomains = true;
|
||||
HasKeyConnector = true;
|
||||
HasScim = true;
|
||||
HasResetPassword = true;
|
||||
UsersGetPremium = true;
|
||||
HasCustomPermissions = true;
|
||||
|
||||
UpgradeSortOrder = 4;
|
||||
DisplaySortOrder = 4;
|
||||
LegacyYear = 2023;
|
||||
|
||||
PasswordManager = new Enterprise2020PasswordManagerFeatures(isAnnual);
|
||||
SecretsManager = new Enterprise2020SecretsManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record Enterprise2020SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public Enterprise2020SecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 200;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||
SeatPrice = 144;
|
||||
AdditionalPricePerServiceAccount = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||
SeatPrice = 13;
|
||||
AdditionalPricePerServiceAccount = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Enterprise2020PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Enterprise2020PasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BaseStorageGb = 1;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "2020-enterprise-org-seat-annually";
|
||||
SeatPrice = 60;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "2020-enterprise-seat-monthly";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
SeatPrice = 6;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
test/Core.Test/Billing/Mocks/Plans/EnterprisePlan.cs
Normal file
106
test/Core.Test/Billing/Mocks/Plans/EnterprisePlan.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record EnterprisePlan : Plan
|
||||
{
|
||||
public EnterprisePlan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.EnterpriseAnnually : PlanType.EnterpriseMonthly;
|
||||
ProductTier = ProductTierType.Enterprise;
|
||||
Name = isAnnual ? "Enterprise (Annually)" : "Enterprise (Monthly)";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameEnterprise";
|
||||
DescriptionLocalizationKey = "planDescEnterprise";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasPolicies = true;
|
||||
HasSelfHost = true;
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
HasSso = true;
|
||||
HasOrganizationDomains = true;
|
||||
HasKeyConnector = true;
|
||||
HasScim = true;
|
||||
HasResetPassword = true;
|
||||
UsersGetPremium = true;
|
||||
HasCustomPermissions = true;
|
||||
|
||||
UpgradeSortOrder = 4;
|
||||
DisplaySortOrder = 4;
|
||||
|
||||
PasswordManager = new EnterprisePasswordManagerFeatures(isAnnual);
|
||||
SecretsManager = new EnterpriseSecretsManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record EnterpriseSecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public EnterpriseSecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 50;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-2024-annually";
|
||||
SeatPrice = 144;
|
||||
AdditionalPricePerServiceAccount = 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-2024-monthly";
|
||||
SeatPrice = 13;
|
||||
AdditionalPricePerServiceAccount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record EnterprisePasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public EnterprisePasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BaseStorageGb = 1;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "2023-enterprise-org-seat-annually";
|
||||
StripeProviderPortalSeatPlanId = "password-manager-provider-portal-enterprise-annually-2024";
|
||||
SeatPrice = 72;
|
||||
ProviderPortalSeatPrice = 72;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "2023-enterprise-seat-monthly";
|
||||
StripeProviderPortalSeatPlanId = "password-manager-provider-portal-enterprise-monthly-2024";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
SeatPrice = 7;
|
||||
ProviderPortalSeatPrice = 6;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
test/Core.Test/Billing/Mocks/Plans/EnterprisePlan2023.cs
Normal file
104
test/Core.Test/Billing/Mocks/Plans/EnterprisePlan2023.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Enterprise2023Plan : Plan
|
||||
{
|
||||
public Enterprise2023Plan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.EnterpriseAnnually2023 : PlanType.EnterpriseMonthly2023;
|
||||
ProductTier = ProductTierType.Enterprise;
|
||||
Name = isAnnual ? "Enterprise (Annually)" : "Enterprise (Monthly)";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameEnterprise";
|
||||
DescriptionLocalizationKey = "planDescEnterprise";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasPolicies = true;
|
||||
HasSelfHost = true;
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
HasSso = true;
|
||||
HasOrganizationDomains = true;
|
||||
HasKeyConnector = true;
|
||||
HasScim = true;
|
||||
HasResetPassword = true;
|
||||
UsersGetPremium = true;
|
||||
HasCustomPermissions = true;
|
||||
|
||||
UpgradeSortOrder = 4;
|
||||
DisplaySortOrder = 4;
|
||||
|
||||
LegacyYear = 2024;
|
||||
|
||||
PasswordManager = new Enterprise2023PasswordManagerFeatures(isAnnual);
|
||||
SecretsManager = new Enterprise2023SecretsManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record Enterprise2023SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public Enterprise2023SecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 200;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||
SeatPrice = 144;
|
||||
AdditionalPricePerServiceAccount = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||
SeatPrice = 13;
|
||||
AdditionalPricePerServiceAccount = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Enterprise2023PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Enterprise2023PasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BaseStorageGb = 1;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "2023-enterprise-org-seat-annually";
|
||||
SeatPrice = 72;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "2023-enterprise-seat-monthly";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
SeatPrice = 7;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
test/Core.Test/Billing/Mocks/Plans/Families2019Plan.cs
Normal file
50
test/Core.Test/Billing/Mocks/Plans/Families2019Plan.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Families2019Plan : Plan
|
||||
{
|
||||
public Families2019Plan()
|
||||
{
|
||||
Type = PlanType.FamiliesAnnually2019;
|
||||
ProductTier = ProductTierType.Families;
|
||||
Name = "Families 2019";
|
||||
IsAnnual = true;
|
||||
NameLocalizationKey = "planNameFamilies";
|
||||
DescriptionLocalizationKey = "planDescFamilies";
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasSelfHost = true;
|
||||
HasTotp = true;
|
||||
|
||||
UpgradeSortOrder = 1;
|
||||
DisplaySortOrder = 1;
|
||||
LegacyYear = 2020;
|
||||
|
||||
PasswordManager = new Families2019PasswordManagerFeatures();
|
||||
}
|
||||
|
||||
private record Families2019PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Families2019PasswordManagerFeatures()
|
||||
{
|
||||
BaseSeats = 5;
|
||||
BaseStorageGb = 1;
|
||||
MaxSeats = 5;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasPremiumAccessOption = true;
|
||||
|
||||
StripePlanId = "personal-org-annually";
|
||||
StripeStoragePlanId = "personal-storage-gb-annually";
|
||||
StripePremiumAccessPlanId = "personal-org-premium-access-annually";
|
||||
BasePrice = 12;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
PremiumAccessOptionPrice = 40;
|
||||
|
||||
AllowSeatAutoscale = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
test/Core.Test/Billing/Mocks/Plans/Families2025Plan.cs
Normal file
47
test/Core.Test/Billing/Mocks/Plans/Families2025Plan.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Families2025Plan : Plan
|
||||
{
|
||||
public Families2025Plan()
|
||||
{
|
||||
Type = PlanType.FamiliesAnnually2025;
|
||||
ProductTier = ProductTierType.Families;
|
||||
Name = "Families 2025";
|
||||
IsAnnual = true;
|
||||
NameLocalizationKey = "planNameFamilies";
|
||||
DescriptionLocalizationKey = "planDescFamilies";
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasSelfHost = true;
|
||||
HasTotp = true;
|
||||
UsersGetPremium = true;
|
||||
|
||||
UpgradeSortOrder = 1;
|
||||
DisplaySortOrder = 1;
|
||||
|
||||
PasswordManager = new Families2025PasswordManagerFeatures();
|
||||
}
|
||||
|
||||
private record Families2025PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Families2025PasswordManagerFeatures()
|
||||
{
|
||||
BaseSeats = 6;
|
||||
BaseStorageGb = 1;
|
||||
MaxSeats = 6;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
|
||||
StripePlanId = "2020-families-org-annually";
|
||||
StripeStoragePlanId = "personal-storage-gb-annually";
|
||||
BasePrice = 40;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
|
||||
AllowSeatAutoscale = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
test/Core.Test/Billing/Mocks/Plans/FamiliesPlan.cs
Normal file
47
test/Core.Test/Billing/Mocks/Plans/FamiliesPlan.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record FamiliesPlan : Plan
|
||||
{
|
||||
public FamiliesPlan()
|
||||
{
|
||||
Type = PlanType.FamiliesAnnually;
|
||||
ProductTier = ProductTierType.Families;
|
||||
Name = "Families";
|
||||
IsAnnual = true;
|
||||
NameLocalizationKey = "planNameFamilies";
|
||||
DescriptionLocalizationKey = "planDescFamilies";
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasSelfHost = true;
|
||||
HasTotp = true;
|
||||
UsersGetPremium = true;
|
||||
|
||||
UpgradeSortOrder = 1;
|
||||
DisplaySortOrder = 1;
|
||||
|
||||
PasswordManager = new FamiliesPasswordManagerFeatures();
|
||||
}
|
||||
|
||||
private record FamiliesPasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public FamiliesPasswordManagerFeatures()
|
||||
{
|
||||
BaseSeats = 6;
|
||||
BaseStorageGb = 1;
|
||||
MaxSeats = 6;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
|
||||
StripePlanId = "2020-families-org-annually";
|
||||
StripeStoragePlanId = "personal-storage-gb-annually";
|
||||
BasePrice = 40;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
|
||||
AllowSeatAutoscale = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
test/Core.Test/Billing/Mocks/Plans/FreePlan.cs
Normal file
48
test/Core.Test/Billing/Mocks/Plans/FreePlan.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record FreePlan : Plan
|
||||
{
|
||||
public FreePlan()
|
||||
{
|
||||
Type = PlanType.Free;
|
||||
ProductTier = ProductTierType.Free;
|
||||
Name = "Free";
|
||||
NameLocalizationKey = "planNameFree";
|
||||
DescriptionLocalizationKey = "planDescFree";
|
||||
|
||||
UpgradeSortOrder = -1; // Always the lowest plan, cannot be upgraded to
|
||||
DisplaySortOrder = -1;
|
||||
|
||||
PasswordManager = new FreePasswordManagerFeatures();
|
||||
SecretsManager = new FreeSecretsManagerFeatures();
|
||||
}
|
||||
|
||||
private record FreeSecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public FreeSecretsManagerFeatures()
|
||||
{
|
||||
BaseSeats = 2;
|
||||
BaseServiceAccount = 3;
|
||||
MaxProjects = 3;
|
||||
MaxSeats = 2;
|
||||
MaxServiceAccounts = 3;
|
||||
|
||||
AllowSeatAutoscale = false;
|
||||
}
|
||||
}
|
||||
|
||||
private record FreePasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public FreePasswordManagerFeatures()
|
||||
{
|
||||
BaseSeats = 2;
|
||||
MaxCollections = 2;
|
||||
MaxSeats = 2;
|
||||
|
||||
AllowSeatAutoscale = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
test/Core.Test/Billing/Mocks/Plans/Teams2019Plan.cs
Normal file
99
test/Core.Test/Billing/Mocks/Plans/Teams2019Plan.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Teams2019Plan : Plan
|
||||
{
|
||||
public Teams2019Plan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.TeamsAnnually2019 : PlanType.TeamsMonthly2019;
|
||||
ProductTier = ProductTierType.Teams;
|
||||
Name = isAnnual ? "Teams (Annually) 2019" : "Teams (Monthly) 2019";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameTeams";
|
||||
DescriptionLocalizationKey = "planDescTeams";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
UsersGetPremium = true;
|
||||
|
||||
UpgradeSortOrder = 3;
|
||||
DisplaySortOrder = 3;
|
||||
LegacyYear = 2020;
|
||||
|
||||
SecretsManager = new Teams2019SecretsManagerFeatures(isAnnual);
|
||||
PasswordManager = new Teams2019PasswordManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record Teams2019SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public Teams2019SecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 50;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||
SeatPrice = 72;
|
||||
AdditionalPricePerServiceAccount = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||
SeatPrice = 7;
|
||||
AdditionalPricePerServiceAccount = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Teams2019PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Teams2019PasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 5;
|
||||
BaseStorageGb = 1;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripePlanId = "teams-org-annually";
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "teams-org-seat-annually";
|
||||
SeatPrice = 24;
|
||||
BasePrice = 60;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripePlanId = "teams-org-monthly";
|
||||
StripeSeatPlanId = "teams-org-seat-monthly";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
BasePrice = 8;
|
||||
SeatPrice = 2.5M;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
test/Core.Test/Billing/Mocks/Plans/Teams2020Plan.cs
Normal file
96
test/Core.Test/Billing/Mocks/Plans/Teams2020Plan.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Teams2020Plan : Plan
|
||||
{
|
||||
public Teams2020Plan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.TeamsAnnually2020 : PlanType.TeamsMonthly2020;
|
||||
ProductTier = ProductTierType.Teams;
|
||||
Name = isAnnual ? "Teams (Annually) 2020" : "Teams (Monthly) 2020";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameTeams";
|
||||
DescriptionLocalizationKey = "planDescTeams";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
UsersGetPremium = true;
|
||||
|
||||
UpgradeSortOrder = 3;
|
||||
DisplaySortOrder = 3;
|
||||
LegacyYear = 2023;
|
||||
|
||||
PasswordManager = new Teams2020PasswordManagerFeatures(isAnnual);
|
||||
SecretsManager = new Teams2020SecretsManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record Teams2020SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public Teams2020SecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 50;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||
SeatPrice = 72;
|
||||
AdditionalPricePerServiceAccount = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||
SeatPrice = 7;
|
||||
AdditionalPricePerServiceAccount = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Teams2020PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Teams2020PasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BaseStorageGb = 1;
|
||||
BasePrice = 0;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "2020-teams-org-seat-annually";
|
||||
SeatPrice = 36;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "2020-teams-org-seat-monthly";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
SeatPrice = 4;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
test/Core.Test/Billing/Mocks/Plans/TeamsPlan.cs
Normal file
98
test/Core.Test/Billing/Mocks/Plans/TeamsPlan.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record TeamsPlan : Plan
|
||||
{
|
||||
public TeamsPlan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.TeamsAnnually : PlanType.TeamsMonthly;
|
||||
ProductTier = ProductTierType.Teams;
|
||||
Name = isAnnual ? "Teams (Annually)" : "Teams (Monthly)";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameTeams";
|
||||
DescriptionLocalizationKey = "planDescTeams";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
UsersGetPremium = true;
|
||||
HasScim = true;
|
||||
|
||||
UpgradeSortOrder = 3;
|
||||
DisplaySortOrder = 3;
|
||||
|
||||
PasswordManager = new TeamsPasswordManagerFeatures(isAnnual);
|
||||
SecretsManager = new TeamsSecretsManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record TeamsSecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public TeamsSecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 20;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-2024-annually";
|
||||
SeatPrice = 72;
|
||||
AdditionalPricePerServiceAccount = 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-2024-monthly";
|
||||
SeatPrice = 7;
|
||||
AdditionalPricePerServiceAccount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record TeamsPasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public TeamsPasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BaseStorageGb = 1;
|
||||
BasePrice = 0;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "2023-teams-org-seat-annually";
|
||||
SeatPrice = 48;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "2023-teams-org-seat-monthly";
|
||||
StripeProviderPortalSeatPlanId = "password-manager-provider-portal-teams-monthly-2024";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
SeatPrice = 5;
|
||||
ProviderPortalSeatPrice = 4;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
test/Core.Test/Billing/Mocks/Plans/TeamsPlan2023.cs
Normal file
97
test/Core.Test/Billing/Mocks/Plans/TeamsPlan2023.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record Teams2023Plan : Plan
|
||||
{
|
||||
public Teams2023Plan(bool isAnnual)
|
||||
{
|
||||
Type = isAnnual ? PlanType.TeamsAnnually2023 : PlanType.TeamsMonthly2023;
|
||||
ProductTier = ProductTierType.Teams;
|
||||
Name = isAnnual ? "Teams (Annually)" : "Teams (Monthly)";
|
||||
IsAnnual = isAnnual;
|
||||
NameLocalizationKey = "planNameTeams";
|
||||
DescriptionLocalizationKey = "planDescTeams";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
UsersGetPremium = true;
|
||||
|
||||
UpgradeSortOrder = 3;
|
||||
DisplaySortOrder = 3;
|
||||
|
||||
LegacyYear = 2024;
|
||||
|
||||
PasswordManager = new Teams2023PasswordManagerFeatures(isAnnual);
|
||||
SecretsManager = new Teams2023SecretsManagerFeatures(isAnnual);
|
||||
}
|
||||
|
||||
private record Teams2023SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public Teams2023SecretsManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 50;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-annually";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||
SeatPrice = 72;
|
||||
AdditionalPricePerServiceAccount = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||
SeatPrice = 7;
|
||||
AdditionalPricePerServiceAccount = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Teams2023PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public Teams2023PasswordManagerFeatures(bool isAnnual)
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BaseStorageGb = 1;
|
||||
BasePrice = 0;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
HasAdditionalSeatsOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
|
||||
if (isAnnual)
|
||||
{
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeSeatPlanId = "2023-teams-org-seat-annually";
|
||||
SeatPrice = 48;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
StripeSeatPlanId = "2023-teams-org-seat-monthly";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
SeatPrice = 5;
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan.cs
Normal file
74
test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record TeamsStarterPlan : Plan
|
||||
{
|
||||
public TeamsStarterPlan()
|
||||
{
|
||||
Type = PlanType.TeamsStarter;
|
||||
ProductTier = ProductTierType.TeamsStarter;
|
||||
Name = "Teams (Starter)";
|
||||
NameLocalizationKey = "planNameTeamsStarter";
|
||||
DescriptionLocalizationKey = "planDescTeams";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
UsersGetPremium = true;
|
||||
|
||||
UpgradeSortOrder = 2;
|
||||
DisplaySortOrder = 2;
|
||||
|
||||
PasswordManager = new TeamsStarterPasswordManagerFeatures();
|
||||
SecretsManager = new TeamsStarterSecretsManagerFeatures();
|
||||
|
||||
LegacyYear = 2024;
|
||||
}
|
||||
|
||||
private record TeamsStarterSecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public TeamsStarterSecretsManagerFeatures()
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 20;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-2024-monthly";
|
||||
SeatPrice = 7;
|
||||
AdditionalPricePerServiceAccount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private record TeamsStarterPasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public TeamsStarterPasswordManagerFeatures()
|
||||
{
|
||||
BaseSeats = 10;
|
||||
BaseStorageGb = 1;
|
||||
BasePrice = 20;
|
||||
|
||||
MaxSeats = 10;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
|
||||
StripePlanId = "teams-org-starter";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan2023.cs
Normal file
73
test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan2023.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Test.Billing.Mocks.Plans;
|
||||
|
||||
public record TeamsStarterPlan2023 : Plan
|
||||
{
|
||||
public TeamsStarterPlan2023()
|
||||
{
|
||||
Type = PlanType.TeamsStarter2023;
|
||||
ProductTier = ProductTierType.TeamsStarter;
|
||||
Name = "Teams (Starter)";
|
||||
NameLocalizationKey = "planNameTeamsStarter";
|
||||
DescriptionLocalizationKey = "planDescTeams";
|
||||
CanBeUsedByBusiness = true;
|
||||
|
||||
TrialPeriodDays = 7;
|
||||
|
||||
HasGroups = true;
|
||||
HasDirectory = true;
|
||||
HasEvents = true;
|
||||
HasTotp = true;
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
UsersGetPremium = true;
|
||||
|
||||
UpgradeSortOrder = 2;
|
||||
DisplaySortOrder = 2;
|
||||
|
||||
PasswordManager = new TeamsStarter2023PasswordManagerFeatures();
|
||||
SecretsManager = new TeamsStarter2023SecretsManagerFeatures();
|
||||
LegacyYear = 2024;
|
||||
}
|
||||
|
||||
private record TeamsStarter2023SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||
{
|
||||
public TeamsStarter2023SecretsManagerFeatures()
|
||||
{
|
||||
BaseSeats = 0;
|
||||
BasePrice = 0;
|
||||
BaseServiceAccount = 50;
|
||||
|
||||
HasAdditionalSeatsOption = true;
|
||||
HasAdditionalServiceAccountOption = true;
|
||||
|
||||
AllowSeatAutoscale = true;
|
||||
AllowServiceAccountsAutoscale = true;
|
||||
|
||||
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||
SeatPrice = 7;
|
||||
AdditionalPricePerServiceAccount = 0.5M;
|
||||
}
|
||||
}
|
||||
|
||||
private record TeamsStarter2023PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||
{
|
||||
public TeamsStarter2023PasswordManagerFeatures()
|
||||
{
|
||||
BaseSeats = 10;
|
||||
BaseStorageGb = 1;
|
||||
BasePrice = 20;
|
||||
|
||||
MaxSeats = 10;
|
||||
|
||||
HasAdditionalStorageOption = true;
|
||||
|
||||
StripePlanId = "teams-org-starter";
|
||||
StripeStoragePlanId = "storage-gb-monthly";
|
||||
AdditionalStoragePricePerGb = 0.5M;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,8 @@ If you believe you need to change the version for a valid reason, please discuss
|
||||
LimitCollectionDeletion = true,
|
||||
AllowAdminAccessToAllCollectionItems = true,
|
||||
UseOrganizationDomains = true,
|
||||
UseAdminSponsoredFamilies = false
|
||||
UseAdminSponsoredFamilies = false,
|
||||
UsePhishingBlocker = false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Billing.Organizations.Commands;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Mocks.Plans;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Stripe;
|
||||
|
||||
@@ -88,7 +88,7 @@ public class UpdateOrganizationLicenseCommandTests
|
||||
"Hash", "Signature", "SignatureBytes", "InstallationId", "Expires",
|
||||
"ExpirationWithoutGracePeriod", "Token", "LimitCollectionCreationDeletion",
|
||||
"LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems",
|
||||
"UseOrganizationDomains", "UseAdminSponsoredFamilies", "UseAutomaticUserConfirmation") &&
|
||||
"UseOrganizationDomains", "UseAdminSponsoredFamilies", "UseAutomaticUserConfirmation", "UsePhishingBlocker") &&
|
||||
// Same property but different name, use explicit mapping
|
||||
org.ExpirationDate == license.Expires));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -163,7 +163,7 @@ public class GetOrganizationMetadataQueryTests
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var result = await sutProvider.Sut.Run(organization);
|
||||
|
||||
@@ -216,7 +216,7 @@ public class GetOrganizationMetadataQueryTests
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var result = await sutProvider.Sut.Run(organization);
|
||||
|
||||
@@ -282,7 +282,7 @@ public class GetOrganizationMetadataQueryTests
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var result = await sutProvider.Sut.Run(organization);
|
||||
|
||||
@@ -349,7 +349,7 @@ public class GetOrganizationMetadataQueryTests
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var result = await sutProvider.Sut.Run(organization);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using Bit.Core.Billing;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -34,7 +33,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -70,7 +68,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -109,7 +106,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -144,7 +140,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -179,7 +174,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -217,7 +211,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -258,7 +251,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -297,7 +289,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -339,33 +330,12 @@ public class PricingClientTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPlan_WhenPricingServiceDisabled_ReturnsStaticStorePlan(
|
||||
SutProvider<PricingClient> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<GlobalSettings>().SelfHosted = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.UsePricingService)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetPlan(PlanType.FamiliesAnnually);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(PlanType.FamiliesAnnually, result.Type);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPlan_WhenLookupKeyNotFound_ReturnsNull(
|
||||
SutProvider<PricingClient> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.UsePricingService)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<GlobalSettings>().SelfHosted = false;
|
||||
|
||||
// Act - Using PlanType that doesn't have a lookup key mapping
|
||||
var result = await sutProvider.Sut.GetPlan(unchecked((PlanType)999));
|
||||
@@ -384,7 +354,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -413,7 +382,6 @@ public class PricingClientTests
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true);
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
@@ -450,26 +418,6 @@ public class PricingClientTests
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ListPlans_WhenPricingServiceDisabled_ReturnsStaticStorePlans(
|
||||
SutProvider<PricingClient> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<GlobalSettings>().SelfHosted = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.UsePricingService)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ListPlans();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result);
|
||||
Assert.Equal(StaticStore.Plans.Count(), result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListPlans_WhenPricingServiceReturnsError_ThrowsBillingException()
|
||||
{
|
||||
@@ -479,7 +427,6 @@ public class PricingClientTests
|
||||
.Respond(HttpStatusCode.InternalServerError);
|
||||
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true);
|
||||
|
||||
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.Sales;
|
||||
@@ -10,7 +11,7 @@ using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -31,10 +32,10 @@ public class OrganizationBillingServiceTests
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(StaticStore.Plans.ToList());
|
||||
sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(MockPlans.Plans.ToList());
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||
var organizationSeatCount = new OrganizationSeatCounts { Users = 1, Sponsored = 0 };
|
||||
@@ -97,10 +98,10 @@ public class OrganizationBillingServiceTests
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(StaticStore.Plans.ToList());
|
||||
sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(MockPlans.Plans.ToList());
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
||||
@@ -134,7 +135,7 @@ public class OrganizationBillingServiceTests
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = StaticStore.GetPlan(PlanType.TeamsAnnually);
|
||||
var plan = MockPlans.Get(PlanType.TeamsAnnually);
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
organization.GatewayCustomerId = "cus_test123";
|
||||
organization.GatewaySubscriptionId = null;
|
||||
@@ -210,7 +211,7 @@ public class OrganizationBillingServiceTests
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = StaticStore.GetPlan(PlanType.TeamsAnnually);
|
||||
var plan = MockPlans.Get(PlanType.TeamsAnnually);
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
organization.GatewayCustomerId = "cus_test123";
|
||||
organization.GatewaySubscriptionId = null;
|
||||
@@ -284,7 +285,7 @@ public class OrganizationBillingServiceTests
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = StaticStore.GetPlan(PlanType.TeamsAnnually);
|
||||
var plan = MockPlans.Get(PlanType.TeamsAnnually);
|
||||
organization.PlanType = PlanType.TeamsAnnually;
|
||||
organization.GatewayCustomerId = "cus_test123";
|
||||
organization.GatewaySubscriptionId = null;
|
||||
@@ -353,4 +354,97 @@ public class OrganizationBillingServiceTests
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_UpdatesStripeCustomer(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
organization.Name = "Short name";
|
||||
|
||||
CustomerUpdateOptions capturedOptions = null;
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.CustomerUpdateAsync(
|
||||
Arg.Is<string>(id => id == organization.GatewayCustomerId),
|
||||
Arg.Do<CustomerUpdateOptions>(options => capturedOptions = options))
|
||||
.Returns(new Customer());
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateOrganizationNameAndEmail(organization);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IStripeAdapter>()
|
||||
.Received(1)
|
||||
.CustomerUpdateAsync(
|
||||
organization.GatewayCustomerId,
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
|
||||
Assert.NotNull(capturedOptions);
|
||||
Assert.Equal(organization.BillingEmail, capturedOptions.Email);
|
||||
Assert.Equal(organization.DisplayName(), capturedOptions.Description);
|
||||
Assert.NotNull(capturedOptions.InvoiceSettings);
|
||||
Assert.NotNull(capturedOptions.InvoiceSettings.CustomFields);
|
||||
Assert.Single(capturedOptions.InvoiceSettings.CustomFields);
|
||||
|
||||
var customField = capturedOptions.InvoiceSettings.CustomFields.First();
|
||||
Assert.Equal(organization.SubscriberType(), customField.Name);
|
||||
Assert.Equal(organization.DisplayName(), customField.Value);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenNameIsLong_TruncatesTo30Characters(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Name = "This is a very long organization name that exceeds thirty characters";
|
||||
|
||||
CustomerUpdateOptions capturedOptions = null;
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.CustomerUpdateAsync(
|
||||
Arg.Is<string>(id => id == organization.GatewayCustomerId),
|
||||
Arg.Do<CustomerUpdateOptions>(options => capturedOptions = options))
|
||||
.Returns(new Customer());
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateOrganizationNameAndEmail(organization);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IStripeAdapter>()
|
||||
.Received(1)
|
||||
.CustomerUpdateAsync(
|
||||
organization.GatewayCustomerId,
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
|
||||
Assert.NotNull(capturedOptions);
|
||||
Assert.NotNull(capturedOptions.InvoiceSettings);
|
||||
Assert.NotNull(capturedOptions.InvoiceSettings.CustomFields);
|
||||
|
||||
var customField = capturedOptions.InvoiceSettings.CustomFields.First();
|
||||
Assert.Equal(30, customField.Value.Length);
|
||||
|
||||
var expectedCustomFieldDisplayName = "This is a very long organizati";
|
||||
Assert.Equal(expectedCustomFieldDisplayName, customField.Value);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenGatewayCustomerIdIsNull_ThrowsBillingException(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.GatewayCustomerId = null;
|
||||
organization.Name = "Test Organization";
|
||||
organization.BillingEmail = "billing@example.com";
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BillingException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationNameAndEmail(organization));
|
||||
|
||||
Assert.Contains("Cannot update an organization in Stripe without a GatewayCustomerId.", exception.Response);
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.CustomerUpdateAsync(Arg.Any<string>(), Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,157 +328,6 @@ public class SubscriberServiceTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetPaymentMethod
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentMethod_NullSubscriber_ThrowsArgumentNullException(
|
||||
SutProvider<SubscriberService> sutProvider) =>
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => sutProvider.Sut.GetPaymentSource(null));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentMethod_WithNegativeStripeAccountBalance_ReturnsCorrectAccountCreditAmount(Organization organization,
|
||||
SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
// Stripe reports balance in cents as a negative number for credit
|
||||
const int stripeAccountBalance = -593; // $5.93 credit (negative cents)
|
||||
const decimal creditAmount = 5.93M; // Same value in dollars
|
||||
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
Balance = stripeAccountBalance,
|
||||
Subscriptions = new StripeList<Subscription>()
|
||||
{
|
||||
Data =
|
||||
[new Subscription { Id = organization.GatewaySubscriptionId, Status = "active" }]
|
||||
},
|
||||
InvoiceSettings = new CustomerInvoiceSettings
|
||||
{
|
||||
DefaultPaymentMethod = new PaymentMethod
|
||||
{
|
||||
Type = StripeConstants.PaymentMethodTypes.USBankAccount,
|
||||
UsBankAccount = new PaymentMethodUsBankAccount { BankName = "Chase", Last4 = "9999" }
|
||||
}
|
||||
}
|
||||
};
|
||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerGetOptions>(options => options.Expand.Contains("default_source") &&
|
||||
options.Expand.Contains("invoice_settings.default_payment_method")
|
||||
&& options.Expand.Contains("subscriptions")
|
||||
&& options.Expand.Contains("tax_ids")))
|
||||
.Returns(customer);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetPaymentMethod(organization);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(creditAmount, result.AccountCredit);
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CustomerGetAsync(
|
||||
organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerGetOptions>(options =>
|
||||
options.Expand.Contains("default_source") &&
|
||||
options.Expand.Contains("invoice_settings.default_payment_method") &&
|
||||
options.Expand.Contains("subscriptions") &&
|
||||
options.Expand.Contains("tax_ids")));
|
||||
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentMethod_WithZeroStripeAccountBalance_ReturnsCorrectAccountCreditAmount(
|
||||
Organization organization, SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
const int stripeAccountBalance = 0;
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
Balance = stripeAccountBalance,
|
||||
Subscriptions = new StripeList<Subscription>()
|
||||
{
|
||||
Data =
|
||||
[new Subscription { Id = organization.GatewaySubscriptionId, Status = "active" }]
|
||||
},
|
||||
InvoiceSettings = new CustomerInvoiceSettings
|
||||
{
|
||||
DefaultPaymentMethod = new PaymentMethod
|
||||
{
|
||||
Type = StripeConstants.PaymentMethodTypes.USBankAccount,
|
||||
UsBankAccount = new PaymentMethodUsBankAccount { BankName = "Chase", Last4 = "9999" }
|
||||
}
|
||||
}
|
||||
};
|
||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerGetOptions>(options => options.Expand.Contains("default_source") &&
|
||||
options.Expand.Contains("invoice_settings.default_payment_method")
|
||||
&& options.Expand.Contains("subscriptions")
|
||||
&& options.Expand.Contains("tax_ids")))
|
||||
.Returns(customer);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetPaymentMethod(organization);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(0, result.AccountCredit);
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CustomerGetAsync(
|
||||
organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerGetOptions>(options =>
|
||||
options.Expand.Contains("default_source") &&
|
||||
options.Expand.Contains("invoice_settings.default_payment_method") &&
|
||||
options.Expand.Contains("subscriptions") &&
|
||||
options.Expand.Contains("tax_ids")));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPaymentMethod_WithPositiveStripeAccountBalance_ReturnsCorrectAccountCreditAmount(
|
||||
Organization organization, SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
const int stripeAccountBalance = 593; // $5.93 charge balance
|
||||
const decimal accountBalance = -5.93M; // account balance
|
||||
var customer = new Customer
|
||||
{
|
||||
Balance = stripeAccountBalance,
|
||||
Subscriptions = new StripeList<Subscription>()
|
||||
{
|
||||
Data =
|
||||
[new Subscription { Id = organization.GatewaySubscriptionId, Status = "active" }]
|
||||
},
|
||||
InvoiceSettings = new CustomerInvoiceSettings
|
||||
{
|
||||
DefaultPaymentMethod = new PaymentMethod
|
||||
{
|
||||
Type = StripeConstants.PaymentMethodTypes.USBankAccount,
|
||||
UsBankAccount = new PaymentMethodUsBankAccount { BankName = "Chase", Last4 = "9999" }
|
||||
}
|
||||
}
|
||||
};
|
||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerGetOptions>(options => options.Expand.Contains("default_source") &&
|
||||
options.Expand.Contains("invoice_settings.default_payment_method")
|
||||
&& options.Expand.Contains("subscriptions")
|
||||
&& options.Expand.Contains("tax_ids")))
|
||||
.Returns(customer);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetPaymentMethod(organization);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(accountBalance, result.AccountCredit);
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CustomerGetAsync(
|
||||
organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerGetOptions>(options =>
|
||||
options.Expand.Contains("default_source") &&
|
||||
options.Expand.Contains("invoice_settings.default_payment_method") &&
|
||||
options.Expand.Contains("subscriptions") &&
|
||||
options.Expand.Contains("tax_ids")));
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GetPaymentSource
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -889,65 +738,6 @@ public class SubscriberServiceTests
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GetTaxInformation
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetTaxInformation_NullSubscriber_ThrowsArgumentNullException(
|
||||
SutProvider<SubscriberService> sutProvider) =>
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => sutProvider.Sut.GetTaxInformation(null));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetTaxInformation_NullAddress_ReturnsNull(
|
||||
Organization organization,
|
||||
SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<CustomerGetOptions>())
|
||||
.Returns(new Customer());
|
||||
|
||||
var taxInformation = await sutProvider.Sut.GetTaxInformation(organization);
|
||||
|
||||
Assert.Null(taxInformation);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetTaxInformation_Success(
|
||||
Organization organization,
|
||||
SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
var address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345",
|
||||
Line1 = "123 Example St.",
|
||||
Line2 = "Unit 1",
|
||||
City = "Example Town",
|
||||
State = "NY"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<CustomerGetOptions>())
|
||||
.Returns(new Customer
|
||||
{
|
||||
Address = address,
|
||||
TaxIds = new StripeList<TaxId>
|
||||
{
|
||||
Data = [new TaxId { Value = "tax_id" }]
|
||||
}
|
||||
});
|
||||
|
||||
var taxInformation = await sutProvider.Sut.GetTaxInformation(organization);
|
||||
|
||||
Assert.NotNull(taxInformation);
|
||||
Assert.Equal(address.Country, taxInformation.Country);
|
||||
Assert.Equal(address.PostalCode, taxInformation.PostalCode);
|
||||
Assert.Equal("tax_id", taxInformation.TaxId);
|
||||
Assert.Equal(address.Line1, taxInformation.Line1);
|
||||
Assert.Equal(address.Line2, taxInformation.Line2);
|
||||
Assert.Equal(address.City, taxInformation.City);
|
||||
Assert.Equal(address.State, taxInformation.State);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RemovePaymentMethod
|
||||
[Theory, BitAutoData]
|
||||
public async Task RemovePaymentMethod_NullSubscriber_ThrowsArgumentNullException(
|
||||
@@ -1844,48 +1634,6 @@ public class SubscriberServiceTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region VerifyBankAccount
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_NoSetupIntentId_ThrowsBillingException(
|
||||
Provider provider,
|
||||
SutProvider<SubscriberService> sutProvider) => await ThrowsBillingExceptionAsync(() => sutProvider.Sut.VerifyBankAccount(provider, ""));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_MakesCorrectInvocations(
|
||||
Provider provider,
|
||||
SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
const string descriptorCode = "SM1234";
|
||||
|
||||
var setupIntent = new SetupIntent
|
||||
{
|
||||
Id = "setup_intent_id",
|
||||
PaymentMethodId = "payment_method_id"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISetupIntentCache>().GetSetupIntentIdForSubscriber(provider.Id).Returns(setupIntent.Id);
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
stripeAdapter.SetupIntentGet(setupIntent.Id).Returns(setupIntent);
|
||||
|
||||
await sutProvider.Sut.VerifyBankAccount(provider, descriptorCode);
|
||||
|
||||
await stripeAdapter.Received(1).SetupIntentVerifyMicroDeposit(setupIntent.Id,
|
||||
Arg.Is<SetupIntentVerifyMicrodepositsOptions>(
|
||||
options => options.DescriptorCode == descriptorCode));
|
||||
|
||||
await stripeAdapter.Received(1).PaymentMethodAttachAsync(setupIntent.PaymentMethodId,
|
||||
Arg.Is<PaymentMethodAttachOptions>(
|
||||
options => options.Customer == provider.GatewayCustomerId));
|
||||
|
||||
await stripeAdapter.Received(1).CustomerUpdateAsync(provider.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(
|
||||
options => options.InvoiceSettings.DefaultPaymentMethod == setupIntent.PaymentMethodId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsValidGatewayCustomerIdAsync
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
||||
@@ -107,30 +107,6 @@ public class CurrentContextTests
|
||||
Assert.Equal(deviceType, sutProvider.Sut.DeviceType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildAsync_HttpContext_SetsCloudflareFlags(
|
||||
SutProvider<CurrentContext> sutProvider)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var globalSettings = new Core.Settings.GlobalSettings();
|
||||
sutProvider.Sut.BotScore = null;
|
||||
// Arrange
|
||||
var botScore = 85;
|
||||
httpContext.Request.Headers["X-Cf-Bot-Score"] = botScore.ToString();
|
||||
httpContext.Request.Headers["X-Cf-Worked-Proxied"] = "1";
|
||||
httpContext.Request.Headers["X-Cf-Is-Bot"] = "1";
|
||||
httpContext.Request.Headers["X-Cf-Maybe-Bot"] = "1";
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.BuildAsync(httpContext, globalSettings);
|
||||
|
||||
// Assert
|
||||
Assert.True(sutProvider.Sut.CloudflareWorkerProxied);
|
||||
Assert.True(sutProvider.Sut.IsBot);
|
||||
Assert.True(sutProvider.Sut.MaybeBot);
|
||||
Assert.Equal(botScore, sutProvider.Sut.BotScore);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildAsync_HttpContext_SetsClientVersion(
|
||||
SutProvider<CurrentContext> sutProvider)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Queries;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.KeyManagement.Queries;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class KeyConnectorConfirmationDetailsQueryTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Run_OrganizationNotFound_Throws(SutProvider<KeyConnectorConfirmationDetailsQuery> sutProvider,
|
||||
Guid userId, string orgSsoIdentifier)
|
||||
{
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Run(orgSsoIdentifier, userId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.GetByOrganizationAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Run_OrganizationNotKeyConnector_Throws(
|
||||
SutProvider<KeyConnectorConfirmationDetailsQuery> sutProvider,
|
||||
Guid userId, string orgSsoIdentifier, Organization org)
|
||||
{
|
||||
org.Identifier = orgSsoIdentifier;
|
||||
org.UseKeyConnector = false;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdentifierAsync(orgSsoIdentifier).Returns(org);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Run(orgSsoIdentifier, userId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.GetByOrganizationAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Run_OrganizationUserNotFound_Throws(SutProvider<KeyConnectorConfirmationDetailsQuery> sutProvider,
|
||||
Guid userId, string orgSsoIdentifier
|
||||
, Organization org)
|
||||
{
|
||||
org.Identifier = orgSsoIdentifier;
|
||||
org.UseKeyConnector = true;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdentifierAsync(orgSsoIdentifier).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByOrganizationAsync(Arg.Any<Guid>(), Arg.Any<Guid>()).Returns(Task.FromResult<OrganizationUser>(null));
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Run(orgSsoIdentifier, userId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationAsync(org.Id, userId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Run_Success(SutProvider<KeyConnectorConfirmationDetailsQuery> sutProvider, Guid userId,
|
||||
string orgSsoIdentifier
|
||||
, Organization org, OrganizationUser orgUser)
|
||||
{
|
||||
org.Identifier = orgSsoIdentifier;
|
||||
org.UseKeyConnector = true;
|
||||
orgUser.OrganizationId = org.Id;
|
||||
orgUser.UserId = userId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdentifierAsync(orgSsoIdentifier).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetByOrganizationAsync(org.Id, userId)
|
||||
.Returns(orgUser);
|
||||
|
||||
var result = await sutProvider.Sut.Run(orgSsoIdentifier, userId);
|
||||
|
||||
Assert.Equal(org.Name, result.OrganizationName);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationAsync(org.Id, userId);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
@@ -17,7 +17,7 @@ public class CompleteSubscriptionUpdateTests
|
||||
public void UpgradeItemOptions_TeamsStarterToTeams_ReturnsCorrectOptions(
|
||||
Organization organization)
|
||||
{
|
||||
var teamsStarterPlan = StaticStore.GetPlan(PlanType.TeamsStarter);
|
||||
var teamsStarterPlan = MockPlans.Get(PlanType.TeamsStarter);
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
@@ -35,7 +35,7 @@ public class CompleteSubscriptionUpdateTests
|
||||
}
|
||||
};
|
||||
|
||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||
var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly);
|
||||
|
||||
var updatedSubscriptionData = new SubscriptionData
|
||||
{
|
||||
@@ -66,7 +66,7 @@ public class CompleteSubscriptionUpdateTests
|
||||
// 5 purchased, 1 base
|
||||
organization.MaxStorageGb = 6;
|
||||
|
||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||
var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly);
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
@@ -102,7 +102,7 @@ public class CompleteSubscriptionUpdateTests
|
||||
}
|
||||
};
|
||||
|
||||
var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly);
|
||||
var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly);
|
||||
|
||||
var updatedSubscriptionData = new SubscriptionData
|
||||
{
|
||||
@@ -173,7 +173,7 @@ public class CompleteSubscriptionUpdateTests
|
||||
// 5 purchased, 1 base
|
||||
organization.MaxStorageGb = 6;
|
||||
|
||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||
var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly);
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
@@ -209,7 +209,7 @@ public class CompleteSubscriptionUpdateTests
|
||||
}
|
||||
};
|
||||
|
||||
var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly);
|
||||
var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly);
|
||||
|
||||
var updatedSubscriptionData = new SubscriptionData
|
||||
{
|
||||
@@ -277,8 +277,8 @@ public class CompleteSubscriptionUpdateTests
|
||||
public void RevertItemOptions_TeamsStarterToTeams_ReturnsCorrectOptions(
|
||||
Organization organization)
|
||||
{
|
||||
var teamsStarterPlan = StaticStore.GetPlan(PlanType.TeamsStarter);
|
||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||
var teamsStarterPlan = MockPlans.Get(PlanType.TeamsStarter);
|
||||
var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly);
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
@@ -325,8 +325,8 @@ public class CompleteSubscriptionUpdateTests
|
||||
// 5 purchased, 1 base
|
||||
organization.MaxStorageGb = 6;
|
||||
|
||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||
var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly);
|
||||
var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly);
|
||||
var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly);
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
@@ -431,8 +431,8 @@ public class CompleteSubscriptionUpdateTests
|
||||
// 5 purchased, 1 base
|
||||
organization.MaxStorageGb = 6;
|
||||
|
||||
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||
var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly);
|
||||
var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly);
|
||||
var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly);
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
@@ -27,7 +27,7 @@ public class SeatSubscriptionUpdateTests
|
||||
|
||||
public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
organization.PlanType = planType;
|
||||
var subscription = new Subscription
|
||||
{
|
||||
@@ -69,7 +69,7 @@ public class SeatSubscriptionUpdateTests
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
organization.PlanType = planType;
|
||||
var subscription = new Subscription
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
@@ -16,7 +16,7 @@ public class SecretsManagerSubscriptionUpdateTests
|
||||
private static TheoryData<Plan> ToPlanTheory(List<PlanType> types)
|
||||
{
|
||||
var theoryData = new TheoryData<Plan>();
|
||||
var plans = types.Select(StaticStore.GetPlan).ToArray();
|
||||
var plans = types.Select(MockPlans.Get).ToArray();
|
||||
theoryData.AddRange(plans);
|
||||
return theoryData;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
@@ -27,7 +27,7 @@ public class ServiceAccountSubscriptionUpdateTests
|
||||
|
||||
public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
organization.PlanType = planType;
|
||||
var subscription = new Subscription
|
||||
{
|
||||
@@ -69,7 +69,7 @@ public class ServiceAccountSubscriptionUpdateTests
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
organization.PlanType = planType;
|
||||
var quantity = 5;
|
||||
var subscription = new Subscription
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
@@ -27,7 +27,7 @@ public class SmSeatSubscriptionUpdateTests
|
||||
|
||||
public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
organization.PlanType = planType;
|
||||
var quantity = 3;
|
||||
var subscription = new Subscription
|
||||
@@ -70,7 +70,7 @@ public class SmSeatSubscriptionUpdateTests
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
organization.PlanType = planType;
|
||||
var quantity = 5;
|
||||
var subscription = new Subscription
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
@@ -26,7 +26,7 @@ public class StorageSubscriptionUpdateTests
|
||||
|
||||
public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Items = new StripeList<SubscriptionItem>
|
||||
@@ -77,7 +77,7 @@ public class StorageSubscriptionUpdateTests
|
||||
[BitAutoData(PlanType.TeamsStarter)]
|
||||
public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
var plan = MockPlans.Get(planType);
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Items = new StripeList<SubscriptionItem>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
|
||||
|
||||
public abstract class FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
public static IEnumerable<object[]> EnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).ProductTier == ProductTierType.Enterprise).Select(p => new object[] { p });
|
||||
Enum.GetValues<PlanType>().Where(p => MockPlans.Get(p).ProductTier == ProductTierType.Enterprise).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).ProductTier != ProductTierType.Enterprise).Select(p => new object[] { p });
|
||||
Enum.GetValues<PlanType>().Where(p => MockPlans.Get(p).ProductTier != ProductTierType.Enterprise).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> FamiliesPlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).ProductTier == ProductTierType.Families).Select(p => new object[] { p });
|
||||
Enum.GetValues<PlanType>().Where(p => MockPlans.Get(p).ProductTier == ProductTierType.Families).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).ProductTier != ProductTierType.Families).Select(p => new object[] { p });
|
||||
Enum.GetValues<PlanType>().Where(p => MockPlans.Get(p).ProductTier != ProductTierType.Families).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
|
||||
Enum.GetValues<OrganizationUserStatusType>()
|
||||
|
||||
@@ -9,7 +9,7 @@ using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -42,7 +42,7 @@ public class AddSecretsManagerSubscriptionCommandTests
|
||||
{
|
||||
organization.PlanType = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(plan);
|
||||
|
||||
await sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts);
|
||||
@@ -88,7 +88,7 @@ public class AddSecretsManagerSubscriptionCommandTests
|
||||
organization.GatewayCustomerId = null;
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts));
|
||||
Assert.Contains("No payment method found.", exception.Message);
|
||||
@@ -106,7 +106,7 @@ public class AddSecretsManagerSubscriptionCommandTests
|
||||
organization.GatewaySubscriptionId = null;
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts));
|
||||
Assert.Contains("No subscription found.", exception.Message);
|
||||
@@ -139,7 +139,7 @@ public class AddSecretsManagerSubscriptionCommandTests
|
||||
provider.Type = ProviderType.Msp;
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).Returns(provider);
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(organization, 10, 10));
|
||||
|
||||
@@ -11,7 +11,7 @@ using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -26,7 +26,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
private static TheoryData<Plan> ToPlanTheory(List<PlanType> types)
|
||||
{
|
||||
var theoryData = new TheoryData<Plan>();
|
||||
var plans = types.Select(StaticStore.GetPlan).ToArray();
|
||||
var plans = types.Select(MockPlans.Get).ToArray();
|
||||
theoryData.AddRange(plans);
|
||||
return theoryData;
|
||||
}
|
||||
@@ -164,7 +164,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
Organization organization,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, autoscaling).AdjustSeats(2);
|
||||
|
||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
|
||||
@@ -180,7 +180,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
|
||||
organization.UseSecretsManager = false;
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false);
|
||||
@@ -289,7 +289,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
organization.MaxAutoscaleSmSeats = maxSeatCount;
|
||||
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
@@ -334,7 +334,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
|
||||
var ownerDetailsList = new List<OrganizationUserUserDetails> { new() { Email = "owner@example.com" } };
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
@@ -372,7 +372,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
organization.SmSeats = null;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustSeats(1);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
@@ -388,7 +388,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
Organization organization,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustSeats(-2);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
@@ -404,7 +404,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
organization.PlanType = planType;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustSeats(1);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
@@ -422,7 +422,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
organization.SmSeats = 9;
|
||||
organization.MaxAutoscaleSmSeats = 10;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustSeats(2);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
@@ -436,7 +436,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
Organization organization,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmSeats = organization.SmSeats + 10,
|
||||
@@ -455,7 +455,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
Organization organization,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmSeats = 0,
|
||||
@@ -475,7 +475,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
organization.SmSeats = 8;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmSeats = 7,
|
||||
@@ -498,7 +498,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
var smServiceAccounts = 300;
|
||||
var existingServiceAccountCount = 299;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmServiceAccounts = smServiceAccounts,
|
||||
@@ -531,7 +531,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var smServiceAccounts = 300;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmServiceAccounts = smServiceAccounts,
|
||||
@@ -571,7 +571,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
organization.SmServiceAccounts = null;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustServiceAccounts(1);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
@@ -585,7 +585,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
Organization organization,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustServiceAccounts(-2);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
@@ -601,7 +601,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
organization.PlanType = planType;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustServiceAccounts(1);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
@@ -619,7 +619,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
organization.SmServiceAccounts = 9;
|
||||
organization.MaxAutoscaleSmServiceAccounts = 10;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustServiceAccounts(2);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
@@ -639,7 +639,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
organization.SmServiceAccounts = smServiceAccount - 5;
|
||||
organization.MaxAutoscaleSmServiceAccounts = 2 * smServiceAccount;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmServiceAccounts = smServiceAccount,
|
||||
@@ -662,7 +662,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
|
||||
organization.SmServiceAccounts = newSmServiceAccounts - 10;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmServiceAccounts = newSmServiceAccounts,
|
||||
@@ -707,7 +707,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
organization.SmSeats = smSeats - 1;
|
||||
organization.MaxAutoscaleSmSeats = smSeats * 2;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
SmSeats = smSeats,
|
||||
@@ -728,7 +728,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
{
|
||||
organization.PlanType = planType;
|
||||
organization.SmSeats = 2;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
MaxAutoscaleSmSeats = 3
|
||||
@@ -748,7 +748,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
{
|
||||
organization.PlanType = planType;
|
||||
organization.SmSeats = 2;
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
MaxAutoscaleSmSeats = 2
|
||||
@@ -769,7 +769,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
organization.PlanType = planType;
|
||||
organization.SmServiceAccounts = 3;
|
||||
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
var plan = MockPlans.Get(organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { MaxAutoscaleSmServiceAccounts = 3 };
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscriptionAsync(update));
|
||||
|
||||
@@ -8,7 +8,7 @@ using Bit.Core.Repositories;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -45,7 +45,7 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
upgrade.Plan = organization.PlanType;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
@@ -61,7 +61,7 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalServiceAccounts = 10;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("already on this plan", exception.Message);
|
||||
@@ -73,11 +73,11 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalSeats = 10;
|
||||
upgrade.Plan = PlanType.TeamsAnnually;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan));
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
||||
{
|
||||
@@ -104,7 +104,7 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
|
||||
organization.PlanType = PlanType.FamiliesAnnually;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
organizationUpgrade.AdditionalSeats = 30;
|
||||
organizationUpgrade.UseSecretsManager = true;
|
||||
@@ -113,7 +113,7 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
organizationUpgrade.AdditionalStorageGb = 3;
|
||||
organizationUpgrade.Plan = planType;
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organizationUpgrade.Plan).Returns(StaticStore.GetPlan(organizationUpgrade.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organizationUpgrade.Plan).Returns(MockPlans.Get(organizationUpgrade.Plan));
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
||||
{
|
||||
@@ -123,7 +123,7 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
await sutProvider.Sut.UpgradePlanAsync(organization.Id, organizationUpgrade);
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).AdjustSubscription(
|
||||
organization,
|
||||
StaticStore.GetPlan(planType),
|
||||
MockPlans.Get(planType),
|
||||
organizationUpgrade.AdditionalSeats,
|
||||
organizationUpgrade.UseSecretsManager,
|
||||
organizationUpgrade.AdditionalSmSeats,
|
||||
@@ -141,12 +141,12 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
public async Task UpgradePlan_SM_Passes(PlanType planType, Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
upgrade.Plan = planType;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan));
|
||||
|
||||
var plan = StaticStore.GetPlan(upgrade.Plan);
|
||||
var plan = MockPlans.Get(upgrade.Plan);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
@@ -184,10 +184,10 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
upgrade.AdditionalSeats = 15;
|
||||
upgrade.AdditionalSmSeats = 1;
|
||||
upgrade.AdditionalServiceAccounts = 0;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan));
|
||||
|
||||
organization.SmSeats = 2;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
@@ -218,11 +218,11 @@ public class UpgradeOrganizationPlanCommandTests
|
||||
upgrade.AdditionalSeats = 15;
|
||||
upgrade.AdditionalSmSeats = 1;
|
||||
upgrade.AdditionalServiceAccounts = 0;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan));
|
||||
|
||||
organization.SmSeats = 1;
|
||||
organization.SmServiceAccounts = currentServiceAccounts;
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
|
||||
@@ -74,7 +74,7 @@ public class SendGridMailDeliveryServiceTests : IDisposable
|
||||
Assert.Equal(mailMessage.HtmlContent, msg.HtmlContent);
|
||||
Assert.Equal(mailMessage.TextContent, msg.PlainTextContent);
|
||||
|
||||
Assert.Contains("type:Cateogry", msg.Categories);
|
||||
Assert.Contains("type:Category", msg.Categories);
|
||||
Assert.Contains(msg.Categories, x => x.StartsWith("env:"));
|
||||
Assert.Contains(msg.Categories, x => x.StartsWith("sender:"));
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.StaticStore.Plans;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Tax.Requests;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
@@ -17,506 +13,6 @@ namespace Bit.Core.Test.Services;
|
||||
[SutProviderCustomize]
|
||||
public class StripePaymentServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task
|
||||
PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithoutAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager =
|
||||
new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually,
|
||||
AdditionalStorage = 0
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" }
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(p =>
|
||||
p.Currency == "usd" &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == familiesPlan.PasswordManager.StripePlanId &&
|
||||
x.Quantity == 1) &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == familiesPlan.PasswordManager.StripeStoragePlanId &&
|
||||
x.Quantity == 0)))
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 4000,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 800 }],
|
||||
Total = 4800
|
||||
});
|
||||
|
||||
var actual = await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
Assert.Equal(8M, actual.TaxAmount);
|
||||
Assert.Equal(48M, actual.TotalAmount);
|
||||
Assert.Equal(40M, actual.TaxableBaseAmount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager =
|
||||
new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually,
|
||||
AdditionalStorage = 1
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" }
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(p =>
|
||||
p.Currency == "usd" &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == familiesPlan.PasswordManager.StripePlanId &&
|
||||
x.Quantity == 1) &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == familiesPlan.PasswordManager.StripeStoragePlanId &&
|
||||
x.Quantity == 1)))
|
||||
.Returns(new Invoice { TotalExcludingTax = 4000, TotalTaxes = [new InvoiceTotalTax { Amount = 800 }], Total = 4800 });
|
||||
|
||||
var actual = await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
Assert.Equal(8M, actual.TaxAmount);
|
||||
Assert.Equal(48M, actual.TotalAmount);
|
||||
Assert.Equal(40M, actual.TaxableBaseAmount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task
|
||||
PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithoutAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually,
|
||||
SponsoredPlan = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
AdditionalStorage = 0
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" }
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(p =>
|
||||
p.Currency == "usd" &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == "2021-family-for-enterprise-annually" &&
|
||||
x.Quantity == 1) &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == familiesPlan.PasswordManager.StripeStoragePlanId &&
|
||||
x.Quantity == 0)))
|
||||
.Returns(new Invoice { TotalExcludingTax = 0, TotalTaxes = [new InvoiceTotalTax { Amount = 0 }], Total = 0 });
|
||||
|
||||
var actual = await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
Assert.Equal(0M, actual.TaxAmount);
|
||||
Assert.Equal(0M, actual.TotalAmount);
|
||||
Assert.Equal(0M, actual.TaxableBaseAmount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task
|
||||
PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually,
|
||||
SponsoredPlan = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
AdditionalStorage = 1
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" }
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
.InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(p =>
|
||||
p.Currency == "usd" &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == "2021-family-for-enterprise-annually" &&
|
||||
x.Quantity == 1) &&
|
||||
p.SubscriptionDetails.Items.Any(x =>
|
||||
x.Plan == familiesPlan.PasswordManager.StripeStoragePlanId &&
|
||||
x.Quantity == 1)))
|
||||
.Returns(new Invoice { TotalExcludingTax = 400, TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 });
|
||||
|
||||
var actual = await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
Assert.Equal(0.08M, actual.TaxAmount);
|
||||
Assert.Equal(4.08M, actual.TotalAmount);
|
||||
Assert.Equal(4M, actual.TaxableBaseAmount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_PersonalUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_BusinessUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_PersonalUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_BusinessUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_PersonalUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_BusinessUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_PersonalUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_BusinessUse_SetsTaxExemptReverse(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
TotalTaxes = [new InvoiceTotalTax { Amount = 8 }],
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == StripeConstants.TaxExempt.Reverse
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetSubscriptionAsync_WithCustomerDiscount_ReturnsDiscountFromCustomer(
|
||||
|
||||
51
test/Core.Test/Utilities/EmailValidationTests.cs
Normal file
51
test/Core.Test/Utilities/EmailValidationTests.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Utilities;
|
||||
|
||||
public class EmailValidationTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("user@Example.COM", "example.com")]
|
||||
[InlineData("user@EXAMPLE.COM", "example.com")]
|
||||
[InlineData("user@example.com", "example.com")]
|
||||
[InlineData("user@Example.Com", "example.com")]
|
||||
[InlineData("User@DOMAIN.CO.UK", "domain.co.uk")]
|
||||
public void GetDomain_WithMixedCaseEmail_ReturnsLowercaseDomain(string email, string expectedDomain)
|
||||
{
|
||||
// Act
|
||||
var result = EmailValidation.GetDomain(email);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDomain, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("hello@world.com", "world.com")] // regular email address
|
||||
[InlineData("hello@world.planet.com", "world.planet.com")] // subdomain
|
||||
[InlineData("hello+1@world.com", "world.com")] // alias
|
||||
[InlineData("hello.there@world.com", "world.com")] // period in local-part
|
||||
[InlineData("hello@wörldé.com", "wörldé.com")] // unicode domain
|
||||
[InlineData("hello@world.cömé", "world.cömé")] // unicode top-level domain
|
||||
public void GetDomain_WithValidEmail_ReturnsLowercaseDomain(string email, string expectedDomain)
|
||||
{
|
||||
// Act
|
||||
var result = EmailValidation.GetDomain(email);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDomain, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid-email")]
|
||||
[InlineData("@example.com")]
|
||||
[InlineData("user@")]
|
||||
[InlineData("")]
|
||||
public void GetDomain_WithInvalidEmail_ThrowsBadRequestException(string email)
|
||||
{
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<BadRequestException>(() => EmailValidation.GetDomain(email));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
}
|
||||
108
test/Core.Test/Utilities/EventIntegrationsCacheConstantsTests.cs
Normal file
108
test/Core.Test/Utilities/EventIntegrationsCacheConstantsTests.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Utilities;
|
||||
|
||||
public class EventIntegrationsCacheConstantsTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void BuildCacheKeyForGroup_ReturnsExpectedKey(Guid groupId)
|
||||
{
|
||||
var expected = $"Group:{groupId:N}";
|
||||
var key = EventIntegrationsCacheConstants.BuildCacheKeyForGroup(groupId);
|
||||
var keyWithDifferentGroup = EventIntegrationsCacheConstants.BuildCacheKeyForGroup(Guid.NewGuid());
|
||||
var keyWithSameGroup = EventIntegrationsCacheConstants.BuildCacheKeyForGroup(groupId);
|
||||
|
||||
Assert.Equal(expected, key);
|
||||
Assert.NotEqual(key, keyWithDifferentGroup);
|
||||
Assert.Equal(key, keyWithSameGroup);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void BuildCacheKeyForOrganization_ReturnsExpectedKey(Guid orgId)
|
||||
{
|
||||
var expected = $"Organization:{orgId:N}";
|
||||
var key = EventIntegrationsCacheConstants.BuildCacheKeyForOrganization(orgId);
|
||||
var keyWithDifferentOrg = EventIntegrationsCacheConstants.BuildCacheKeyForOrganization(Guid.NewGuid());
|
||||
var keyWithSameOrg = EventIntegrationsCacheConstants.BuildCacheKeyForOrganization(orgId);
|
||||
|
||||
Assert.Equal(expected, key);
|
||||
Assert.NotEqual(key, keyWithDifferentOrg);
|
||||
Assert.Equal(key, keyWithSameOrg);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void BuildCacheKeyForOrganizationIntegrationConfigurationDetails_ReturnsExpectedKey(Guid orgId)
|
||||
{
|
||||
var integrationType = IntegrationType.Hec;
|
||||
|
||||
var expectedWithEvent = $"OrganizationIntegrationConfigurationDetails:{orgId:N}:Hec:User_LoggedIn";
|
||||
var keyWithEvent = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, integrationType, EventType.User_LoggedIn);
|
||||
var keyWithDifferentEvent = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, integrationType, EventType.Cipher_Created);
|
||||
var keyWithDifferentIntegration = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, IntegrationType.Webhook, EventType.User_LoggedIn);
|
||||
var keyWithDifferentOrganization = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
Guid.NewGuid(), integrationType, EventType.User_LoggedIn);
|
||||
var keyWithSameDetails = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, integrationType, EventType.User_LoggedIn);
|
||||
|
||||
Assert.Equal(expectedWithEvent, keyWithEvent);
|
||||
Assert.NotEqual(keyWithEvent, keyWithDifferentEvent);
|
||||
Assert.NotEqual(keyWithEvent, keyWithDifferentIntegration);
|
||||
Assert.NotEqual(keyWithEvent, keyWithDifferentOrganization);
|
||||
Assert.Equal(keyWithEvent, keyWithSameDetails);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void BuildCacheTagForOrganizationIntegration_ReturnsExpectedKey(Guid orgId)
|
||||
{
|
||||
var expected = $"OrganizationIntegration:{orgId:N}:Hec";
|
||||
var tag = EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
orgId, IntegrationType.Hec);
|
||||
var tagWithDifferentOrganization = EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
Guid.NewGuid(), IntegrationType.Hec);
|
||||
var tagWithDifferentIntegrationType = EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
orgId, IntegrationType.Webhook);
|
||||
var tagWithSameDetails = EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
orgId, IntegrationType.Hec);
|
||||
|
||||
Assert.Equal(expected, tag);
|
||||
Assert.NotEqual(tag, tagWithDifferentOrganization);
|
||||
Assert.NotEqual(tag, tagWithDifferentIntegrationType);
|
||||
Assert.Equal(tag, tagWithSameDetails);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void BuildCacheKeyForOrganizationUser_ReturnsExpectedKey(Guid orgId, Guid userId)
|
||||
{
|
||||
var expected = $"OrganizationUserUserDetails:{orgId:N}:{userId:N}";
|
||||
var key = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationUser(orgId, userId);
|
||||
var keyWithDifferentOrg = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationUser(Guid.NewGuid(), userId);
|
||||
var keyWithDifferentUser = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationUser(orgId, Guid.NewGuid());
|
||||
var keyWithSameDetails = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationUser(orgId, userId);
|
||||
|
||||
Assert.Equal(expected, key);
|
||||
Assert.NotEqual(key, keyWithDifferentOrg);
|
||||
Assert.NotEqual(key, keyWithDifferentUser);
|
||||
Assert.Equal(key, keyWithSameDetails);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CacheName_ReturnsExpected()
|
||||
{
|
||||
Assert.Equal("EventIntegrations", EventIntegrationsCacheConstants.CacheName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DurationForOrganizationIntegrationConfigurationDetails_ReturnsExpected()
|
||||
{
|
||||
Assert.Equal(
|
||||
TimeSpan.FromDays(1),
|
||||
EventIntegrationsCacheConstants.DurationForOrganizationIntegrationConfigurationDetails
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using NSubstitute;
|
||||
using StackExchange.Redis;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using ZiggyCreatures.Caching.Fusion.Backplane;
|
||||
|
||||
namespace Bit.Core.Test.Utilities;
|
||||
|
||||
@@ -14,6 +15,7 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private const string _cacheName = "TestCache";
|
||||
|
||||
public ExtendedCacheServiceCollectionExtensionsTests()
|
||||
{
|
||||
@@ -33,129 +35,450 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddFusionCoreServices_CustomSettings_OverridesDefaults()
|
||||
public void AddExtendedCache_CustomSettings_OverridesDefaults()
|
||||
{
|
||||
var settings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
{ "GlobalSettings:DistributedCache:Duration", "00:12:00" },
|
||||
{ "GlobalSettings:DistributedCache:FailSafeMaxDuration", "01:30:00" },
|
||||
{ "GlobalSettings:DistributedCache:FailSafeThrottleDuration", "00:01:00" },
|
||||
{ "GlobalSettings:DistributedCache:EagerRefreshThreshold", "0.75" },
|
||||
{ "GlobalSettings:DistributedCache:FactorySoftTimeout", "00:00:00.020" },
|
||||
{ "GlobalSettings:DistributedCache:FactoryHardTimeout", "00:00:03" },
|
||||
{ "GlobalSettings:DistributedCache:DistributedCacheSoftTimeout", "00:00:00.500" },
|
||||
{ "GlobalSettings:DistributedCache:DistributedCacheHardTimeout", "00:00:01.500" },
|
||||
{ "GlobalSettings:DistributedCache:JitterMaxDuration", "00:00:05" },
|
||||
{ "GlobalSettings:DistributedCache:IsFailSafeEnabled", "false" },
|
||||
{ "GlobalSettings:DistributedCache:AllowBackgroundDistributedCacheOperations", "false" },
|
||||
Duration = TimeSpan.FromMinutes(12),
|
||||
FailSafeMaxDuration = TimeSpan.FromHours(1.5),
|
||||
FailSafeThrottleDuration = TimeSpan.FromMinutes(1),
|
||||
EagerRefreshThreshold = 0.75f,
|
||||
FactorySoftTimeout = TimeSpan.FromMilliseconds(20),
|
||||
FactoryHardTimeout = TimeSpan.FromSeconds(3),
|
||||
DistributedCacheSoftTimeout = TimeSpan.FromSeconds(0.5),
|
||||
DistributedCacheHardTimeout = TimeSpan.FromSeconds(1.5),
|
||||
JitterMaxDuration = TimeSpan.FromSeconds(5),
|
||||
IsFailSafeEnabled = false,
|
||||
AllowBackgroundDistributedCacheOperations = false,
|
||||
};
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
var opt = cache.DefaultEntryOptions;
|
||||
|
||||
Assert.Equal(TimeSpan.FromMinutes(12), opt.Duration);
|
||||
Assert.False(opt.IsFailSafeEnabled);
|
||||
Assert.Equal(TimeSpan.FromHours(1.5), opt.FailSafeMaxDuration);
|
||||
Assert.Equal(TimeSpan.FromMinutes(1), opt.FailSafeThrottleDuration);
|
||||
Assert.Equal(0.75f, opt.EagerRefreshThreshold);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(20), opt.FactorySoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(3000), opt.FactoryHardTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(0.5), opt.DistributedCacheSoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(1.5), opt.DistributedCacheHardTimeout);
|
||||
Assert.False(opt.AllowBackgroundDistributedCacheOperations);
|
||||
Assert.Equal(TimeSpan.FromSeconds(5), opt.JitterMaxDuration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_DefaultSettings_ConfiguresExpectedValues()
|
||||
{
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
var opt = cache.DefaultEntryOptions;
|
||||
|
||||
Assert.Equal(TimeSpan.FromMinutes(30), opt.Duration);
|
||||
Assert.True(opt.IsFailSafeEnabled);
|
||||
Assert.Equal(TimeSpan.FromHours(2), opt.FailSafeMaxDuration);
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), opt.FailSafeThrottleDuration);
|
||||
Assert.Equal(0.9f, opt.EagerRefreshThreshold);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(100), opt.FactorySoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(1500), opt.FactoryHardTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(1), opt.DistributedCacheSoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(2), opt.DistributedCacheHardTimeout);
|
||||
Assert.True(opt.AllowBackgroundDistributedCacheOperations);
|
||||
Assert.Equal(TimeSpan.FromSeconds(2), opt.JitterMaxDuration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_DisabledDistributedCache_DoesNotRegisterBackplaneOrRedis()
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
EnableDistributedCache = false,
|
||||
};
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.False(cache.HasDistributedCache);
|
||||
Assert.False(cache.HasBackplane);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_EmptyCacheName_DoesNothing()
|
||||
{
|
||||
_services.AddExtendedCache(string.Empty, _globalSettings);
|
||||
|
||||
var regs = _services.Where(s => s.ServiceType == typeof(IFusionCache)).ToList();
|
||||
Assert.Empty(regs);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetKeyedService<IFusionCache>(_cacheName);
|
||||
Assert.Null(cache);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_MultipleCalls_OnlyAddsOneCacheService()
|
||||
{
|
||||
var settings = CreateGlobalSettings(new()
|
||||
{
|
||||
{ "GlobalSettings:DistributedCache:Redis:ConnectionString", "localhost:6379" }
|
||||
});
|
||||
|
||||
_services.TryAddExtendedCacheServices(settings);
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var fusionCache = provider.GetRequiredService<IFusionCache>();
|
||||
var options = fusionCache.DefaultEntryOptions;
|
||||
// Provide a multiplexer (shared)
|
||||
_services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
|
||||
Assert.Equal(TimeSpan.FromMinutes(12), options.Duration);
|
||||
Assert.False(options.IsFailSafeEnabled);
|
||||
Assert.Equal(TimeSpan.FromHours(1.5), options.FailSafeMaxDuration);
|
||||
Assert.Equal(TimeSpan.FromMinutes(1), options.FailSafeThrottleDuration);
|
||||
Assert.Equal(0.75f, options.EagerRefreshThreshold);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(20), options.FactorySoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(3000), options.FactoryHardTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(0.5), options.DistributedCacheSoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(1.5), options.DistributedCacheHardTimeout);
|
||||
Assert.False(options.AllowBackgroundDistributedCacheOperations);
|
||||
Assert.Equal(TimeSpan.FromSeconds(5), options.JitterMaxDuration);
|
||||
_services.AddExtendedCache(_cacheName, settings);
|
||||
_services.AddExtendedCache(_cacheName, settings);
|
||||
_services.AddExtendedCache(_cacheName, settings);
|
||||
|
||||
var regs = _services.Where(s => s.ServiceType == typeof(IFusionCache)).ToList();
|
||||
Assert.Single(regs);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
Assert.NotNull(cache);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddFusionCoreServices_DefaultSettings_ConfiguresExpectedValues()
|
||||
public void AddExtendedCache_MultipleDifferentCaches_AddsAll()
|
||||
{
|
||||
_services.TryAddExtendedCacheServices(_globalSettings);
|
||||
_services.AddExtendedCache("Cache1", _globalSettings);
|
||||
_services.AddExtendedCache("Cache2", _globalSettings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
|
||||
var fusionCache = provider.GetRequiredService<IFusionCache>();
|
||||
var options = fusionCache.DefaultEntryOptions;
|
||||
var cache1 = provider.GetRequiredKeyedService<IFusionCache>("Cache1");
|
||||
var cache2 = provider.GetRequiredKeyedService<IFusionCache>("Cache2");
|
||||
|
||||
Assert.Equal(TimeSpan.FromMinutes(30), options.Duration);
|
||||
Assert.True(options.IsFailSafeEnabled);
|
||||
Assert.Equal(TimeSpan.FromHours(2), options.FailSafeMaxDuration);
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), options.FailSafeThrottleDuration);
|
||||
Assert.Equal(0.9f, options.EagerRefreshThreshold);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(100), options.FactorySoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(1500), options.FactoryHardTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(1), options.DistributedCacheSoftTimeout);
|
||||
Assert.Equal(TimeSpan.FromSeconds(2), options.DistributedCacheHardTimeout);
|
||||
Assert.True(options.AllowBackgroundDistributedCacheOperations);
|
||||
Assert.Equal(TimeSpan.FromSeconds(2), options.JitterMaxDuration);
|
||||
Assert.NotNull(cache1);
|
||||
Assert.NotNull(cache2);
|
||||
Assert.NotSame(cache1, cache2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddFusionCoreServices_MultipleCalls_OnlyConfiguresOnce()
|
||||
public void AddExtendedCache_WithRedis_EnablesDistributedCacheAndBackplane()
|
||||
{
|
||||
var settings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
var settings = CreateGlobalSettings(new()
|
||||
{
|
||||
{ "GlobalSettings:DistributedCache:Redis:ConnectionString", "localhost:6379" },
|
||||
{ "GlobalSettings:DistributedCache:DefaultExtendedCache:UseSharedDistributedCache", "true" }
|
||||
});
|
||||
_services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
_services.TryAddExtendedCacheServices(settings);
|
||||
_services.TryAddExtendedCacheServices(settings);
|
||||
_services.TryAddExtendedCacheServices(settings);
|
||||
|
||||
var registrations = _services.Where(s => s.ServiceType == typeof(IFusionCache)).ToList();
|
||||
Assert.Single(registrations);
|
||||
// Provide a multiplexer (shared)
|
||||
_services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
|
||||
_services.AddExtendedCache(_cacheName, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var fusionCache = provider.GetRequiredService<IFusionCache>();
|
||||
Assert.NotNull(fusionCache);
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.True(cache.HasDistributedCache);
|
||||
Assert.True(cache.HasBackplane);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddFusionCoreServices_WithRedis_EnablesDistributedCacheAndBackplane()
|
||||
public void AddExtendedCache_InvalidRedisConnection_LogsAndThrows()
|
||||
{
|
||||
var settings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
{ "GlobalSettings:DistributedCache:Redis:ConnectionString", "localhost:6379" },
|
||||
});
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "invalid:9999" }
|
||||
};
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
_services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
_services.TryAddExtendedCacheServices(settings);
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
|
||||
var fusionCache = provider.GetRequiredService<IFusionCache>();
|
||||
Assert.True(fusionCache.HasDistributedCache);
|
||||
Assert.True(fusionCache.HasBackplane);
|
||||
Assert.Throws<RedisConnectionException>(() =>
|
||||
{
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
// Trigger lazy initialization
|
||||
cache.GetOrDefault<string>("test");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddFusionCoreServices_WithExistingRedis_EnablesDistributedCacheAndBackplane()
|
||||
public void AddExtendedCache_WithExistingRedis_UsesExistingDistributedCacheAndBackplane()
|
||||
{
|
||||
var settings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
var settings = CreateGlobalSettings(new()
|
||||
{
|
||||
{ "GlobalSettings:DistributedCache:Redis:ConnectionString", "localhost:6379" },
|
||||
});
|
||||
|
||||
_services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
_services.AddSingleton(Substitute.For<IDistributedCache>());
|
||||
_services.TryAddExtendedCacheServices(settings);
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
|
||||
var fusionCache = provider.GetRequiredService<IFusionCache>();
|
||||
Assert.True(fusionCache.HasDistributedCache);
|
||||
Assert.True(fusionCache.HasBackplane);
|
||||
var distributedCache = provider.GetRequiredService<IDistributedCache>();
|
||||
Assert.NotNull(distributedCache);
|
||||
_services.AddExtendedCache(_cacheName, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.True(cache.HasDistributedCache);
|
||||
Assert.True(cache.HasBackplane);
|
||||
|
||||
var existingCache = provider.GetRequiredService<IDistributedCache>();
|
||||
Assert.NotNull(existingCache);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddFusionCoreServices_WithoutRedis_DisablesDistributedCacheAndBackplane()
|
||||
public void AddExtendedCache_NoRedis_DisablesDistributedCacheAndBackplane()
|
||||
{
|
||||
_services.TryAddExtendedCacheServices(_globalSettings);
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.False(cache.HasDistributedCache);
|
||||
Assert.False(cache.HasBackplane);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_NoSharedRedisButNoConnectionString_DisablesDistributedCacheAndBackplane()
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = false,
|
||||
// No Redis connection string
|
||||
};
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.False(cache.HasDistributedCache);
|
||||
Assert.False(cache.HasBackplane);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_KeyedRedis_UsesSeparateMultiplexers()
|
||||
{
|
||||
var settingsA = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
EnableDistributedCache = true,
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "localhost:6379" }
|
||||
};
|
||||
var settingsB = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
EnableDistributedCache = true,
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "localhost:6380" }
|
||||
};
|
||||
|
||||
_services.AddKeyedSingleton("CacheA", Substitute.For<IConnectionMultiplexer>());
|
||||
_services.AddKeyedSingleton("CacheB", Substitute.For<IConnectionMultiplexer>());
|
||||
|
||||
_services.AddExtendedCache("CacheA", _globalSettings, settingsA);
|
||||
_services.AddExtendedCache("CacheB", _globalSettings, settingsB);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var muxA = provider.GetRequiredKeyedService<IConnectionMultiplexer>("CacheA");
|
||||
var muxB = provider.GetRequiredKeyedService<IConnectionMultiplexer>("CacheB");
|
||||
|
||||
Assert.NotNull(muxA);
|
||||
Assert.NotNull(muxB);
|
||||
Assert.NotSame(muxA, muxB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_WithExistingKeyedDistributedCache_ReusesIt()
|
||||
{
|
||||
var existingCache = Substitute.For<IDistributedCache>();
|
||||
_services.AddKeyedSingleton<IDistributedCache>(_cacheName, existingCache);
|
||||
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "localhost:6379" }
|
||||
};
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var resolved = provider.GetRequiredKeyedService<IDistributedCache>(_cacheName);
|
||||
|
||||
Assert.Same(existingCache, resolved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_SharedNonRedisCache_UsesDistributedCacheWithoutBackplane()
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = true,
|
||||
EnableDistributedCache = true,
|
||||
// No Redis.ConnectionString
|
||||
};
|
||||
|
||||
// Register non-Redis distributed cache
|
||||
_services.AddSingleton(Substitute.For<IDistributedCache>());
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.True(cache.HasDistributedCache);
|
||||
Assert.False(cache.HasBackplane); // No backplane for non-Redis
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_SharedRedisWithMockedMultiplexer_ReusesExistingMultiplexer()
|
||||
{
|
||||
// Override GlobalSettings to include Redis connection string
|
||||
var globalSettings = CreateGlobalSettings(new()
|
||||
{
|
||||
{ "GlobalSettings:DistributedCache:Redis:ConnectionString", "localhost:6379" }
|
||||
});
|
||||
|
||||
// Custom settings for this cache
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = true,
|
||||
EnableDistributedCache = true,
|
||||
};
|
||||
|
||||
// Pre-register mocked multiplexer (simulates AddDistributedCache already called)
|
||||
var mockMultiplexer = Substitute.For<IConnectionMultiplexer>();
|
||||
_services.AddSingleton(mockMultiplexer);
|
||||
|
||||
_services.AddExtendedCache(_cacheName, globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.True(cache.HasDistributedCache);
|
||||
Assert.True(cache.HasBackplane);
|
||||
|
||||
// Verify same multiplexer was reused (TryAdd didn't replace it)
|
||||
var resolvedMux = provider.GetRequiredService<IConnectionMultiplexer>();
|
||||
Assert.Same(mockMultiplexer, resolvedMux);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_KeyedNonRedisCache_UsesKeyedDistributedCacheWithoutBackplane()
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = false,
|
||||
EnableDistributedCache = true,
|
||||
// No Redis.ConnectionString
|
||||
};
|
||||
|
||||
// Register keyed non-Redis distributed cache
|
||||
_services.AddKeyedSingleton(_cacheName, Substitute.For<IDistributedCache>());
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.True(cache.HasDistributedCache);
|
||||
Assert.False(cache.HasBackplane);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_KeyedRedisWithConnectionString_CreatesIsolatedInfrastructure()
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = false,
|
||||
EnableDistributedCache = true,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings
|
||||
{
|
||||
ConnectionString = "localhost:6379"
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-register mocked keyed multiplexer to avoid connection attempt
|
||||
_services.AddKeyedSingleton(_cacheName, Substitute.For<IConnectionMultiplexer>());
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.True(cache.HasDistributedCache);
|
||||
Assert.True(cache.HasBackplane);
|
||||
|
||||
// Verify keyed services exist
|
||||
var keyedMux = provider.GetRequiredKeyedService<IConnectionMultiplexer>(_cacheName);
|
||||
Assert.NotNull(keyedMux);
|
||||
var keyedRedis = provider.GetRequiredKeyedService<IDistributedCache>(_cacheName);
|
||||
Assert.NotNull(keyedRedis);
|
||||
var keyedBackplane = provider.GetRequiredKeyedService<IFusionCacheBackplane>(_cacheName);
|
||||
Assert.NotNull(keyedBackplane);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_NoDistributedCacheRegistered_WorksWithMemoryOnly()
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = true,
|
||||
EnableDistributedCache = true,
|
||||
// No Redis connection string, no IDistributedCache registered
|
||||
// This is technically a misconfiguration, but we handle it without failing
|
||||
};
|
||||
|
||||
_services.AddExtendedCache(_cacheName, _globalSettings, settings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(_cacheName);
|
||||
|
||||
Assert.False(cache.HasDistributedCache);
|
||||
Assert.False(cache.HasBackplane);
|
||||
|
||||
// Verify L1 memory cache still works
|
||||
cache.Set("key", "value");
|
||||
var result = cache.GetOrDefault<string>("key");
|
||||
Assert.Equal("value", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddExtendedCache_MultipleKeyedCachesWithDifferentTypes_EachHasCorrectConfig()
|
||||
{
|
||||
var redisSettings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = false,
|
||||
EnableDistributedCache = true,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "localhost:6379" }
|
||||
};
|
||||
|
||||
var nonRedisSettings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedDistributedCache = false,
|
||||
EnableDistributedCache = true,
|
||||
// No Redis connection string
|
||||
};
|
||||
|
||||
// Setup Cache1 (Redis)
|
||||
_services.AddKeyedSingleton("Cache1", Substitute.For<IConnectionMultiplexer>());
|
||||
_services.AddExtendedCache("Cache1", _globalSettings, redisSettings);
|
||||
|
||||
// Setup Cache2 (non-Redis)
|
||||
_services.AddKeyedSingleton("Cache2", Substitute.For<IDistributedCache>());
|
||||
_services.AddExtendedCache("Cache2", _globalSettings, nonRedisSettings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
|
||||
var fusionCache = provider.GetRequiredService<IFusionCache>();
|
||||
Assert.False(fusionCache.HasDistributedCache);
|
||||
Assert.False(fusionCache.HasBackplane);
|
||||
var cache1 = provider.GetRequiredKeyedService<IFusionCache>("Cache1");
|
||||
var cache2 = provider.GetRequiredKeyedService<IFusionCache>("Cache2");
|
||||
|
||||
Assert.True(cache1.HasDistributedCache);
|
||||
Assert.True(cache1.HasBackplane);
|
||||
|
||||
Assert.True(cache2.HasDistributedCache);
|
||||
Assert.False(cache2.HasBackplane);
|
||||
|
||||
Assert.NotSame(cache1, cache2);
|
||||
}
|
||||
|
||||
private static GlobalSettings CreateGlobalSettings(Dictionary<string, string?> data)
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
@@ -23,18 +19,6 @@ public class LoggerFactoryExtensionsTests
|
||||
Assert.Empty(providers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSerilog_IsDevelopment_DevLoggingEnabled_AddsSerilog()
|
||||
{
|
||||
var providers = GetProviders(new Dictionary<string, string?>
|
||||
{
|
||||
{ "GlobalSettings:EnableDevLogging", "true" },
|
||||
}, "Development");
|
||||
|
||||
var provider = Assert.Single(providers);
|
||||
Assert.IsAssignableFrom<SerilogLoggerProvider>(provider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSerilog_IsProduction_AddsSerilog()
|
||||
{
|
||||
@@ -52,7 +36,7 @@ public class LoggerFactoryExtensionsTests
|
||||
var providers = GetProviders(new Dictionary<string, string?>
|
||||
{
|
||||
{ "GlobalSettings:ProjectName", "Test" },
|
||||
{ "GlobalSetting:LogDirectoryByProject", "true" },
|
||||
{ "GlobalSettings:LogDirectoryByProject", "true" },
|
||||
{ "GlobalSettings:LogDirectory", tempDir.FullName },
|
||||
});
|
||||
|
||||
@@ -62,6 +46,8 @@ public class LoggerFactoryExtensionsTests
|
||||
var logger = provider.CreateLogger("Test");
|
||||
logger.LogWarning("This is a test");
|
||||
|
||||
provider.Dispose();
|
||||
|
||||
var logFile = Assert.Single(tempDir.EnumerateFiles("Test/*.txt"));
|
||||
|
||||
var logFileContents = await File.ReadAllTextAsync(logFile.FullName);
|
||||
@@ -106,62 +92,6 @@ public class LoggerFactoryExtensionsTests
|
||||
logFileContents
|
||||
);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Only for local development.")]
|
||||
public async Task AddSerilog_SyslogConfigured_Warns()
|
||||
{
|
||||
// Setup a fake syslog server
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||
using var listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 25000);
|
||||
listener.Start();
|
||||
|
||||
var provider = GetServiceProvider(new Dictionary<string, string?>
|
||||
{
|
||||
{ "GlobalSettings:SysLog:Destination", "tcp://127.0.0.1:25000" },
|
||||
{ "GlobalSettings:SiteName", "TestSite" },
|
||||
{ "GlobalSettings:ProjectName", "TestProject" },
|
||||
}, "Production");
|
||||
|
||||
var loggerFactory = provider.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("Test");
|
||||
|
||||
logger.LogWarning("This is a test");
|
||||
|
||||
// Look in syslog for data
|
||||
using var socket = await listener.AcceptSocketAsync(cts.Token);
|
||||
|
||||
// This is rather lazy as opposed to implementing smarter syslog message
|
||||
// reading but thats not what this test about, so instead just give
|
||||
// the sink time to finish its work in the background
|
||||
|
||||
List<string> messages = [];
|
||||
|
||||
while (true)
|
||||
{
|
||||
var buffer = new byte[1024];
|
||||
var received = await socket.ReceiveAsync(buffer, SocketFlags.None, cts.Token);
|
||||
|
||||
if (received == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var response = Encoding.ASCII.GetString(buffer, 0, received);
|
||||
messages.Add(response);
|
||||
|
||||
if (messages.Count == 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Collection(
|
||||
messages,
|
||||
(firstMessage) => Assert.Contains("Syslog for logging has been deprecated", firstMessage),
|
||||
(secondMessage) => Assert.Contains("This is a test", secondMessage)
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<ILoggerProvider> GetProviders(Dictionary<string, string?> initialData, string environment = "Production")
|
||||
{
|
||||
var provider = GetServiceProvider(initialData, environment);
|
||||
@@ -174,23 +104,34 @@ public class LoggerFactoryExtensionsTests
|
||||
.AddInMemoryCollection(initialData)
|
||||
.Build();
|
||||
|
||||
var hostingEnvironment = Substitute.For<IWebHostEnvironment>();
|
||||
var hostingEnvironment = Substitute.For<IHostEnvironment>();
|
||||
|
||||
hostingEnvironment
|
||||
.EnvironmentName
|
||||
.Returns(environment);
|
||||
|
||||
var context = new WebHostBuilderContext
|
||||
var context = new HostBuilderContext(new Dictionary<object, object>())
|
||||
{
|
||||
HostingEnvironment = hostingEnvironment,
|
||||
Configuration = config,
|
||||
};
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.AddSerilog(context);
|
||||
});
|
||||
|
||||
var hostBuilder = Substitute.For<IHostBuilder>();
|
||||
hostBuilder
|
||||
.When(h => h.ConfigureServices(Arg.Any<Action<HostBuilderContext, IServiceCollection>>()))
|
||||
.Do(call =>
|
||||
{
|
||||
var configureAction = call.Arg<Action<HostBuilderContext, IServiceCollection>>();
|
||||
configureAction(context, services);
|
||||
});
|
||||
|
||||
hostBuilder.AddSerilogFileLogging();
|
||||
|
||||
hostBuilder
|
||||
.ConfigureServices(Arg.Any<Action<HostBuilderContext, IServiceCollection>>())
|
||||
.Received(1);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Utilities;
|
||||
@@ -7,28 +6,6 @@ namespace Bit.Core.Test.Utilities;
|
||||
|
||||
public class StaticStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void StaticStore_Initialization_Success()
|
||||
{
|
||||
var plans = StaticStore.Plans.ToList();
|
||||
Assert.NotNull(plans);
|
||||
Assert.NotEmpty(plans);
|
||||
Assert.Equal(23, plans.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PlanType.EnterpriseAnnually)]
|
||||
[InlineData(PlanType.EnterpriseMonthly)]
|
||||
[InlineData(PlanType.TeamsMonthly)]
|
||||
[InlineData(PlanType.TeamsAnnually)]
|
||||
[InlineData(PlanType.TeamsStarter)]
|
||||
public void StaticStore_GetPlan_Success(PlanType planType)
|
||||
{
|
||||
var plan = StaticStore.GetPlan(planType);
|
||||
Assert.NotNull(plan);
|
||||
Assert.Equal(planType, plan.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticStore_GlobalEquivalentDomains_OnlyAsciiAllowed()
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ public class NormalCipherPermissionTests
|
||||
var cipherDetails = new CipherDetails { UserId = null, OrganizationId = Guid.NewGuid() };
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<Exception>(() => NormalCipherPermissions.CanDelete(user, cipherDetails, organizationAbility));
|
||||
var exception = Assert.Throws<Exception>(() => NormalCipherPermissions.CanRestore(user, cipherDetails, organizationAbility));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Cipher does not belong to the input organization.", exception.Message);
|
||||
@@ -92,11 +92,11 @@ public class NormalCipherPermissionTests
|
||||
// Arrange
|
||||
var user = new User { Id = Guid.Empty };
|
||||
var organizationId = Guid.NewGuid();
|
||||
var cipherDetails = new CipherDetails { Manage = manage, Edit = edit, UserId = null, OrganizationId = organizationId };
|
||||
var cipherDetails = new CipherDetails { Manage = manage, Edit = edit, UserId = user.Id, OrganizationId = organizationId };
|
||||
var organizationAbility = new OrganizationAbility { Id = organizationId, LimitItemDeletion = limitItemDeletion };
|
||||
|
||||
// Act
|
||||
var result = NormalCipherPermissions.CanRestore(user, cipherDetails, organizationAbility);
|
||||
var result = NormalCipherPermissions.CanDelete(user, cipherDetails, organizationAbility);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result, expectedResult);
|
||||
|
||||
Reference in New Issue
Block a user