1
0
mirror of https://github.com/bitwarden/server synced 2025-12-21 10:43:44 +00:00

Refactor event integration service collection extensions into their own extension (#6714)

* Add CQRS and caching support for OrganizationIntegrationConfigurations

* Refactor event integration service collection extensions into their own extension
This commit is contained in:
Brant DeBow
2025-12-12 16:17:43 -05:00
committed by GitHub
parent 4fdc4b1b49
commit 196e555116
8 changed files with 1178 additions and 300 deletions

View File

@@ -1,12 +1,19 @@
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;
@@ -185,6 +192,666 @@ public class EventIntegrationServiceCollectionExtensionsTests
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"
});
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"
});
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"
});
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"
});
Assert.False(EventIntegrationsServiceCollectionExtensions.IsRabbitMqEnabled(globalSettings));
}
[Fact]
public void IsRabbitMqEnabled_MissingExchangeName_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
});
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"
});
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"
});
Assert.False(EventIntegrationsServiceCollectionExtensions.IsAzureServiceBusEnabled(globalSettings));
}
[Fact]
public void IsAzureServiceBusEnabled_MissingTopicName_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
});
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"
});
// 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"
});
// 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:AzureServiceBus:ConnectionString"] = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
["GlobalSettings:EventLogging:AzureServiceBus:EventTopicName"] = "events"
});
// 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"
});
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"
});
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"
});
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:RabbitMq:HostName"] = "localhost",
["GlobalSettings:EventLogging:RabbitMq:Username"] = "user",
["GlobalSettings:EventLogging:RabbitMq:Password"] = "pass",
["GlobalSettings:EventLogging:RabbitMq:EventExchangeName"] = "exchange"
});
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"
});
// 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"
});
// 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()