diff --git a/Source/Messaging/MessagingActorEntryOptions.cs b/Source/Messaging/MessagingActorEntryOptions.cs new file mode 100644 index 0000000..ad7a087 --- /dev/null +++ b/Source/Messaging/MessagingActorEntryOptions.cs @@ -0,0 +1,22 @@ +namespace Fossa.Messaging; + +/// +/// Represents the options for a messaging actor entry. +/// +public class MessagingActorEntryOptions +{ + /// + /// Gets or sets the name. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the plain text value. + /// + public string? PlainTextValue { get; set; } + + /// + /// Gets or sets the Base64 value. + /// + public string? Base64Value { get; set; } +} diff --git a/Source/Messaging/MessagingOptions.cs b/Source/Messaging/MessagingOptions.cs index 59906c9..8c426ea 100644 --- a/Source/Messaging/MessagingOptions.cs +++ b/Source/Messaging/MessagingOptions.cs @@ -8,7 +8,7 @@ public class MessagingOptions /// /// Gets the actor. /// - public Dictionary? Actor { get; init; } + public IReadOnlyList? Actor { get; init; } /// /// Gets the topic. diff --git a/Source/Messaging/ProducerProvider.cs b/Source/Messaging/ProducerProvider.cs index 2e9751d..505e588 100644 --- a/Source/Messaging/ProducerProvider.cs +++ b/Source/Messaging/ProducerProvider.cs @@ -1,7 +1,9 @@ namespace Fossa.Messaging; +using System.Text; using Confluent.Kafka; using Microsoft.Extensions.Options; +using static LanguageExt.Prelude; /// /// Provides an . @@ -40,7 +42,12 @@ public void Dispose() #pragma warning restore CA1508 // Avoid dead conditional code { var serviceIdentity = this.serviceIdentityProvider.GetIdentity(); - var producerConfig = new ProducerConfig(this.options.Value.Actor) + var messagingActorEntryOptions = this.options.Value.Actor ?? []; + var messagingActorOptions = messagingActorEntryOptions + .ToDictionary( + k => k?.Name ?? throw new InvalidOperationException("One of the Message Actor Entry Options Name is not provided."), + ResolveActorEntryValue); + var producerConfig = new ProducerConfig(messagingActorOptions) { ClientId = serviceIdentity.ToString(), }; @@ -69,4 +76,31 @@ protected virtual void Dispose(bool disposing) this.disposedValue = true; } } + + private static string ResolveActorEntryValue(MessagingActorEntryOptions? options) + { + if (options is null) + { + throw new InvalidOperationException("One of the Message Actor Entry Options is null."); + } + + var providedValues = Seq( + Tuple(nameof(options.PlainTextValue), Optional(options.PlainTextValue)), + Tuple(nameof(options.Base64Value), Optional(options.Base64Value) + .Map(x => Encoding.UTF8.GetString(Convert.FromBase64String(x))))) + .Choose(x => x.Item2.Map(v => Tuple(x.Item1, v))); + + if (providedValues.Count == 1) + { + return providedValues.Single().Item2; + } + else if (providedValues.Count == 0) + { + throw new InvalidOperationException($"Messaging actor entry '{options.Name}'. One of the value properties must be set."); + } + else + { + throw new InvalidOperationException($"Messaging actor entry '{options.Name}' has multiple value properties set. Only one of these '{providedValues.Select(x => x.Item1)}' properties should be set."); + } + } } diff --git a/Tests/Messaging.Test/MessagePublisherTests.cs b/Tests/Messaging.Test/MessagePublisherTests.cs new file mode 100644 index 0000000..d4004fd --- /dev/null +++ b/Tests/Messaging.Test/MessagePublisherTests.cs @@ -0,0 +1,71 @@ +namespace Fossa.Messaging.Test; + +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Fossa.Messaging.Messages.Events; +using IdGen.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Time.Testing; +using NSubstitute; +using TIKSN.DependencyInjection; +using TIKSN.Identity; +using Xunit; + +[Trait("Category", "Integration")] +public class MessagePublisherTests +{ + private readonly IServiceProvider serviceProvider; + + public MessagePublisherTests() + { + var configuration = new ConfigurationBuilder() + .AddUserSecrets() + .Build(); + var services = new ServiceCollection(); + _ = services.AddMessaging(configuration, "Fossa", Seq("Messaging", "Test")); + _ = services.AddFrameworkCore(); + _ = services.AddIdGen(9); + + var fakeTimeProvider = new FakeTimeProvider( + new DateTimeOffset(2022, 9, 24, 0, 0, 0, TimeSpan.Zero)); + _ = services.AddSingleton(fakeTimeProvider); + + var serviceIdentityProvider = Substitute.For(); + _ = serviceIdentityProvider + .GetIdentity().Returns( + new ServiceIdentity( + applicationName: "Fossa", + componentNames: Seq("Messaging", "Test"), + instanceId: ServiceInstanceId.Create(Ulid.NewUlid()))); + + _ = services.AddSingleton(serviceIdentityProvider); + + ContainerBuilder containerBuilder = new(); + _ = containerBuilder.RegisterModule(); + containerBuilder.Populate(services); + + this.serviceProvider = new AutofacServiceProvider(containerBuilder.Build()); + } + + [Fact] + public async Task GivenPublisherAndMessage_WhenMessageIsPublished_ThenDeliveryShouldSucceedAsync() + { + // Arrange + var messagePublisher = this.serviceProvider.GetRequiredService(); + const string topic = "test"; + + var message = new CompanyDeletedProtoEvent { CompanyId = 123L }; + + // Act + + var deliveryResult = await messagePublisher.PublishAsync(message, message.CompanyId, "Company", message.CompanyId, default).ConfigureAwait(true); + + // Assert + + Assert.NotNull(deliveryResult); + Assert.NotNull(deliveryResult.Key); + Assert.NotNull(deliveryResult.Value); + Assert.Equal(topic, deliveryResult.Topic); + } +} diff --git a/Tests/Messaging.Test/Messaging.Test.csproj b/Tests/Messaging.Test/Messaging.Test.csproj index b574345..052366a 100644 --- a/Tests/Messaging.Test/Messaging.Test.csproj +++ b/Tests/Messaging.Test/Messaging.Test.csproj @@ -2,6 +2,7 @@ net10.0 + ad336f2b-09b8-4c53-96cb-ed9913b583f1 diff --git a/build.cake b/build.cake index 3954abe..180188e 100644 --- a/build.cake +++ b/build.cake @@ -49,6 +49,7 @@ Task("Test") Blame = true, Collectors = new string[] { "Code Coverage", "XPlat Code Coverage" }, Configuration = configuration, + Filter = !BuildSystem.IsLocalBuild ? "Category!=Integration" : null, Loggers = new string[] { $"trx;LogFileName={project.GetFilenameWithoutExtension()}.trx",