diff --git a/.editorconfig b/.editorconfig index 144d3c8..4ced911 100644 --- a/.editorconfig +++ b/.editorconfig @@ -506,4 +506,5 @@ dotnet_diagnostic.S1135.severity = suggestion # https://github.com/atc-net dotnet_diagnostic.CA1062.severity = none # Do not demand null-checking when using nullable reference types. dotnet_diagnostic.SA1011.severity = none # Space needed after closing square bracet "]" needed for nullable arrays dotnet_diagnostic.SA1401.severity = none # Field should be private -dotnet_diagnostic.CA1051.severity = none # Do not declare visible instance fields \ No newline at end of file +dotnet_diagnostic.CA1051.severity = none # Do not declare visible instance fields +dotnet_diagnostic.CA1812.severity = none # Internal classes are instantiated by IoC \ No newline at end of file diff --git a/Atc.Azure.Messaging.sln b/Atc.Azure.Messaging.sln index fd0dfb2..22da089 100644 --- a/Atc.Azure.Messaging.sln +++ b/Atc.Azure.Messaging.sln @@ -1,31 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.Azure.Messaging", "src\Atc.Azure.Messaging\Atc.Azure.Messaging.csproj", "{2F7702F2-A407-41FB-8C88-7C066237BC75}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.Azure.Messaging.Tests", "test\Atc.Azure.Messaging.Tests\Atc.Azure.Messaging.Tests.csproj", "{0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2F7702F2-A407-41FB-8C88-7C066237BC75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F7702F2-A407-41FB-8C88-7C066237BC75}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F7702F2-A407-41FB-8C88-7C066237BC75}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F7702F2-A407-41FB-8C88-7C066237BC75}.Release|Any CPU.Build.0 = Release|Any CPU - {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1EC093F7-1E03-4088-BA72-0DEB39AFEE82} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.Azure.Messaging", "src\Atc.Azure.Messaging\Atc.Azure.Messaging.csproj", "{2F7702F2-A407-41FB-8C88-7C066237BC75}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.Azure.Messaging.Tests", "test\Atc.Azure.Messaging.Tests\Atc.Azure.Messaging.Tests.csproj", "{0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2F7702F2-A407-41FB-8C88-7C066237BC75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F7702F2-A407-41FB-8C88-7C066237BC75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F7702F2-A407-41FB-8C88-7C066237BC75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F7702F2-A407-41FB-8C88-7C066237BC75}.Release|Any CPU.Build.0 = Release|Any CPU + {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C49EE44-DAE3-4A7C-9D2D-D1190CAC85F0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1EC093F7-1E03-4088-BA72-0DEB39AFEE82} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index e504439..b73f98b 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,46 @@ builder.Services.AddSwaggerGen(); builder.Services.ConfigureMessagingServices(builder.Configuration); ``` +## Connecting with Managed Identity + +When connecting with Managed Identity you need to setup your configuration accordingly. + +The following is an example of how you would configure your EventHub or ServiceBus with Managed Identity for [ATC Azure Options](https://github.com/atc-net/atc-azure-options) + +```json +{ + "EventHubOptions": { + "FullyQualifiedNamespace": "[your eventhub namespace].servicebus.windows.net" + }, + "ServiceBusOptions": { + "FullyQualifiedNamespace": "[your servicebus namespace].servicebus.windows.net" + }, + "ClientAuthorizationOptions": { + "TenantId": "[your tenant id]" + }, + "EnvironmentOptions": { + "EnvironmentType": "[Local/DevTest/Production]" + } +} +``` + +It is important that the logged-in user/application has the `Azure Service Bus Data Sender` or `Azure Service Bus Data Owner` build-in role for [ServiceBus](https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-managed-service-identity#azure-built-in-roles-for-azure-service-bus). + +And the corresponding `Azure Event Hubs Data Sender` or `Azure Event Hubs Data Owner` build-in roles for [Eventhubs](https://learn.microsoft.com/en-us/azure/event-hubs/authenticate-application#built-in-roles-for-azure-event-hubs) + +The dependencies are registered using `ConfigureMessagingServices(IConfiguration, bool)` for the default implementation of [IAzureCredentialOptionsProvider](https://github.com/atc-net/atc-azure-options/blob/main/src/Atc.Azure.Options/Providers/AzureCredentialOptionsProvider.cs) and `ConfigureMessagingServices(IConfiguration, bool, IAzureCredentialOptionsProvider)` if you wish to use your own implementation of `IAzureCredentialOptionsProvider`. + +Here's an example of the default implementation using a Minimal API setup + +```csharp +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// Register Atc.Azure.Messaging dependencies +builder.Services.ConfigureMessagingServices(builder.Configuration, true); +``` + ## Publishing to EventHub To publish events to an EventHub you need an instance of `IEventHubPublisher`, this can be constructed via the `IEventHubPublisherFactory` which exposes the `Create(string eventHubName)` method diff --git a/sample/SampleApi/Program.cs b/sample/SampleApi/Program.cs index 1514f4c..8535a25 100644 --- a/sample/SampleApi/Program.cs +++ b/sample/SampleApi/Program.cs @@ -12,7 +12,8 @@ builder.Services.AddSingleton(); // Register Atc.Azure.Messaging dependencies -builder.Services.ConfigureMessagingServices(builder.Configuration); +var useManagedIdentity = false; +builder.Services.ConfigureMessagingServices(builder.Configuration, useManagedIdentity); var app = builder.Build(); app.UseHttpsRedirection(); @@ -38,15 +39,14 @@ public SendDataHandler( IEventHubPublisherFactory eventHubFactory, IServiceBusPublisher serviceBusPublisher) { - eventHubPublisher = eventHubFactory.Create("[existing eventhub"); + eventHubPublisher = eventHubFactory.Create("[existing eventhub]"); this.serviceBusPublisher = serviceBusPublisher; } public async Task Post(Request request) { await eventHubPublisher.PublishAsync(request); - - await serviceBusPublisher.PublishAsync("existing topic|queue", request); + await serviceBusPublisher.PublishAsync("[existing topic|queue]", request); return new Response( Guid.NewGuid().ToString("N"), diff --git a/sample/SampleApi/appsettings.json b/sample/SampleApi/appsettings.json index 3fcf4cd..0627260 100644 --- a/sample/SampleApi/appsettings.json +++ b/sample/SampleApi/appsettings.json @@ -8,5 +8,14 @@ "AllowedHosts": "*", "EventHubOptions": { "ConnectionString": "Endpoint=sb://[your eventhub namespace].servicebus.windows.net/;SharedAccessKeyName=[eventhub name];SharedAccessKey=[sas key]" + }, + "ServiceBusOptions": { + "FullyQualifiedNamespace": "[your servicebus namespace].servicebus.windows.net" + }, + "ClientAuthorizationOptions": { + "TenantId": "[your tenant id]" + }, + "EnvironmentOptions": { + "EnvironmentType": "Local" } } diff --git a/src/Atc.Azure.Messaging/Atc.Azure.Messaging.csproj b/src/Atc.Azure.Messaging/Atc.Azure.Messaging.csproj index 2f7c42d..9f8e1b1 100644 --- a/src/Atc.Azure.Messaging/Atc.Azure.Messaging.csproj +++ b/src/Atc.Azure.Messaging/Atc.Azure.Messaging.csproj @@ -11,11 +11,11 @@ - + - + diff --git a/src/Atc.Azure.Messaging/EventHub/EventHubCredentialsPublisherFactory.cs b/src/Atc.Azure.Messaging/EventHub/EventHubCredentialsPublisherFactory.cs new file mode 100644 index 0000000..7bcc625 --- /dev/null +++ b/src/Atc.Azure.Messaging/EventHub/EventHubCredentialsPublisherFactory.cs @@ -0,0 +1,39 @@ +using System.Diagnostics.CodeAnalysis; +using Atc.Azure.Options.Authorization; +using Atc.Azure.Options.Environment; +using Atc.Azure.Options.EventHub; +using Atc.Azure.Options.Providers; +using Azure.Identity; +using Azure.Messaging.EventHubs.Producer; + +namespace Atc.Azure.Messaging.EventHub; + +[SuppressMessage( + "Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "EventHubPublisher is responsible for disposing EventHubProducerClient")] +internal sealed class EventHubCredentialsPublisherFactory : IEventHubPublisherFactory +{ + private readonly string fullyQualifiedNamespace; + private readonly DefaultAzureCredentialOptions credentialOptions; + + public EventHubCredentialsPublisherFactory( + EventHubOptions options, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions clientCredentialOptions, + IAzureCredentialOptionsProvider credentialOptionsProvider) + { + this.fullyQualifiedNamespace = options.FullyQualifiedNamespace; + this.credentialOptions = credentialOptionsProvider + .GetAzureCredentialOptions( + environmentOptions, + clientCredentialOptions); + } + + public IEventHubPublisher Create(string eventHubName) + => new EventHubPublisher( + new EventHubProducerClient( + fullyQualifiedNamespace, + eventHubName, + new DefaultAzureCredential(credentialOptions))); +} \ No newline at end of file diff --git a/src/Atc.Azure.Messaging/EventHub/EventHubPublisher.cs b/src/Atc.Azure.Messaging/EventHub/EventHubPublisher.cs index d58d1b6..cfb4af8 100644 --- a/src/Atc.Azure.Messaging/EventHub/EventHubPublisher.cs +++ b/src/Atc.Azure.Messaging/EventHub/EventHubPublisher.cs @@ -5,7 +5,7 @@ namespace Atc.Azure.Messaging.EventHub; -public sealed class EventHubPublisher : IEventHubPublisher +internal sealed class EventHubPublisher : IEventHubPublisher { private readonly EventHubProducerClient client; diff --git a/src/Atc.Azure.Messaging/EventHub/EventHubPublisherFactory.cs b/src/Atc.Azure.Messaging/EventHub/EventHubPublisherFactory.cs index 041919b..87eda45 100644 --- a/src/Atc.Azure.Messaging/EventHub/EventHubPublisherFactory.cs +++ b/src/Atc.Azure.Messaging/EventHub/EventHubPublisherFactory.cs @@ -1,11 +1,14 @@ +using System.Diagnostics.CodeAnalysis; using Atc.Azure.Options.EventHub; using Azure.Messaging.EventHubs.Producer; namespace Atc.Azure.Messaging.EventHub; -#pragma warning disable CA2000 // Dispose objects before losing scope - -public class EventHubPublisherFactory : IEventHubPublisherFactory +[SuppressMessage( + "Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "EventHubPublisher is responsible for disposing EventHubProducerClient")] +internal sealed class EventHubPublisherFactory : IEventHubPublisherFactory { private readonly string connectionString; diff --git a/src/Atc.Azure.Messaging/EventHub/IEventHubPublisher.cs b/src/Atc.Azure.Messaging/EventHub/IEventHubPublisher.cs index b40f242..2a77aab 100644 --- a/src/Atc.Azure.Messaging/EventHub/IEventHubPublisher.cs +++ b/src/Atc.Azure.Messaging/EventHub/IEventHubPublisher.cs @@ -1,5 +1,11 @@ -namespace Atc.Azure.Messaging.EventHub; +namespace Atc.Azure.Messaging.EventHub; +/// +/// Publisher responsible for publishing objects with metadata to a specific EventHub. +/// +/// +/// Is safe to cache and use in singletons for the lifetime of the application. +/// public interface IEventHubPublisher : IAsyncDisposable { Task PublishAsync( diff --git a/src/Atc.Azure.Messaging/EventHub/IEventHubPublisherFactory.cs b/src/Atc.Azure.Messaging/EventHub/IEventHubPublisherFactory.cs index ce6ee00..25e615a 100644 --- a/src/Atc.Azure.Messaging/EventHub/IEventHubPublisherFactory.cs +++ b/src/Atc.Azure.Messaging/EventHub/IEventHubPublisherFactory.cs @@ -1,5 +1,8 @@ namespace Atc.Azure.Messaging.EventHub; +/// +/// Factory responsible for creating s for a specific EventHub namespace. +/// public interface IEventHubPublisherFactory { IEventHubPublisher Create(string eventHubName); diff --git a/src/Atc.Azure.Messaging/ServiceBus/IServiceBusClientFactory.cs b/src/Atc.Azure.Messaging/ServiceBus/IServiceBusClientFactory.cs index 1775e8a..463e7b9 100644 --- a/src/Atc.Azure.Messaging/ServiceBus/IServiceBusClientFactory.cs +++ b/src/Atc.Azure.Messaging/ServiceBus/IServiceBusClientFactory.cs @@ -1,8 +1,8 @@ -using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus; namespace Atc.Azure.Messaging.ServiceBus; -public interface IServiceBusClientFactory +internal interface IServiceBusClientFactory { ServiceBusClient Create(); } \ No newline at end of file diff --git a/src/Atc.Azure.Messaging/ServiceBus/IServiceBusPublisher.cs b/src/Atc.Azure.Messaging/ServiceBus/IServiceBusPublisher.cs index ba98702..8c199fe 100644 --- a/src/Atc.Azure.Messaging/ServiceBus/IServiceBusPublisher.cs +++ b/src/Atc.Azure.Messaging/ServiceBus/IServiceBusPublisher.cs @@ -1,5 +1,8 @@ namespace Atc.Azure.Messaging.ServiceBus; +/// +/// Publisher responsible for publishing objects with metadata to a specific ServiceBus. +/// public interface IServiceBusPublisher { Task PublishAsync( diff --git a/src/Atc.Azure.Messaging/ServiceBus/IServiceBusSenderProvider.cs b/src/Atc.Azure.Messaging/ServiceBus/IServiceBusSenderProvider.cs index b95f643..4c6e71d 100644 --- a/src/Atc.Azure.Messaging/ServiceBus/IServiceBusSenderProvider.cs +++ b/src/Atc.Azure.Messaging/ServiceBus/IServiceBusSenderProvider.cs @@ -2,7 +2,7 @@ namespace Atc.Azure.Messaging.ServiceBus; -public interface IServiceBusSenderProvider +internal interface IServiceBusSenderProvider { ServiceBusSender GetSender(string topicName); } \ No newline at end of file diff --git a/src/Atc.Azure.Messaging/ServiceBus/ServiceBusClientFactory.cs b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusClientFactory.cs index 551dffe..d7c9c5f 100644 --- a/src/Atc.Azure.Messaging/ServiceBus/ServiceBusClientFactory.cs +++ b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusClientFactory.cs @@ -3,7 +3,7 @@ namespace Atc.Azure.Messaging.ServiceBus; -public class ServiceBusClientFactory : IServiceBusClientFactory +internal sealed class ServiceBusClientFactory : IServiceBusClientFactory { private readonly ServiceBusOptions options; @@ -12,5 +12,5 @@ public ServiceBusClientFactory(ServiceBusOptions options) this.options = options; } - public ServiceBusClient Create() => new (options.ConnectionString); + public ServiceBusClient Create() => new ServiceBusClient(options.ConnectionString); } \ No newline at end of file diff --git a/src/Atc.Azure.Messaging/ServiceBus/ServiceBusCredentialsClientFactory.cs b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusCredentialsClientFactory.cs new file mode 100644 index 0000000..6f67eee --- /dev/null +++ b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusCredentialsClientFactory.cs @@ -0,0 +1,31 @@ +using Atc.Azure.Options.Authorization; +using Atc.Azure.Options.Environment; +using Atc.Azure.Options.Providers; +using Atc.Azure.Options.ServiceBus; +using Azure.Identity; +using Azure.Messaging.ServiceBus; + +namespace Atc.Azure.Messaging.ServiceBus; + +internal sealed class ServiceBusCredentialsClientFactory : IServiceBusClientFactory +{ + private readonly string fullyQualifiedNamespace; + private readonly DefaultAzureCredentialOptions credentialOptions; + + public ServiceBusCredentialsClientFactory( + ServiceBusOptions options, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions clientCredentialOptions, + IAzureCredentialOptionsProvider credentialOptionsProvider) + { + this.fullyQualifiedNamespace = options.FullyQualifiedNamespace; + this.credentialOptions = credentialOptionsProvider + .GetAzureCredentialOptions( + environmentOptions, + clientCredentialOptions); + } + + public ServiceBusClient Create() => new ServiceBusClient( + fullyQualifiedNamespace, + new DefaultAzureCredential(credentialOptions)); +} \ No newline at end of file diff --git a/src/Atc.Azure.Messaging/ServiceBus/ServiceBusPublisher.cs b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusPublisher.cs index fdbca93..557304d 100644 --- a/src/Atc.Azure.Messaging/ServiceBus/ServiceBusPublisher.cs +++ b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusPublisher.cs @@ -3,7 +3,7 @@ namespace Atc.Azure.Messaging.ServiceBus; -public class ServiceBusPublisher : IServiceBusPublisher +internal sealed class ServiceBusPublisher : IServiceBusPublisher { private readonly IServiceBusSenderProvider clientProvider; diff --git a/src/Atc.Azure.Messaging/ServiceBus/ServiceBusSenderProvider.cs b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusSenderProvider.cs index 971f850..067d500 100644 --- a/src/Atc.Azure.Messaging/ServiceBus/ServiceBusSenderProvider.cs +++ b/src/Atc.Azure.Messaging/ServiceBus/ServiceBusSenderProvider.cs @@ -3,7 +3,7 @@ namespace Atc.Azure.Messaging.ServiceBus; -public sealed class ServiceBusSenderProvider : IServiceBusSenderProvider +internal sealed class ServiceBusSenderProvider : IServiceBusSenderProvider { private readonly ServiceBusClient client; private readonly ConcurrentDictionary senders; diff --git a/src/Atc.Azure.Messaging/ServiceCollectionExtensions.cs b/src/Atc.Azure.Messaging/ServiceCollectionExtensions.cs index 91233b9..badd217 100644 --- a/src/Atc.Azure.Messaging/ServiceCollectionExtensions.cs +++ b/src/Atc.Azure.Messaging/ServiceCollectionExtensions.cs @@ -1,26 +1,68 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Atc.Azure.Messaging.EventHub; using Atc.Azure.Messaging.ServiceBus; +using Atc.Azure.Options.Authorization; +using Atc.Azure.Options.Environment; using Atc.Azure.Options.EventHub; +using Atc.Azure.Options.Providers; using Atc.Azure.Options.ServiceBus; +using Azure.Identity; using Microsoft.Extensions.Configuration; - +using Microsoft.Extensions.DependencyInjection.Extensions; + namespace Microsoft.Extensions.DependencyInjection; [ExcludeFromCodeCoverage] public static class ServiceCollectionExtensions { - public static void ConfigureMessagingServices( - this IServiceCollection services, - IConfiguration configuration) - { - services.AddOptions(configuration); - services.AddOptions(configuration); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + /// + /// Adds messaging services from the configuration. + /// + /// The service collection + /// The application configurations + /// + /// Flag indicating whether to use managed identity with azure credentials or + /// connection strings for the messaging service configuration. + /// + /// Defaults to using connection strings. + /// + /// + /// + /// The to be used for configuring + /// the . + /// + /// Defaults to if + /// is null. + /// + /// + /// Parameter is only considered if is set to true. + /// + /// + public static void ConfigureMessagingServices( + this IServiceCollection services, + IConfiguration configuration, + bool useAzureCredentials = false, + IAzureCredentialOptionsProvider? credentialOptionsProvider = null) + { + services.AddOptions(configuration); + services.AddOptions(configuration); + + if (useAzureCredentials) + { + services.AddOptions(configuration); + services.AddOptions(configuration); + + credentialOptionsProvider ??= new AzureCredentialOptionsProvider(); + services.AddSingleton(credentialOptionsProvider); + + services.AddSingleton(); + services.AddSingleton(); + } + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } private static T AddOptions( diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 252a27e..4ce8b38 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -57,4 +57,13 @@ + + + <_Parameter1>$(MSBuildProjectName).Tests + + + <_Parameter1>DynamicProxyGenAssembly2 + + + \ No newline at end of file diff --git a/test/Atc.Azure.Messaging.Tests/Atc.Azure.Messaging.Tests.csproj b/test/Atc.Azure.Messaging.Tests/Atc.Azure.Messaging.Tests.csproj index e7f345e..82ec135 100644 --- a/test/Atc.Azure.Messaging.Tests/Atc.Azure.Messaging.Tests.csproj +++ b/test/Atc.Azure.Messaging.Tests/Atc.Azure.Messaging.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/test/Atc.Azure.Messaging.Tests/EventHub/EventHubCredentialsPublisherFactoryTests.cs b/test/Atc.Azure.Messaging.Tests/EventHub/EventHubCredentialsPublisherFactoryTests.cs new file mode 100644 index 0000000..4cfe2dc --- /dev/null +++ b/test/Atc.Azure.Messaging.Tests/EventHub/EventHubCredentialsPublisherFactoryTests.cs @@ -0,0 +1,60 @@ +using Atc.Azure.Messaging.EventHub; +using Atc.Azure.Options.Authorization; +using Atc.Azure.Options.Environment; +using Atc.Azure.Options.EventHub; +using Atc.Azure.Options.Providers; + +namespace Atc.Azure.Messaging.Tests.EventHub; + +public class EventHubCredentialsPublisherFactoryTests +{ + private const string FullyQualifiedNamespace = "eventHubNamespace.servicebus.windows.net"; + + [Theory, AutoNSubstituteData] + public void Create_Returns_NotNull( + [Frozen] IAzureCredentialOptionsProvider credentialOptionsProvider, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions authorizationOptions, + string eventHubName) + => new EventHubCredentialsPublisherFactory( + new EventHubOptions { FullyQualifiedNamespace = FullyQualifiedNamespace }, + environmentOptions, + authorizationOptions, + credentialOptionsProvider) + .Create(eventHubName) + .Should() + .NotBeNull(); + + [Theory, AutoNSubstituteData] + public void Create_Returns_IEventHubPublisher( + [Frozen] IAzureCredentialOptionsProvider credentialOptionsProvider, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions authorizationOptions, + string eventHubName) + => new EventHubCredentialsPublisherFactory( + new EventHubOptions { FullyQualifiedNamespace = FullyQualifiedNamespace }, + environmentOptions, + authorizationOptions, + credentialOptionsProvider) + .Create(eventHubName) + .Should() + .BeAssignableTo(); + + [Theory, AutoNSubstituteData] + public void Constructor_Calls_AzureCredentialsOptionsProvider( + [Frozen] IAzureCredentialOptionsProvider credentialOptionsProvider, + EventHubOptions eventHubOptions, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions authorizationOptions) + { + _ = new EventHubCredentialsPublisherFactory( + eventHubOptions, + environmentOptions, + authorizationOptions, + credentialOptionsProvider); + + credentialOptionsProvider + .Received(1) + .GetAzureCredentialOptions(environmentOptions, authorizationOptions); + } +} \ No newline at end of file diff --git a/test/Atc.Azure.Messaging.Tests/EventHub/EventHubPublisherTests.cs b/test/Atc.Azure.Messaging.Tests/EventHub/EventHubPublisherTests.cs index 7016975..fc85365 100644 --- a/test/Atc.Azure.Messaging.Tests/EventHub/EventHubPublisherTests.cs +++ b/test/Atc.Azure.Messaging.Tests/EventHub/EventHubPublisherTests.cs @@ -10,7 +10,7 @@ namespace Atc.Azure.Messaging.Tests.EventHub; public class EventHubPublisherTests { [Theory, AutoNSubstituteData] - public async Task PublishAsync_Calls_Client( + internal async Task PublishAsync_Calls_Client( [Frozen, Substitute] EventHubProducerClient client, EventHubPublisher sut, object messageBody, @@ -30,7 +30,7 @@ await sut.PublishAsync( } [Theory, AutoNSubstituteData] - public async Task PublishAsync_Calls_Client_With_Correct_MessageBody( + internal async Task PublishAsync_Calls_Client_With_Correct_MessageBody( [Frozen, Substitute] EventHubProducerClient client, EventHubPublisher sut, object messageBody, @@ -54,7 +54,7 @@ await sut.PublishAsync( } [Theory, AutoNSubstituteData] - public async Task PublishAsync_Calls_Client_With_Correct_Properties( + internal async Task PublishAsync_Calls_Client_With_Correct_Properties( [Frozen, Substitute] EventHubProducerClient client, EventHubPublisher sut, object messageBody, @@ -76,7 +76,7 @@ await sut.PublishAsync( } [Theory, AutoNSubstituteData] - public async Task Disposes_EventHubProducerClient( + internal async Task Disposes_EventHubProducerClient( [Frozen, Substitute] EventHubProducerClient client, EventHubPublisher sut) { diff --git a/test/Atc.Azure.Messaging.Tests/ServiceBus/ServiceBusCredentialsClientFactoryTests.cs b/test/Atc.Azure.Messaging.Tests/ServiceBus/ServiceBusCredentialsClientFactoryTests.cs new file mode 100644 index 0000000..aee7fdb --- /dev/null +++ b/test/Atc.Azure.Messaging.Tests/ServiceBus/ServiceBusCredentialsClientFactoryTests.cs @@ -0,0 +1,59 @@ +using Atc.Azure.Messaging.ServiceBus; +using Atc.Azure.Options.Authorization; +using Atc.Azure.Options.Environment; +using Atc.Azure.Options.Providers; +using Atc.Azure.Options.ServiceBus; + +namespace Atc.Azure.Messaging.Tests.ServiceBus; + +public class ServiceBusCredentialsClientFactoryTests +{ + private const string FullyQualifiedNamespace = "serviceBusNamespace.servicebus.windows.net"; + + [Theory, AutoNSubstituteData] + public void Create_Returns_Not_Null( + [Frozen] IAzureCredentialOptionsProvider credentialOptionsProvider, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions authorizationOptions) => + new ServiceBusCredentialsClientFactory( + new ServiceBusOptions { FullyQualifiedNamespace = FullyQualifiedNamespace }, + environmentOptions, + authorizationOptions, + credentialOptionsProvider) + .Create() + .Should() + .NotBeNull(); + + [Theory, AutoNSubstituteData] + public void Create_Returns_Client_With_FullyQualifiedNamespace( + [Frozen] IAzureCredentialOptionsProvider credentialOptionsProvider, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions authorizationOptions) => + new ServiceBusCredentialsClientFactory( + new ServiceBusOptions { FullyQualifiedNamespace = FullyQualifiedNamespace }, + environmentOptions, + authorizationOptions, + credentialOptionsProvider) + .Create() + .FullyQualifiedNamespace + .Should() + .Be(FullyQualifiedNamespace); + + [Theory, AutoNSubstituteData] + public void Constructor_Calls_AzureCredentialsOptionsProvider( + [Frozen] IAzureCredentialOptionsProvider credentialOptionsProvider, + ServiceBusOptions serviceBusOptions, + EnvironmentOptions environmentOptions, + ClientAuthorizationOptions authorizationOptions) + { + _ = new ServiceBusCredentialsClientFactory( + serviceBusOptions, + environmentOptions, + authorizationOptions, + credentialOptionsProvider); + + credentialOptionsProvider + .Received(1) + .GetAzureCredentialOptions(environmentOptions, authorizationOptions); + } +} \ No newline at end of file diff --git a/test/Atc.Azure.Messaging.Tests/ServiceBus/ServiceBusPublisherTests.cs b/test/Atc.Azure.Messaging.Tests/ServiceBus/ServiceBusPublisherTests.cs index 6de4f01..49440af 100644 --- a/test/Atc.Azure.Messaging.Tests/ServiceBus/ServiceBusPublisherTests.cs +++ b/test/Atc.Azure.Messaging.Tests/ServiceBus/ServiceBusPublisherTests.cs @@ -8,7 +8,7 @@ namespace Atc.Azure.Messaging.Tests.ServiceBus public class ServiceBusPublisherTests { [Theory, AutoNSubstituteData] - public async Task Should_Get_ServiceBusSender_For_Topic( + internal async Task Should_Get_ServiceBusSender_For_Topic( [Frozen] IServiceBusSenderProvider provider, ServiceBusPublisher sut, [Substitute] ServiceBusSender sender, @@ -37,7 +37,7 @@ await sut.PublishAsync( } [Theory, AutoNSubstituteData] - public async Task Should_Send_Message_On_ServiceBusSender( + internal async Task Should_Send_Message_On_ServiceBusSender( [Frozen] IServiceBusSenderProvider provider, ServiceBusPublisher sut, [Substitute] ServiceBusSender sender, @@ -85,7 +85,7 @@ await sut.PublishAsync( } [Theory, AutoNSubstituteData] - public async Task Should_Handle_Default_Parameters( + internal async Task Should_Handle_Default_Parameters( [Frozen] IServiceBusSenderProvider provider, ServiceBusPublisher sut, [Substitute] ServiceBusSender sender, diff --git a/test/Atc.Azure.Messaging.Tests/ServiceCollectionExtensionsTests.cs b/test/Atc.Azure.Messaging.Tests/ServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..f8b6ca0 --- /dev/null +++ b/test/Atc.Azure.Messaging.Tests/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,73 @@ +using Atc.Azure.Messaging.EventHub; +using Atc.Azure.Messaging.ServiceBus; +using Atc.Azure.Options.Providers; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Atc.Azure.Messaging.Tests; + +public class ServiceCollectionExtensionsTests +{ + private readonly IServiceCollection services = new ServiceCollection(); + + [Theory] + [InlineAutoNSubstituteData(false, typeof(IEventHubPublisherFactory), typeof(EventHubPublisherFactory))] + [InlineAutoNSubstituteData(false, typeof(IServiceBusClientFactory), typeof(ServiceBusClientFactory))] + [InlineAutoNSubstituteData(false, typeof(IServiceBusPublisher), typeof(ServiceBusPublisher))] + [InlineAutoNSubstituteData(false, typeof(IServiceBusSenderProvider), typeof(ServiceBusSenderProvider))] + [InlineAutoNSubstituteData(true, typeof(IEventHubPublisherFactory), typeof(EventHubCredentialsPublisherFactory))] + [InlineAutoNSubstituteData(true, typeof(IServiceBusClientFactory), typeof(ServiceBusCredentialsClientFactory))] + [InlineAutoNSubstituteData(true, typeof(IServiceBusPublisher), typeof(ServiceBusPublisher))] + [InlineAutoNSubstituteData(true, typeof(IServiceBusSenderProvider), typeof(ServiceBusSenderProvider))] + public void ConfigureMessagingServices_Adds_Messaging_Dependencies( + bool useAzureCredentials, + Type serviceType, + Type implementationType, + [Frozen] IConfiguration configuration) + { + services.ConfigureMessagingServices(configuration, useAzureCredentials); + + services + .Should() + .ContainSingle(s => + s.Lifetime == ServiceLifetime.Singleton && + s.ServiceType == serviceType && + s.ImplementationType == implementationType); + } + + [Theory, AutoNSubstituteData] + public void ConfigureMessagingServices_Adds_AzureCredentialOptionsProvider_Default_Dependency( + [Frozen] IConfiguration configuration) + { + services.ConfigureMessagingServices(configuration, true, null); + + services + .Should() + .ContainSingle(s => + s.Lifetime == ServiceLifetime.Singleton && + s.ServiceType == typeof(IAzureCredentialOptionsProvider)) + .Which + .ImplementationInstance + .Should() + .BeOfType(); + } + + [Theory, AutoNSubstituteData] + public void ConfigureMessagingServices_Adds_AzureCredentialOptionsProvider_Instance_Dependency( + [Frozen] IConfiguration configuration) + { + var azureCredentialOptionsProviderInstance = new AzureCredentialOptionsProvider(); + + services.ConfigureMessagingServices(configuration, true, azureCredentialOptionsProviderInstance); + + services + .Should() + .ContainSingle(s => + s.Lifetime == ServiceLifetime.Singleton && + s.ServiceType == typeof(IAzureCredentialOptionsProvider)) + .Which + .ImplementationInstance + .Should() + .BeSameAs(azureCredentialOptionsProviderInstance); + } +}