public Task Fail_WhenReturn()
        {
            #region Arrange

            const ulong taskId = 4;

            var modelMock = new Mock <IModel>();
            modelMock.Setup(m => m.NextPublishSeqNo)
            .Returns(taskId);

            var propertiesMock = new FakeOptions
            {
                Headers = new Dictionary <string, object>()
            };

            var confirmableChannel = new PublishConfirmableChannel(modelMock.Object);

            #endregion Arrange

            #region Act

            var publishTask = confirmableChannel.BasicPublishAsync(
                string.Empty,
                string.Empty,
                true,
                propertiesMock,
                ReadOnlyMemory <byte> .Empty
                );

            Assert.IsTrue(propertiesMock.Headers.ContainsKey("publishTag"), "publishTag header not set!");
            Assert.IsTrue(confirmableChannel.HasTaskWith(taskId), "PublishTaskInfo not added!");

            // emulate rabbitmq internal encoding.
            propertiesMock.Headers["publishTag"] = Encoding.UTF8.GetBytes(taskId.ToString());

            modelMock.Raise(m => m.BasicReturn += null, new BasicReturnEventArgs
            {
                ReplyCode       = 500,
                ReplyText       = "Something went wrong!",
                BasicProperties = propertiesMock
            });

            #endregion Act

            #region Assert

            return(publishTask.ContinueWith(t =>
            {
                Assert.IsTrue(t.IsFaulted, "Task should be faulted");
                Assert.AreEqual("The message was returned by RabbitMQ: 500-Something went wrong!", t.Exception?.InnerException?.Message, "Error message does not equal!");
                Assert.IsFalse(confirmableChannel.HasTaskWith(taskId), "PublishTaskInfo not removed!");
            }));

            #endregion Assert
        }
        public Task Fail_WhenNotConfirmed()
        {
            #region Arrange

            const int taskId = 3;

            var modelMock = new Mock <IModel>();

            modelMock.Setup(m => m.NextPublishSeqNo)
            .Returns(taskId);

            var propertiesMock = new FakeOptions
            {
                Headers = new Dictionary <string, object>()
            };

            var confirmableChannel = new PublishConfirmableChannel(modelMock.Object);

            #endregion Arrange

            #region Act

            var publishTask = confirmableChannel.BasicPublishAsync(
                string.Empty,
                string.Empty,
                true,
                propertiesMock,
                ReadOnlyMemory <byte> .Empty
                );

            Assert.IsTrue(confirmableChannel.HasTaskWith(taskId), "PublishTaskInfo not added!");

            modelMock.Raise(m => m.ModelShutdown += null, new ShutdownEventArgs(
                                ShutdownInitiator.Application,
                                500,
                                "REASON_FROM_RABBIT"
                                )
                            );

            #endregion Act

            #region Assert

            return(publishTask.ContinueWith(t =>
            {
                Assert.IsTrue(t.IsFaulted, "Task should be faulted.");
                Assert.AreEqual(
                    "The message was not confirmed by RabbitMQ within the specified period. REASON_FROM_RABBIT",
                    t.Exception?.InnerException?.Message
                    );
                Assert.IsFalse(confirmableChannel.HasTaskWith(taskId));
            }));

            #endregion Assert
        }
        public Task Fail_WhenNack()
        {
            #region Arrange

            const int taskId = 2;

            var modelMock = new Mock <IModel>();

            modelMock.Setup(m => m.NextPublishSeqNo)
            .Returns(taskId);

            var propertiesMock = new FakeOptions
            {
                Headers = new Dictionary <string, object>()
            };

            var confirmableChannel = new PublishConfirmableChannel(modelMock.Object);

            #endregion Arrange

            #region Act

            var publishTask = confirmableChannel.BasicPublishAsync(
                string.Empty,
                string.Empty,
                true,
                propertiesMock,
                ReadOnlyMemory <byte> .Empty
                );

            Assert.IsTrue(confirmableChannel.HasTaskWith(taskId), "PublishTaskInfo not added!");

            modelMock.Raise(m => m.BasicNacks += null, new BasicNackEventArgs
            {
                DeliveryTag = taskId,
                Multiple    = false
            });

            #endregion Act

            #region Assert

            return(publishTask.ContinueWith(t =>
            {
                Assert.IsTrue(t.IsFaulted);
                Assert.AreEqual("The message was not acknowledged by RabbitMQ", t.Exception?.InnerException?.Message);
                Assert.IsFalse(confirmableChannel.HasTaskWith(taskId));
            }));

            #endregion Assert
        }
        public Task Success_WhenAck()
        {
            #region Arrange

            const int taskId = 1;

            var modelMock = new Mock <IModel>();

            modelMock.Setup(m => m.NextPublishSeqNo)
            .Returns(taskId);

            var propertiesMock = new FakeOptions
            {
                Headers = new Dictionary <string, object>()
            };

            var confirmableChannel = new PublishConfirmableChannel(modelMock.Object);

            #endregion Arrange

            #region Act

            var publishTask = confirmableChannel.BasicPublishAsync(
                string.Empty,
                string.Empty,
                true,
                propertiesMock,
                ReadOnlyMemory <byte> .Empty
                );

            Assert.IsTrue(confirmableChannel.HasTaskWith(taskId), "PublishTaskInfo not added!");

            modelMock.Raise(m => m.BasicAcks += null, new BasicAckEventArgs
            {
                DeliveryTag = taskId,
                Multiple    = false
            });

            #endregion Act

            #region Assert

            return(publishTask.ContinueWith(t =>
            {
                Assert.IsTrue(t.IsCompletedSuccessfully, "Task should complete successful");
                Assert.IsFalse(confirmableChannel.HasTaskWith(taskId), "PublishTaskInfo not removed!");
            }));

            #endregion Assert
        }
        public Task Fail_WhenTimedOut()
        {
            #region Arrange

            const int taskId = 5;

            var modelMock = new Mock <IModel>();

            modelMock.Setup(m => m.NextPublishSeqNo)
            .Returns(taskId);

            var propertiesMock = new FakeOptions
            {
                Headers = new Dictionary <string, object>()
            };

            // timeout confirm immediatelly - we can add task to tracker, but timeout cancel it fast
            var confirmTimeout = TimeSpan.FromMilliseconds(10);

            var confirmableChannel = new PublishConfirmableChannel(modelMock.Object, confirmTimeout);

            #endregion Arrange

            #region Act

            var publishTask = confirmableChannel.BasicPublishAsync(
                string.Empty,
                string.Empty,
                true,
                propertiesMock,
                ReadOnlyMemory <byte> .Empty,
                0
                );

            Assert.IsTrue(confirmableChannel.HasTaskWith(taskId), "PublishTaskInfo not added!");

            #endregion Act

            #region Assert

            return(publishTask.ContinueWith(t =>
            {
                Assert.IsTrue(t.IsCanceled, "Task should be cancelled.");
                Assert.IsNull(t.Exception);
                Assert.IsFalse(confirmableChannel.HasTaskWith(taskId));
            }));

            #endregion Assert
        }
