mirror of
https://github.com/bitwarden/server
synced 2026-01-13 14:03:58 +00:00
Move all event integration code to Dirt (#6757)
* Move all event integration code to Dirt * Format to fix lint
This commit is contained in:
@@ -1,912 +0,0 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.AdminConsole.Services.NoopImplementations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NSubstitute;
|
||||
using StackExchange.Redis;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations;
|
||||
|
||||
public class EventIntegrationServiceCollectionExtensionsTests
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public EventIntegrationServiceCollectionExtensionsTests()
|
||||
{
|
||||
_services = new ServiceCollection();
|
||||
_globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
// Add required infrastructure services
|
||||
_services.TryAddSingleton(_globalSettings);
|
||||
_services.TryAddSingleton<IGlobalSettings>(_globalSettings);
|
||||
_services.AddLogging();
|
||||
|
||||
// Mock Redis connection for cache
|
||||
_services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
|
||||
// Mock required repository dependencies for commands
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationRepository>());
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationRepository>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_RegistersAllServices()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
using var provider = _services.BuildServiceProvider();
|
||||
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName);
|
||||
Assert.NotNull(cache);
|
||||
|
||||
var validator = provider.GetRequiredService<IOrganizationIntegrationConfigurationValidator>();
|
||||
Assert.NotNull(validator);
|
||||
|
||||
using var scope = provider.CreateScope();
|
||||
var sp = scope.ServiceProvider;
|
||||
|
||||
Assert.NotNull(sp.GetService<ICreateOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IGetOrganizationIntegrationsQuery>());
|
||||
|
||||
Assert.NotNull(sp.GetService<ICreateOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IGetOrganizationIntegrationConfigurationsQuery>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_CommandsQueries_AreRegisteredAsScoped()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var createIntegrationDescriptor = _services.First(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
||||
var createConfigDescriptor = _services.First(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand));
|
||||
|
||||
Assert.Equal(ServiceLifetime.Scoped, createIntegrationDescriptor.Lifetime);
|
||||
Assert.Equal(ServiceLifetime.Scoped, createConfigDescriptor.Lifetime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_CommandsQueries_DifferentInstancesPerScope()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var provider = _services.BuildServiceProvider();
|
||||
|
||||
ICreateOrganizationIntegrationCommand? instance1, instance2, instance3;
|
||||
using (var scope1 = provider.CreateScope())
|
||||
{
|
||||
instance1 = scope1.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
}
|
||||
using (var scope2 = provider.CreateScope())
|
||||
{
|
||||
instance2 = scope2.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
}
|
||||
using (var scope3 = provider.CreateScope())
|
||||
{
|
||||
instance3 = scope3.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
}
|
||||
|
||||
Assert.NotNull(instance1);
|
||||
Assert.NotNull(instance2);
|
||||
Assert.NotNull(instance3);
|
||||
Assert.NotSame(instance1, instance2);
|
||||
Assert.NotSame(instance2, instance3);
|
||||
Assert.NotSame(instance1, instance3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_CommandsQueries__SameInstanceWithinScope()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
var provider = _services.BuildServiceProvider();
|
||||
|
||||
using var scope = provider.CreateScope();
|
||||
var instance1 = scope.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
var instance2 = scope.ServiceProvider.GetService<ICreateOrganizationIntegrationCommand>();
|
||||
|
||||
Assert.NotNull(instance1);
|
||||
Assert.NotNull(instance2);
|
||||
Assert.Same(instance1, instance2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationsCommandsQueries_MultipleCalls_IsIdempotent()
|
||||
{
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var createConfigCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand)).ToList();
|
||||
Assert.Single(createConfigCmdDescriptors);
|
||||
|
||||
var updateIntegrationCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(IUpdateOrganizationIntegrationCommand)).ToList();
|
||||
Assert.Single(updateIntegrationCmdDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationCommandsQueries_RegistersAllIntegrationServices()
|
||||
{
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IUpdateOrganizationIntegrationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IDeleteOrganizationIntegrationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IGetOrganizationIntegrationsQuery));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationCommandsQueries_MultipleCalls_IsIdempotent()
|
||||
{
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationCommandsQueries();
|
||||
|
||||
var createCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand)).ToList();
|
||||
Assert.Single(createCmdDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationConfigurationCommandsQueries_RegistersAllConfigurationServices()
|
||||
{
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IUpdateOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IDeleteOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IGetOrganizationIntegrationConfigurationsQuery));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationConfigurationCommandsQueries_MultipleCalls_IsIdempotent()
|
||||
{
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
var createCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand)).ToList();
|
||||
Assert.Single(createCmdDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRabbitMqEnabled_AllSettingsPresent_ReturnsTrue()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.True(EventIntegrationsServiceCollectionExtensions.IsRabbitMqEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRabbitMqEnabled_MissingHostName_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = null,
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsRabbitMqEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRabbitMqEnabled_MissingUsername_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = null,
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsRabbitMqEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRabbitMqEnabled_MissingPassword_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = null,
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsRabbitMqEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRabbitMqEnabled_MissingEventExchangeName_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = null,
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsRabbitMqEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRabbitMqEnabled_MissingIntegrationExchangeName_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = null
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsRabbitMqEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAzureServiceBusEnabled_AllSettingsPresent_ReturnsTrue()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.True(EventIntegrationsServiceCollectionExtensions.IsAzureServiceBusEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAzureServiceBusEnabled_MissingConnectionString_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = null,
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsAzureServiceBusEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAzureServiceBusEnabled_MissingEventTopicName_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = null,
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration"
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsAzureServiceBusEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAzureServiceBusEnabled_MissingIntegrationTopicName_ReturnsFalse()
|
||||
{
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = null
|
||||
});
|
||||
|
||||
Assert.False(EventIntegrationsServiceCollectionExtensions.IsAzureServiceBusEnabled(globalSettings));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSlackService_AllSettingsPresent_RegistersSlackService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:Slack:ClientId"] = "test-client-id",
|
||||
["GlobalSettings:Slack:ClientSecret"] = "test-client-secret",
|
||||
["GlobalSettings:Slack:Scopes"] = "test-scopes"
|
||||
});
|
||||
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.AddLogging();
|
||||
services.AddSlackService(globalSettings);
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var slackService = provider.GetService<ISlackService>();
|
||||
|
||||
Assert.NotNull(slackService);
|
||||
Assert.IsType<SlackService>(slackService);
|
||||
|
||||
var httpClientDescriptor = services.FirstOrDefault(s =>
|
||||
s.ServiceType == typeof(IHttpClientFactory));
|
||||
Assert.NotNull(httpClientDescriptor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSlackService_SettingsMissing_RegistersNoopService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:Slack:ClientId"] = null,
|
||||
["GlobalSettings:Slack:ClientSecret"] = null,
|
||||
["GlobalSettings:Slack:Scopes"] = null
|
||||
});
|
||||
|
||||
services.AddSlackService(globalSettings);
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var slackService = provider.GetService<ISlackService>();
|
||||
|
||||
Assert.NotNull(slackService);
|
||||
Assert.IsType<NoopSlackService>(slackService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTeamsService_AllSettingsPresent_RegistersTeamsServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:Teams:ClientId"] = "test-client-id",
|
||||
["GlobalSettings:Teams:ClientSecret"] = "test-client-secret",
|
||||
["GlobalSettings:Teams:Scopes"] = "test-scopes"
|
||||
});
|
||||
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.AddLogging();
|
||||
services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationRepository>());
|
||||
services.AddTeamsService(globalSettings);
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
var teamsService = provider.GetService<ITeamsService>();
|
||||
Assert.NotNull(teamsService);
|
||||
Assert.IsType<TeamsService>(teamsService);
|
||||
|
||||
var bot = provider.GetService<IBot>();
|
||||
Assert.NotNull(bot);
|
||||
Assert.IsType<TeamsService>(bot);
|
||||
|
||||
var adapter = provider.GetService<IBotFrameworkHttpAdapter>();
|
||||
Assert.NotNull(adapter);
|
||||
Assert.IsType<BotFrameworkHttpAdapter>(adapter);
|
||||
|
||||
var httpClientDescriptor = services.FirstOrDefault(s =>
|
||||
s.ServiceType == typeof(IHttpClientFactory));
|
||||
Assert.NotNull(httpClientDescriptor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTeamsService_SettingsMissing_RegistersNoopService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:Teams:ClientId"] = null,
|
||||
["GlobalSettings:Teams:ClientSecret"] = null,
|
||||
["GlobalSettings:Teams:Scopes"] = null
|
||||
});
|
||||
|
||||
services.AddTeamsService(globalSettings);
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var teamsService = provider.GetService<ITeamsService>();
|
||||
|
||||
Assert.NotNull(teamsService);
|
||||
Assert.IsType<NoopTeamsService>(teamsService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRabbitMqIntegration_RegistersEventIntegrationHandler()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var listenerConfig = new TestListenerConfiguration();
|
||||
|
||||
// Add required dependencies
|
||||
services.TryAddSingleton(Substitute.For<IEventIntegrationPublisher>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationFilterService>());
|
||||
services.TryAddKeyedSingleton(EventIntegrationsCacheConstants.CacheName, Substitute.For<IFusionCache>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IGroupRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationUserRepository>());
|
||||
services.AddLogging();
|
||||
|
||||
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, TestListenerConfiguration>(listenerConfig);
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var handler = provider.GetRequiredKeyedService<IEventMessageHandler>(listenerConfig.RoutingKey);
|
||||
|
||||
Assert.NotNull(handler);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRabbitMqIntegration_RegistersEventListenerService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var listenerConfig = new TestListenerConfiguration();
|
||||
|
||||
// Add required dependencies
|
||||
services.TryAddSingleton(Substitute.For<IEventIntegrationPublisher>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationFilterService>());
|
||||
services.TryAddKeyedSingleton(EventIntegrationsCacheConstants.CacheName, Substitute.For<IFusionCache>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IGroupRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationUserRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IRabbitMqService>());
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, TestListenerConfiguration>(listenerConfig);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// AddRabbitMqIntegration should register 2 hosted services (Event + Integration listeners)
|
||||
Assert.Equal(2, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRabbitMqIntegration_RegistersIntegrationListenerService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var listenerConfig = new TestListenerConfiguration();
|
||||
|
||||
// Add required dependencies
|
||||
services.TryAddSingleton(Substitute.For<IEventIntegrationPublisher>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationFilterService>());
|
||||
services.TryAddKeyedSingleton(EventIntegrationsCacheConstants.CacheName, Substitute.For<IFusionCache>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IGroupRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationUserRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IRabbitMqService>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationHandler<WebhookIntegrationConfigurationDetails>>());
|
||||
services.TryAddSingleton(TimeProvider.System);
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, TestListenerConfiguration>(listenerConfig);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// AddRabbitMqIntegration should register 2 hosted services (Event + Integration listeners)
|
||||
Assert.Equal(2, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAzureServiceBusIntegration_RegistersEventIntegrationHandler()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var listenerConfig = new TestListenerConfiguration();
|
||||
|
||||
// Add required dependencies
|
||||
services.TryAddSingleton(Substitute.For<IEventIntegrationPublisher>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationFilterService>());
|
||||
services.TryAddKeyedSingleton(EventIntegrationsCacheConstants.CacheName, Substitute.For<IFusionCache>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IGroupRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationUserRepository>());
|
||||
services.AddLogging();
|
||||
|
||||
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, TestListenerConfiguration>(listenerConfig);
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var handler = provider.GetRequiredKeyedService<IEventMessageHandler>(listenerConfig.RoutingKey);
|
||||
|
||||
Assert.NotNull(handler);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAzureServiceBusIntegration_RegistersEventListenerService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var listenerConfig = new TestListenerConfiguration();
|
||||
|
||||
// Add required dependencies
|
||||
services.TryAddSingleton(Substitute.For<IEventIntegrationPublisher>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationFilterService>());
|
||||
services.TryAddKeyedSingleton(EventIntegrationsCacheConstants.CacheName, Substitute.For<IFusionCache>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IGroupRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationUserRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IAzureServiceBusService>());
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, TestListenerConfiguration>(listenerConfig);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// AddAzureServiceBusIntegration should register 2 hosted services (Event + Integration listeners)
|
||||
Assert.Equal(2, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAzureServiceBusIntegration_RegistersIntegrationListenerService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var listenerConfig = new TestListenerConfiguration();
|
||||
|
||||
// Add required dependencies
|
||||
services.TryAddSingleton(Substitute.For<IEventIntegrationPublisher>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationFilterService>());
|
||||
services.TryAddKeyedSingleton(EventIntegrationsCacheConstants.CacheName, Substitute.For<IFusionCache>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IGroupRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IOrganizationUserRepository>());
|
||||
services.TryAddSingleton(Substitute.For<IAzureServiceBusService>());
|
||||
services.TryAddSingleton(Substitute.For<IIntegrationHandler<WebhookIntegrationConfigurationDetails>>());
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, TestListenerConfiguration>(listenerConfig);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// AddAzureServiceBusIntegration should register 2 hosted services (Event + Integration listeners)
|
||||
Assert.Equal(2, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationServices_RegistersCommonServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
|
||||
// Verify common services are registered
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IIntegrationFilterService));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(TimeProvider));
|
||||
|
||||
// Verify HttpClients for handlers are registered
|
||||
var httpClientDescriptors = services.Where(s => s.ServiceType == typeof(IHttpClientFactory)).ToList();
|
||||
Assert.NotEmpty(httpClientDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationServices_RegistersIntegrationHandlers()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
|
||||
// Verify integration handlers are registered
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IIntegrationHandler<SlackIntegrationConfigurationDetails>));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IIntegrationHandler<WebhookIntegrationConfigurationDetails>));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IIntegrationHandler<DatadogIntegrationConfigurationDetails>));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IIntegrationHandler<TeamsIntegrationConfigurationDetails>));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationServices_RabbitMqEnabled_RegistersRabbitMqListeners()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// Should register 11 hosted services for RabbitMQ: 1 repository + 5*2 integration listeners (event+integration)
|
||||
Assert.Equal(11, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationServices_AzureServiceBusEnabled_RegistersAzureListeners()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration"
|
||||
});
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// Should register 11 hosted services for Azure Service Bus: 1 repository + 5*2 integration listeners (event+integration)
|
||||
Assert.Equal(11, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationServices_BothEnabled_AzureServiceBusTakesPrecedence()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration"
|
||||
});
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// Should register 11 hosted services for Azure Service Bus: 1 repository + 5*2 integration listeners (event+integration)
|
||||
// NO RabbitMQ services should be enabled because ASB takes precedence
|
||||
Assert.Equal(11, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventIntegrationServices_NeitherEnabled_RegistersNoListeners()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
var beforeCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
var afterCount = services.Count(s => s.ServiceType == typeof(IHostedService));
|
||||
|
||||
// Should register no hosted services when neither RabbitMQ nor Azure Service Bus is enabled
|
||||
Assert.Equal(0, afterCount - beforeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventWriteServices_AzureServiceBusEnabled_RegistersAzureServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration"
|
||||
});
|
||||
|
||||
services.AddEventWriteServices(globalSettings);
|
||||
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventIntegrationPublisher) && s.ImplementationType == typeof(AzureServiceBusService));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(EventIntegrationEventWriteService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventWriteServices_RabbitMqEnabled_RegistersRabbitMqServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
services.AddEventWriteServices(globalSettings);
|
||||
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventIntegrationPublisher) && s.ImplementationType == typeof(RabbitMqService));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(EventIntegrationEventWriteService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventWriteServices_EventsConnectionStringPresent_RegistersAzureQueueService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:Events:ConnectionString"] = "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;EndpointSuffix=core.windows.net",
|
||||
["GlobalSettings:Events:QueueName"] = "event"
|
||||
});
|
||||
|
||||
services.AddEventWriteServices(globalSettings);
|
||||
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(AzureQueueEventWriteService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventWriteServices_SelfHosted_RegistersRepositoryService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:SelfHosted"] = "true"
|
||||
});
|
||||
|
||||
services.AddEventWriteServices(globalSettings);
|
||||
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(RepositoryEventWriteService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventWriteServices_NothingEnabled_RegistersNoopService()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
services.AddEventWriteServices(globalSettings);
|
||||
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(NoopEventWriteService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEventWriteServices_AzureTakesPrecedenceOverRabbitMq()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration",
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
services.AddEventWriteServices(globalSettings);
|
||||
|
||||
// Should use Azure Service Bus, not RabbitMQ
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventIntegrationPublisher) && s.ImplementationType == typeof(AzureServiceBusService));
|
||||
Assert.DoesNotContain(services, s => s.ServiceType == typeof(IEventIntegrationPublisher) && s.ImplementationType == typeof(RabbitMqService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAzureServiceBusListeners_AzureServiceBusEnabled_RegistersAzureServiceBusServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events",
|
||||
["GlobalSettings:EventLogging:AzureServiceBus:IntegrationTopicName"] = "integration"
|
||||
});
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
services.AddAzureServiceBusListeners(globalSettings);
|
||||
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IAzureServiceBusService));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IEventRepository));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(AzureTableStorageEventHandler));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAzureServiceBusListeners_AzureServiceBusDisabled_ReturnsEarly()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
var initialCount = services.Count;
|
||||
services.AddAzureServiceBusListeners(globalSettings);
|
||||
var finalCount = services.Count;
|
||||
|
||||
Assert.Equal(initialCount, finalCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRabbitMqListeners_RabbitMqEnabled_RegistersRabbitMqServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
|
||||
{
|
||||
["GlobalSettings:EventLogging:RabbitMq:HostName"] = "localhost",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
|
||||
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
|
||||
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange",
|
||||
["GlobalSettings:EventLogging:RabbitMq:IntegrationExchangeName"] = "integration"
|
||||
});
|
||||
|
||||
// Add prerequisites
|
||||
services.TryAddSingleton(globalSettings);
|
||||
services.TryAddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
services.AddLogging();
|
||||
|
||||
services.AddRabbitMqListeners(globalSettings);
|
||||
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(IRabbitMqService));
|
||||
Assert.Contains(services, s => s.ServiceType == typeof(EventRepositoryHandler));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRabbitMqListeners_RabbitMqDisabled_ReturnsEarly()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var globalSettings = CreateGlobalSettings([]);
|
||||
|
||||
var initialCount = services.Count;
|
||||
services.AddRabbitMqListeners(globalSettings);
|
||||
var finalCount = services.Count;
|
||||
|
||||
Assert.Equal(initialCount, finalCount);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CreateOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_Success_CreatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(configuration)
|
||||
.Returns(configuration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
configuration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(configuration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_WildcardSuccess_CreatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(configuration)
|
||||
.Returns(configuration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
Assert.Equal(configuration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_ValidationFails_ThrowsBadRequest(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(false);
|
||||
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.Template = "template";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_Success_DeletesConfigurationAndInvalidatesCache(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
configuration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_WildcardSuccess_DeletesConfigurationAndInvalidatesCache(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_ConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns((OrganizationIntegrationConfiguration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_ConfigurationDoesNotBelongToIntegration_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = Guid.NewGuid(); // Different integration
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GetOrganizationIntegrationConfigurationsQueryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_Success_ReturnsConfigurations(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
List<OrganizationIntegrationConfiguration> configurations)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(integrationId)
|
||||
.Returns(configurations);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetManyByIntegrationAsync(integrationId);
|
||||
Assert.Equal(configurations.Count, result.Count);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_NoConfigurations_ReturnsEmptyList(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(integrationId)
|
||||
.Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UpdateOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_Success_UpdatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
existingConfiguration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WildcardSuccess_UpdatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = null;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedEventType_UpdatesConfigurationAndInvalidatesCacheForBothTypes(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.EventType = EventType.Cipher_Created;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
existingConfiguration.EventType.Value));
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
updatedConfiguration.EventType.Value));
|
||||
// Verify RemoveByTagAsync was NOT called since both are specific event types
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns((OrganizationIntegrationConfiguration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ConfigurationDoesNotBelongToIntegration_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = Guid.NewGuid(); // Different integration
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ValidationFails_ThrowsBadRequest(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(false);
|
||||
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.Template = "template";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedFromWildcardToSpecific_InvalidatesAllCaches(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration,
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider)
|
||||
{
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = null; // Wildcard
|
||||
updatedConfiguration.EventType = EventType.User_LoggedIn; // Specific
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId).Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(existingConfiguration.Id).Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, existingConfiguration.Id, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedFromSpecificToWildcard_InvalidatesAllCaches(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration,
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider)
|
||||
{
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn; // Specific
|
||||
updatedConfiguration.EventType = null; // Wildcard
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId).Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(existingConfiguration.Id).Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, existingConfiguration.Id, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
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>());
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
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>());
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Models.Data.EventIntegrations;
|
||||
|
||||
public class IntegrationHandlerResultTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void Succeed_SetsSuccessTrue_CategoryNull(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Null(result.Category);
|
||||
Assert.Equal(message, result.Message);
|
||||
Assert.Null(result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Fail_WithCategory_SetsSuccessFalse_CategorySet(IntegrationMessage message)
|
||||
{
|
||||
var category = IntegrationFailureCategory.AuthenticationFailed;
|
||||
var failureReason = "Invalid credentials";
|
||||
|
||||
var result = IntegrationHandlerResult.Fail(message, category, failureReason);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(category, result.Category);
|
||||
Assert.Equal(failureReason, result.FailureReason);
|
||||
Assert.Equal(message, result.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Fail_WithDelayUntil_SetsDelayUntilDate(IntegrationMessage message)
|
||||
{
|
||||
var delayUntil = DateTime.UtcNow.AddMinutes(5);
|
||||
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.RateLimited,
|
||||
"Rate limited",
|
||||
delayUntil
|
||||
);
|
||||
|
||||
Assert.Equal(delayUntil, result.DelayUntilDate);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_RateLimited_ReturnsTrue(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.RateLimited,
|
||||
"Rate limited"
|
||||
);
|
||||
|
||||
Assert.True(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_TransientError_ReturnsTrue(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.TransientError,
|
||||
"Temporary network issue"
|
||||
);
|
||||
|
||||
Assert.True(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_AuthenticationFailed_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.AuthenticationFailed,
|
||||
"Invalid token"
|
||||
);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_ConfigurationError_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
"Channel not found"
|
||||
);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_ServiceUnavailable_ReturnsTrue(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
"Service is down"
|
||||
);
|
||||
|
||||
Assert.True(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_PermanentFailure_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.PermanentFailure,
|
||||
"Permanent failure"
|
||||
);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_SuccessCase_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models.Data.EventIntegrations;
|
||||
|
||||
public class IntegrationMessageTests
|
||||
{
|
||||
private const string _messageId = "TestMessageId";
|
||||
private const string _organizationId = "TestOrganizationId";
|
||||
|
||||
[Fact]
|
||||
public void ApplyRetry_IncrementsRetryCountAndSetsDelayUntilDate()
|
||||
{
|
||||
var message = new IntegrationMessage<WebhookIntegrationConfigurationDetails>
|
||||
{
|
||||
Configuration = new WebhookIntegrationConfigurationDetails(new Uri("https://localhost"), "Bearer", "AUTH-TOKEN"),
|
||||
MessageId = _messageId,
|
||||
OrganizationId = _organizationId,
|
||||
RetryCount = 2,
|
||||
RenderedTemplate = string.Empty,
|
||||
DelayUntilDate = null
|
||||
};
|
||||
|
||||
var baseline = DateTime.UtcNow;
|
||||
message.ApplyRetry(baseline);
|
||||
|
||||
Assert.Equal(3, message.RetryCount);
|
||||
Assert.NotNull(message.DelayUntilDate);
|
||||
Assert.True(message.DelayUntilDate > baseline);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromToJson_SerializesCorrectly()
|
||||
{
|
||||
var message = new IntegrationMessage<WebhookIntegrationConfigurationDetails>
|
||||
{
|
||||
Configuration = new WebhookIntegrationConfigurationDetails(new Uri("https://localhost"), "Bearer", "AUTH-TOKEN"),
|
||||
MessageId = _messageId,
|
||||
OrganizationId = _organizationId,
|
||||
RenderedTemplate = "This is the message",
|
||||
IntegrationType = IntegrationType.Webhook,
|
||||
RetryCount = 2,
|
||||
DelayUntilDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var json = message.ToJson();
|
||||
var result = IntegrationMessage<WebhookIntegrationConfigurationDetails>.FromJson(json);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(message.Configuration, result.Configuration);
|
||||
Assert.Equal(message.MessageId, result.MessageId);
|
||||
Assert.Equal(message.OrganizationId, result.OrganizationId);
|
||||
Assert.Equal(message.RenderedTemplate, result.RenderedTemplate);
|
||||
Assert.Equal(message.IntegrationType, result.IntegrationType);
|
||||
Assert.Equal(message.RetryCount, result.RetryCount);
|
||||
Assert.Equal(message.DelayUntilDate, result.DelayUntilDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromJson_InvalidJson_ThrowsJsonException()
|
||||
{
|
||||
var json = "{ Invalid JSON";
|
||||
Assert.Throws<JsonException>(() => IntegrationMessage<WebhookIntegrationConfigurationDetails>.FromJson(json));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_BaseIntegrationMessage_DeserializesCorrectly()
|
||||
{
|
||||
var message = new IntegrationMessage
|
||||
{
|
||||
MessageId = _messageId,
|
||||
OrganizationId = _organizationId,
|
||||
RenderedTemplate = "This is the message",
|
||||
IntegrationType = IntegrationType.Webhook,
|
||||
RetryCount = 2,
|
||||
DelayUntilDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var json = message.ToJson();
|
||||
var result = JsonSerializer.Deserialize<IntegrationMessage>(json);
|
||||
|
||||
Assert.Equal(message.MessageId, result.MessageId);
|
||||
Assert.Equal(message.OrganizationId, result.OrganizationId);
|
||||
Assert.Equal(message.RenderedTemplate, result.RenderedTemplate);
|
||||
Assert.Equal(message.IntegrationType, result.IntegrationType);
|
||||
Assert.Equal(message.RetryCount, result.RetryCount);
|
||||
Assert.Equal(message.DelayUntilDate, result.DelayUntilDate);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Models.Data.EventIntegrations;
|
||||
|
||||
public class IntegrationOAuthStateTests
|
||||
{
|
||||
private readonly FakeTimeProvider _fakeTimeProvider = new(
|
||||
new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc)
|
||||
);
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void FromIntegration_ToString_RoundTripsCorrectly(OrganizationIntegration integration)
|
||||
{
|
||||
var state = IntegrationOAuthState.FromIntegration(integration, _fakeTimeProvider);
|
||||
var parsed = IntegrationOAuthState.FromString(state.ToString(), _fakeTimeProvider);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(state.IntegrationId, parsed.IntegrationId);
|
||||
Assert.True(parsed.ValidateOrg(integration.OrganizationId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("not-a-valid-state")]
|
||||
public void FromString_InvalidString_ReturnsNull(string state)
|
||||
{
|
||||
var parsed = IntegrationOAuthState.FromString(state, _fakeTimeProvider);
|
||||
|
||||
Assert.Null(parsed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromString_InvalidGuid_ReturnsNull()
|
||||
{
|
||||
var badState = $"not-a-guid.ABCD1234.1706313600";
|
||||
|
||||
var parsed = IntegrationOAuthState.FromString(badState, _fakeTimeProvider);
|
||||
|
||||
Assert.Null(parsed);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void FromString_ExpiredState_ReturnsNull(OrganizationIntegration integration)
|
||||
{
|
||||
var state = IntegrationOAuthState.FromIntegration(integration, _fakeTimeProvider);
|
||||
|
||||
// Advance time 30 minutes to exceed the 20-minute max age
|
||||
_fakeTimeProvider.Advance(TimeSpan.FromMinutes(30));
|
||||
|
||||
var parsed = IntegrationOAuthState.FromString(state.ToString(), _fakeTimeProvider);
|
||||
|
||||
Assert.Null(parsed);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidateOrg_WithCorrectOrgId_ReturnsTrue(OrganizationIntegration integration)
|
||||
{
|
||||
var state = IntegrationOAuthState.FromIntegration(integration, _fakeTimeProvider);
|
||||
|
||||
Assert.True(state.ValidateOrg(integration.OrganizationId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidateOrg_WithWrongOrgId_ReturnsFalse(OrganizationIntegration integration)
|
||||
{
|
||||
var state = IntegrationOAuthState.FromIntegration(integration, _fakeTimeProvider);
|
||||
|
||||
Assert.False(state.ValidateOrg(Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidateOrg_ModifiedTimestamp_ReturnsFalse(OrganizationIntegration integration)
|
||||
{
|
||||
var state = IntegrationOAuthState.FromIntegration(integration, _fakeTimeProvider);
|
||||
var parts = state.ToString().Split('.');
|
||||
|
||||
parts[2] = $"{_fakeTimeProvider.GetUtcNow().ToUnixTimeSeconds() - 1}";
|
||||
var modifiedState = IntegrationOAuthState.FromString(string.Join(".", parts), _fakeTimeProvider);
|
||||
|
||||
Assert.True(state.ValidateOrg(integration.OrganizationId));
|
||||
Assert.NotNull(modifiedState);
|
||||
Assert.False(modifiedState.ValidateOrg(integration.OrganizationId));
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Models.Data.EventIntegrations;
|
||||
|
||||
public class IntegrationTemplateContextTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void EventMessage_ReturnsSerializedJsonOfEvent(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage: eventMessage);
|
||||
var expected = JsonSerializer.Serialize(eventMessage);
|
||||
|
||||
Assert.Equal(expected, sut.EventMessage);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void DateIso8601_ReturnsIso8601FormattedDate(EventMessage eventMessage)
|
||||
{
|
||||
var testDate = new DateTime(2025, 10, 27, 13, 30, 0, DateTimeKind.Utc);
|
||||
eventMessage.Date = testDate;
|
||||
var sut = new IntegrationTemplateContext(eventMessage);
|
||||
|
||||
var result = sut.DateIso8601;
|
||||
|
||||
Assert.Equal("2025-10-27T13:30:00.0000000Z", result);
|
||||
// Verify it's valid ISO 8601
|
||||
Assert.True(DateTime.TryParse(result, out _));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserName_WhenUserIsSet_ReturnsName(EventMessage eventMessage, OrganizationUserUserDetails user)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
|
||||
|
||||
Assert.Equal(user.Name, sut.UserName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserName_WhenUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = null };
|
||||
|
||||
Assert.Null(sut.UserName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserEmail_WhenUserIsSet_ReturnsEmail(EventMessage eventMessage, OrganizationUserUserDetails user)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
|
||||
|
||||
Assert.Equal(user.Email, sut.UserEmail);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserEmail_WhenUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = null };
|
||||
|
||||
Assert.Null(sut.UserEmail);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserType_WhenUserIsSet_ReturnsType(EventMessage eventMessage, OrganizationUserUserDetails user)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
|
||||
|
||||
Assert.Equal(user.Type, sut.UserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void UserType_WhenUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { User = null };
|
||||
|
||||
Assert.Null(sut.UserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserName_WhenActingUserIsSet_ReturnsName(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
|
||||
|
||||
Assert.Equal(actingUser.Name, sut.ActingUserName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserName_WhenActingUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = null };
|
||||
|
||||
Assert.Null(sut.ActingUserName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserEmail_WhenActingUserIsSet_ReturnsEmail(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
|
||||
|
||||
Assert.Equal(actingUser.Email, sut.ActingUserEmail);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserEmail_WhenActingUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = null };
|
||||
|
||||
Assert.Null(sut.ActingUserEmail);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserType_WhenActingUserIsSet_ReturnsType(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
|
||||
|
||||
Assert.Equal(actingUser.Type, sut.ActingUserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ActingUserType_WhenActingUserIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = null };
|
||||
|
||||
Assert.Null(sut.ActingUserType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void OrganizationName_WhenOrganizationIsSet_ReturnsDisplayName(EventMessage eventMessage, Organization organization)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { Organization = organization };
|
||||
|
||||
Assert.Equal(organization.DisplayName(), sut.OrganizationName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void OrganizationName_WhenOrganizationIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { Organization = null };
|
||||
|
||||
Assert.Null(sut.OrganizationName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void GroupName_WhenGroupIsSet_ReturnsName(EventMessage eventMessage, Group group)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { Group = group };
|
||||
|
||||
Assert.Equal(group.Name, sut.GroupName);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void GroupName_WhenGroupIsNull_ReturnsNull(EventMessage eventMessage)
|
||||
{
|
||||
var sut = new IntegrationTemplateContext(eventMessage) { Group = null };
|
||||
|
||||
Assert.Null(sut.GroupName);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
|
||||
public class TestListenerConfiguration : IIntegrationListenerConfiguration
|
||||
{
|
||||
public string EventQueueName => "event_queue";
|
||||
public string EventSubscriptionName => "event_subscription";
|
||||
public string EventTopicName => "event_topic";
|
||||
public IntegrationType IntegrationType => IntegrationType.Webhook;
|
||||
public string IntegrationQueueName => "integration_queue";
|
||||
public string IntegrationRetryQueueName => "integration_retry_queue";
|
||||
public string IntegrationSubscriptionName => "integration_subscription";
|
||||
public string IntegrationTopicName => "integration_topic";
|
||||
public int MaxRetries => 3;
|
||||
public int EventMaxConcurrentCalls => 1;
|
||||
public int EventPrefetchCount => 0;
|
||||
public int IntegrationMaxConcurrentCalls => 1;
|
||||
public int IntegrationPrefetchCount => 0;
|
||||
public string RoutingKey => IntegrationType.ToRoutingKey();
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models.Data.Organizations;
|
||||
|
||||
public class OrganizationIntegrationConfigurationDetailsTests
|
||||
{
|
||||
[Fact]
|
||||
public void MergedConfiguration_WithValidConfigAndIntegration_ReturnsMergedJson()
|
||||
{
|
||||
var config = new { config = "A new config value" };
|
||||
var integration = new { integration = "An integration value" };
|
||||
var expectedObj = new { integration = "An integration value", config = "A new config value" };
|
||||
var expected = JsonSerializer.Serialize(expectedObj);
|
||||
|
||||
var sut = new OrganizationIntegrationConfigurationDetails();
|
||||
sut.Configuration = JsonSerializer.Serialize(config);
|
||||
sut.IntegrationConfiguration = JsonSerializer.Serialize(integration);
|
||||
|
||||
var result = sut.MergedConfiguration;
|
||||
Assert.Equal(expected, result.ToJsonString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergedConfiguration_WithSameKeyIndConfigAndIntegration_GivesPrecedenceToConfiguration()
|
||||
{
|
||||
var config = new { config = "A new config value" };
|
||||
var integration = new { config = "An integration value" };
|
||||
var expectedObj = new { config = "A new config value" };
|
||||
var expected = JsonSerializer.Serialize(expectedObj);
|
||||
|
||||
var sut = new OrganizationIntegrationConfigurationDetails();
|
||||
sut.Configuration = JsonSerializer.Serialize(config);
|
||||
sut.IntegrationConfiguration = JsonSerializer.Serialize(integration);
|
||||
|
||||
var result = sut.MergedConfiguration;
|
||||
Assert.Equal(expected, result.ToJsonString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergedConfiguration_WithInvalidJsonConfigAndIntegration_ReturnsEmptyJson()
|
||||
{
|
||||
var expectedObj = new { };
|
||||
var expected = JsonSerializer.Serialize(expectedObj);
|
||||
|
||||
var sut = new OrganizationIntegrationConfigurationDetails();
|
||||
sut.Configuration = "Not JSON";
|
||||
sut.IntegrationConfiguration = "Not JSON";
|
||||
|
||||
var result = sut.MergedConfiguration;
|
||||
Assert.Equal(expected, result.ToJsonString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergedConfiguration_WithNullConfigAndIntegration_ReturnsEmptyJson()
|
||||
{
|
||||
var expectedObj = new { };
|
||||
var expected = JsonSerializer.Serialize(expectedObj);
|
||||
|
||||
var sut = new OrganizationIntegrationConfigurationDetails();
|
||||
sut.Configuration = null;
|
||||
sut.IntegrationConfiguration = null;
|
||||
|
||||
var result = sut.MergedConfiguration;
|
||||
Assert.Equal(expected, result.ToJsonString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergedConfiguration_WithValidIntegrationAndNullConfig_ReturnsIntegrationJson()
|
||||
{
|
||||
var integration = new { integration = "An integration value" };
|
||||
var expectedObj = new { integration = "An integration value" };
|
||||
var expected = JsonSerializer.Serialize(expectedObj);
|
||||
|
||||
var sut = new OrganizationIntegrationConfigurationDetails();
|
||||
sut.Configuration = null;
|
||||
sut.IntegrationConfiguration = JsonSerializer.Serialize(integration);
|
||||
|
||||
var result = sut.MergedConfiguration;
|
||||
Assert.Equal(expected, result.ToJsonString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergedConfiguration_WithValidConfigAndNullIntegration_ReturnsConfigJson()
|
||||
{
|
||||
var config = new { config = "A new config value" };
|
||||
var expectedObj = new { config = "A new config value" };
|
||||
var expected = JsonSerializer.Serialize(expectedObj);
|
||||
|
||||
var sut = new OrganizationIntegrationConfigurationDetails();
|
||||
sut.Configuration = JsonSerializer.Serialize(config);
|
||||
sut.IntegrationConfiguration = null;
|
||||
|
||||
var result = sut.MergedConfiguration;
|
||||
Assert.Equal(expected, result.ToJsonString());
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Models.Teams;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models.Data.Teams;
|
||||
|
||||
public class TeamsBotCredentialProviderTests
|
||||
{
|
||||
private string _clientId = "client id";
|
||||
private string _clientSecret = "client secret";
|
||||
|
||||
[Fact]
|
||||
public async Task IsValidAppId_MustMatchClientId()
|
||||
{
|
||||
var sut = new TeamsBotCredentialProvider(_clientId, _clientSecret);
|
||||
|
||||
Assert.True(await sut.IsValidAppIdAsync(_clientId));
|
||||
Assert.False(await sut.IsValidAppIdAsync("Different id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAppPasswordAsync_MatchingClientId_ReturnsClientSecret()
|
||||
{
|
||||
var sut = new TeamsBotCredentialProvider(_clientId, _clientSecret);
|
||||
var password = await sut.GetAppPasswordAsync(_clientId);
|
||||
Assert.Equal(_clientSecret, password);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAppPasswordAsync_NotMatchingClientId_ReturnsNull()
|
||||
{
|
||||
var sut = new TeamsBotCredentialProvider(_clientId, _clientSecret);
|
||||
Assert.Null(await sut.GetAppPasswordAsync("Different id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsAuthenticationDisabledAsync_ReturnsFalse()
|
||||
{
|
||||
var sut = new TeamsBotCredentialProvider(_clientId, _clientSecret);
|
||||
Assert.False(await sut.IsAuthenticationDisabledAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateIssuerAsync_ExpectedIssuer_ReturnsTrue()
|
||||
{
|
||||
var sut = new TeamsBotCredentialProvider(_clientId, _clientSecret);
|
||||
Assert.True(await sut.ValidateIssuerAsync(AuthenticationConstants.ToBotFromChannelTokenIssuer));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateIssuerAsync_UnexpectedIssuer_ReturnsFalse()
|
||||
{
|
||||
var sut = new TeamsBotCredentialProvider(_clientId, _clientSecret);
|
||||
Assert.False(await sut.ValidateIssuerAsync("unexpected issuer"));
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Azure.Messaging.ServiceBus;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class AzureServiceBusEventListenerServiceTests
|
||||
{
|
||||
private const string _messageId = "messageId";
|
||||
private readonly TestListenerConfiguration _config = new();
|
||||
private readonly ILogger _logger = Substitute.For<ILogger>();
|
||||
|
||||
private SutProvider<AzureServiceBusEventListenerService<TestListenerConfiguration>> GetSutProvider()
|
||||
{
|
||||
var loggerFactory = Substitute.For<ILoggerFactory>();
|
||||
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
|
||||
return new SutProvider<AzureServiceBusEventListenerService<TestListenerConfiguration>>()
|
||||
.SetDependency(_config)
|
||||
.SetDependency(loggerFactory)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CreatesLogWithCorrectCategory()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
var fullName = typeof(AzureServiceBusEventListenerService<>).FullName ?? "";
|
||||
var tickIndex = fullName.IndexOf('`');
|
||||
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
|
||||
var categoryName = cleanedName + '.' + _config.EventSubscriptionName;
|
||||
|
||||
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CreatesProcessor()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
sutProvider.GetDependency<IAzureServiceBusService>().Received(1).CreateProcessor(
|
||||
Arg.Is(_config.EventTopicName),
|
||||
Arg.Is(_config.EventSubscriptionName),
|
||||
Arg.Any<ServiceBusProcessorOptions>()
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessErrorAsync_LogsError(ProcessErrorEventArgs args)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
await sutProvider.Sut.ProcessErrorAsync(args);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_EmptyJson_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(string.Empty, _messageId);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_InvalidJson_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync("{ Invalid JSON }", _messageId);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Invalid JSON")),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_InvalidJsonArray_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(
|
||||
"{ \"not a valid\", \"list of event messages\" }",
|
||||
_messageId
|
||||
);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_InvalidJsonObject_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(
|
||||
JsonSerializer.Serialize(DateTime.UtcNow), // wrong object - not EventMessage
|
||||
_messageId
|
||||
);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_SingleEvent_DelegatesToHandler(EventMessage message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(
|
||||
JsonSerializer.Serialize(message),
|
||||
_messageId
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IEventMessageHandler>().Received(1).HandleEventAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message, new[] { "IdempotencyId" })));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_ManyEvents_DelegatesToHandler(IEnumerable<EventMessage> messages)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(
|
||||
JsonSerializer.Serialize(messages),
|
||||
_messageId
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IEventMessageHandler>().Received(1).HandleManyEventsAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(messages, new[] { "IdempotencyId" })));
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Azure.Messaging.ServiceBus;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
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 AzureServiceBusIntegrationListenerServiceTests
|
||||
{
|
||||
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
|
||||
private readonly ILogger _logger = Substitute.For<ILogger>();
|
||||
private readonly IAzureServiceBusService _serviceBusService = Substitute.For<IAzureServiceBusService>();
|
||||
private readonly TestListenerConfiguration _config = new();
|
||||
|
||||
private SutProvider<AzureServiceBusIntegrationListenerService<TestListenerConfiguration>> GetSutProvider()
|
||||
{
|
||||
var loggerFactory = Substitute.For<ILoggerFactory>();
|
||||
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
|
||||
return new SutProvider<AzureServiceBusIntegrationListenerService<TestListenerConfiguration>>()
|
||||
.SetDependency(_config)
|
||||
.SetDependency(loggerFactory)
|
||||
.SetDependency(_handler)
|
||||
.SetDependency(_serviceBusService)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CreatesLogWithCorrectCategory()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
var fullName = typeof(AzureServiceBusIntegrationListenerService<>).FullName ?? "";
|
||||
var tickIndex = fullName.IndexOf('`');
|
||||
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
|
||||
var categoryName = cleanedName + '.' + _config.IntegrationSubscriptionName;
|
||||
|
||||
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CreatesProcessor()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
sutProvider.GetDependency<IAzureServiceBusService>().Received(1).CreateProcessor(
|
||||
Arg.Is(_config.IntegrationTopicName),
|
||||
Arg.Is(_config.IntegrationSubscriptionName),
|
||||
Arg.Any<ServiceBusProcessorOptions>()
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessErrorAsync_LogsError(ProcessErrorEventArgs args)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.ProcessErrorAsync(args);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.RetryCount = 0;
|
||||
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.AuthenticationFailed, // NOT retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
Assert.NotNull(expected);
|
||||
|
||||
Assert.False(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - non-recoverable error or max retries exceeded.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.RetryCount = _config.MaxRetries;
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
Assert.NotNull(expected);
|
||||
|
||||
Assert.False(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - non-recoverable error or max retries exceeded.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.RetryCount = 0;
|
||||
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
Assert.NotNull(expected);
|
||||
|
||||
Assert.True(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
await _serviceBusService.Received(1).PublishToRetryAsync(message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
Assert.NotNull(expected);
|
||||
|
||||
Assert.True(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleMessageAsync_UnknownError_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
_handler.HandleAsync(Arg.Any<string>()).ThrowsAsync<JsonException>();
|
||||
|
||||
Assert.True(await sutProvider.Sut.HandleMessageAsync("Bad JSON"));
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Unhandled error processing ASB message")),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
|
||||
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Net;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Bit.Test.Common.MockedHttpClient;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DatadogIntegrationHandlerTests
|
||||
{
|
||||
private readonly MockedHttpMessageHandler _handler;
|
||||
private readonly HttpClient _httpClient;
|
||||
private const string _apiKey = "AUTH_TOKEN";
|
||||
private static readonly Uri _datadogUri = new Uri("https://localhost");
|
||||
|
||||
public DatadogIntegrationHandlerTests()
|
||||
{
|
||||
_handler = new MockedHttpMessageHandler();
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
_httpClient = _handler.ToHttpClient();
|
||||
}
|
||||
|
||||
private SutProvider<DatadogIntegrationHandler> GetSutProvider()
|
||||
{
|
||||
var clientFactory = Substitute.For<IHttpClientFactory>();
|
||||
clientFactory.CreateClient(DatadogIntegrationHandler.HttpClientName).Returns(_httpClient);
|
||||
|
||||
return new SutProvider<DatadogIntegrationHandler>()
|
||||
.SetDependency(clientFactory)
|
||||
.WithFakeTimeProvider()
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_SuccessfulRequest_ReturnsSuccess(IntegrationMessage<DatadogIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new DatadogIntegrationConfigurationDetails(ApiKey: _apiKey, Uri: _datadogUri);
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Null(result.FailureReason);
|
||||
|
||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(DatadogIntegrationHandler.HttpClientName))
|
||||
);
|
||||
|
||||
Assert.Single(_handler.CapturedRequests);
|
||||
var request = _handler.CapturedRequests[0];
|
||||
Assert.NotNull(request);
|
||||
Assert.NotNull(request.Content);
|
||||
var returned = await request.Content.ReadAsStringAsync();
|
||||
|
||||
Assert.Equal(HttpMethod.Post, request.Method);
|
||||
Assert.Equal(_apiKey, request.Headers.GetValues("DD-API-KEY").Single());
|
||||
Assert.Equal(_datadogUri, request.RequestUri);
|
||||
AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<DatadogIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
var retryAfter = now.AddSeconds(60);
|
||||
|
||||
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
|
||||
message.Configuration = new DatadogIntegrationConfigurationDetails(ApiKey: _apiKey, Uri: _datadogUri);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
||||
.WithHeader("Retry-After", "60")
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.True(result.DelayUntilDate.HasValue);
|
||||
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
|
||||
Assert.Equal("Too Many Requests", result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<DatadogIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
var retryAfter = now.AddSeconds(60);
|
||||
message.Configuration = new DatadogIntegrationConfigurationDetails(ApiKey: _apiKey, Uri: _datadogUri);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
||||
.WithHeader("Retry-After", retryAfter.ToString("r"))
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.True(result.DelayUntilDate.HasValue);
|
||||
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
|
||||
Assert.Equal("Too Many Requests", result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_InternalServerError_ReturnsFailureSetsRetryable(IntegrationMessage<DatadogIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new DatadogIntegrationConfigurationDetails(ApiKey: _apiKey, Uri: _datadogUri);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.InternalServerError)
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.False(result.DelayUntilDate.HasValue);
|
||||
Assert.Equal("Internal Server Error", result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_UnexpectedRedirect_ReturnsFailureNotRetryable(IntegrationMessage<DatadogIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new DatadogIntegrationConfigurationDetails(ApiKey: _apiKey, Uri: _datadogUri);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.TemporaryRedirect)
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Null(result.DelayUntilDate);
|
||||
Assert.Equal("Temporary Redirect", result.FailureReason);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class EventIntegrationEventWriteServiceTests
|
||||
{
|
||||
private readonly IEventIntegrationPublisher _eventIntegrationPublisher = Substitute.For<IEventIntegrationPublisher>();
|
||||
private readonly EventIntegrationEventWriteService Subject;
|
||||
|
||||
public EventIntegrationEventWriteServiceTests()
|
||||
{
|
||||
Subject = new EventIntegrationEventWriteService(_eventIntegrationPublisher);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_EventPublishedToEventQueue(EventMessage eventMessage)
|
||||
{
|
||||
await Subject.CreateAsync(eventMessage);
|
||||
await _eventIntegrationPublisher.Received(1).PublishEventAsync(
|
||||
body: Arg.Is<string>(body => AssertJsonStringsMatch(eventMessage, body)),
|
||||
organizationId: Arg.Is<string>(orgId => eventMessage.OrganizationId.ToString().Equals(orgId)));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateManyAsync_EventsPublishedToEventQueue(IEnumerable<EventMessage> eventMessages)
|
||||
{
|
||||
var eventMessage = eventMessages.First();
|
||||
await Subject.CreateManyAsync(eventMessages);
|
||||
await _eventIntegrationPublisher.Received(1).PublishEventAsync(
|
||||
body: Arg.Is<string>(body => AssertJsonStringsMatch(eventMessages, body)),
|
||||
organizationId: Arg.Is<string>(orgId => eventMessage.OrganizationId.ToString().Equals(orgId)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateManyAsync_EmptyList_DoesNothing()
|
||||
{
|
||||
await Subject.CreateManyAsync([]);
|
||||
await _eventIntegrationPublisher.DidNotReceiveWithAnyArgs().PublishEventAsync(Arg.Any<string>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_DisposesEventIntegrationPublisher()
|
||||
{
|
||||
await Subject.DisposeAsync();
|
||||
await _eventIntegrationPublisher.Received(1).DisposeAsync();
|
||||
}
|
||||
|
||||
private static bool AssertJsonStringsMatch(EventMessage expected, string body)
|
||||
{
|
||||
var actual = JsonSerializer.Deserialize<EventMessage>(body);
|
||||
AssertHelper.AssertPropertyEqual(expected, actual, new[] { "IdempotencyId" });
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool AssertJsonStringsMatch(IEnumerable<EventMessage> expected, string body)
|
||||
{
|
||||
using var actual = JsonSerializer.Deserialize<IEnumerable<EventMessage>>(body).GetEnumerator();
|
||||
|
||||
foreach (var expectedMessage in expected)
|
||||
{
|
||||
actual.MoveNext();
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, actual.Current, new[] { "IdempotencyId" });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,710 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class EventIntegrationHandlerTests
|
||||
{
|
||||
private const string _templateBase = "Date: #Date#, Type: #Type#, UserId: #UserId#";
|
||||
private const string _templateWithGroup = "Group: #GroupName#";
|
||||
private const string _templateWithOrganization = "Org: #OrganizationName#";
|
||||
private const string _templateWithUser = "#UserName#, #UserEmail#, #UserType#";
|
||||
private const string _templateWithActingUser = "#ActingUserName#, #ActingUserEmail#, #ActingUserType#";
|
||||
private static readonly Guid _organizationId = Guid.NewGuid();
|
||||
private static readonly Uri _uri = new Uri("https://localhost");
|
||||
private static readonly Uri _uri2 = new Uri("https://example.com");
|
||||
private readonly IEventIntegrationPublisher _eventIntegrationPublisher = Substitute.For<IEventIntegrationPublisher>();
|
||||
private readonly ILogger<EventIntegrationHandler<WebhookIntegrationConfigurationDetails>> _logger =
|
||||
Substitute.For<ILogger<EventIntegrationHandler<WebhookIntegrationConfigurationDetails>>>();
|
||||
|
||||
private SutProvider<EventIntegrationHandler<WebhookIntegrationConfigurationDetails>> GetSutProvider(
|
||||
List<OrganizationIntegrationConfigurationDetails> 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(cache)
|
||||
.SetDependency(_eventIntegrationPublisher)
|
||||
.SetDependency(IntegrationType.Webhook)
|
||||
.SetDependency(_logger)
|
||||
.Create();
|
||||
}
|
||||
|
||||
private static IntegrationMessage<WebhookIntegrationConfigurationDetails> ExpectedMessage(string template)
|
||||
{
|
||||
return new IntegrationMessage<WebhookIntegrationConfigurationDetails>()
|
||||
{
|
||||
IntegrationType = IntegrationType.Webhook,
|
||||
MessageId = "TestMessageId",
|
||||
OrganizationId = _organizationId.ToString(),
|
||||
Configuration = new WebhookIntegrationConfigurationDetails(_uri),
|
||||
RenderedTemplate = template,
|
||||
RetryCount = 0,
|
||||
DelayUntilDate = null
|
||||
};
|
||||
}
|
||||
|
||||
private static List<OrganizationIntegrationConfigurationDetails> NoConfigurations()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private static List<OrganizationIntegrationConfigurationDetails> OneConfiguration(string template)
|
||||
{
|
||||
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||
config.Configuration = null;
|
||||
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Uri = _uri });
|
||||
config.Template = template;
|
||||
|
||||
return [config];
|
||||
}
|
||||
|
||||
private static List<OrganizationIntegrationConfigurationDetails> TwoConfigurations(string template)
|
||||
{
|
||||
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||
config.Configuration = null;
|
||||
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Uri = _uri });
|
||||
config.Template = template;
|
||||
var config2 = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||
config2.Configuration = null;
|
||||
config2.IntegrationConfiguration = JsonSerializer.Serialize(new { Uri = _uri2 });
|
||||
config2.Template = template;
|
||||
|
||||
return [config, config2];
|
||||
}
|
||||
|
||||
private static List<OrganizationIntegrationConfigurationDetails> InvalidFilterConfiguration()
|
||||
{
|
||||
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||
config.Configuration = null;
|
||||
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Uri = _uri });
|
||||
config.Template = _templateBase;
|
||||
config.Filters = "Invalid Configuration!";
|
||||
|
||||
return [config];
|
||||
}
|
||||
|
||||
private static List<OrganizationIntegrationConfigurationDetails> ValidFilterConfiguration()
|
||||
{
|
||||
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||
config.Configuration = null;
|
||||
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Uri = _uri });
|
||||
config.Template = _templateBase;
|
||||
config.Filters = JsonSerializer.Serialize(new IntegrationFilterGroup());
|
||||
|
||||
return [config];
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserIdPresent_UsesCache(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
).Returns(actingUser);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Equal(actingUser, context.ActingUser);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.ActingUserId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
Assert.Null(context.ActingUser);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserOrganizationIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId = null;
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
Assert.Null(context.ActingUser);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_ActingUserFactory_CallsOrganizationUserRepository(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
organizationUserRepository.GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.ActingUserId.Value).Returns(actingUser);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>(f => capturedFactory = f)
|
||||
).Returns(actingUser);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithActingUser);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await organizationUserRepository.Received(1).GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.ActingUserId.Value);
|
||||
Assert.Equal(actingUser, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_GroupIdPresent_UsesCache(EventMessage eventMessage, Group group)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithGroup));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.GroupId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
).Returns(group);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithGroup);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
);
|
||||
Assert.Equal(group, context.Group);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_GroupIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithGroup));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
eventMessage.GroupId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithGroup);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
);
|
||||
Assert.Null(context.Group);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_GroupFactory_CallsGroupRepository(EventMessage eventMessage, Group group)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithGroup));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var groupRepository = sutProvider.GetDependency<IGroupRepository>();
|
||||
|
||||
eventMessage.GroupId ??= Guid.NewGuid();
|
||||
groupRepository.GetByIdAsync(eventMessage.GroupId.Value).Returns(group);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>(f => capturedFactory = f)
|
||||
).Returns(group);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithGroup);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await groupRepository.Received(1).GetByIdAsync(eventMessage.GroupId.Value);
|
||||
Assert.Equal(group, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationIdPresent_UsesCache(EventMessage eventMessage, Organization organization)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithOrganization));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
).Returns(organization);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithOrganization);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
);
|
||||
Assert.Equal(organization, context.Organization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithOrganization));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithOrganization);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
);
|
||||
Assert.Null(context.Organization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationFactory_CallsOrganizationRepository(EventMessage eventMessage, Organization organization)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithOrganization));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
organizationRepository.GetByIdAsync(eventMessage.OrganizationId.Value).Returns(organization);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>(f => capturedFactory = f)
|
||||
).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithOrganization);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await organizationRepository.Received(1).GetByIdAsync(eventMessage.OrganizationId.Value);
|
||||
Assert.Equal(organization, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_UserIdPresent_UsesCache(EventMessage eventMessage, OrganizationUserUserDetails userDetails)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
).Returns(userDetails);
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
await cache.Received(1).GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Equal(userDetails, context.User);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_UserIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId = null;
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Null(context.User);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_OrganizationUserIdNull_SkipsCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId = null;
|
||||
|
||||
var context = await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
|
||||
Assert.Null(context.User);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_UserFactory_CallsOrganizationUserRepository(EventMessage eventMessage, OrganizationUserUserDetails userDetails)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
organizationUserRepository.GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.UserId.Value).Returns(userDetails);
|
||||
|
||||
// Capture the factory function passed to the cache
|
||||
Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>? capturedFactory = null;
|
||||
cache.GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Do<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>(f => capturedFactory = f)
|
||||
).Returns(userDetails);
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateWithUser);
|
||||
|
||||
Assert.NotNull(capturedFactory);
|
||||
var result = await capturedFactory(null!, CancellationToken.None);
|
||||
|
||||
await organizationUserRepository.Received(1).GetDetailsByOrganizationIdUserIdAsync(
|
||||
eventMessage.OrganizationId.Value,
|
||||
eventMessage.UserId.Value);
|
||||
Assert.Equal(userDetails, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BuildContextAsync_NoSpecialTokens_DoesNotCallAnyCache(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
|
||||
eventMessage.ActingUserId ??= Guid.NewGuid();
|
||||
eventMessage.GroupId ??= Guid.NewGuid();
|
||||
eventMessage.OrganizationId ??= Guid.NewGuid();
|
||||
eventMessage.UserId ??= Guid.NewGuid();
|
||||
|
||||
await sutProvider.Sut.BuildContextAsync(eventMessage, _templateBase);
|
||||
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Group?>, CancellationToken, Task<Group?>>>()
|
||||
);
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<Organization?>, CancellationToken, Task<Organization?>>>()
|
||||
);
|
||||
await cache.DidNotReceive().GetOrSetAsync(
|
||||
key: Arg.Any<string>(),
|
||||
factory: Arg.Any<Func<FusionCacheFactoryExecutionContext<OrganizationUserUserDetails?>, CancellationToken, Task<OrganizationUserUserDetails?>>>()
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_BaseTemplateNoConfigurations_DoesNothing(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(NoConfigurations());
|
||||
var cache = sutProvider.GetDependency<IFusionCache>();
|
||||
cache.GetOrSetAsync<List<OrganizationIntegrationConfigurationDetails>>(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<Func<object, CancellationToken, Task<List<OrganizationIntegrationConfigurationDetails>>>>(),
|
||||
Arg.Any<FusionCacheEntryOptions>()
|
||||
).Returns(NoConfigurations());
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
Assert.Empty(_eventIntegrationPublisher.ReceivedCalls());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_NoOrganizationId_DoesNothing(EventMessage eventMessage)
|
||||
{
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateBase));
|
||||
eventMessage.OrganizationId = null;
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
Assert.Empty(_eventIntegrationPublisher.ReceivedCalls());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_BaseTemplateOneConfiguration_PublishesIntegrationMessage(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(OneConfiguration(_templateBase));
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
|
||||
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_BaseTemplateTwoConfigurations_PublishesIntegrationMessages(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(TwoConfigurations(_templateBase));
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
|
||||
expectedMessage.Configuration = new WebhookIntegrationConfigurationDetails(_uri2);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_FilterReturnsFalse_DoesNothing(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(ValidFilterConfiguration());
|
||||
sutProvider.GetDependency<IIntegrationFilterService>().EvaluateFilterGroup(
|
||||
Arg.Any<IntegrationFilterGroup>(), Arg.Any<EventMessage>()).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
Assert.Empty(_eventIntegrationPublisher.ReceivedCalls());
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
|
||||
var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
|
||||
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_InvalidFilter_LogsErrorDoesNothing(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.OrganizationId = _organizationId;
|
||||
var sutProvider = GetSutProvider(InvalidFilterConfiguration());
|
||||
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
Assert.Empty(_eventIntegrationPublisher.ReceivedCalls());
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
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);
|
||||
Assert.Empty(_eventIntegrationPublisher.ReceivedCalls());
|
||||
}
|
||||
|
||||
[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 = ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
|
||||
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId", "OrganizationId" })));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
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 = ExpectedMessage(
|
||||
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
|
||||
);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(AssertHelper.AssertPropertyEqual(
|
||||
expectedMessage, new[] { "MessageId", "OrganizationId" })));
|
||||
|
||||
expectedMessage.Configuration = new WebhookIntegrationConfigurationDetails(_uri2);
|
||||
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(AssertHelper.AssertPropertyEqual(
|
||||
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,35 +0,0 @@
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class EventRepositoryHandlerTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleEventAsync_WritesEventToIEventWriteService(
|
||||
EventMessage eventMessage,
|
||||
SutProvider<EventRepositoryHandler> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(eventMessage))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleManyEventAsync_WritesEventsToIEventWriteService(
|
||||
IEnumerable<EventMessage> eventMessages,
|
||||
SutProvider<EventRepositoryHandler> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(eventMessages))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
public class IntegrationFilterFactoryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void BuildEqualityFilter_ReturnsCorrectMatch(EventMessage message)
|
||||
{
|
||||
var different = Guid.NewGuid();
|
||||
var expected = Guid.NewGuid();
|
||||
message.UserId = expected;
|
||||
|
||||
var filter = IntegrationFilterFactory.BuildEqualityFilter<Guid?>("UserId");
|
||||
|
||||
Assert.True(filter(message, expected));
|
||||
Assert.False(filter(message, different));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void BuildEqualityFilter_UserIdIsNull_ReturnsFalse(EventMessage message)
|
||||
{
|
||||
message.UserId = null;
|
||||
|
||||
var filter = IntegrationFilterFactory.BuildEqualityFilter<Guid?>("UserId");
|
||||
|
||||
Assert.False(filter(message, Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void BuildInFilter_ReturnsCorrectMatch(EventMessage message)
|
||||
{
|
||||
var match = Guid.NewGuid();
|
||||
message.UserId = match;
|
||||
var inList = new List<Guid?> { Guid.NewGuid(), match, Guid.NewGuid() };
|
||||
var outList = new List<Guid?> { Guid.NewGuid(), Guid.NewGuid() };
|
||||
|
||||
var filter = IntegrationFilterFactory.BuildInFilter<Guid?>("UserId");
|
||||
|
||||
Assert.True(filter(message, inList));
|
||||
Assert.False(filter(message, outList));
|
||||
}
|
||||
}
|
||||
@@ -1,467 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
public class IntegrationFilterServiceTests
|
||||
{
|
||||
private readonly IntegrationFilterService _service = new();
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_EqualsUserId_Matches(EventMessage eventMessage)
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
eventMessage.UserId = userId;
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "UserId",
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Value = userId
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.True(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_EqualsUserIdString_Matches(EventMessage eventMessage)
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
eventMessage.UserId = userId;
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "UserId",
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Value = userId.ToString()
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.True(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_EqualsUserId_DoesNotMatch(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.UserId = Guid.NewGuid();
|
||||
var otherUserId = Guid.NewGuid();
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "UserId",
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Value = otherUserId
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.False(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_NotEqualsUniqueUserId_ReturnsTrue(EventMessage eventMessage)
|
||||
{
|
||||
var otherId = Guid.NewGuid();
|
||||
eventMessage.UserId = otherId;
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "UserId",
|
||||
Operation = IntegrationFilterOperation.NotEquals,
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.True(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_NotEqualsMatchingUserId_ReturnsFalse(EventMessage eventMessage)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
eventMessage.UserId = id;
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "UserId",
|
||||
Operation = IntegrationFilterOperation.NotEquals,
|
||||
Value = id
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.False(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_InCollectionId_Matches(EventMessage eventMessage)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
eventMessage.CollectionId = id;
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "CollectionId",
|
||||
Operation = IntegrationFilterOperation.In,
|
||||
Value = new Guid?[] { Guid.NewGuid(), id }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.True(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_InCollectionId_DoesNotMatch(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.CollectionId = Guid.NewGuid();
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "CollectionId",
|
||||
Operation = IntegrationFilterOperation.In,
|
||||
Value = new Guid?[] { Guid.NewGuid(), Guid.NewGuid() }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.False(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_NotInCollectionIdUniqueId_ReturnsTrue(EventMessage eventMessage)
|
||||
{
|
||||
eventMessage.CollectionId = Guid.NewGuid();
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "CollectionId",
|
||||
Operation = IntegrationFilterOperation.NotIn,
|
||||
Value = new Guid?[] { Guid.NewGuid(), Guid.NewGuid() }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.True(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_NotInCollectionIdPresent_ReturnsFalse(EventMessage eventMessage)
|
||||
{
|
||||
var matchId = Guid.NewGuid();
|
||||
eventMessage.CollectionId = matchId;
|
||||
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "CollectionId",
|
||||
Operation = IntegrationFilterOperation.NotIn,
|
||||
Value = new Guid?[] { Guid.NewGuid(), matchId }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.False(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(group);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_NestedGroups_AllMatch(EventMessage eventMessage)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
var collectionId = Guid.NewGuid();
|
||||
eventMessage.UserId = id;
|
||||
eventMessage.CollectionId = collectionId;
|
||||
|
||||
var nestedGroup = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules =
|
||||
[
|
||||
new() { Property = "UserId", Operation = IntegrationFilterOperation.Equals, Value = id },
|
||||
new()
|
||||
{
|
||||
Property = "CollectionId",
|
||||
Operation = IntegrationFilterOperation.In,
|
||||
Value = new Guid?[] { collectionId, Guid.NewGuid() }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var topGroup = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = true,
|
||||
Groups = [nestedGroup]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(topGroup, eventMessage);
|
||||
Assert.True(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(topGroup);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_NestedGroups_AnyMatch(EventMessage eventMessage)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
var collectionId = Guid.NewGuid();
|
||||
eventMessage.UserId = id;
|
||||
eventMessage.CollectionId = collectionId;
|
||||
|
||||
var nestedGroup = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = false,
|
||||
Rules =
|
||||
[
|
||||
new() { Property = "UserId", Operation = IntegrationFilterOperation.Equals, Value = id },
|
||||
new()
|
||||
{
|
||||
Property = "CollectionId",
|
||||
Operation = IntegrationFilterOperation.In,
|
||||
Value = new Guid?[] { Guid.NewGuid() }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var topGroup = new IntegrationFilterGroup
|
||||
{
|
||||
AndOperator = false,
|
||||
Groups = [nestedGroup]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(topGroup, eventMessage);
|
||||
Assert.True(result);
|
||||
|
||||
var jsonGroup = JsonSerializer.Serialize(topGroup);
|
||||
var roundtrippedGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(jsonGroup);
|
||||
Assert.NotNull(roundtrippedGroup);
|
||||
Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_UnknownProperty_ReturnsFalse(EventMessage eventMessage)
|
||||
{
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
Rules =
|
||||
[
|
||||
new() { Property = "NotARealProperty", Operation = IntegrationFilterOperation.Equals, Value = "test" }
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_UnsupportedOperation_ReturnsFalse(EventMessage eventMessage)
|
||||
{
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "UserId",
|
||||
Operation = (IntegrationFilterOperation)999, // Unknown operation
|
||||
Value = eventMessage.UserId
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_WrongTypeForInList_ThrowsException(EventMessage eventMessage)
|
||||
{
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "CollectionId",
|
||||
Operation = IntegrationFilterOperation.In,
|
||||
Value = "not an array" // Should be Guid[]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Assert.Throws<InvalidCastException>(() =>
|
||||
_service.EvaluateFilterGroup(group, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_NullValue_ThrowsException(EventMessage eventMessage)
|
||||
{
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "UserId",
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Value = null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Assert.Throws<InvalidCastException>(() =>
|
||||
_service.EvaluateFilterGroup(group, eventMessage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_EmptyRuleList_ReturnsTrue(EventMessage eventMessage)
|
||||
{
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
Rules = [],
|
||||
Groups = [],
|
||||
AndOperator = true
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.True(result); // Nothing to fail, returns true by design
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void EvaluateFilterGroup_InvalidNestedGroup_ReturnsFalse(EventMessage eventMessage)
|
||||
{
|
||||
var group = new IntegrationFilterGroup
|
||||
{
|
||||
Groups =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Property = "Nope",
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Value = "bad"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
AndOperator = true
|
||||
};
|
||||
|
||||
var result = _service.EvaluateFilterGroup(group, eventMessage);
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
using System.Net;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
public class IntegrationHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConvertsJsonToTypedIntegrationMessage()
|
||||
{
|
||||
var sut = new TestIntegrationHandler();
|
||||
var expected = new IntegrationMessage<WebhookIntegrationConfigurationDetails>()
|
||||
{
|
||||
Configuration = new WebhookIntegrationConfigurationDetails(new Uri("https://localhost"), "Bearer", "AUTH-TOKEN"),
|
||||
MessageId = "TestMessageId",
|
||||
OrganizationId = "TestOrganizationId",
|
||||
IntegrationType = IntegrationType.Webhook,
|
||||
RenderedTemplate = "Template",
|
||||
DelayUntilDate = null,
|
||||
RetryCount = 0
|
||||
};
|
||||
|
||||
var result = await sut.HandleAsync(expected.ToJson());
|
||||
var typedResult = Assert.IsType<IntegrationMessage<WebhookIntegrationConfigurationDetails>>(result.Message);
|
||||
|
||||
Assert.Equal(expected.MessageId, typedResult.MessageId);
|
||||
Assert.Equal(expected.OrganizationId, typedResult.OrganizationId);
|
||||
Assert.Equal(expected.Configuration, typedResult.Configuration);
|
||||
Assert.Equal(expected.RenderedTemplate, typedResult.RenderedTemplate);
|
||||
Assert.Equal(expected.IntegrationType, typedResult.IntegrationType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.Unauthorized)]
|
||||
[InlineData(HttpStatusCode.Forbidden)]
|
||||
public void ClassifyHttpStatusCode_AuthenticationFailed(HttpStatusCode code)
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.AuthenticationFailed,
|
||||
TestIntegrationHandler.Classify(code));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.NotFound)]
|
||||
[InlineData(HttpStatusCode.Gone)]
|
||||
[InlineData(HttpStatusCode.MovedPermanently)]
|
||||
[InlineData(HttpStatusCode.TemporaryRedirect)]
|
||||
[InlineData(HttpStatusCode.PermanentRedirect)]
|
||||
public void ClassifyHttpStatusCode_ConfigurationError(HttpStatusCode code)
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
TestIntegrationHandler.Classify(code));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_TooManyRequests_IsRateLimited()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.RateLimited,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.TooManyRequests));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_RequestTimeout_IsTransient()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.TransientError,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.RequestTimeout));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.InternalServerError)]
|
||||
[InlineData(HttpStatusCode.BadGateway)]
|
||||
[InlineData(HttpStatusCode.GatewayTimeout)]
|
||||
public void ClassifyHttpStatusCode_Common5xx_AreTransient(HttpStatusCode code)
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.TransientError,
|
||||
TestIntegrationHandler.Classify(code));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_ServiceUnavailable_IsServiceUnavailable()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.ServiceUnavailable));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_NotImplemented_IsPermanentFailure()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.PermanentFailure,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.NotImplemented));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FClassifyHttpStatusCode_Unhandled3xx_IsConfigurationError()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.Found));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_Unhandled4xx_IsConfigurationError()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.BadRequest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_Unhandled5xx_IsServiceUnavailable()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.HttpVersionNotSupported));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_UnknownCode_DefaultsToServiceUnavailable()
|
||||
{
|
||||
// cast an out-of-range value to ensure default path is stable
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
TestIntegrationHandler.Classify((HttpStatusCode)799));
|
||||
}
|
||||
|
||||
private class TestIntegrationHandler : IntegrationHandlerBase<WebhookIntegrationConfigurationDetails>
|
||||
{
|
||||
public override Task<IntegrationHandlerResult> HandleAsync(
|
||||
IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
return Task.FromResult(IntegrationHandlerResult.Succeed(message: message));
|
||||
}
|
||||
|
||||
public static IntegrationFailureCategory Classify(HttpStatusCode code) => ClassifyHttpStatusCode(code);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Dirt.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Services;
|
||||
|
||||
public class OrganizationIntegrationConfigurationValidatorTests
|
||||
{
|
||||
private readonly OrganizationIntegrationConfigurationValidator _sut = new();
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_CloudBillingSyncIntegration_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.CloudBillingSync, configuration));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void ValidateConfiguration_EmptyTemplate_ReturnsFalse(string? template)
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration(ChannelId: "C12345")),
|
||||
Template = template
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Slack, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://example.com"))),
|
||||
Template = template
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, config2));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void ValidateConfiguration_EmptyNonNullConfiguration_ReturnsFalse(string? config)
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Hec, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Datadog, config2));
|
||||
|
||||
var config3 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Teams, config3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_NullConfiguration_ReturnsTrue()
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Hec, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Datadog, config2));
|
||||
|
||||
var config3 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Teams, config3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_InvalidJsonConfiguration_ReturnsFalse()
|
||||
{
|
||||
var config = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{not valid json}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Slack, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Hec, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Datadog, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Teams, config));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_InvalidJsonFilters_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://example.com"))),
|
||||
Template = "template",
|
||||
Filters = "{Not valid json}"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ScimIntegration_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Scim, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidSlackConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration(ChannelId: "C12345")),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Slack, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidSlackConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration("C12345")),
|
||||
Template = "template",
|
||||
Filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
})
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Slack, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidNoAuthWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"))),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://localhost"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN")),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidWebhookConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://example.com"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN")),
|
||||
Template = "template",
|
||||
Filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
})
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_UnknownIntegrationType_ReturnsFalse()
|
||||
{
|
||||
var unknownType = (IntegrationType)999;
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(unknownType, configuration));
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RabbitMqEventListenerServiceTests
|
||||
{
|
||||
private readonly TestListenerConfiguration _config = new();
|
||||
private readonly ILogger _logger = Substitute.For<ILogger>();
|
||||
|
||||
private SutProvider<RabbitMqEventListenerService<TestListenerConfiguration>> GetSutProvider()
|
||||
{
|
||||
var loggerFactory = Substitute.For<ILoggerFactory>();
|
||||
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
|
||||
return new SutProvider<RabbitMqEventListenerService<TestListenerConfiguration>>()
|
||||
.SetDependency(_config)
|
||||
.SetDependency(loggerFactory)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CreatesLogWithCorrectCategory()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
var fullName = typeof(RabbitMqEventListenerService<>).FullName ?? "";
|
||||
var tickIndex = fullName.IndexOf('`');
|
||||
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
|
||||
var categoryName = cleanedName + '.' + _config.EventQueueName;
|
||||
|
||||
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_CreatesQueue()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
await sutProvider.GetDependency<IRabbitMqService>().Received(1).CreateEventQueueAsync(
|
||||
Arg.Is(_config.EventQueueName),
|
||||
Arg.Is(cancellationToken)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_EmptyJson_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: Array.Empty<byte>());
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_InvalidJson_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: JsonSerializer.SerializeToUtf8Bytes("{ Invalid JSON"));
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Invalid JSON")),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_InvalidJsonArray_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: JsonSerializer.SerializeToUtf8Bytes(new[] { "not a valid", "list of event messages" }));
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessReceivedMessageAsync_InvalidJsonObject_LogsError()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: JsonSerializer.SerializeToUtf8Bytes(DateTime.UtcNow)); // wrong object - not EventMessage
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
|
||||
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Any<JsonException>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_SingleEvent_DelegatesToHandler(EventMessage message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: JsonSerializer.SerializeToUtf8Bytes(message));
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
|
||||
|
||||
await sutProvider.GetDependency<IEventMessageHandler>().Received(1).HandleEventAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message, new[] { "IdempotencyId" })));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_ManyEvents_DelegatesToHandler(IEnumerable<EventMessage> messages)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: JsonSerializer.SerializeToUtf8Bytes(messages));
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
|
||||
|
||||
await sutProvider.GetDependency<IEventMessageHandler>().Received(1).HandleManyEventsAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(messages, new[] { "IdempotencyId" })));
|
||||
}
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RabbitMqIntegrationListenerServiceTests
|
||||
{
|
||||
private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
|
||||
private readonly ILogger _logger = Substitute.For<ILogger>();
|
||||
private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>();
|
||||
private readonly TestListenerConfiguration _config = new();
|
||||
|
||||
private SutProvider<RabbitMqIntegrationListenerService<TestListenerConfiguration>> GetSutProvider()
|
||||
{
|
||||
var loggerFactory = Substitute.For<ILoggerFactory>();
|
||||
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
|
||||
var sutProvider = new SutProvider<RabbitMqIntegrationListenerService<TestListenerConfiguration>>()
|
||||
.SetDependency(_config)
|
||||
.SetDependency(_handler)
|
||||
.SetDependency(loggerFactory)
|
||||
.SetDependency(_rabbitMqService)
|
||||
.WithFakeTimeProvider()
|
||||
.Create();
|
||||
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(_now);
|
||||
|
||||
return sutProvider;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CreatesLogWithCorrectCategory()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
var fullName = typeof(RabbitMqIntegrationListenerService<>).FullName ?? "";
|
||||
var tickIndex = fullName.IndexOf('`');
|
||||
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
|
||||
var categoryName = cleanedName + '.' + _config.IntegrationQueueName;
|
||||
|
||||
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_CreatesQueues()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
await sutProvider.GetDependency<IRabbitMqService>().Received(1).CreateIntegrationQueuesAsync(
|
||||
Arg.Is(_config.IntegrationQueueName),
|
||||
Arg.Is(_config.IntegrationRetryQueueName),
|
||||
Arg.Is(((IIntegrationListenerConfiguration)_config).RoutingKey),
|
||||
Arg.Is(cancellationToken)
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = null;
|
||||
message.RetryCount = 0;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.AuthenticationFailed, // NOT retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
Assert.NotNull(expected);
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
|
||||
await _rabbitMqService.Received(1).PublishToDeadLetterAsync(
|
||||
Arg.Any<IChannel>(),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "DelayUntilDate" })),
|
||||
Arg.Any<CancellationToken>());
|
||||
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - non-retryable.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = null;
|
||||
message.RetryCount = _config.MaxRetries;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
Assert.NotNull(expected);
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
|
||||
|
||||
expected.ApplyRetry(result.DelayUntilDate);
|
||||
await _rabbitMqService.Received(1).PublishToDeadLetterAsync(
|
||||
Arg.Any<IChannel>(),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "DelayUntilDate" })),
|
||||
Arg.Any<CancellationToken>());
|
||||
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - max retries exceeded.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = null;
|
||||
message.RetryCount = 0;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
Assert.NotNull(expected);
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
|
||||
expected.ApplyRetry(result.DelayUntilDate);
|
||||
await _rabbitMqService.Received(1).PublishToRetryAsync(
|
||||
Arg.Any<IChannel>(),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "DelayUntilDate" })),
|
||||
Arg.Any<CancellationToken>());
|
||||
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.PublishToDeadLetterAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = null;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(message.ToJson()));
|
||||
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.PublishToDeadLetterAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ProcessReceivedMessageAsync_TooEarlyRetry_RepublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = _now.AddMinutes(1);
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
redelivered: true,
|
||||
exchange: string.Empty,
|
||||
routingKey: string.Empty,
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
|
||||
|
||||
await _rabbitMqService.Received(1)
|
||||
.RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
|
||||
|
||||
await _handler.DidNotReceiveWithAnyArgs().HandleAsync(Arg.Any<string>());
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
|
||||
await _rabbitMqService.DidNotReceiveWithAnyArgs()
|
||||
.PublishToDeadLetterAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Models.Slack;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class SlackIntegrationHandlerTests
|
||||
{
|
||||
private readonly ISlackService _slackService = Substitute.For<ISlackService>();
|
||||
private readonly string _channelId = "C12345";
|
||||
private readonly string _token = "xoxb-test-token";
|
||||
|
||||
private SutProvider<SlackIntegrationHandler> GetSutProvider()
|
||||
{
|
||||
return new SutProvider<SlackIntegrationHandler>()
|
||||
.SetDependency(_slackService)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_SuccessfulRequest_ReturnsSuccess(IntegrationMessage<SlackIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new SlackIntegrationConfigurationDetails(_channelId, _token);
|
||||
|
||||
_slackService.SendSlackMessageByChannelIdAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(new SlackSendMessageResponse() { Ok = true, Channel = _channelId });
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ISlackService>().Received(1).SendSlackMessageByChannelIdAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_token)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("service_unavailable")]
|
||||
[InlineData("ratelimited")]
|
||||
[InlineData("rate_limited")]
|
||||
[InlineData("internal_error")]
|
||||
[InlineData("message_limit_exceeded")]
|
||||
public async Task HandleAsync_FailedRetryableRequest_ReturnsFailureWithRetryable(string error)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var message = new IntegrationMessage<SlackIntegrationConfigurationDetails>()
|
||||
{
|
||||
Configuration = new SlackIntegrationConfigurationDetails(_channelId, _token),
|
||||
MessageId = "MessageId",
|
||||
RenderedTemplate = "Test Message"
|
||||
};
|
||||
|
||||
_slackService.SendSlackMessageByChannelIdAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(new SlackSendMessageResponse() { Ok = false, Channel = _channelId, Error = error });
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.NotNull(result.FailureReason);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ISlackService>().Received(1).SendSlackMessageByChannelIdAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_token)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("access_denied")]
|
||||
[InlineData("channel_not_found")]
|
||||
[InlineData("token_expired")]
|
||||
[InlineData("token_revoked")]
|
||||
public async Task HandleAsync_FailedNonRetryableRequest_ReturnsNonRetryableFailure(string error)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var message = new IntegrationMessage<SlackIntegrationConfigurationDetails>()
|
||||
{
|
||||
Configuration = new SlackIntegrationConfigurationDetails(_channelId, _token),
|
||||
MessageId = "MessageId",
|
||||
RenderedTemplate = "Test Message"
|
||||
};
|
||||
|
||||
_slackService.SendSlackMessageByChannelIdAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(new SlackSendMessageResponse() { Ok = false, Channel = _channelId, Error = error });
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.NotNull(result.FailureReason);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ISlackService>().Received(1).SendSlackMessageByChannelIdAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_token)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId))
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_NullResponse_ReturnsRetryableFailure()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var message = new IntegrationMessage<SlackIntegrationConfigurationDetails>()
|
||||
{
|
||||
Configuration = new SlackIntegrationConfigurationDetails(_channelId, _token),
|
||||
MessageId = "MessageId",
|
||||
RenderedTemplate = "Test Message"
|
||||
};
|
||||
|
||||
_slackService.SendSlackMessageByChannelIdAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns((SlackSendMessageResponse?)null);
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable); // Null response is classified as TransientError (retryable)
|
||||
Assert.Equal("Slack response was null", result.FailureReason);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ISlackService>().Received(1).SendSlackMessageByChannelIdAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_token)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,499 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.MockedHttpClient;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class SlackServiceTests
|
||||
{
|
||||
private readonly MockedHttpMessageHandler _handler;
|
||||
private readonly HttpClient _httpClient;
|
||||
private const string _token = "xoxb-test-token";
|
||||
|
||||
public SlackServiceTests()
|
||||
{
|
||||
_handler = new MockedHttpMessageHandler();
|
||||
_httpClient = _handler.ToHttpClient();
|
||||
}
|
||||
|
||||
private SutProvider<SlackService> GetSutProvider()
|
||||
{
|
||||
var clientFactory = Substitute.For<IHttpClientFactory>();
|
||||
clientFactory.CreateClient(SlackService.HttpClientName).Returns(_httpClient);
|
||||
|
||||
var globalSettings = Substitute.For<GlobalSettings>();
|
||||
globalSettings.Slack.ApiBaseUrl.Returns("https://slack.com/api");
|
||||
|
||||
return new SutProvider<SlackService>()
|
||||
.SetDependency(clientFactory)
|
||||
.SetDependency(globalSettings)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetChannelIdsAsync_ReturnsCorrectChannelIds()
|
||||
{
|
||||
var response = JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
ok = true,
|
||||
channels =
|
||||
new[] {
|
||||
new { id = "C12345", name = "general" },
|
||||
new { id = "C67890", name = "random" }
|
||||
},
|
||||
response_metadata = new { next_cursor = "" }
|
||||
}
|
||||
);
|
||||
_handler.When(HttpMethod.Get)
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(response));
|
||||
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelNames = new List<string> { "general", "random" };
|
||||
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains("C12345", result);
|
||||
Assert.Contains("C67890", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetChannelIdsAsync_WithPagination_ReturnsCorrectChannelIds()
|
||||
{
|
||||
var firstPageResponse = JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
ok = true,
|
||||
channels = new[] { new { id = "C12345", name = "general" } },
|
||||
response_metadata = new { next_cursor = "next_cursor_value" }
|
||||
}
|
||||
);
|
||||
var secondPageResponse = JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
ok = true,
|
||||
channels = new[] { new { id = "C67890", name = "random" } },
|
||||
response_metadata = new { next_cursor = "" }
|
||||
}
|
||||
);
|
||||
|
||||
_handler.When("https://slack.com/api/conversations.list?types=public_channel%2cprivate_channel&limit=1000")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(firstPageResponse));
|
||||
_handler.When("https://slack.com/api/conversations.list?types=public_channel%2cprivate_channel&limit=1000&cursor=next_cursor_value")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(secondPageResponse));
|
||||
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelNames = new List<string> { "general", "random" };
|
||||
|
||||
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains("C12345", result);
|
||||
Assert.Contains("C67890", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetChannelIdsAsync_ApiError_ReturnsEmptyResult()
|
||||
{
|
||||
var errorResponse = JsonSerializer.Serialize(
|
||||
new { ok = false, error = "rate_limited" }
|
||||
);
|
||||
|
||||
_handler.When(HttpMethod.Get)
|
||||
.RespondWith(HttpStatusCode.TooManyRequests)
|
||||
.WithContent(new StringContent(errorResponse));
|
||||
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelNames = new List<string> { "general", "random" };
|
||||
|
||||
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetChannelIdsAsync_NoChannelsFound_ReturnsEmptyResult()
|
||||
{
|
||||
var emptyResponse = JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
ok = true,
|
||||
channels = Array.Empty<string>(),
|
||||
response_metadata = new { next_cursor = "" }
|
||||
});
|
||||
|
||||
_handler.When(HttpMethod.Get)
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(emptyResponse));
|
||||
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelNames = new List<string> { "general", "random" };
|
||||
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetChannelIdAsync_NoChannelFound_ReturnsEmptyResult()
|
||||
{
|
||||
var emptyResponse = JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
ok = true,
|
||||
channels = Array.Empty<string>(),
|
||||
response_metadata = new { next_cursor = "" }
|
||||
});
|
||||
|
||||
_handler.When(HttpMethod.Get)
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(emptyResponse));
|
||||
|
||||
var sutProvider = GetSutProvider();
|
||||
var result = await sutProvider.Sut.GetChannelIdAsync(_token, "general");
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetChannelIdAsync_ReturnsCorrectChannelId()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var response = new
|
||||
{
|
||||
ok = true,
|
||||
channels = new[]
|
||||
{
|
||||
new { id = "C12345", name = "general" },
|
||||
new { id = "C67890", name = "random" }
|
||||
},
|
||||
response_metadata = new { next_cursor = "" }
|
||||
};
|
||||
|
||||
_handler.When(HttpMethod.Get)
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(JsonSerializer.Serialize(response)));
|
||||
|
||||
var result = await sutProvider.Sut.GetChannelIdAsync(_token, "general");
|
||||
|
||||
Assert.Equal("C12345", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDmChannelByEmailAsync_ReturnsCorrectDmChannelId()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var email = "user@example.com";
|
||||
var userId = "U12345";
|
||||
var dmChannelId = "D67890";
|
||||
|
||||
var userResponse = new
|
||||
{
|
||||
ok = true,
|
||||
user = new { id = userId }
|
||||
};
|
||||
|
||||
var dmResponse = new
|
||||
{
|
||||
ok = true,
|
||||
channel = new { id = dmChannelId }
|
||||
};
|
||||
|
||||
_handler.When($"https://slack.com/api/users.lookupByEmail?email={email}")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(JsonSerializer.Serialize(userResponse)));
|
||||
|
||||
_handler.When("https://slack.com/api/conversations.open")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(JsonSerializer.Serialize(dmResponse)));
|
||||
|
||||
var result = await sutProvider.Sut.GetDmChannelByEmailAsync(_token, email);
|
||||
|
||||
Assert.Equal(dmChannelId, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDmChannelByEmailAsync_ApiErrorDmResponse_ReturnsEmptyString()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var email = "user@example.com";
|
||||
var userId = "U12345";
|
||||
|
||||
var userResponse = new
|
||||
{
|
||||
ok = true,
|
||||
user = new { id = userId }
|
||||
};
|
||||
|
||||
var dmResponse = new
|
||||
{
|
||||
ok = false,
|
||||
error = "An error occured"
|
||||
};
|
||||
|
||||
_handler.When($"https://slack.com/api/users.lookupByEmail?email={email}")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(JsonSerializer.Serialize(userResponse)));
|
||||
|
||||
_handler.When("https://slack.com/api/conversations.open")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(JsonSerializer.Serialize(dmResponse)));
|
||||
|
||||
var result = await sutProvider.Sut.GetDmChannelByEmailAsync(_token, email);
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDmChannelByEmailAsync_ApiErrorUnparsableDmResponse_ReturnsEmptyString()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var email = "user@example.com";
|
||||
var userId = "U12345";
|
||||
|
||||
var userResponse = new
|
||||
{
|
||||
ok = true,
|
||||
user = new { id = userId }
|
||||
};
|
||||
|
||||
_handler.When($"https://slack.com/api/users.lookupByEmail?email={email}")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(JsonSerializer.Serialize(userResponse)));
|
||||
|
||||
_handler.When("https://slack.com/api/conversations.open")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent("NOT JSON"));
|
||||
|
||||
var result = await sutProvider.Sut.GetDmChannelByEmailAsync(_token, email);
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDmChannelByEmailAsync_ApiErrorUserResponse_ReturnsEmptyString()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var email = "user@example.com";
|
||||
|
||||
var userResponse = new
|
||||
{
|
||||
ok = false,
|
||||
error = "An error occurred"
|
||||
};
|
||||
|
||||
_handler.When($"https://slack.com/api/users.lookupByEmail?email={email}")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(JsonSerializer.Serialize(userResponse)));
|
||||
|
||||
var result = await sutProvider.Sut.GetDmChannelByEmailAsync(_token, email);
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDmChannelByEmailAsync_ApiErrorUnparsableUserResponse_ReturnsEmptyString()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var email = "user@example.com";
|
||||
|
||||
_handler.When($"https://slack.com/api/users.lookupByEmail?email={email}")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent("Not JSON"));
|
||||
|
||||
var result = await sutProvider.Sut.GetDmChannelByEmailAsync(_token, email);
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRedirectUrl_ReturnsCorrectUrl()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var clientId = sutProvider.GetDependency<GlobalSettings>().Slack.ClientId;
|
||||
var scopes = sutProvider.GetDependency<GlobalSettings>().Slack.Scopes;
|
||||
var callbackUrl = "https://example.com/callback";
|
||||
var state = Guid.NewGuid().ToString();
|
||||
var result = sutProvider.Sut.GetRedirectUrl(callbackUrl, state);
|
||||
|
||||
var uri = new Uri(result);
|
||||
var query = HttpUtility.ParseQueryString(uri.Query);
|
||||
|
||||
Assert.Equal(clientId, query["client_id"]);
|
||||
Assert.Equal(scopes, query["scope"]);
|
||||
Assert.Equal(callbackUrl, query["redirect_uri"]);
|
||||
Assert.Equal(state, query["state"]);
|
||||
Assert.Equal("slack.com", uri.Host);
|
||||
Assert.Equal("/oauth/v2/authorize", uri.AbsolutePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObtainTokenViaOAuth_ReturnsAccessToken_WhenSuccessful()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var jsonResponse = JsonSerializer.Serialize(new
|
||||
{
|
||||
ok = true,
|
||||
access_token = "test-access-token"
|
||||
});
|
||||
|
||||
_handler.When("https://slack.com/api/oauth.v2.access")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(jsonResponse));
|
||||
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth("test-code", "https://example.com/callback");
|
||||
|
||||
Assert.Equal("test-access-token", result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test-code", "")]
|
||||
[InlineData("", "https://example.com/callback")]
|
||||
[InlineData("", "")]
|
||||
public async Task ObtainTokenViaOAuth_ReturnsEmptyString_WhenCodeOrRedirectUrlIsEmpty(string code, string redirectUrl)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth(code, redirectUrl);
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObtainTokenViaOAuth_ReturnsEmptyString_WhenErrorResponse()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var jsonResponse = JsonSerializer.Serialize(new
|
||||
{
|
||||
ok = false,
|
||||
error = "invalid_code"
|
||||
});
|
||||
|
||||
_handler.When("https://slack.com/api/oauth.v2.access")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(jsonResponse));
|
||||
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth("test-code", "https://example.com/callback");
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObtainTokenViaOAuth_ReturnsEmptyString_WhenHttpCallFails()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
_handler.When("https://slack.com/api/oauth.v2.access")
|
||||
.RespondWith(HttpStatusCode.InternalServerError)
|
||||
.WithContent(new StringContent(string.Empty));
|
||||
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth("test-code", "https://example.com/callback");
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendSlackMessageByChannelId_Success_ReturnsSuccessfulResponse()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelId = "C12345";
|
||||
var message = "Hello, Slack!";
|
||||
|
||||
var jsonResponse = JsonSerializer.Serialize(new
|
||||
{
|
||||
ok = true,
|
||||
channel = channelId,
|
||||
});
|
||||
|
||||
_handler.When(HttpMethod.Post)
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(jsonResponse));
|
||||
|
||||
var result = await sutProvider.Sut.SendSlackMessageByChannelIdAsync(_token, message, channelId);
|
||||
|
||||
// Response was parsed correctly
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.Ok);
|
||||
|
||||
// Request was sent correctly
|
||||
Assert.Single(_handler.CapturedRequests);
|
||||
var request = _handler.CapturedRequests[0];
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal(HttpMethod.Post, request.Method);
|
||||
Assert.NotNull(request.Headers.Authorization);
|
||||
Assert.Equal($"Bearer {_token}", request.Headers.Authorization.ToString());
|
||||
Assert.NotNull(request.Content);
|
||||
var returned = (await request.Content.ReadAsStringAsync());
|
||||
var json = JsonDocument.Parse(returned);
|
||||
Assert.Equal(message, json.RootElement.GetProperty("text").GetString() ?? string.Empty);
|
||||
Assert.Equal(channelId, json.RootElement.GetProperty("channel").GetString() ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendSlackMessageByChannelId_Failure_ReturnsErrorResponse()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelId = "C12345";
|
||||
var message = "Hello, Slack!";
|
||||
|
||||
var jsonResponse = JsonSerializer.Serialize(new
|
||||
{
|
||||
ok = false,
|
||||
channel = channelId,
|
||||
error = "error"
|
||||
});
|
||||
|
||||
_handler.When(HttpMethod.Post)
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(jsonResponse));
|
||||
|
||||
var result = await sutProvider.Sut.SendSlackMessageByChannelIdAsync(_token, message, channelId);
|
||||
|
||||
// Response was parsed correctly
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.Ok);
|
||||
Assert.NotNull(result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendSlackMessageByChannelIdAsync_InvalidJson_ReturnsNull()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelId = "C12345";
|
||||
var message = "Hello world!";
|
||||
|
||||
_handler.When(HttpMethod.Post)
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent("Not JSON"));
|
||||
|
||||
var result = await sutProvider.Sut.SendSlackMessageByChannelIdAsync(_token, message, channelId);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendSlackMessageByChannelIdAsync_HttpServerError_ReturnsNull()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var channelId = "C12345";
|
||||
var message = "Hello world!";
|
||||
|
||||
_handler.When(HttpMethod.Post)
|
||||
.RespondWith(HttpStatusCode.InternalServerError)
|
||||
.WithContent(new StringContent(string.Empty));
|
||||
|
||||
var result = await sutProvider.Sut.SendSlackMessageByChannelIdAsync(_token, message, channelId);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.Rest;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class TeamsIntegrationHandlerTests
|
||||
{
|
||||
private readonly ITeamsService _teamsService = Substitute.For<ITeamsService>();
|
||||
private readonly string _channelId = "C12345";
|
||||
private readonly Uri _serviceUrl = new Uri("http://localhost");
|
||||
|
||||
private SutProvider<TeamsIntegrationHandler> GetSutProvider()
|
||||
{
|
||||
return new SutProvider<TeamsIntegrationHandler>()
|
||||
.SetDependency(_teamsService)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_SuccessfulRequest_ReturnsSuccess(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_ArgumentException_ReturnsConfigurationError(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new ArgumentException("argument error"));
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.ConfigurationError, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_JsonException_ReturnsPermanentFailure(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new JsonException("JSON error"));
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.PermanentFailure, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_UriFormatException_ReturnsConfigurationError(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new UriFormatException("Bad URI"));
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.ConfigurationError, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_HttpExceptionForbidden_ReturnsAuthenticationFailed(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new HttpOperationException("Server error")
|
||||
{
|
||||
Response = new HttpResponseMessageWrapper(
|
||||
new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden),
|
||||
"Forbidden"
|
||||
)
|
||||
}
|
||||
);
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.AuthenticationFailed, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_HttpExceptionTooManyRequests_ReturnsRateLimited(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new HttpOperationException("Server error")
|
||||
{
|
||||
Response = new HttpResponseMessageWrapper(
|
||||
new HttpResponseMessage(System.Net.HttpStatusCode.TooManyRequests),
|
||||
"Too Many Requests"
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.RateLimited, result.Category);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_UnknownException_ReturnsTransientError(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new Exception("Unknown error"));
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.TransientError, result.Category);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Models.Teams;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.MockedHttpClient;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class TeamsServiceTests
|
||||
{
|
||||
private readonly MockedHttpMessageHandler _handler;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public TeamsServiceTests()
|
||||
{
|
||||
_handler = new MockedHttpMessageHandler();
|
||||
_httpClient = _handler.ToHttpClient();
|
||||
}
|
||||
|
||||
private SutProvider<TeamsService> GetSutProvider()
|
||||
{
|
||||
var clientFactory = Substitute.For<IHttpClientFactory>();
|
||||
clientFactory.CreateClient(TeamsService.HttpClientName).Returns(_httpClient);
|
||||
|
||||
var globalSettings = Substitute.For<GlobalSettings>();
|
||||
globalSettings.Teams.LoginBaseUrl.Returns("https://login.example.com");
|
||||
globalSettings.Teams.GraphBaseUrl.Returns("https://graph.example.com");
|
||||
|
||||
return new SutProvider<TeamsService>()
|
||||
.SetDependency(clientFactory)
|
||||
.SetDependency(globalSettings)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRedirectUrl_ReturnsCorrectUrl()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var clientId = sutProvider.GetDependency<GlobalSettings>().Teams.ClientId;
|
||||
var scopes = sutProvider.GetDependency<GlobalSettings>().Teams.Scopes;
|
||||
var callbackUrl = "https://example.com/callback";
|
||||
var state = Guid.NewGuid().ToString();
|
||||
var result = sutProvider.Sut.GetRedirectUrl(callbackUrl, state);
|
||||
|
||||
var uri = new Uri(result);
|
||||
var query = HttpUtility.ParseQueryString(uri.Query);
|
||||
|
||||
Assert.Equal(clientId, query["client_id"]);
|
||||
Assert.Equal(scopes, query["scope"]);
|
||||
Assert.Equal(callbackUrl, query["redirect_uri"]);
|
||||
Assert.Equal(state, query["state"]);
|
||||
Assert.Equal("login.example.com", uri.Host);
|
||||
Assert.Equal("/common/oauth2/v2.0/authorize", uri.AbsolutePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObtainTokenViaOAuth_Success_ReturnsAccessToken()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var jsonResponse = JsonSerializer.Serialize(new
|
||||
{
|
||||
access_token = "test-access-token"
|
||||
});
|
||||
|
||||
_handler.When("https://login.example.com/common/oauth2/v2.0/token")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(jsonResponse));
|
||||
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth("test-code", "https://example.com/callback");
|
||||
|
||||
Assert.Equal("test-access-token", result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test-code", "")]
|
||||
[InlineData("", "https://example.com/callback")]
|
||||
[InlineData("", "")]
|
||||
public async Task ObtainTokenViaOAuth_CodeOrRedirectUrlIsEmpty_ReturnsEmptyString(string code, string redirectUrl)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth(code, redirectUrl);
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObtainTokenViaOAuth_HttpFailure_ReturnsEmptyString()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
_handler.When("https://login.example.com/common/oauth2/v2.0/token")
|
||||
.RespondWith(HttpStatusCode.InternalServerError)
|
||||
.WithContent(new StringContent(string.Empty));
|
||||
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth("test-code", "https://example.com/callback");
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObtainTokenViaOAuth_UnknownResponse_ReturnsEmptyString()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
_handler.When("https://login.example.com/common/oauth2/v2.0/token")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent("Not an expected response"));
|
||||
|
||||
var result = await sutProvider.Sut.ObtainTokenViaOAuth("test-code", "https://example.com/callback");
|
||||
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJoinedTeamsAsync_Success_ReturnsTeams()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
var jsonResponse = JsonSerializer.Serialize(new
|
||||
{
|
||||
value = new[]
|
||||
{
|
||||
new { id = "team1", displayName = "Team One" },
|
||||
new { id = "team2", displayName = "Team Two" }
|
||||
}
|
||||
});
|
||||
|
||||
_handler.When("https://graph.example.com/me/joinedTeams")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(jsonResponse));
|
||||
|
||||
var result = await sutProvider.Sut.GetJoinedTeamsAsync("fake-access-token");
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, t => t is { Id: "team1", DisplayName: "Team One" });
|
||||
Assert.Contains(result, t => t is { Id: "team2", DisplayName: "Team Two" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJoinedTeamsAsync_ServerReturnsEmpty_ReturnsEmptyList()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
var jsonResponse = JsonSerializer.Serialize(new { value = (object?)null });
|
||||
|
||||
_handler.When("https://graph.example.com/me/joinedTeams")
|
||||
.RespondWith(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent(jsonResponse));
|
||||
|
||||
var result = await sutProvider.Sut.GetJoinedTeamsAsync("fake-access-token");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJoinedTeamsAsync_ServerErrorCode_ReturnsEmptyList()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
_handler.When("https://graph.example.com/me/joinedTeams")
|
||||
.RespondWith(HttpStatusCode.Unauthorized)
|
||||
.WithContent(new StringContent("Unauthorized"));
|
||||
|
||||
var result = await sutProvider.Sut.GetJoinedTeamsAsync("fake-access-token");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleIncomingAppInstall_Success_UpdatesTeamsIntegration(
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var tenantId = Guid.NewGuid().ToString();
|
||||
var teamId = Guid.NewGuid().ToString();
|
||||
var conversationId = Guid.NewGuid().ToString();
|
||||
var serviceUrl = new Uri("https://localhost");
|
||||
var initiatedConfiguration = new TeamsIntegration(TenantId: tenantId, Teams:
|
||||
[
|
||||
new TeamInfo() { Id = teamId, DisplayName = "test team", TenantId = tenantId },
|
||||
new TeamInfo() { Id = Guid.NewGuid().ToString(), DisplayName = "other team", TenantId = tenantId },
|
||||
new TeamInfo() { Id = Guid.NewGuid().ToString(), DisplayName = "third team", TenantId = tenantId }
|
||||
]);
|
||||
integration.Configuration = JsonSerializer.Serialize(initiatedConfiguration);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByTeamsConfigurationTenantIdTeamId(tenantId, teamId)
|
||||
.Returns(integration);
|
||||
|
||||
OrganizationIntegration? capturedIntegration = null;
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.UpsertAsync(Arg.Do<OrganizationIntegration>(x => capturedIntegration = x));
|
||||
|
||||
await sutProvider.Sut.HandleIncomingAppInstallAsync(
|
||||
conversationId: conversationId,
|
||||
serviceUrl: serviceUrl,
|
||||
teamId: teamId,
|
||||
tenantId: tenantId
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1).GetByTeamsConfigurationTenantIdTeamId(tenantId, teamId);
|
||||
Assert.NotNull(capturedIntegration);
|
||||
var configuration = JsonSerializer.Deserialize<TeamsIntegration>(capturedIntegration.Configuration ?? string.Empty);
|
||||
Assert.NotNull(configuration);
|
||||
Assert.NotNull(configuration.ServiceUrl);
|
||||
Assert.Equal(serviceUrl, configuration.ServiceUrl);
|
||||
Assert.Equal(conversationId, configuration.ChannelId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleIncomingAppInstall_NoIntegrationMatched_DoesNothing()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
await sutProvider.Sut.HandleIncomingAppInstallAsync(
|
||||
conversationId: "conversationId",
|
||||
serviceUrl: new Uri("https://localhost"),
|
||||
teamId: "teamId",
|
||||
tenantId: "tenantId"
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1).GetByTeamsConfigurationTenantIdTeamId("tenantId", "teamId");
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive().UpsertAsync(Arg.Any<OrganizationIntegration>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleIncomingAppInstall_MatchedIntegrationAlreadySetup_DoesNothing(
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var tenantId = Guid.NewGuid().ToString();
|
||||
var teamId = Guid.NewGuid().ToString();
|
||||
var initiatedConfiguration = new TeamsIntegration(
|
||||
TenantId: tenantId,
|
||||
Teams: [new TeamInfo() { Id = teamId, DisplayName = "test team", TenantId = tenantId }],
|
||||
ChannelId: "ChannelId",
|
||||
ServiceUrl: new Uri("https://localhost")
|
||||
);
|
||||
integration.Configuration = JsonSerializer.Serialize(initiatedConfiguration);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByTeamsConfigurationTenantIdTeamId(tenantId, teamId)
|
||||
.Returns(integration);
|
||||
|
||||
await sutProvider.Sut.HandleIncomingAppInstallAsync(
|
||||
conversationId: "conversationId",
|
||||
serviceUrl: new Uri("https://localhost"),
|
||||
teamId: teamId,
|
||||
tenantId: tenantId
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1).GetByTeamsConfigurationTenantIdTeamId(tenantId, teamId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive().UpsertAsync(Arg.Any<OrganizationIntegration>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleIncomingAppInstall_MatchedIntegrationWithMissingConfiguration_DoesNothing(
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
integration.Configuration = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByTeamsConfigurationTenantIdTeamId("tenantId", "teamId")
|
||||
.Returns(integration);
|
||||
|
||||
await sutProvider.Sut.HandleIncomingAppInstallAsync(
|
||||
conversationId: "conversationId",
|
||||
serviceUrl: new Uri("https://localhost"),
|
||||
teamId: "teamId",
|
||||
tenantId: "tenantId"
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1).GetByTeamsConfigurationTenantIdTeamId("tenantId", "teamId");
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().DidNotReceive().UpsertAsync(Arg.Any<OrganizationIntegration>());
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Bit.Test.Common.MockedHttpClient;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class WebhookIntegrationHandlerTests
|
||||
{
|
||||
private readonly MockedHttpMessageHandler _handler;
|
||||
private readonly HttpClient _httpClient;
|
||||
private const string _scheme = "Bearer";
|
||||
private const string _token = "AUTH_TOKEN";
|
||||
private static readonly Uri _webhookUri = new Uri("https://localhost");
|
||||
|
||||
public WebhookIntegrationHandlerTests()
|
||||
{
|
||||
_handler = new MockedHttpMessageHandler();
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.OK)
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
_httpClient = _handler.ToHttpClient();
|
||||
}
|
||||
|
||||
private SutProvider<WebhookIntegrationHandler> GetSutProvider()
|
||||
{
|
||||
var clientFactory = Substitute.For<IHttpClientFactory>();
|
||||
clientFactory.CreateClient(WebhookIntegrationHandler.HttpClientName).Returns(_httpClient);
|
||||
|
||||
return new SutProvider<WebhookIntegrationHandler>()
|
||||
.SetDependency(clientFactory)
|
||||
.WithFakeTimeProvider()
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_SuccessfulRequestWithoutAuth_ReturnsSuccess(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUri);
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Null(result.FailureReason);
|
||||
|
||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookIntegrationHandler.HttpClientName))
|
||||
);
|
||||
|
||||
Assert.Single(_handler.CapturedRequests);
|
||||
var request = _handler.CapturedRequests[0];
|
||||
Assert.NotNull(request);
|
||||
Assert.NotNull(request.Content);
|
||||
var returned = await request.Content.ReadAsStringAsync();
|
||||
|
||||
Assert.Equal(HttpMethod.Post, request.Method);
|
||||
Assert.Null(request.Headers.Authorization);
|
||||
Assert.Equal(_webhookUri, request.RequestUri);
|
||||
AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_SuccessfulRequestWithAuthorizationHeader_ReturnsSuccess(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUri, _scheme, _token);
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Null(result.FailureReason);
|
||||
|
||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookIntegrationHandler.HttpClientName))
|
||||
);
|
||||
|
||||
Assert.Single(_handler.CapturedRequests);
|
||||
var request = _handler.CapturedRequests[0];
|
||||
Assert.NotNull(request);
|
||||
Assert.NotNull(request.Content);
|
||||
var returned = await request.Content.ReadAsStringAsync();
|
||||
|
||||
Assert.Equal(HttpMethod.Post, request.Method);
|
||||
Assert.Equal(new AuthenticationHeaderValue(_scheme, _token), request.Headers.Authorization);
|
||||
Assert.Equal(_webhookUri, request.RequestUri);
|
||||
AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
var retryAfter = now.AddSeconds(60);
|
||||
|
||||
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUri, _scheme, _token);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
||||
.WithHeader("Retry-After", "60")
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.True(result.DelayUntilDate.HasValue);
|
||||
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
|
||||
Assert.Equal("Too Many Requests", result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
var retryAfter = now.AddSeconds(60);
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUri, _scheme, _token);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
||||
.WithHeader("Retry-After", retryAfter.ToString("r"))
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.True(result.DelayUntilDate.HasValue);
|
||||
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
|
||||
Assert.Equal("Too Many Requests", result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_InternalServerError_ReturnsFailureSetsRetryable(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUri, _scheme, _token);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.InternalServerError)
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.False(result.DelayUntilDate.HasValue);
|
||||
Assert.Equal("Internal Server Error", result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_UnexpectedRedirect_ReturnsFailureNotRetryable(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUri, _scheme, _token);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.TemporaryRedirect)
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Null(result.DelayUntilDate);
|
||||
Assert.Equal("Temporary Redirect", result.FailureReason);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user