mirror of
https://github.com/bitwarden/server
synced 2026-01-02 16:43:25 +00:00
Merge branch 'main' into auth/pm-22975/client-version-validator
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||
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<IOrganizationRepository>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_RegistersAllServices()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName);
|
||||
Assert.NotNull(cache);
|
||||
|
||||
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>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_CommandsQueries_AreRegisteredAsScoped()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var createIntegrationDescriptor = _services.First(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
||||
|
||||
Assert.Equal(ServiceLifetime.Scoped, createIntegrationDescriptor.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(ICreateOrganizationIntegrationCommand)).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);
|
||||
}
|
||||
|
||||
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,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,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>());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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.AdminConsole.Repositories;
|
||||
@@ -8,6 +10,7 @@ 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;
|
||||
@@ -36,12 +39,16 @@ 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)
|
||||
@@ -173,6 +180,37 @@ public class EventIntegrationHandlerTests
|
||||
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)
|
||||
{
|
||||
@@ -211,6 +249,32 @@ public class EventIntegrationHandlerTests
|
||||
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)
|
||||
{
|
||||
@@ -250,6 +314,32 @@ public class EventIntegrationHandlerTests
|
||||
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)
|
||||
{
|
||||
@@ -313,6 +403,38 @@ public class EventIntegrationHandlerTests
|
||||
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)
|
||||
{
|
||||
@@ -344,6 +466,12 @@ public class EventIntegrationHandlerTests
|
||||
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());
|
||||
@@ -362,8 +490,8 @@ 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);
|
||||
|
||||
@@ -382,8 +510,8 @@ public class EventIntegrationHandlerTests
|
||||
[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);
|
||||
|
||||
@@ -405,6 +533,7 @@ public class EventIntegrationHandlerTests
|
||||
[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);
|
||||
@@ -416,10 +545,10 @@ 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);
|
||||
|
||||
@@ -435,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);
|
||||
@@ -444,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);
|
||||
@@ -459,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(
|
||||
@@ -477,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(
|
||||
@@ -494,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>>());
|
||||
}
|
||||
}
|
||||
@@ -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>()
|
||||
|
||||
@@ -1017,6 +1017,7 @@ public class RegisterUserCommandTests
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
[BitAutoData(PlanType.FamiliesAnnually2019)]
|
||||
[BitAutoData(PlanType.FamiliesAnnually2025)]
|
||||
[BitAutoData(PlanType.Free)]
|
||||
public async Task SendWelcomeEmail_FamilyOrg_SendsFamilyWelcomeEmail(
|
||||
PlanType planType,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
@@ -11,8 +12,12 @@ public class EventIntegrationsCacheConstantsTests
|
||||
{
|
||||
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]
|
||||
@@ -20,8 +25,69 @@ public class EventIntegrationsCacheConstantsTests
|
||||
{
|
||||
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);
|
||||
|
||||
var expectedWithNullEvent = $"OrganizationIntegrationConfigurationDetails:{orgId:N}:Hec:";
|
||||
var keyWithNullEvent = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, integrationType, null);
|
||||
var keyWithNullEventDifferentIntegration = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, IntegrationType.Webhook, null);
|
||||
var keyWithNullEventDifferentOrganization = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
Guid.NewGuid(), integrationType, null);
|
||||
|
||||
Assert.Equal(expectedWithNullEvent, keyWithNullEvent);
|
||||
Assert.NotEqual(keyWithEvent, keyWithNullEvent);
|
||||
Assert.NotEqual(keyWithNullEvent, keyWithDifferentEvent);
|
||||
Assert.NotEqual(keyWithNullEvent, keyWithNullEventDifferentIntegration);
|
||||
Assert.NotEqual(keyWithNullEvent, keyWithNullEventDifferentOrganization);
|
||||
}
|
||||
|
||||
[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]
|
||||
@@ -29,8 +95,14 @@ public class EventIntegrationsCacheConstantsTests
|
||||
{
|
||||
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]
|
||||
@@ -38,4 +110,13 @@ public class EventIntegrationsCacheConstantsTests
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -167,7 +168,7 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
var settings = CreateGlobalSettings(new()
|
||||
{
|
||||
{ "GlobalSettings:DistributedCache:Redis:ConnectionString", "localhost:6379" },
|
||||
{ "GlobalSettings:DistributedCache:DefaultExtendedCache:UseSharedRedisCache", "true" }
|
||||
{ "GlobalSettings:DistributedCache:DefaultExtendedCache:UseSharedDistributedCache", "true" }
|
||||
});
|
||||
|
||||
// Provide a multiplexer (shared)
|
||||
@@ -187,7 +188,7 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedRedisCache = false,
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "invalid:9999" }
|
||||
};
|
||||
|
||||
@@ -242,7 +243,7 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
{
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedRedisCache = false,
|
||||
UseSharedDistributedCache = false,
|
||||
// No Redis connection string
|
||||
};
|
||||
|
||||
@@ -261,13 +262,13 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
var settingsA = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
EnableDistributedCache = true,
|
||||
UseSharedRedisCache = false,
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "localhost:6379" }
|
||||
};
|
||||
var settingsB = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
EnableDistributedCache = true,
|
||||
UseSharedRedisCache = false,
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "localhost:6380" }
|
||||
};
|
||||
|
||||
@@ -294,7 +295,7 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
|
||||
var settings = new GlobalSettings.ExtendedCacheSettings
|
||||
{
|
||||
UseSharedRedisCache = false,
|
||||
UseSharedDistributedCache = false,
|
||||
Redis = new GlobalSettings.ConnectionStringSettings { ConnectionString = "localhost:6379" }
|
||||
};
|
||||
|
||||
@@ -306,6 +307,180 @@ public class ExtendedCacheServiceCollectionExtensionsTests
|
||||
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 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)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
|
||||
@@ -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