Beispiel #6
0
        /// <summary>
        /// Подписаться на сообщения.
        /// </summary>
        /// <param name="messageHandler">Обработчик сообщений.</param>
        /// <param name="settings">Настройки очереди.</param>
        /// <param name="onUnsubscribed">
        /// Функция обратного вызова, для отслеживания ситуации, когда остановлено потребление сообщений.
        /// </param>
        /// <returns>Канал, на котором работает подписчик.</returns>
        /// <typeparam name="TMessage">Тип сообщения.</typeparam>
        public async Task <IModel> SubscribeAsync <TMessage>(
            AcknowledgableMessageHandler <TMessage> messageHandler,
            SubscriberSettings settings,
            Action <bool, string>?onUnsubscribed = null
            )
            where TMessage : class, IMessage
        {
            var channel = await BindAsync <TMessage>(settings);

            if (settings.UsePublisherConfirms)
            {
                // создаем канал с подтверждениями публикаций сообщений
                channel = new PublishConfirmableChannel(
                    channel,
                    TimeSpan.FromSeconds(10),
                    _logger
                    );
            }

            channel.BasicQos(0, settings.ScalingSettings.MessagesPerConsumer, false);

            // если стоит лимит на канал, то делаем глобальным.
            if (settings.ScalingSettings.MessagesPerChannel > 0)
            {
                channel.BasicQos(0, settings.ScalingSettings.MessagesPerChannel, true);
            }

            var queueName = _namingConvention.QueueNamingConvention(typeof(TMessage), settings);

            for (var i = 0; i < settings.ScalingSettings.ConsumersPerChannel; i++)
            {
                var consumer = GetBasicConsumer(channel, settings, queueName, messageHandler, onUnsubscribed);

                channel.BasicConsume(
                    queue: queueName,
                    autoAck: settings.AutoAck,
                    exclusive: settings.Exclusive,
                    consumer: consumer,
                    consumerTag: _namingConvention.ConsumerTagNamingConvention(settings, channel.ChannelNumber, i)
                    );
            }

            channel.CallbackException += (sender, ea) =>
            {
                _logger.RabbitHandlerRestarted(ea.Exception);

                onUnsubscribed?.Invoke(false, ea.Exception?.Message ?? "Channel callback exception.");
            };

            channel.ModelShutdown += (sender, ea) =>
            {
                // если отключаем с админки
                if (ea.Initiator == ShutdownInitiator.Peer && (ea.ReplyText.Contains("stop") || ea.ReplyText.Contains("Closed via management plugin")))
                {
                    _logger.RabbitHandlerForceStopped(ea);

                    onUnsubscribed?.Invoke(true, ea.ToString()); // force
                }
                else
                {
                    _logger.RabbitHandlerRestartedAfterReconnect(ea);

                    onUnsubscribed?.Invoke(false, ea.ToString());
                }
            };

            return(channel);
        }