private async Task ProcessedScheduledEnvelopeAsync(Timer timer, OutboundEnvelope outboundEnvelope) { if (_messageRetries == outboundEnvelope.TotalResents) { _logger?.LogWarning( $"Reached max resends for connectionId {outboundEnvelope.ConnectionId} on topic {outboundEnvelope.Envelope.Topic} "); await TriggerAsyncEvent(OutboundEnvelopeReachedMaxResends, new OutboundEnvelopeReachedMaxResendsEvent { ConnectionId = outboundEnvelope.ConnectionId }); await RemoveQueuedEnvelopeAsync(outboundEnvelope.Id); return; } await _whisperRpc.SendMessageAsync( outboundEnvelope.Envelope.Topic, outboundEnvelope.Envelope.EncryptionKey, outboundEnvelope.Envelope.EncryptionType, outboundEnvelope.Payload); outboundEnvelope.TotalResents += 1; }
public void LogProduced_Envelope_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" } }, new TestProducerEndpoint("test1"), true, new TestOffset("a", "42")); var expectedMessage = "Message produced. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "unused1: (null), " + "unused2: (null)"; _outboundLogger.LogProduced(envelope); _loggerSubstitute.Received(LogLevel.Information, null, expectedMessage, 1031); }
public void LogProduceError_Envelope_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" } }, new TestProducerEndpoint("test1"), true, new TestOffset("a", "42")); var expectedMessage = "Error occurred producing the message. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "unused1: (null), " + "unused2: (null)"; _outboundLogger.LogProduceError(envelope, new InvalidDataException()); _loggerSubstitute.Received(LogLevel.Warning, typeof(InvalidDataException), expectedMessage, 1032); }
public void LogWrittenToOutbox_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" } }, new TestProducerEndpoint("test1"), true, new TestOffset("a", "42")); var expectedMessage = "Writing the outbound message to the transactional outbox. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "unused1: (null), " + "unused2: (null)"; _outboundLogger.LogWrittenToOutbox(envelope); _loggerSubstitute.Received(LogLevel.Debug, null, expectedMessage, 1073); }
public void LogErrorProducingOutboxStoredMessage_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" } }, new TestProducerEndpoint("test1"), true, new TestOffset("a", "42")); var expectedMessage = "Failed to produce the message stored in the outbox. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "unused1: (null), " + "unused2: (null)"; _outboundLogger.LogErrorProducingOutboxStoredMessage(envelope, new InvalidOperationException()); _loggerSubstitute.Received( LogLevel.Error, typeof(InvalidOperationException), expectedMessage, 1077); }
public void LogProduced_Envelope_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" }, { KafkaMessageHeaders.KafkaMessageKey, "key1234" } }, new KafkaProducerEndpoint("test1"), true, new KafkaOffset("topic2", 2, 42)); var expectedMessage = "Message produced. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "offset: [2]@42, " + "kafkaKey: key1234"; _outboundLogger.LogProduced(envelope); _loggerSubstitute.Received(LogLevel.Information, null, expectedMessage, 1031); }
public async Task HandleAsync_NonBinaryFileMessage_EnvelopeUntouched() { var message = new BinaryFileMessage { Content = new MemoryStream(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }) }; var endpoint = TestProducerEndpoint.GetDefault(); endpoint.Serializer = new BinaryFileMessageSerializer(); var envelope = new OutboundEnvelope(message, null, endpoint); IOutboundEnvelope?result = null; await new BinaryFileHandlerProducerBehavior().HandleAsync( new ProducerPipelineContext( envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), context => { result = context.Envelope; return(Task.CompletedTask); }); result.Should().NotBeNull(); result !.Should().BeSameAs(envelope); }
public async Task HandleAsync_OutboundMessageWithOutboxStrategy_RelayedToOutbox() { var outboundEnvelope = new OutboundEnvelope <TestEventOne>( new TestEventOne(), Array.Empty <MessageHeader>(), new TestProducerEndpoint("test") { Strategy = new OutboxProduceStrategy() }); await _behavior.HandleAsync( outboundEnvelope, message => Task.FromResult(new[] { message }.AsReadOnlyCollection()) !); await _behavior.HandleAsync( outboundEnvelope, message => Task.FromResult(new[] { message }.AsReadOnlyCollection()) !); await _behavior.HandleAsync( outboundEnvelope, message => Task.FromResult(new[] { message }.AsReadOnlyCollection()) !); await _outbox.CommitAsync(); var queued = await _outbox.ReadAsync(10); queued.Should().HaveCount(3); _broker.ProducedMessages.Should().BeEmpty(); }
public void LogProduceError_Envelope_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" }, { KafkaMessageHeaders.KafkaMessageKey, "key1234" } }, new KafkaProducerEndpoint("test1"), true, new KafkaOffset("topic2", 2, 42)); var expectedMessage = "Error occurred producing the message. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "offset: (null), " + "kafkaKey: key1234"; _outboundLogger.LogProduceError(envelope, new InvalidDataException()); _loggerSubstitute.Received(LogLevel.Warning, typeof(InvalidDataException), expectedMessage, 1032); }
public async Task HandleAsync_ValidMessage_NoLogAndNoException(MessageValidationMode validationMode) { var message = new TestValidationMessage { Id = "1", String10 = "123", IntRange = 5, NumbersOnly = "123" }; var endpoint = TestProducerEndpoint.GetDefault(); endpoint.MessageValidationMode = validationMode; var envelope = new OutboundEnvelope(message, null, endpoint); IOutboundEnvelope?result = null; Func <Task> act = () => new ValidatorProducerBehavior(_outboundLogger).HandleAsync( new ProducerPipelineContext( envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), context => { result = context.Envelope; return(Task.CompletedTask); }); await act.Should().NotThrowAsync <ValidationException>(); result.Should().NotBeNull(); result !.Message.Should().NotBeNull(); _loggerSubstitute.DidNotReceive(LogLevel.Warning, null).Should().BeTrue(); }
public async Task HandleAsync_LogWarning_WarningIsLogged( IIntegrationMessage message, string expectedValidationMessage) { var endpoint = TestProducerEndpoint.GetDefault(); endpoint.MessageValidationMode = MessageValidationMode.LogWarning; var envelope = new OutboundEnvelope(message, null, endpoint); IOutboundEnvelope?result = null; await new ValidatorProducerBehavior(_outboundLogger).HandleAsync( new ProducerPipelineContext( envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), context => { result = context.Envelope; return(Task.CompletedTask); }); result.Should().NotBeNull(); result !.Message.Should().NotBeNull(); _loggerSubstitute.Received(LogLevel.Warning, null, expectedValidationMessage, 1079); }
public async Task HandleAsync_ThrowException_ExceptionIsThrown() { var message = new TestValidationMessage { Id = "1", String10 = "123456789abc", IntRange = 5, NumbersOnly = "123" }; var expectedMessage = $"The message is not valid:{Environment.NewLine}- The field String10 must be a string with a maximum length of 10."; var endpoint = TestProducerEndpoint.GetDefault(); endpoint.MessageValidationMode = MessageValidationMode.ThrowException; var envelope = new OutboundEnvelope(message, null, endpoint); IOutboundEnvelope?result = null; Func <Task> act = () => new ValidatorProducerBehavior(_outboundLogger).HandleAsync( new ProducerPipelineContext( envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), context => { result = context.Envelope; return(Task.CompletedTask); }); result.Should().BeNull(); await act.Should().ThrowAsync <MessageValidationException>().WithMessage(expectedMessage); }
public void LogWrittenToOutbox_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" }, { KafkaMessageHeaders.KafkaMessageKey, "key1234" } }, new KafkaProducerEndpoint("test1"), true, new KafkaOffset("topic2", 2, 42)); var expectedMessage = "Writing the outbound message to the transactional outbox. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "offset: [2]@42, " + "kafkaKey: key1234"; _outboundLogger.LogWrittenToOutbox(envelope); _loggerSubstitute.Received(LogLevel.Debug, null, expectedMessage, 1073); }
public void LogErrorProducingOutboxStoredMessage_Logged() { var envelope = new OutboundEnvelope( null, new MessageHeaderCollection { { DefaultMessageHeaders.MessageType, "Message.Type" }, { DefaultMessageHeaders.MessageId, "1234" }, { KafkaMessageHeaders.KafkaMessageKey, "key1234" } }, new KafkaProducerEndpoint("test1"), true, new KafkaOffset("topic2", 2, 42)); var expectedMessage = "Failed to produce the message stored in the outbox. | " + "endpointName: test1, " + "messageType: Message.Type, " + "messageId: 1234, " + "offset: [2]@42, " + "kafkaKey: key1234"; _outboundLogger.LogErrorProducingOutboxStoredMessage(envelope, new InvalidOperationException()); _loggerSubstitute.Received( LogLevel.Error, typeof(InvalidOperationException), expectedMessage, 1077); }
public void HandleAsync_NoStartedActivity_ActivityStartedAndTraceIdHeaderIsSet() { var envelope = new OutboundEnvelope(null, null, TestProducerEndpoint.GetDefault()); new ActivityProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); envelope.Headers.Should().Contain( header => header.Name == DefaultMessageHeaders.TraceId && !string.IsNullOrEmpty(header.Value)); }
public void MustCreateSequence_NoChunking_FalseReturned() { var rawMessage = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 }; var envelope = new OutboundEnvelope( rawMessage, null, new TestProducerEndpoint("test")); var writer = new ChunkSequenceWriter(); var result = writer.CanHandle(envelope); result.Should().BeFalse(); }
public async Task HandleAsync_OutboundMessage_RelayedToEndpoint() { var outboundEnvelope = new OutboundEnvelope <TestEventOne>( new TestEventOne(), Array.Empty <MessageHeader>(), new TestProducerEndpoint("test")); await _behavior.HandleAsync(new[] { outboundEnvelope, outboundEnvelope, outboundEnvelope }, Task.FromResult !); await _outbox.CommitAsync(); var queued = await _outbox.ReadAsync(10); queued.Should().BeEmpty(); _broker.ProducedMessages.Should().HaveCount(3); }
public async Task ProcessMessage_LargeMessage_ChunkEnvelopesReturned() { var rawMessage = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 }; var sourceEnvelope = new OutboundEnvelope( rawMessage, new MessageHeaderCollection { { DefaultMessageHeaders.MessageId, "123" }, { "some-custom-header", "abc" } }, new TestProducerEndpoint("test") { Chunk = new ChunkSettings { Size = 3 } }, true); var writer = new ChunkSequenceWriter(); var envelopes = await writer.ProcessMessageAsync(sourceEnvelope).ToListAsync(); envelopes.Should().HaveCount(4); envelopes.ForEach(envelope => envelope.Endpoint.Should().BeSameAs(sourceEnvelope.Endpoint)); envelopes.ForEach(envelope => envelope.Headers.Should().Contain(sourceEnvelope.Headers)); envelopes.ForEach(envelope => envelope.AutoUnwrap.Should().Be(sourceEnvelope.AutoUnwrap)); envelopes[0].RawMessage.ReadAll().Should().BeEquivalentTo(new byte[] { 0x01, 0x02, 0x03 }); envelopes[0].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunkIndex, "0")); envelopes[0].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunksCount, "4")); envelopes[1].RawMessage.ReadAll().Should().BeEquivalentTo(new byte[] { 0x04, 0x05, 0x06 }); envelopes[1].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunkIndex, "1")); envelopes[1].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunksCount, "4")); envelopes[2].RawMessage.ReadAll().Should().BeEquivalentTo(new byte[] { 0x07, 0x08, 0x09 }); envelopes[2].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunkIndex, "2")); envelopes[2].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunksCount, "4")); envelopes[3].RawMessage.ReadAll().Should().BeEquivalentTo(new byte[] { 0x10 }); envelopes[3].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunkIndex, "3")); envelopes[3].Headers.Should() .ContainEquivalentOf(new MessageHeader(DefaultMessageHeaders.ChunksCount, "4")); }
public async Task SendEnvelopeAsync(OutboundEnvelope outboundEnvelope, bool doRetries) { await _whisperRpc.SendMessageAsync( outboundEnvelope.Envelope.Topic, outboundEnvelope.Envelope.EncryptionKey, outboundEnvelope.Envelope.EncryptionType, outboundEnvelope.Payload); if (doRetries) { var timer = new Timer(_envelopeExpiryInSeconds * 1000); timer.Elapsed += async(sender, e) => await ProcessedScheduledEnvelopeAsync(timer, outboundEnvelope); _timers[outboundEnvelope.Id] = timer; timer.Start(); } }
public void HandleAsync_StartedActivity_TraceIdHeaderIsSet() { var activity = new Activity("test"); activity.SetParentId("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"); activity.Start(); var envelope = new OutboundEnvelope(null, null, TestProducerEndpoint.GetDefault()); new ActivityProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); envelope.Headers.Should().Contain( header => header.Name == DefaultMessageHeaders.TraceId && header.Value != null && header.Value.StartsWith("00-0af7651916cd43dd8448eb211c80319c", StringComparison.Ordinal)); }
public async Task HandleAsync_NoRoutingKeyAttribute_KeyHeaderIsNotSet() { var envelope = new OutboundEnvelope <NoRoutingKeyMessage>( new NoRoutingKeyMessage { Id = Guid.NewGuid(), One = "1", Two = "2", Three = "3" }, null, new RabbitExchangeProducerEndpoint("test-endpoint")); await new RabbitRoutingKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); envelope.Headers.Should().NotContain(h => h.Name == "x-rabbit-routing-key"); }
public void HandleAsync_SingleRoutingKeyAttribute_KeyHeaderIsSet() { var envelope = new OutboundEnvelope <RoutingKeyMessage>( new RoutingKeyMessage { Id = Guid.NewGuid(), One = "1", Two = "2", Three = "3" }, null, new RabbitExchangeProducerEndpoint("test-endpoint")); new RabbitRoutingKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); envelope.Headers.Should().ContainEquivalentOf(new MessageHeader("x-rabbit-routing-key", "1")); }
public void MustCreateSequence_MessageExceedsChunkSize_TrueReturned() { var rawMessage = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 }; var envelope = new OutboundEnvelope( rawMessage, null, new TestProducerEndpoint("test") { Chunk = new ChunkSettings { Size = 3 } }); var writer = new ChunkSequenceWriter(); var result = writer.CanHandle(envelope); result.Should().BeTrue(); }
public async Task HandleAsync_MultipleKeyMemberAttributes_KeyHeaderIsSet() { var envelope = new OutboundEnvelope <MultipleKeyMembersMessage>( new MultipleKeyMembersMessage { Id = Guid.NewGuid(), One = "1", Two = "2", Three = "3" }, null, new KafkaProducerEndpoint("test-endpoint")); await new KafkaMessageKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); envelope.Headers.Should().ContainEquivalentOf(new MessageHeader("x-kafka-message-key", "One=1,Two=2")); }
public void HandleAsync_MultipleRoutingKeyAttributes_KeyHeaderIsSet() { var envelope = new OutboundEnvelope <MultipleRoutingKeyAttributesMessage>( new MultipleRoutingKeyAttributesMessage { Id = Guid.NewGuid(), One = "1", Two = "2", Three = "3" }, null, new RabbitExchangeProducerEndpoint("test-endpoint")); Func <Task> act = () => new RabbitRoutingKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); act.Should().Throw <InvalidOperationException>(); }
public void EnrichOutboundActivity_TagsAdded() { KafkaActivityEnricher enricher = new(); var envelope = new OutboundEnvelope <SingleKeyMemberMessage>( new SingleKeyMemberMessage(), new[] { new MessageHeader(KafkaMessageHeaders.KafkaMessageKey, "MyKey") }, new KafkaProducerEndpoint("test-endpoint")); ProducerPipelineContext context = new( envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()); Activity activity = new("Test Activity"); enricher.EnrichOutboundActivity(activity, context); activity.Tags.Should().Contain( pair => pair.Key == KafkaActivityEnricher.KafkaMessageKey && pair.Value == "MyKey"); }
public void MustCreateSequence_MessageSmallerThanChunkSize_ReturnedAccordingToAlwaysAddHeadersFlag( bool alwaysAddHeaders) { var rawMessage = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 }; var envelope = new OutboundEnvelope( rawMessage, null, new TestProducerEndpoint("test") { Chunk = new ChunkSettings { Size = 10, AlwaysAddHeaders = alwaysAddHeaders } }); var writer = new ChunkSequenceWriter(); var result = writer.CanHandle(envelope); result.Should().Be(alwaysAddHeaders); }
public async Task HandleAsync_NoKeyMemberAttributeAndNoMessageId_RandomKafkaKeyIsGenerated() { var envelope = new OutboundEnvelope <NoKeyMembersMessage>( new NoKeyMembersMessage { Id = Guid.NewGuid(), One = "1", Two = "2", Three = "3" }, null, new KafkaProducerEndpoint("test-endpoint")); await new KafkaMessageKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); var keyValue = envelope.Headers.GetValue("x-kafka-message-key"); keyValue.Should().NotBeNullOrEmpty(); new Guid(keyValue !).Should().NotBeEmpty(); }
public async Task HandleAsync_NoKeyMemberAttribute_MessageIdUsedAsKey() { var envelope = new OutboundEnvelope <NoKeyMembersMessage>( new NoKeyMembersMessage { Id = Guid.NewGuid(), One = "1", Two = "2", Three = "3" }, new MessageHeaderCollection { { "x-message-id", "Heidi!" } }, new KafkaProducerEndpoint("test-endpoint")); await new KafkaMessageKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For <IProducer>(), Substitute.For <IServiceProvider>()), _ => Task.CompletedTask); envelope.Headers.Should().ContainEquivalentOf(new MessageHeader("x-kafka-message-key", "Heidi!")); }
public OutboxWorkerTests() { var serviceProvider = ServiceProviderHelper.GetServiceProvider( services => services .AddFakeLogger() .AddSilverback() .WithConnectionToMessageBroker( options => options .AddBroker <TestBroker>() .AddOutbox <InMemoryOutbox>() .AddOutboxWorker()) .AddEndpoints( endpoints => endpoints .AddOutbound <TestEventOne>(new TestProducerEndpoint("topic1")) .AddOutbound <TestEventTwo>(new TestProducerEndpoint("topic2")) .AddOutbound <TestEventThree>(new TestProducerEndpoint("topic3a")) .AddOutbound <TestEventThree>(new TestProducerEndpoint("topic3b")))); _broker = serviceProvider.GetRequiredService <TestBroker>(); _broker.ConnectAsync().Wait(); _worker = serviceProvider.GetRequiredService <IOutboxWorker>(); _outboxWriter = serviceProvider.GetRequiredService <IOutboxWriter>(); _sampleOutboundEnvelope = new OutboundEnvelope <TestEventOne>( new TestEventOne { Content = "Test" }, null, new TestProducerEndpoint("topic1")); _sampleOutboundEnvelope.RawMessage = AsyncHelper.RunValueTaskSynchronously( () => new JsonMessageSerializer().SerializeAsync( _sampleOutboundEnvelope.Message, _sampleOutboundEnvelope.Headers, MessageSerializationContext.Empty)); }