private static string ValidateContext(MessagePublishingContext context) { var errors = new List <string>(); if (context.Metadata == null) { errors.Add(nameof(context.Metadata)); } if (context.Payload == null) { errors.Add(nameof(context.Payload)); } if (context.Key == null) { errors.Add(nameof(context.Key)); } if (context.Api == null) { errors.Add(nameof(context.Api)); } if (context.QueueNames == null) { errors.Add(nameof(context.QueueNames)); } if (context.Broker == null) { errors.Add(nameof(context.Broker)); } return(string.Join(", ", errors)); }
public void when_publishing_cancelled_it_should_report_about_it() { var producer = new TestProducer <int, byte[]>(new ProducerConfig(), new Dictionary <string, object>()); var sut = new DefaultApiProducer <int, byte[]>( producer, Mock.Of <IHeaderValueCodec>(), Mock.Of <ILogger <DefaultApiProducer <int, byte[]> > >()); var cancelledToken = new CancellationToken(canceled: true); var context = new MessagePublishingContext { Key = 555, Payload = Encoding.UTF8.GetBytes("payload"), Api = Mock.Of <IEgressApi>(), Metadata = new Dictionary <string, string> { { "Type", "Message1" } }, QueueNames = new[] { "commands1", "audit2" }, Broker = Mock.Of <IMessageBroker>() }; var publish = sut.Publish(context, cancelledToken); publish.IsCanceled.Should().BeTrue("publishing was cancelled"); }
public void when_executing_without_causation_id_in_context_it_should_fail() { var step = new ValidateMessagePublishingContextStep(); var context = new MessagePublishingContext { Api = CreateEgressApi(), Message = new Message1(), CorrelationId = ExpectedCorrelationId, CausationId = ExpectedCausationId, MessageId = ExpectedMessageId }; Func <Task> sut = () => step.Execute(context); context.CausationId = null; sut.Should().Throw <ArgumentException>().Where( exception => exception.ParamName.Equals("context") && exception.Message.Contains("Causation ID should"), "null is not a valid causation ID"); context.CausationId = string.Empty; sut.Should().Throw <ArgumentException>().Where( exception => exception.ParamName.Equals("context") && exception.Message.Contains("Causation ID should"), "an empty string is not a valid causation ID"); context.CausationId = WhitespaceString; sut.Should().Throw <ArgumentException>().Where( exception => exception.ParamName.Equals("context") && exception.Message.Contains("Causation ID should"), "a whitespace string is not a valid causation ID"); }
/// <inheritdoc /> public Task Publish(MessagePublishingContext context, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var errors = ValidateContext(context); if (!string.IsNullOrWhiteSpace(errors)) { throw new PoezdOperationException($"Message publishing context has missing data: {errors}."); } var headers = MakeHeaders(context.Metadata); var timestamp = new Timestamp(context.Timestamp); var brokerId = context.Broker.Id; // TODO: Handle type conversion errors. var key = (TKey)context.Key; // TODO: Handle type conversion errors. var payload = (TValue)context.Payload; var message = new Message <TKey, TValue> { Key = key, Value = payload, Headers = headers, Timestamp = timestamp }; var publishTasks = context.QueueNames.Select( async queueName => { try { _logger.LogDebug( "Publishing a message with key {Key} to topic {Topic} of broker with ID {BrokerId}.", key, queueName, brokerId); await _producer.ProduceAsync( queueName, message, cancellationToken); _logger.LogDebug( "Successfully published a message with key {Key} to topic {Topic} of broker with ID {BrokerId}.", key, queueName, brokerId); } catch (Exception exception) { _logger.LogDebug( exception, "Failed to publish a message with key {Key} to topic {Topic} of broker with ID {BrokerId}.", key, queueName, brokerId); throw; } }); return(Task.WhenAll(publishTasks)); }
public void when_executed_it_should_store_queue_name_into_context() { var context = new MessagePublishingContext { Api = CreateEgressApi(), Message = new Message1() }; var sut = new GetTopicNameStep(); sut.Execute(context); context.QueueNames.Should().BeEquivalentTo(new[] { ExpectedQueueName }, "queue name should be set after step executed"); }
/// <inheritdoc /> public Task Publish(MessagePublishingContext context, CancellationToken cancellationToken) { if (!_isInitialized) { throw new PoezdOperationException("Kafka driver should be initialized before it can publish messages."); } if (context == null) { throw new ArgumentNullException(nameof(context)); } return(_producerRegistry.Get(context.Api).Publish(context, cancellationToken)); }
public async Task when_executing_it_should_serialize_message_into_byte_array() { const byte expected = 0x23; var context = new MessagePublishingContext { Api = CreateEgressApi(), Message = new Message1 { Byte = expected } }; var sut = new SerializeMessageStep(); await sut.Execute(context); ((byte[])context.Payload)[0].Should().Be(expected, "message content should be serialized"); }
public void when_executed_it_should_store_message_key_in_context() { const byte expected = 0x23; var context = new MessagePublishingContext { Api = CreateEgressApi(), Message = new Message1 { Byte = expected } }; var sut = new GetMessageKeyStep(); sut.Execute(context); context.Key.Should().Be(expected, "key should be set after step executed"); }
public async Task when_publishing_to_properly_initialized_driver_it_should_publish_messages_and_log() { var container = new Container().AddLogging(_testOutput); const string brokerId = "broker-1"; var brokerMock = new Mock <IMessageBroker>(); brokerMock.SetupGet(broker => broker.Id).Returns(brokerId); var producer = new TestProducer <int, byte[]>(new ProducerConfig(), new Dictionary <string, object>()); const int key = 555; const string topic1 = "commands1"; const string topic2 = "audit2"; var context = new MessagePublishingContext { Key = key, Payload = Encoding.UTF8.GetBytes("payload"), Api = Mock.Of <IEgressApi>(), Metadata = new Dictionary <string, string> { { "Type", "Message1" } }, QueueNames = new[] { topic1, topic2 }, Broker = brokerMock.Object }; var apiProducer = new DefaultApiProducer <int, byte[]>( producer, Mock.Of <IHeaderValueCodec>(), container.GetInstance <ILogger <DefaultApiProducer <int, byte[]> > >()); await apiProducer.Publish(context, CancellationToken.None); InMemorySink.Instance.LogEvents.Should().HaveCount(2 * 2, "message was published to 2 topics"); InMemorySink.Instance.LogEvents.ElementAt(index: 0).RenderMessage().Should().Contain("Publishing") .And.Contain(key.ToString()) .And.Contain(topic1) .And.Contain(brokerId); InMemorySink.Instance.LogEvents.ElementAt(index: 1).RenderMessage().Should().Contain("Successfully") .And.Contain(key.ToString()) .And.Contain(topic1) .And.Contain(brokerId); InMemorySink.Instance.LogEvents.ElementAt(index: 2).RenderMessage().Should().Contain("Publishing") .And.Contain(key.ToString()) .And.Contain(topic2) .And.Contain(brokerId); InMemorySink.Instance.LogEvents.ElementAt(index: 3).RenderMessage().Should().Contain("Successfully") .And.Contain(key.ToString()) .And.Contain(topic2) .And.Contain(brokerId); }
public void when_publishing_and_error_happens_it_should_log_error_and_fail() { var container = new Container().AddLogging(_testOutput); const string brokerId = "broker-1"; var brokerMock = new Mock <IMessageBroker>(); brokerMock.SetupGet(broker => broker.Id).Returns(brokerId); var producer = new TestProducer <int, byte[]>( new ProducerConfig(), new Dictionary <string, object>(), new IOException()); const int key = 555; const string topic1 = "commands1"; const string topic2 = "audit2"; var context = new MessagePublishingContext { Key = key, Payload = Encoding.UTF8.GetBytes("payload"), Api = Mock.Of <IEgressApi>(), Metadata = new Dictionary <string, string> { { "Type", "Message1" } }, QueueNames = new[] { topic1, topic2 }, Broker = brokerMock.Object }; var apiProducer = new DefaultApiProducer <int, byte[]>( producer, Mock.Of <IHeaderValueCodec>(), container.GetInstance <ILogger <DefaultApiProducer <int, byte[]> > >()); Func <Task> sut = () => apiProducer.Publish(context, CancellationToken.None); sut.Should().ThrowExactly <IOException>("should fail if publishing failed"); InMemorySink.Instance.LogEvents.Should().HaveCount(2 * 2, "message publishing failed in both topics"); InMemorySink.Instance.LogEvents.ElementAt(index: 0).RenderMessage().Should().Contain("Publishing") .And.Contain(key.ToString()) .And.Contain(topic1) .And.Contain(brokerId); InMemorySink.Instance.LogEvents.ElementAt(index: 1).RenderMessage().Should().Contain("Failed") .And.Contain(key.ToString()) .And.Contain(topic1) .And.Contain(brokerId); }
public void when_executing_without_message_in_context_it_should_fail() { var step = new ValidateMessagePublishingContextStep(); var context = new MessagePublishingContext { Api = CreateEgressApi(), Message = null, CorrelationId = ExpectedCorrelationId, CausationId = ExpectedCausationId, MessageId = ExpectedMessageId }; Func <Task> sut = () => step.Execute(context); sut.Should().Throw <ArgumentException>() .Where( exception => exception.ParamName.Equals("context") && exception.Message.Contains("Message should"), "message is required"); }
public async Task when_create_producer_it_should_create_producer() { var published = 0; var producerMock = new Mock <IProducer <int, string> >(); producerMock .Setup( producer => producer.ProduceAsync( It.IsAny <string>(), It.IsAny <Message <int, string> >(), It.IsAny <CancellationToken>())) .Callback(() => published++) .Returns(() => Task.FromResult <DeliveryResult <int, string> >(new DeliveryReport <int, string>())); var producerFactoryMock = new Mock <IProducerFactory>(); producerFactoryMock.Setup(factory => factory.Create <int, string>(It.IsAny <ProducerConfig>())).Returns(producerMock.Object); var loggerFactoryMock = new Mock <ILoggerFactory>(); loggerFactoryMock.Setup(factory => factory.CreateLogger(It.IsAny <string>())).Returns(Mock.Of <ILogger>); var sut = new DefaultApiProducerFactory( producerFactoryMock.Object, Mock.Of <IHeaderValueCodec>(), loggerFactoryMock.Object); var apiProducer = sut.Create <int, string>(new ProducerConfig()); var context = new MessagePublishingContext { Key = 555, Payload = "payload", Api = Mock.Of <IEgressApi>(), Metadata = new Dictionary <string, string>(), QueueNames = new[] { "topic1" }, Broker = Mock.Of <IMessageBroker>() }; await apiProducer.Publish(context, CancellationToken.None); published.Should().Be(expected: 1, "factory should create functional producer"); }
public void when_executed_it_should_store_message_metadata_into_context() { var sut = new GetBrokerMetadataStep(); var context = new MessagePublishingContext { Api = CreateEgressApi(), Message = new Message1(), CorrelationId = ExpectedCorrelationId, CausationId = ExpectedCausationId, MessageId = ExpectedMessageId }; sut.Execute(context); var expectedMetadata = new Dictionary <string, string> { { VentureApi.Headers.MessageTypeName, ExpectedMessageTypeName }, { VentureApi.Headers.CorrelationId, ExpectedCorrelationId }, { VentureApi.Headers.CausationId, ExpectedCausationId }, { VentureApi.Headers.MessageId, ExpectedMessageId } }; context.Metadata.Should().BeEquivalentTo(expectedMetadata, "it should store metadata with expected content"); }
public Task Publish(MessagePublishingContext context, CancellationToken cancellationToken) { _state.PublishingContext = context; _state.PublishedMessageCount++; return(Task.CompletedTask); }
public async Task when_publishing_to_properly_initialized_driver_it_should_publish_message_to_producer() { var container = new Container().AddLogging(_testOutput); var publishedMessages = new Dictionary <string, Message <int, byte[]> >(); var producerMock = new Mock <IProducer <int, byte[]> >(); producerMock .Setup( producer => producer.ProduceAsync( It.IsAny <string>(), It.IsAny <Message <int, byte[]> >(), CancellationToken.None)) .Callback( ( string topic, Message <int, byte[]> message, CancellationToken token) => publishedMessages.Add(topic, message)) .Returns(() => Task.FromResult(new DeliveryResult <int, byte[]>())); var apiProducer = new DefaultApiProducer <int, byte[]>( producerMock.Object, new Utf8ByteStringHeaderValueCodec(), container.GetInstance <ILogger <DefaultApiProducer <int, byte[]> > >()); const int key = 555; var payload = Encoding.UTF8.GetBytes("payload"); const string headerKey = "Type"; const string headerValue = "Message1"; const string topic1 = "commands1"; const string topic2 = "audit2"; var context = new MessagePublishingContext { Key = key, Payload = payload, Api = Mock.Of <IEgressApi>(), Metadata = new Dictionary <string, string> { { headerKey, headerValue } }, QueueNames = new[] { topic1, topic2 }, Broker = Mock.Of <IMessageBroker>() }; await apiProducer.Publish(context, CancellationToken.None); publishedMessages.Should().HaveCount(expected: 2, "published 1 message to 2 topics"); publishedMessages.ElementAt(index: 0).Key.Should().Be(topic1, "should publish to topics in order of occurrence"); publishedMessages.ElementAt(index: 0).Value.As <Message <int, byte[]> >().Key.Should().Be(key, "key should have expected value"); publishedMessages.ElementAt(index: 0).Value.As <Message <int, byte[]> >().Value.Should() .Equal(payload, "payload should have expected value"); var header = publishedMessages.ElementAt(index: 0).Value.As <Message <int, byte[]> >().Headers.Single(); header.Key.Should().Be(headerKey, "single header should have expected value"); header.GetValueBytes().Should().Equal(Encoding.UTF8.GetBytes(headerValue), "single header should have expected value"); publishedMessages.ElementAt(index: 1).Key.Should().Be(topic2, "should publish to topics in order of occurrence"); publishedMessages.ElementAt(index: 1).Value.As <Message <int, byte[]> >().Key.Should().Be(key, "key should have expected value"); publishedMessages.ElementAt(index: 1).Value.As <Message <int, byte[]> >().Value.Should() .Equal(payload, "payload should have expected value"); header = publishedMessages.ElementAt(index: 1).Value.As <Message <int, byte[]> >().Headers.Single(); header.Key.Should().Be(headerKey, "single header should have expected value"); header.GetValueBytes().Should().Equal(Encoding.UTF8.GetBytes(headerValue), "single header should have expected value"); }
public void when_publishing_with_invalid_context_content_it_should_fail() { var apiProducer = new DefaultApiProducer <string, string>( Mock.Of <IProducer <string, string> >(), Mock.Of <IHeaderValueCodec>(), Mock.Of <ILogger <DefaultApiProducer <string, string> > >()); const int key = 555; var payload = Encoding.UTF8.GetBytes("payload"); var metadata = new Dictionary <string, string> { { "Type", "Message1" } }; var queueNames = new[] { "commands1", "audit2" }; var broker = Mock.Of <IMessageBroker>(); var api = Mock.Of <IEgressApi>(); var context = new MessagePublishingContext { Key = key, Payload = payload, Api = api, Metadata = metadata, QueueNames = queueNames, Broker = broker }; Func <Task> sut = () => apiProducer.Publish(context, CancellationToken.None); context.Metadata = null; sut.Should().ThrowExactly <PoezdOperationException>().Where( exception => exception.Message.Contains("metadata", StringComparison.InvariantCultureIgnoreCase), "null is an invalid metadata"); context.Metadata = metadata; context.QueueNames = null; sut.Should().ThrowExactly <PoezdOperationException>().Where( exception => exception.Message.Contains("queueNames", StringComparison.InvariantCultureIgnoreCase), "null is an invalid queue names"); context.QueueNames = queueNames; context.Key = null; sut.Should().ThrowExactly <PoezdOperationException>().Where( exception => exception.Message.Contains("key", StringComparison.InvariantCultureIgnoreCase), "null is an invalid key"); context.Key = key; context.Payload = null; sut.Should().ThrowExactly <PoezdOperationException>().Where( exception => exception.Message.Contains("payload", StringComparison.InvariantCultureIgnoreCase), "null is an invalid payload"); context.Payload = payload; context.Broker = null; sut.Should().ThrowExactly <PoezdOperationException>().Where( exception => exception.Message.Contains("broker", StringComparison.InvariantCultureIgnoreCase), "null is an invalid broker"); context.Broker = broker; context.Api = null; sut.Should().ThrowExactly <PoezdOperationException>().Where( exception => exception.Message.Contains("API", StringComparison.InvariantCultureIgnoreCase), "null is an invalid API"); }