├── example.txt ├── JustSaying.Tools ├── Commands │ ├── ExitCommand.cs │ ├── ICommand.cs │ ├── QuitCommand.cs │ └── HelpCommand.cs ├── Program.cs ├── app.config ├── JustSaying.Tools.csproj └── CommandParser.cs ├── JustSaying ├── Properties │ └── InternalsVisibleTo.cs ├── Messaging │ ├── Interrogation │ │ ├── IPublisher.cs │ │ ├── ISubscriber.cs │ │ ├── IAmJustInterrogating.cs │ │ ├── INotificationSubscriberInterrogation.cs │ │ ├── IInterrogationResponse.cs │ │ ├── Publisher.cs │ │ ├── Subscriber.cs │ │ └── InterrogationResponse.cs │ ├── MessageSerialisation │ │ ├── IMessageSerialisationFactory.cs │ │ ├── MessageFormatNotSupportedException.cs │ │ ├── NewtonsoftSerialisationFactory.cs │ │ ├── TypeSerialiser.cs │ │ ├── IMessageSerialiser.cs │ │ ├── IMessageSerialisationRegister.cs │ │ ├── MessageSerialisationRegister.cs │ │ └── NewtonsoftSerialiser.cs │ ├── Monitoring │ │ ├── IMeasureHandlerExecutionTime.cs │ │ ├── IMessageMonitor.cs │ │ ├── NullOpMessageMonitor.cs │ │ └── StopwatchHandler.cs │ ├── MessageProcessingStrategies │ │ ├── MessageConstants.cs │ │ ├── IMessageBackoffStrategy.cs │ │ ├── DefaultThrottledThroughput.cs │ │ └── IMessageProcessingStrategy.cs │ ├── IMessagePublisher.cs │ ├── MessageHandling │ │ ├── ExactlyOnceAttribute.cs │ │ ├── IHandlerAsync.cs │ │ ├── FutureHandler.cs │ │ ├── IHandler.cs │ │ ├── ListHandler.cs │ │ ├── BlockingHandler.cs │ │ ├── IMessageLock.cs │ │ └── ExactlyOnceHandler.cs │ └── INotificationSubscriber.cs ├── AwsTools │ ├── MessageHandling │ │ ├── ITopicArnProvider.cs │ │ ├── PublishException.cs │ │ ├── ForeignTopicArnProvider.cs │ │ ├── HandlerMap.cs │ │ ├── SqsQueueByUrl.cs │ │ ├── LocalTopicArnProvider.cs │ │ ├── ExactlyOnceReader.cs │ │ └── SqsPolicy.cs │ ├── IAwsClientFactoryProxy.cs │ ├── QueueCreation │ │ ├── IRegionResourceCache.cs │ │ ├── ConfigurationErrorsException.cs │ │ ├── IVerifyAmazonQueues.cs │ │ ├── RedrivePolicy.cs │ │ ├── SqsWriteConfiguration.cs │ │ └── RegionResourceCache.cs │ ├── IAwsClientFactory.cs │ ├── AwsClientFactoryProxy.cs │ └── DefaultAwsClientFactory.cs ├── JustSayingFluentlyLogging.cs ├── IHandlerResolver.cs ├── INamingStrategy.cs ├── HandlerResolutionContext.cs ├── IPublishConfiguration.cs ├── IMessagingConfig.cs ├── HandlerNotRegisteredWithContainerException.cs ├── CreateMeABus.cs ├── DefaultNamingStrategy.cs ├── Extensions │ └── TypeExtensions.cs ├── IAmJustSaying.cs ├── MessagingConfig.cs └── JustSaying.csproj ├── JustSaying.TestingFramework ├── TestException.cs ├── IntegrationTestConfig.cs ├── Logging.cs ├── MessageStubs.cs ├── JustSaying.TestingFramework.csproj ├── Patiently.cs ├── Tasks.cs └── IntegrationAwsClientFactory.cs ├── .editorconfig ├── JustSaying.IntegrationTests ├── TestHandlers │ ├── OrderPlaced.cs │ ├── OrderDispatcher.cs │ ├── OrderProcessor.cs │ ├── BlockingOrderProcessor.cs │ ├── ThrowingHandler.cs │ └── Future.cs ├── WhenRegisteringHandlersViaResolver │ ├── BlockingHandlerRegistry.cs │ ├── SingleHandlerRegistry.cs │ ├── MultipleHandlerRegistry.cs │ ├── StructureMapHandlerResolver.cs │ ├── WhenRegisteringAHandlerViaContainerWithMissingRegistration.cs │ ├── NamedHandlerResolverTests.cs │ ├── WhenRegisteringMultipleHandlersViaContainer.cs │ ├── WhenRegisteringASingleHandlerViaContainer.cs │ ├── GivenAPublisher.cs │ ├── WhenRegisteringABlockingHandlerViaContainer.cs │ └── StructuremapNamedHandlerResolver.cs ├── GlobalSetup.cs ├── AwsTools │ ├── WhenIAccessAnExistingQueueWithoutAnErrorQueue.cs │ ├── WhenQueueIsDeleted.cs │ ├── SqsQueueIntegrationTests.cs │ ├── WhenUpdatingRedrivePolicy.cs │ ├── WhenICreateAQueueByName.cs │ ├── WhenCreatingTopicThatExistsAlready.cs │ ├── WhenUpdatingDeliveryDelay.cs │ ├── WhenUpdatingRetentionPeriod.cs │ ├── WhenCreatingTopicByName.cs │ ├── WhenCreatingTopicAndNoPermission.cs │ ├── NoTopicCreationAwsClientFactory.cs │ └── WhenCreatingErrorQueue.cs ├── WhenRegisteringASqsSubscriber │ ├── WhenRegisteringASqsGenericMessageTopicSubscriber.cs │ ├── WhenRegisteringLongNameMessageTypeTopicSubscriber.cs │ └── WhenHandlingMultipleTopics.cs ├── WhenRegisteringAPublisher │ ├── WhenPublishingAndNotInstantiated.cs │ ├── WhenRegisteringAPublisherInANonDefaultRegion.cs │ └── WhenRegisteringAPublisherAPublisherIsAddedToTheNotificationStack.cs ├── JustSayingFluently │ ├── WhenAMessageIsPublishedViaSnsToSqsSubscriber.cs │ ├── WhenAMessageIsPublishedViaSqsToSqsSubscriber.cs │ ├── WhenHandlersThrowAnException.cs │ └── WhenPublishingWithoutAMonitor.cs ├── JustSaying.IntegrationTests.csproj └── WhenOptingOutOfErrorQueue.cs ├── JustSaying.Models ├── JustSaying.Models.csproj └── Message.cs ├── .travis.yml ├── JustSaying.UnitTests ├── AwsTools │ ├── MessageHandling │ │ ├── SqsNotificationListener │ │ │ ├── WhenAttemptingToInterrogateASubscriber.cs │ │ │ ├── Support │ │ │ │ ├── ExactlyOnceSignallingHandler.cs │ │ │ │ ├── ExplicitExactlyOnceSignallingHandler.cs │ │ │ │ ├── SignallingHandler.cs │ │ │ │ ├── ThrowingDuringMessageProcessingStrategy.cs │ │ │ │ └── ThrowingBeforeMessageProcessingStrategy.cs │ │ │ ├── WhenMessageHandlingFails.cs │ │ │ ├── WhenMessageHandlingSucceeds.cs │ │ │ ├── WhenMessageHandlingThrows.cs │ │ │ ├── WhenPassingAHandledAndUnhandledMessage.cs │ │ │ ├── WhenMessageProcessingThrowsBefore.cs │ │ │ ├── WhenMessageProcessingThrowsDuring.cs │ │ │ ├── WhenExactlyOnceIsAppliedToHandler.cs │ │ │ ├── WhenExactlyOnceIsAppliedToHandlerWithoutExplicitTimeout.cs │ │ │ └── WhenThereAreNoMessagesToProcess.cs │ │ ├── ExactlyOnceReaderTests.cs │ │ ├── HandlersWithMetadata.cs │ │ ├── Sqs │ │ │ ├── WhenPublishingDelayedMessage.cs │ │ │ ├── WhenPublishingDelayedMessageAsync.cs │ │ │ ├── WhenPublishing.cs │ │ │ └── WhenFetchingQueueByName.cs │ │ ├── HandlerMetadataTest.cs │ │ └── MessageHandlerWrapperTests.cs │ ├── QueueCreation │ │ └── WhenSerializingRedrivePolicy.cs │ └── SqsQueueConfiguration │ │ └── Validation │ │ └── WhenPublishEndpointIsNotProvided.cs ├── JustSayingBus │ ├── CustomMonitor.cs │ ├── WhenPublishingWithoutRegistering.cs │ ├── WhenSubscribingAndNotPassingATopic.cs │ ├── WhenAddingAPublisherWithNoTopic.cs │ ├── GivenAServiceBusWithoutMonitoring.cs │ ├── GivenAServiceBus.cs │ ├── WhenUsingMultipleRegions.cs │ ├── WhenPublishingMessages.cs │ ├── WhenStartingThenStopping.cs │ ├── WhenStopping.cs │ ├── WhenPublishingFails.cs │ ├── WhenPublishingMessageWithoutMonitor.cs │ ├── WhenRegisteringTheSamePublisherTwice.cs │ └── WhenRegisteringPublishers.cs ├── Messaging │ ├── Serialisation │ │ ├── Newtonsoft │ │ │ ├── WhenAskingForAnewSerialiser.cs │ │ │ ├── DealingWithPotentiallyMissingConversation.cs │ │ │ ├── WhenUsingCustomSettings.cs │ │ │ └── WhenSerialisingAndDeserialising.cs │ │ └── SerialisationRegister │ │ │ ├── WhenAddingASerialiserTwice.cs │ │ │ └── WhenDeserializingMessage.cs │ └── MessageHandling │ │ ├── WhenEnsuringMessageIsOnlyHandledExactlyOnce.cs │ │ └── BlockingHandlerTests.cs ├── JustSayingFluently │ ├── Publishing │ │ └── WhenPublishing.cs │ ├── AddingMonitoring │ │ └── WhenAddingACustomMonitor.cs │ ├── ConfigValidation │ │ └── WhenNoRegionIsProvided.cs │ └── AddingHandlers │ │ ├── WhenAddingASubscriptionHandlerWithoutCustomConfig.cs │ │ └── WhenAddingASubscriptionHandlerForAGenericMessage.cs ├── WhenPublishEndpointIsNotProvided.cs ├── JustSaying.UnitTests.csproj └── CreateMe │ └── WhenCreatingABus.cs ├── packages └── repositories.config ├── appveyor.yml ├── release.ps1 ├── CONTRIBUTING.md └── .gitignore /example.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /JustSaying.Tools/Commands/ExitCommand.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.Tools.Commands 2 | { 3 | public class ExitCommand : QuitCommand { } 4 | } -------------------------------------------------------------------------------- /JustSaying.Tools/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.Tools.Commands 2 | { 3 | public interface ICommand 4 | { 5 | bool Execute(); 6 | } 7 | } -------------------------------------------------------------------------------- /JustSaying.Tools/Commands/QuitCommand.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.Tools.Commands 2 | { 3 | public class QuitCommand : ICommand 4 | { 5 | public bool Execute() => true; 6 | } 7 | } -------------------------------------------------------------------------------- /JustSaying/Properties/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("JustSaying.UnitTests")] 4 | [assembly: InternalsVisibleTo("JustSaying.IntegrationTests")] -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/IPublisher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.Interrogation 4 | { 5 | public interface IPublisher 6 | { 7 | Type MessageType { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/ISubscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.Interrogation 4 | { 5 | public interface ISubscriber 6 | { 7 | Type MessageType { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/IAmJustInterrogating.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.Messaging.Interrogation 2 | { 3 | public interface IAmJustInterrogating 4 | { 5 | IInterrogationResponse WhatDoIHave(); 6 | } 7 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/ITopicArnProvider.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.AwsTools.MessageHandling 2 | { 3 | public interface ITopicArnProvider 4 | { 5 | bool ArnExists(); 6 | string GetArn(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /JustSaying/JustSayingFluentlyLogging.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace JustSaying 4 | { 5 | public class JustSayingFluentlyLogging 6 | { 7 | public ILoggerFactory LoggerFactory { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/IHandlerResolver.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.Messaging.MessageHandling; 2 | 3 | namespace JustSaying 4 | { 5 | public interface IHandlerResolver 6 | { 7 | IHandlerAsync ResolveHandler(HandlerResolutionContext context); 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/IAwsClientFactoryProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.AwsTools 4 | { 5 | public interface IAwsClientFactoryProxy 6 | { 7 | IAwsClientFactory GetAwsClientFactory(); 8 | void SetAwsClientFactory(Func func); 9 | } 10 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/QueueCreation/IRegionResourceCache.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.AwsTools.QueueCreation 2 | { 3 | public interface IRegionResourceCache 4 | { 5 | T TryGetFromCache(string region, string key); 6 | void AddToCache(string region, string key, T value); 7 | } 8 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/IMessageSerialisationFactory.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.Models; 2 | 3 | namespace JustSaying.Messaging.MessageSerialisation 4 | { 5 | public interface IMessageSerialisationFactory 6 | { 7 | IMessageSerialiser GetSerialiser() where T : Message; 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Monitoring/IMeasureHandlerExecutionTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.Monitoring 4 | { 5 | public interface IMeasureHandlerExecutionTime 6 | { 7 | void HandlerExecutionTime(string typeName, string eventName, TimeSpan executionTime); 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/INotificationSubscriberInterrogation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace JustSaying.Messaging.Interrogation 4 | { 5 | public interface INotificationSubscriberInterrogation 6 | { 7 | ICollection Subscribers { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageProcessingStrategies/MessageConstants.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.Messaging.MessageProcessingStrategies 2 | { 3 | public static class MessageConstants 4 | { 5 | public const int MaxAmazonMessageCap = 10; 6 | public static int ParallelHandlerExecutionPerCore = 8; 7 | } 8 | } -------------------------------------------------------------------------------- /JustSaying/INamingStrategy.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools.QueueCreation; 2 | 3 | namespace JustSaying 4 | { 5 | public interface INamingStrategy 6 | { 7 | string GetTopicName(string topicName, string messageType); 8 | string GetQueueName(SqsReadConfiguration sqsConfig, string messageType); 9 | } 10 | } -------------------------------------------------------------------------------- /JustSaying/HandlerResolutionContext.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying 2 | { 3 | public class HandlerResolutionContext 4 | { 5 | public HandlerResolutionContext(string queueName) 6 | { 7 | QueueName = queueName; 8 | } 9 | 10 | public string QueueName { get; private set; } 11 | } 12 | } -------------------------------------------------------------------------------- /JustSaying.TestingFramework/TestException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.TestingFramework 4 | { 5 | [Serializable] 6 | public class TestException : Exception 7 | { 8 | public TestException() { } 9 | public TestException(string message) : base(message) 10 | { } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | end_of_line = crlf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_size = 2 11 | 12 | [*.cs] 13 | indent_size = 4 14 | 15 | [*.lock] 16 | end_of_line = lf 17 | 18 | [*.sln] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /JustSaying/Messaging/IMessagePublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Models; 3 | 4 | namespace JustSaying.Messaging 5 | { 6 | public interface IMessagePublisher 7 | { 8 | #if AWS_SDK_HAS_SYNC 9 | void Publish(Message message); 10 | #endif 11 | Task PublishAsync(Message message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /JustSaying.TestingFramework/IntegrationTestConfig.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.TestingFramework 2 | { 3 | public static class IntegrationTestConfig 4 | { 5 | /// 6 | /// Use "default" AWS profile for running integration tests 7 | /// 8 | public static string AwsProfileName = "default"; 9 | } 10 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/MessageFormatNotSupportedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.MessageSerialisation 4 | { 5 | public class MessageFormatNotSupportedException : Exception 6 | { 7 | public MessageFormatNotSupportedException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/NewtonsoftSerialisationFactory.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.Models; 2 | 3 | namespace JustSaying.Messaging.MessageSerialisation 4 | { 5 | public class NewtonsoftSerialisationFactory : IMessageSerialisationFactory 6 | { 7 | public IMessageSerialiser GetSerialiser() where T : Message => new NewtonsoftSerialiser(); 8 | } 9 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/IAwsClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using Amazon.SimpleNotificationService; 3 | using Amazon.SQS; 4 | 5 | namespace JustSaying.AwsTools 6 | { 7 | public interface IAwsClientFactory 8 | { 9 | IAmazonSimpleNotificationService GetSnsClient(RegionEndpoint region); 10 | IAmazonSQS GetSqsClient(RegionEndpoint region); 11 | } 12 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/TestHandlers/OrderPlaced.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.Models; 2 | 3 | namespace JustSaying.IntegrationTests.TestHandlers 4 | { 5 | public class OrderPlaced : Message 6 | { 7 | public OrderPlaced(string orderId) 8 | { 9 | OrderId = orderId; 10 | } 11 | public string OrderId { get; private set; } 12 | } 13 | } -------------------------------------------------------------------------------- /JustSaying.Tools/Commands/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Tools.Commands 4 | { 5 | public class HelpCommand : ICommand 6 | { 7 | public bool Execute() 8 | { 9 | Console.WriteLine("Move -from \"sourceUrl\" -to \"destinationUrl\" -in \"region\" -count \"10\""); 10 | return true; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /JustSaying/IPublishConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace JustSaying 4 | { 5 | public interface IPublishConfiguration 6 | { 7 | int PublishFailureReAttempts { get; set; } 8 | int PublishFailureBackoffMilliseconds { get; set; } 9 | IReadOnlyCollection AdditionalSubscriberAccounts { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageProcessingStrategies/IMessageBackoffStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.Models; 3 | 4 | namespace JustSaying.Messaging.MessageProcessingStrategies 5 | { 6 | public interface IMessageBackoffStrategy 7 | { 8 | TimeSpan GetBackoffDuration(Message message, int approximateReceiveCount, Exception lastException = null); 9 | } 10 | } -------------------------------------------------------------------------------- /JustSaying/IMessagingConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace JustSaying 5 | { 6 | public interface IMessagingConfig : IPublishConfiguration //ToDo: This vs publish config. Clean it up. not good. 7 | { 8 | IList Regions { get; } 9 | Func GetActiveRegion { get; set; } 10 | 11 | void Validate(); 12 | } 13 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/IInterrogationResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace JustSaying.Messaging.Interrogation 4 | { 5 | public interface IInterrogationResponse 6 | { 7 | IEnumerable Regions { get; set; } 8 | IEnumerable Subscribers { get; set; } 9 | IEnumerable Publishers { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /JustSaying.Models/JustSaying.Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6;net451 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/QueueCreation/ConfigurationErrorsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.AwsTools.QueueCreation 4 | { 5 | [Serializable] 6 | public class ConfigurationErrorsException : Exception 7 | { 8 | public ConfigurationErrorsException() 9 | { 10 | } 11 | 12 | public ConfigurationErrorsException(string message) : base(message) 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /JustSaying/HandlerNotRegisteredWithContainerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying 4 | { 5 | [Serializable] 6 | public class HandlerNotRegisteredWithContainerException: Exception 7 | { 8 | public HandlerNotRegisteredWithContainerException(string message) : base(message){} 9 | public HandlerNotRegisteredWithContainerException(string message, Exception inner) : base(message, inner){} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | env: 5 | global: 6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true 7 | - NUGET_XMLDOC_MODE=skip 8 | 9 | branches: 10 | only: 11 | - master 12 | 13 | cache: 14 | directories: 15 | - /home/travis/.nuget/packages 16 | 17 | addons: 18 | apt: 19 | packages: 20 | - gettext 21 | - libcurl4-openssl-dev 22 | - libicu-dev 23 | - libssl-dev 24 | - libunwind8 25 | 26 | script: 27 | - ./build.sh 28 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/ExactlyOnceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.MessageHandling 4 | { 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class ExactlyOnceAttribute : Attribute 7 | { 8 | public ExactlyOnceAttribute() 9 | { 10 | //TimeOut = int.MaxValue; 11 | TimeOut = (int)TimeSpan.MaxValue.TotalSeconds; 12 | } 13 | public int TimeOut { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/TypeSerialiser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.MessageSerialisation 4 | { 5 | public class TypeSerialiser 6 | { 7 | public Type Type { get; private set; } 8 | public IMessageSerialiser Serialiser { get; private set; } 9 | 10 | public TypeSerialiser(Type type, IMessageSerialiser serialiser) 11 | { 12 | Type = type; 13 | Serialiser = serialiser; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/PublishException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.AwsTools.MessageHandling 4 | { 5 | [Serializable] 6 | public class PublishException : Exception 7 | { 8 | public PublishException() 9 | { 10 | } 11 | 12 | public PublishException(string message) : base(message) 13 | { 14 | } 15 | public PublishException(string message, Exception inner) : base(message, inner) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /JustSaying/Messaging/INotificationSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.Messaging.Interrogation; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.Models; 5 | 6 | namespace JustSaying.Messaging 7 | { 8 | public interface INotificationSubscriber : INotificationSubscriberInterrogation 9 | { 10 | void AddMessageHandler(Func> handler) where T : Message; 11 | void Listen(); 12 | void StopListening(); 13 | string Queue { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/BlockingHandlerRegistry.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.IntegrationTests.TestHandlers; 2 | using JustSaying.Messaging.MessageHandling; 3 | using StructureMap; 4 | 5 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 6 | { 7 | public class BlockingHandlerRegistry : Registry 8 | { 9 | public BlockingHandlerRegistry() 10 | { 11 | For>().Singleton().Use(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/QueueCreation/IVerifyAmazonQueues.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools.MessageHandling; 2 | using JustSaying.Messaging.MessageSerialisation; 3 | 4 | namespace JustSaying.AwsTools.QueueCreation 5 | { 6 | public interface IVerifyAmazonQueues 7 | { 8 | SqsQueueByName EnsureTopicExistsWithQueueSubscribed(string region, IMessageSerialisationRegister serialisationRegister, SqsReadConfiguration queueConfig); 9 | SqsQueueByName EnsureQueueExists(string region, SqsReadConfiguration queueConfig); 10 | } 11 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/Publisher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.Interrogation 4 | { 5 | public class Publisher : IPublisher 6 | { 7 | public Publisher(Type messageType) 8 | { 9 | MessageType = messageType; 10 | } 11 | 12 | public Type MessageType { get; set; } 13 | 14 | public override bool Equals(object obj) => MessageType == ((Publisher)obj).MessageType; 15 | 16 | public override int GetHashCode() => MessageType?.GetHashCode() ?? 0; 17 | } 18 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/Subscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.Interrogation 4 | { 5 | public class Subscriber : ISubscriber 6 | { 7 | public Subscriber(Type messageType) 8 | { 9 | MessageType = messageType; 10 | } 11 | 12 | public Type MessageType { get; set; } 13 | 14 | public override bool Equals(object obj) => MessageType == ((Subscriber)obj).MessageType; 15 | 16 | public override int GetHashCode() => MessageType?.GetHashCode() ?? 0; 17 | } 18 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Monitoring/IMessageMonitor.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.Messaging.Monitoring 2 | { 3 | public interface IMessageMonitor 4 | { 5 | void HandleException(string messageType); 6 | void HandleTime(long handleTimeMs); 7 | void IssuePublishingMessage(); 8 | void IncrementThrottlingStatistic(); 9 | void HandleThrottlingTime(long handleTimeMs); 10 | void PublishMessageTime(long handleTimeMs); 11 | void ReceiveMessageTime(long handleTimeMs, string queueName, string region); 12 | } 13 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/SingleHandlerRegistry.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.IntegrationTests.TestHandlers; 2 | using JustSaying.Messaging.MessageHandling; 3 | using StructureMap; 4 | 5 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 6 | { 7 | public class SingleHandlerRegistry : Registry 8 | { 9 | public SingleHandlerRegistry() 10 | { 11 | For>().Singleton().Use() 12 | .Ctor>().Is(new Future()); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/IHandlerAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace JustSaying.Messaging.MessageHandling 4 | { 5 | /// 6 | /// Async message handler 7 | /// 8 | /// Type of message to be handled 9 | public interface IHandlerAsync 10 | { 11 | /// 12 | /// Handle a message of a given type 13 | /// 14 | /// Message to handle 15 | /// Was handling successful? 16 | Task Handle(T message); 17 | } 18 | } -------------------------------------------------------------------------------- /JustSaying/CreateMeABus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.AwsTools; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace JustSaying 6 | { 7 | public static class CreateMeABus 8 | { 9 | /// 10 | /// Allows to override default globally. 11 | /// 12 | public static Func DefaultClientFactory = () => new DefaultAwsClientFactory(); 13 | 14 | public static JustSayingFluentlyLogging WithLogging(ILoggerFactory loggerFactory) => 15 | new JustSayingFluentlyLogging {LoggerFactory = loggerFactory}; 16 | } 17 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageProcessingStrategies/DefaultThrottledThroughput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.Messaging.Monitoring; 3 | 4 | namespace JustSaying.Messaging.MessageProcessingStrategies 5 | { 6 | public class DefaultThrottledThroughput : Throttled 7 | { 8 | public DefaultThrottledThroughput(IMessageMonitor messageMonitor) : 9 | base(MaxActiveHandlersForProcessors(), messageMonitor) 10 | { 11 | 12 | } 13 | 14 | private static int MaxActiveHandlersForProcessors() 15 | => Environment.ProcessorCount * MessageConstants.ParallelHandlerExecutionPerCore; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /JustSaying.Tools/Program.cs: -------------------------------------------------------------------------------- 1 | using Magnum.CommandLineParser; 2 | using Magnum.Extensions; 3 | 4 | namespace JustSaying.Tools 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | var line = CommandLine.GetUnparsedCommandLine().Trim(); 11 | if (line.IsNotEmpty()) 12 | { 13 | ProcessLine(line); 14 | } 15 | } 16 | 17 | private static bool ProcessLine(string line) 18 | { 19 | var commandParser = new CommandParser(); 20 | return commandParser.Parse(line); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/FutureHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Models; 4 | 5 | namespace JustSaying.Messaging.MessageHandling 6 | { 7 | public class FutureHandler : IHandlerAsync where T : Message 8 | { 9 | private readonly Func> _futureHandler; 10 | 11 | public FutureHandler(Func> futureHandler) 12 | { 13 | _futureHandler = futureHandler; 14 | } 15 | 16 | public async Task Handle(T message) 17 | => await _futureHandler().Handle(message).ConfigureAwait(false); 18 | } 19 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/IHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.MessageHandling 4 | { 5 | /// 6 | /// Synchronous message handler, will be obsoleted by IHandlerAsync 7 | /// 8 | /// Type of message to be handled 9 | [Obsolete("Use IHandlerAsync")] 10 | public interface IHandler 11 | { 12 | /// 13 | /// Handle a message of a given type 14 | /// 15 | /// Message to handle 16 | /// Was handling successful? 17 | bool Handle(T message); 18 | } 19 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Monitoring/NullOpMessageMonitor.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.Messaging.Monitoring 2 | { 3 | public class NullOpMessageMonitor : IMessageMonitor 4 | { 5 | public void HandleException(string messageType) { } 6 | 7 | public void HandleTime(long handleTimeMs) { } 8 | 9 | public void IssuePublishingMessage() { } 10 | 11 | public void IncrementThrottlingStatistic() { } 12 | 13 | public void HandleThrottlingTime(long handleTimeMs) { } 14 | 15 | public void PublishMessageTime(long handleTimeMs) { } 16 | 17 | public void ReceiveMessageTime(long handleTimeMs, string queueName, string region) { } 18 | } 19 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenAttemptingToInterrogateASubscriber.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JustSaying.TestingFramework; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 7 | { 8 | public class WhenAttemptingToInterrogateASubscriber : BaseQueuePollingTest 9 | { 10 | [Fact] 11 | public void SubscriptedMessagesAreAddedToTheInterrogationDetails() 12 | { 13 | SystemUnderTest.Subscribers.Count.ShouldBe(1); 14 | SystemUnderTest.Subscribers.First(x => x.MessageType == typeof (GenericMessage)).ShouldNotBe(null); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/TestHandlers/OrderDispatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | 4 | namespace JustSaying.IntegrationTests.TestHandlers 5 | { 6 | public class OrderDispatcher : IHandlerAsync 7 | { 8 | private readonly Future _future; 9 | 10 | public OrderDispatcher(Future future) 11 | { 12 | _future = future; 13 | } 14 | 15 | public async Task Handle(OrderPlaced message) 16 | { 17 | await _future.Complete(message); 18 | return true; 19 | } 20 | 21 | public Future Future => _future; 22 | } 23 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/TestHandlers/OrderProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | 4 | namespace JustSaying.IntegrationTests.TestHandlers 5 | { 6 | public class OrderProcessor : IHandlerAsync 7 | { 8 | private readonly Future _future; 9 | 10 | public OrderProcessor(Future future) 11 | { 12 | _future = future; 13 | } 14 | 15 | public async Task Handle(OrderPlaced message) 16 | { 17 | await _future.Complete(message); 18 | return true; 19 | } 20 | 21 | public Future Future => _future; 22 | } 23 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/Interrogation/InterrogationResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace JustSaying.Messaging.Interrogation 4 | { 5 | public class InterrogationResponse : IInterrogationResponse 6 | { 7 | public InterrogationResponse(IEnumerable regions, IEnumerable subscribers, IEnumerable publishers) 8 | { 9 | Regions = regions; 10 | Subscribers = subscribers; 11 | Publishers = publishers; 12 | } 13 | 14 | public IEnumerable Regions { get; set; } 15 | public IEnumerable Subscribers { get; set; } 16 | public IEnumerable Publishers { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/GlobalSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.TestingFramework; 3 | using Xunit; 4 | 5 | namespace JustSaying.IntegrationTests 6 | { 7 | public class GlobalSetup : IDisposable 8 | { 9 | public const string CollectionName = "Global Fixture Setup"; 10 | 11 | public GlobalSetup() 12 | { 13 | CreateMeABus.DefaultClientFactory = () => new IntegrationAwsClientFactory(); 14 | } 15 | 16 | public void Dispose() 17 | { 18 | } 19 | } 20 | 21 | [CollectionDefinition(GlobalSetup.CollectionName)] 22 | public class GlobalSetupCollection : ICollectionFixture 23 | { 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/TestHandlers/BlockingOrderProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.TestingFramework; 4 | 5 | namespace JustSaying.IntegrationTests.TestHandlers 6 | { 7 | public class BlockingOrderProcessor : IHandlerAsync 8 | { 9 | 10 | public BlockingOrderProcessor() 11 | { 12 | } 13 | 14 | public int ReceivedMessageCount { get; private set; } 15 | 16 | public TaskCompletionSource DoneSignal { get; private set; } 17 | 18 | public Task Handle(OrderPlaced message) 19 | { 20 | ReceivedMessageCount++; 21 | return Task.FromResult(true); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /JustSaying.Tools/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/QueueCreation/WhenSerializingRedrivePolicy.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools.QueueCreation; 2 | using Shouldly; 3 | using Xunit; 4 | 5 | namespace JustSaying.UnitTests.AwsTools.QueueCreation 6 | { 7 | public class WhenSerializingRedrivePolicy 8 | { 9 | [Fact] 10 | public void CanDeserializeIntoRedrivePolicy() 11 | { 12 | var policy = new RedrivePolicy(1, "queue"); 13 | var policySerialized = policy.ToString(); 14 | 15 | var outputPolicy = RedrivePolicy.ConvertFromString(policySerialized); 16 | 17 | outputPolicy.MaximumReceives.ShouldBe(policy.MaximumReceives); 18 | outputPolicy.DeadLetterQueue.ShouldBe(policy.DeadLetterQueue); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/MultipleHandlerRegistry.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.IntegrationTests.TestHandlers; 2 | using JustSaying.Messaging.MessageHandling; 3 | using StructureMap; 4 | 5 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 6 | { 7 | public class MultipleHandlerRegistry : Registry 8 | { 9 | public MultipleHandlerRegistry() 10 | { 11 | For>().Transient().Use() 12 | .Ctor>().Is(new Future()); 13 | 14 | For>().Transient().Use() 15 | .Ctor>().Is(new Future()); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/AwsClientFactoryProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.AwsTools 4 | { 5 | public class AwsClientFactoryProxy : IAwsClientFactoryProxy 6 | { 7 | private Func _awsClientFactoryFunc; 8 | 9 | public AwsClientFactoryProxy() 10 | { 11 | _awsClientFactoryFunc = () => new DefaultAwsClientFactory(); 12 | } 13 | 14 | public AwsClientFactoryProxy(Func awsClientFactoryFunc) 15 | { 16 | _awsClientFactoryFunc = awsClientFactoryFunc; 17 | } 18 | 19 | public IAwsClientFactory GetAwsClientFactory() => _awsClientFactoryFunc(); 20 | 21 | public void SetAwsClientFactory(Func func) => _awsClientFactoryFunc = func; 22 | } 23 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/ForeignTopicArnProvider.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | 3 | namespace JustSaying.AwsTools.MessageHandling 4 | { 5 | public class ForeignTopicArnProvider : ITopicArnProvider 6 | { 7 | 8 | private readonly string _arn; 9 | 10 | public ForeignTopicArnProvider(RegionEndpoint regionEndpoint, string accountId, string topicName) 11 | { 12 | _arn = $"arn:aws:sns:{regionEndpoint.SystemName}:{accountId}:{topicName}"; 13 | } 14 | 15 | public bool ArnExists() 16 | { 17 | // Assume foreign topics exist, we actually find out when we attempt to subscribe 18 | return true; 19 | } 20 | 21 | public string GetArn() 22 | { 23 | return _arn; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/ListHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace JustSaying.Messaging.MessageHandling 6 | { 7 | public class ListHandler : IHandlerAsync 8 | { 9 | private readonly IEnumerable> _handlers; 10 | 11 | public ListHandler(IEnumerable> handlers) 12 | { 13 | _handlers = handlers; 14 | } 15 | 16 | public async Task Handle(T message) 17 | { 18 | var handlerTasks = _handlers.Select(h => h.Handle(message)); 19 | var handlerResults = await Task.WhenAll(handlerTasks) 20 | .ConfigureAwait(false); 21 | 22 | return handlerResults.All(x => x); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/CustomMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.Messaging.Monitoring; 3 | 4 | namespace JustSaying.UnitTests.JustSayingBus 5 | { 6 | public class CustomMonitor : IMessageMonitor, IMeasureHandlerExecutionTime 7 | { 8 | public void HandleException(string messageType) { } 9 | public void HandleTime(long handleTimeMs) { } 10 | public void IssuePublishingMessage() { } 11 | public void IncrementThrottlingStatistic() { } 12 | public void HandleThrottlingTime(long handleTimeMs) { } 13 | public void PublishMessageTime(long handleTimeMs) { } 14 | public void ReceiveMessageTime(long handleTimeMs, string queueName, string region) { } 15 | public void HandlerExecutionTime(string typeName, string eventName, TimeSpan executionTime) { } 16 | } 17 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/HandlerMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using HandlerFunc = System.Func>; 4 | 5 | namespace JustSaying.AwsTools.MessageHandling 6 | { 7 | public class HandlerMap 8 | { 9 | private readonly Dictionary _handlers = new Dictionary(); 10 | 11 | public bool ContainsKey(Type messageType) => _handlers.ContainsKey(messageType); 12 | 13 | public void Add(Type messageType, HandlerFunc handlerFunc) => _handlers.Add(messageType, handlerFunc); 14 | 15 | public HandlerFunc Get(Type messageType) 16 | { 17 | HandlerFunc handler; 18 | return _handlers.TryGetValue(messageType, out handler) ? handler : null; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenIAccessAnExistingQueueWithoutAnErrorQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.AwsTools.QueueCreation; 3 | using Xunit; 4 | using JustSaying.TestingFramework; 5 | 6 | namespace JustSaying.IntegrationTests.AwsTools 7 | { 8 | [Collection(GlobalSetup.CollectionName)] 9 | public class WhenIAccessAnExistingQueueWithoutAnErrorQueue : WhenCreatingQueuesByName 10 | { 11 | protected override Task When() 12 | { 13 | SystemUnderTest.Create(new SqsBasicConfiguration {ErrorQueueOptOut = true}, attempt: 0); 14 | return Task.CompletedTask; 15 | } 16 | 17 | [Fact] 18 | public async Task ThereIsNoErrorQueue() 19 | { 20 | await Patiently.AssertThatAsync(() => !SystemUnderTest.ErrorQueue.Exists()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenPublishingWithoutRegistering.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Models; 4 | using NSubstitute; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.JustSayingBus 9 | { 10 | public class WhenPublishingWithoutRegistering : GivenAServiceBus 11 | { 12 | protected override void Given() 13 | { 14 | base.Given(); 15 | RecordAnyExceptionsThrown(); 16 | } 17 | 18 | protected override async Task When() 19 | { 20 | await SystemUnderTest.PublishAsync(Substitute.For()); 21 | } 22 | 23 | [Fact] 24 | public void InvalidOperationIsThrown() 25 | { 26 | ThrownException.ShouldBeAssignableTo(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenSubscribingAndNotPassingATopic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace JustSaying.UnitTests.JustSayingBus 7 | { 8 | public class WhenSubscribingAndNotPassingATopic : GivenAServiceBus 9 | { 10 | protected override void Given() 11 | { 12 | base.Given(); 13 | RecordAnyExceptionsThrown(); 14 | } 15 | 16 | protected override Task When() 17 | { 18 | SystemUnderTest.AddNotificationSubscriber(" ", null); 19 | return Task.CompletedTask; 20 | } 21 | 22 | [Fact] 23 | public void ArgExceptionThrown() 24 | { 25 | ((ArgumentException) ThrownException).ParamName.ShouldBe("region"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/Serialisation/Newtonsoft/WhenAskingForAnewSerialiser.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.Messaging.MessageSerialisation; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.Messaging.Serialisation.Newtonsoft 8 | { 9 | public class WhenAskingForANewSerialiser : XBehaviourTest 10 | { 11 | private IMessageSerialiser _result; 12 | 13 | protected override void Given() 14 | { 15 | 16 | } 17 | 18 | protected override void When() 19 | { 20 | _result = SystemUnderTest.GetSerialiser(); 21 | } 22 | 23 | [Fact] 24 | public void OneIsProvided() 25 | { 26 | _result.ShouldNotBeNull(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingFluently/Publishing/WhenPublishing.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Models; 3 | using JustSaying.TestingFramework; 4 | using NSubstitute; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.JustSayingFluently.Publishing 8 | { 9 | public class WhenPublishing : JustSayingFluentlyTestBase 10 | { 11 | private readonly Message _message = new GenericMessage(); 12 | 13 | protected override void Given(){} 14 | 15 | protected override async Task When() 16 | { 17 | await SystemUnderTest.PublishAsync(_message); 18 | } 19 | 20 | [Fact] 21 | public void TheMessageIsPublished() 22 | { 23 | // If this ever fails, I have serious questions 24 | Received.InOrder(async () => await Bus.PublishAsync(_message)); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenAddingAPublisherWithNoTopic.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging; 3 | using JustSaying.TestingFramework; 4 | using NSubstitute; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.JustSayingBus 9 | { 10 | public class WhenAddingAPublisherWithNoTopic : GivenAServiceBus 11 | { 12 | protected override void Given() 13 | { 14 | RecordAnyExceptionsThrown(); 15 | } 16 | 17 | protected override Task When() 18 | { 19 | SystemUnderTest.AddMessagePublisher(Substitute.For(), string.Empty); 20 | 21 | return Task.CompletedTask; 22 | } 23 | 24 | [Fact] 25 | public void ExceptionThrown() 26 | { 27 | ThrownException.ShouldNotBeNull(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/GivenAServiceBusWithoutMonitoring.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.Messaging.Monitoring; 3 | using Microsoft.Extensions.Logging; 4 | using NSubstitute; 5 | 6 | namespace JustSaying.UnitTests.JustSayingBus 7 | { 8 | public abstract class GivenAServiceBusWithoutMonitoring : XAsyncBehaviourTest 9 | { 10 | protected IMessagingConfig Config; 11 | protected IMessageMonitor Monitor; 12 | protected ILoggerFactory LoggerFactory; 13 | 14 | protected override void Given() 15 | { 16 | Config = Substitute.For(); 17 | LoggerFactory = Substitute.For(); 18 | } 19 | 20 | protected override JustSaying.JustSayingBus CreateSystemUnderTest() 21 | => new JustSaying.JustSayingBus(Config, null, LoggerFactory); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/TestHandlers/ThrowingHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.TestingFramework; 4 | 5 | namespace JustSaying.IntegrationTests.TestHandlers 6 | { 7 | public class ThrowingHandler : IHandlerAsync 8 | { 9 | public ThrowingHandler() 10 | { 11 | DoneSignal = new TaskCompletionSource(); 12 | } 13 | 14 | public GenericMessage MessageReceived { get; set; } 15 | 16 | public TaskCompletionSource DoneSignal { get; private set; } 17 | 18 | public async Task Handle(GenericMessage message) 19 | { 20 | MessageReceived = message; 21 | await Task.Delay(0); 22 | Tasks.DelaySendDone(DoneSignal); 23 | throw new TestException("ThrowingHandler has thrown"); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenQueueIsDeleted.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.AwsTools.QueueCreation; 3 | using JustSaying.TestingFramework; 4 | using Xunit; 5 | 6 | namespace JustSaying.IntegrationTests.AwsTools 7 | { 8 | [Collection(GlobalSetup.CollectionName)] 9 | public class WhenQueueIsDeleted : WhenCreatingQueuesByName 10 | { 11 | protected override Task When() 12 | { 13 | SystemUnderTest.Create( 14 | new SqsReadConfiguration(SubscriptionType.ToTopic), 15 | attempt:600); 16 | SystemUnderTest.Delete(); 17 | 18 | return Task.CompletedTask; 19 | } 20 | 21 | [Fact] 22 | public async Task TheErrorQueueIsDeleted() 23 | { 24 | await Patiently.AssertThatAsync( 25 | () => !SystemUnderTest.ErrorQueue.Exists()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringASqsSubscriber/WhenRegisteringASqsGenericMessageTopicSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.Models; 4 | using NSubstitute; 5 | 6 | namespace JustSaying.IntegrationTests.WhenRegisteringASqsSubscriber 7 | { 8 | public class GenericMessage : Message 9 | { 10 | public T Contents { get; set; } 11 | } 12 | 13 | public class MyMessage { } 14 | 15 | public class WhenRegisteringASqsGenericMessageTopicSubscriber : WhenRegisteringASqsTopicSubscriber 16 | { 17 | protected override Task When() 18 | { 19 | SystemUnderTest.WithSqsTopicSubscriber() 20 | .IntoQueue(QueueName) 21 | .WithMessageHandler(Substitute.For>>()); 22 | 23 | return Task.CompletedTask; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/BlockingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace JustSaying.Messaging.MessageHandling 5 | { 6 | 7 | /// 8 | /// Used to convert "IHandler " instances into IAsyncHandler 9 | /// So that the rest of the system only has to deal with IAsyncHandler 10 | /// 11 | /// 12 | [Obsolete("Use IHandlerAsync")] 13 | public class BlockingHandler : IHandlerAsync 14 | { 15 | public BlockingHandler(IHandler inner) 16 | { 17 | if (inner == null) 18 | { 19 | throw new ArgumentNullException(nameof(inner)); 20 | } 21 | 22 | Inner = inner; 23 | } 24 | 25 | public IHandler Inner { get; } 26 | 27 | public Task Handle(T message) => Task.FromResult(Inner.Handle(message)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /JustSaying.TestingFramework/Logging.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using NLog.Config; 3 | using NLog.Targets; 4 | 5 | namespace JustSaying.TestingFramework 6 | { 7 | public static class Logging 8 | { 9 | public static void ToConsole() 10 | { 11 | const string layout = @"${time}|${level}|${message}${onexception:inner=|${exception:format=ShortType,Message}}"; 12 | ToConsole(layout); 13 | } 14 | 15 | public static void ToConsole(string layout) 16 | { 17 | var consoleTarget = new ConsoleTarget 18 | { 19 | Layout = layout 20 | }; 21 | 22 | var config = new LoggingConfiguration(); 23 | 24 | config.AddTarget("console", consoleTarget); 25 | config.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, consoleTarget)); 26 | 27 | LogManager.Configuration = config; 28 | 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/GivenAServiceBus.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.Messaging.Monitoring; 3 | using Microsoft.Extensions.Logging; 4 | using NSubstitute; 5 | 6 | namespace JustSaying.UnitTests.JustSayingBus 7 | { 8 | public abstract class GivenAServiceBus : XAsyncBehaviourTest 9 | { 10 | protected IMessagingConfig Config; 11 | protected IMessageMonitor Monitor; 12 | protected ILoggerFactory LoggerFactory; 13 | 14 | protected override void Given() 15 | { 16 | Config = Substitute.For(); 17 | Monitor = Substitute.For(); 18 | LoggerFactory = Substitute.For(); 19 | } 20 | 21 | protected override JustSaying.JustSayingBus CreateSystemUnderTest() 22 | => new JustSaying.JustSayingBus(Config, null, LoggerFactory) {Monitor = Monitor}; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/SqsQueueByUrl.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Amazon; 4 | using Amazon.SQS; 5 | using Amazon.SQS.Model; 6 | 7 | namespace JustSaying.AwsTools.MessageHandling 8 | { 9 | public class SqsQueueByUrl : SqsQueueBase 10 | { 11 | public SqsQueueByUrl(RegionEndpoint region, string queueUrl, IAmazonSQS client) 12 | : base(region, client) 13 | { 14 | Url = queueUrl; 15 | } 16 | 17 | public override async Task ExistsAsync() 18 | { 19 | var result = await Client.ListQueuesAsync(new ListQueuesRequest()); 20 | 21 | if (result.QueueUrls.Any(x => x == Url)) 22 | { 23 | await SetQueuePropertiesAsync(); 24 | // Need to set the prefix yet! 25 | return true; 26 | } 27 | 28 | return false; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /JustSaying/DefaultNamingStrategy.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools.QueueCreation; 2 | 3 | namespace JustSaying 4 | { 5 | /// 6 | /// A default namign strategy for JustSaying bus. 7 | /// Topic names are defaulted to message type name, lowercase (one topic per message type). 8 | /// Queue name is default to queue name. 9 | /// 10 | /// Such configuration gives a queue for each IntoQueue configuration , and a queue is subcribed to multiple topics, where one topic per message. 11 | /// 12 | class DefaultNamingStrategy : INamingStrategy 13 | { 14 | public string GetTopicName(string topicName, string messageType) => messageType.ToLower(); 15 | 16 | public string GetQueueName(SqsReadConfiguration sqsConfig, string messageType) 17 | => string.IsNullOrWhiteSpace(sqsConfig.BaseQueueName) 18 | ? messageType.ToLower() 19 | : sqsConfig.BaseQueueName.ToLower(); 20 | } 21 | } -------------------------------------------------------------------------------- /JustSaying.Models/Message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Models 4 | { 5 | public abstract class Message 6 | { 7 | protected Message() 8 | { 9 | TimeStamp = DateTime.UtcNow; 10 | Id = Guid.NewGuid(); 11 | } 12 | 13 | public Guid Id { get; set; } 14 | public DateTime TimeStamp { get; set; } 15 | public string RaisingComponent { get; set; } 16 | public string Version{ get; private set; } 17 | public string SourceIp { get; private set; } 18 | public string Tenant { get; set; } 19 | public string Conversation { get; set; } 20 | public string ReceiptHandle { get; set; } 21 | public string QueueUrl { get; set; } 22 | public int? DelaySeconds { get; set; } 23 | 24 | //footprint in order to avoid the same message being processed multiple times. 25 | public virtual string UniqueKey() => Id.ToString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/DefaultAwsClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using Amazon.Runtime; 3 | using Amazon.SimpleNotificationService; 4 | using Amazon.SQS; 5 | 6 | namespace JustSaying.AwsTools 7 | { 8 | public class DefaultAwsClientFactory : IAwsClientFactory 9 | { 10 | private readonly AWSCredentials _credentials; 11 | 12 | public DefaultAwsClientFactory() 13 | { 14 | _credentials = FallbackCredentialsFactory.GetCredentials(); 15 | } 16 | 17 | public DefaultAwsClientFactory(AWSCredentials customCredentials) 18 | { 19 | _credentials = customCredentials; 20 | } 21 | 22 | public IAmazonSimpleNotificationService GetSnsClient(RegionEndpoint region) 23 | => new AmazonSimpleNotificationServiceClient(_credentials, region); 24 | 25 | public IAmazonSQS GetSqsClient(RegionEndpoint region) 26 | => new AmazonSQSClient(_credentials, region); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/Support/ExactlyOnceSignallingHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.TestingFramework; 4 | 5 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support 6 | { 7 | [ExactlyOnce] 8 | public class ExactlyOnceSignallingHandler : IHandlerAsync 9 | { 10 | private readonly TaskCompletionSource _doneSignal; 11 | 12 | public ExactlyOnceSignallingHandler(TaskCompletionSource doneSignal) 13 | { 14 | _doneSignal = doneSignal; 15 | } 16 | 17 | public Task Handle(GenericMessage message) 18 | { 19 | HandleWasCalled = true; 20 | Tasks.DelaySendDone(_doneSignal); 21 | return Task.FromResult(true); 22 | } 23 | 24 | public bool HandleWasCalled { get; private set; } 25 | } 26 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/IMessageLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JustSaying.Messaging.MessageHandling 4 | { 5 | public class MessageLockResponse 6 | { 7 | public bool DoIHaveExclusiveLock { get; set; } 8 | public bool IsMessagePermanentlyLocked{ get; set; } 9 | public DateTimeOffset ExpiryAt { get; set; } 10 | public long ExpiryAtTicks { get; set; } 11 | 12 | public override string ToString() 13 | => DoIHaveExclusiveLock 14 | ? $"Message is exclusively locked. The expiry is {ExpiryAt}, {ExpiryAtTicks} ticks." 15 | : $"Message could NOT be exclusively locked. The lock will expire at {ExpiryAt}, {ExpiryAtTicks} ticks."; 16 | } 17 | 18 | public interface IMessageLock 19 | { 20 | MessageLockResponse TryAquireLockPermanently(string key); 21 | MessageLockResponse TryAquireLock(string key, TimeSpan howLong); 22 | void ReleaseLock(string key); 23 | } 24 | } -------------------------------------------------------------------------------- /JustSaying.TestingFramework/MessageStubs.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.Models; 2 | 3 | namespace JustSaying.TestingFramework 4 | { 5 | public class OrderAccepted : Message 6 | { } 7 | 8 | public class OrderRejected : Message 9 | { } 10 | 11 | public class GenericMessage : Message 12 | { 13 | public string Content { get; set; } 14 | } 15 | 16 | public class AnotherGenericMessage : Message 17 | { 18 | public string Content { get; set; } 19 | } 20 | 21 | public class DelayedMessage : Message 22 | { 23 | public DelayedMessage(int delaySeconds) 24 | { 25 | DelaySeconds = delaySeconds; 26 | } 27 | } 28 | 29 | public class MessageWithEnum : Message 30 | { 31 | public MessageWithEnum(Values enumVal) 32 | { 33 | EnumVal = enumVal; 34 | } 35 | 36 | public Values EnumVal { get; private set; } 37 | } 38 | 39 | public enum Values { One, Two }; 40 | } -------------------------------------------------------------------------------- /JustSaying.TestingFramework/JustSaying.TestingFramework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/Serialisation/SerialisationRegister/WhenAddingASerialiserTwice.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.Messaging.MessageSerialisation; 3 | using JustSaying.Models; 4 | using NSubstitute; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.Messaging.Serialisation.SerialisationRegister 9 | { 10 | public class WhenAddingASerialiserTwice : XBehaviourTest 11 | { 12 | protected override void Given() 13 | { 14 | RecordAnyExceptionsThrown(); 15 | } 16 | 17 | protected override void When() 18 | { 19 | SystemUnderTest.AddSerialiser(Substitute.For()); 20 | SystemUnderTest.AddSerialiser(Substitute.For()); 21 | } 22 | 23 | [Fact] 24 | public void ExceptionIsNotThrown() 25 | { 26 | ThrownException.ShouldBeNull(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/QueueCreation/RedrivePolicy.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace JustSaying.AwsTools.QueueCreation 4 | { 5 | public class RedrivePolicy 6 | { 7 | [JsonProperty(PropertyName = "maxReceiveCount")] 8 | public int MaximumReceives { get; set; } 9 | 10 | [JsonProperty(PropertyName = "deadLetterTargetArn")] 11 | public string DeadLetterQueue { get; set; } 12 | 13 | public RedrivePolicy(int maximumReceives, string deadLetterQueue) 14 | { 15 | MaximumReceives = maximumReceives; 16 | DeadLetterQueue = deadLetterQueue; 17 | } 18 | 19 | protected RedrivePolicy() { } 20 | 21 | public override string ToString() 22 | => "{\"maxReceiveCount\":\"" + MaximumReceives + "\", \"deadLetterTargetArn\":\"" + DeadLetterQueue + "\"}"; 23 | 24 | public static RedrivePolicy ConvertFromString(string policy) 25 | => JsonConvert.DeserializeObject(policy); 26 | } 27 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/Support/ExplicitExactlyOnceSignallingHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.TestingFramework; 4 | 5 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support 6 | { 7 | [ExactlyOnce(TimeOut = 5)] 8 | public class ExplicitExactlyOnceSignallingHandler : IHandlerAsync 9 | { 10 | private readonly TaskCompletionSource _doneSignal; 11 | 12 | public ExplicitExactlyOnceSignallingHandler(TaskCompletionSource doneSignal) 13 | { 14 | _doneSignal = doneSignal; 15 | } 16 | 17 | public Task Handle(GenericMessage message) 18 | { 19 | HandleWasCalled = true; 20 | Tasks.DelaySendDone(_doneSignal); 21 | return Task.FromResult(true); 22 | } 23 | 24 | public bool HandleWasCalled { get; private set; } 25 | } 26 | } -------------------------------------------------------------------------------- /JustSaying/AwsTools/QueueCreation/SqsWriteConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace JustSaying.AwsTools.QueueCreation 2 | { 3 | public class SqsWriteConfiguration : SqsBasicConfiguration 4 | { 5 | public SqsWriteConfiguration() 6 | { 7 | MessageRetentionSeconds = JustSayingConstants.DEFAULT_RETENTION_PERIOD; 8 | ErrorQueueRetentionPeriodSeconds = JustSayingConstants.MAXIMUM_RETENTION_PERIOD; 9 | VisibilityTimeoutSeconds = JustSayingConstants.DEFAULT_VISIBILITY_TIMEOUT; 10 | RetryCountBeforeSendingToErrorQueue = JustSayingConstants.DEFAULT_HANDLER_RETRY_COUNT; 11 | } 12 | 13 | public string QueueName { get; set; } 14 | 15 | public override void Validate() 16 | { 17 | base.Validate(); 18 | 19 | if (string.IsNullOrWhiteSpace(QueueName)) 20 | { 21 | throw new ConfigurationErrorsException("Invalid configuration. QueueName must be provided."); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/IMessageSerialiser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.Models; 3 | 4 | namespace JustSaying.Messaging.MessageSerialisation 5 | { 6 | public interface IMessageSerialiser 7 | { 8 | string GetMessageType(string sqsMessge); 9 | Message Deserialise(string message, Type type); 10 | 11 | /// 12 | /// Serialises a message for publishing 13 | /// 14 | /// 15 | /// If set to false, then message will be wrapped in extra object with Subject and Message fields, e.g.: 16 | /// new { Subject = message.GetType().Name, Message = serializedMessage }; 17 | /// 18 | /// AWS SNS service adds these automatically, so for publishing to topics don't add these properties 19 | /// 20 | /// 21 | string Serialise(Message message, bool serializeForSnsPublishing); 22 | } 23 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenUsingMultipleRegions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using NSubstitute; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.JustSayingBus 9 | { 10 | public class WhenUsingMultipleRegions : GivenAServiceBus 11 | { 12 | protected override void Given() 13 | { 14 | base.Given(); 15 | Config.Regions.Returns(new List{"region1", "region2"}); 16 | } 17 | 18 | protected override Task When() 19 | { 20 | return Task.CompletedTask; 21 | } 22 | 23 | [Fact] 24 | public void RegionsAreReturnedInTheInterrogationResult() 25 | { 26 | var response = SystemUnderTest.WhatDoIHave(); 27 | 28 | response.Regions.Count().ShouldBe(2); 29 | response.Regions.ShouldContain("region1"); 30 | response.Regions.ShouldContain("region2"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/ExactlyOnceReaderTests.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools.MessageHandling; 2 | using Shouldly; 3 | using Xunit; 4 | 5 | namespace JustSaying.UnitTests.AwsTools.MessageHandling 6 | { 7 | public class ExactlyOnceReaderTests 8 | { 9 | [Fact] 10 | public void ObjectTypeDoesNotHaveExactlyOnce() 11 | { 12 | var reader = new ExactlyOnceReader(typeof (object)); 13 | 14 | reader.Enabled.ShouldBeFalse(); 15 | } 16 | 17 | [Fact] 18 | public void UnadornedHandlerType_DoesNotHaveExactlyOnce() 19 | { 20 | var reader = new ExactlyOnceReader(typeof(UnadornedHandlerAsync)); 21 | 22 | reader.Enabled.ShouldBeFalse(); 23 | } 24 | 25 | [Fact] 26 | public void OnceTestHandlerAsyncType_HasExactlyOnce() 27 | { 28 | var reader = new ExactlyOnceReader(typeof (OnceTestHandlerAsync)); 29 | 30 | reader.Enabled.ShouldBeTrue(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/WhenPublishEndpointIsNotProvided.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools; 2 | using JustSaying.AwsTools.QueueCreation; 3 | using JustBehave; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests 8 | { 9 | public class WhenPublishEndpointIsNotProvided : XBehaviourTest 10 | { 11 | protected override void Given() 12 | { 13 | RecordAnyExceptionsThrown(); 14 | } 15 | 16 | protected override void When() 17 | { 18 | SystemUnderTest.Validate(); 19 | } 20 | 21 | [Fact] 22 | public void ThrowsException() 23 | { 24 | ThrownException.ShouldNotBeNull(); 25 | } 26 | 27 | protected override SqsReadConfiguration CreateSystemUnderTest() 28 | => new SqsReadConfiguration(SubscriptionType.ToTopic) { MessageRetentionSeconds = JustSayingConstants.MINIMUM_RETENTION_PERIOD +1, Topic = "ATopic", PublishEndpoint = null }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringAPublisher/WhenPublishingAndNotInstantiated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.IntegrationTests.WhenRegisteringAPublisher 8 | { 9 | [Collection(GlobalSetup.CollectionName)] 10 | public class WhenRegisteringAPublisherAndNotInstantiated : FluentNotificationStackTestBase 11 | { 12 | protected override void Given() 13 | { 14 | base.Given(); 15 | 16 | Configuration = new MessagingConfig(); 17 | 18 | RecordAnyExceptionsThrown(); 19 | } 20 | 21 | protected override async Task When() 22 | { 23 | await SystemUnderTest.PublishAsync(new GenericMessage()); 24 | } 25 | 26 | [Fact] 27 | public void ExceptionIsRaised() 28 | { 29 | ThrownException.ShouldBeAssignableTo(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/QueueCreation/RegionResourceCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace JustSaying.AwsTools.QueueCreation 4 | { 5 | public class RegionResourceCache : Dictionary>, IRegionResourceCache 6 | { 7 | public T TryGetFromCache(string region, string key) 8 | { 9 | if (! ContainsKey(region)) 10 | { 11 | return default(T); 12 | } 13 | 14 | var regionDict = this[region]; 15 | if (! regionDict.ContainsKey(key)) 16 | { 17 | return default(T); 18 | } 19 | 20 | return regionDict[key]; 21 | } 22 | 23 | public void AddToCache(string region, string key, T value) 24 | { 25 | if (!ContainsKey(region)) 26 | { 27 | this[region] = new Dictionary(); 28 | } 29 | var regionDict = this[region]; 30 | regionDict[key] = value; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/Support/SignallingHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.TestingFramework; 4 | 5 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support 6 | { 7 | public class SignallingHandler : IHandlerAsync 8 | { 9 | private readonly TaskCompletionSource _doneSignal; 10 | private readonly IHandlerAsync _inner; 11 | 12 | public SignallingHandler(TaskCompletionSource doneSignal, IHandlerAsync inner) 13 | { 14 | _doneSignal = doneSignal; 15 | _inner = inner; 16 | } 17 | 18 | public async Task Handle(T message) 19 | { 20 | try 21 | { 22 | return await _inner.Handle(message); 23 | } 24 | finally 25 | { 26 | Tasks.DelaySendDone(_doneSignal); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /JustSaying.Tools/JustSaying.Tools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net46 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenPublishingMessages.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging; 3 | using JustSaying.TestingFramework; 4 | using NSubstitute; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.JustSayingBus 8 | { 9 | public class WhenPublishingMessages : GivenAServiceBus 10 | { 11 | private readonly IMessagePublisher _publisher = Substitute.For(); 12 | 13 | protected override async Task When() 14 | { 15 | SystemUnderTest.AddMessagePublisher(_publisher, string.Empty); 16 | 17 | await SystemUnderTest.PublishAsync(new GenericMessage()); 18 | } 19 | 20 | [Fact] 21 | public void PublisherIsCalledToPublish() 22 | { 23 | _publisher.Received().PublishAsync(Arg.Any()); 24 | } 25 | 26 | [Fact] 27 | public void PublishMessageTimeStatsSent() 28 | { 29 | Monitor.Received(1).PublishMessageTime(Arg.Any()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/MessageHandling/WhenEnsuringMessageIsOnlyHandledExactlyOnce.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.TestingFramework; 5 | using NSubstitute; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | namespace JustSaying.UnitTests.Messaging.MessageHandling 10 | { 11 | public class WhenEnsuringMessageIsOnlyHandledExactlyOnce 12 | { 13 | [Fact] 14 | public async Task WhenMessageIsLockedByAnotherHandler_MessageWillBeLeftInTheQueue() 15 | { 16 | var messageLock = Substitute.For(); 17 | messageLock.TryAquireLock(Arg.Any(), Arg.Any()).Returns(new MessageLockResponse {DoIHaveExclusiveLock = false}); 18 | var sut = new ExactlyOnceHandler(Substitute.For>(), messageLock, 1, "handlerName"); 19 | 20 | var result = await sut.Handle(new OrderAccepted()); 21 | 22 | result.ShouldBeFalse(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/SqsQueueIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Amazon; 3 | using JustBehave; 4 | using JustSaying.AwsTools.MessageHandling; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace JustSaying.IntegrationTests.AwsTools 8 | { 9 | public abstract class WhenCreatingQueuesByName : XAsyncBehaviourTest 10 | { 11 | protected string QueueUniqueKey; 12 | 13 | protected override void Given() 14 | { } 15 | 16 | protected override SqsQueueByName CreateSystemUnderTest() 17 | { 18 | QueueUniqueKey = "test" + DateTime.Now.Ticks; 19 | var queue = new SqsQueueByName(RegionEndpoint.EUWest1, QueueUniqueKey, CreateMeABus.DefaultClientFactory().GetSqsClient(RegionEndpoint.EUWest1), 1, new LoggerFactory()); 20 | queue.Exists(); 21 | return queue; 22 | } 23 | 24 | protected override void PostAssertTeardown() 25 | { 26 | SystemUnderTest.Delete(); 27 | base.PostAssertTeardown(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /JustSaying/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace JustSaying.Extensions 7 | { 8 | internal static class TypeExtensions 9 | { 10 | private const int MAX_TOPIC_NAME_LENGTH = 256; 11 | 12 | public static string ToTopicName(this Type type) 13 | { 14 | var name = type.GetTypeInfo().IsGenericType 15 | ? Regex.Replace(type.FullName, "\\W", "_").ToLower() 16 | : type.Name.ToLower(); 17 | 18 | if (name.Length > MAX_TOPIC_NAME_LENGTH) 19 | { 20 | var suffix = name.GetInvariantHashCode().ToString(); 21 | name = name.Substring(0, MAX_TOPIC_NAME_LENGTH - suffix.Length) + suffix; 22 | } 23 | 24 | return name; 25 | } 26 | 27 | private static int GetInvariantHashCode(this string value) 28 | { 29 | return value.Aggregate(5381, (current, character) => (current*397) ^ character); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /JustSaying/IAmJustSaying.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.Messaging; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.Messaging.MessageSerialisation; 5 | using JustSaying.Messaging.Monitoring; 6 | using JustSaying.Models; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace JustSaying 10 | { 11 | public interface IAmJustSaying : IMessagePublisher 12 | { 13 | bool Listening { get; } 14 | void AddNotificationSubscriber(string region, INotificationSubscriber subscriber); 15 | void AddMessageHandler(string region, string queueName, Func> handler) where T : Message; 16 | 17 | // TODO - swap params 18 | void AddMessagePublisher(IMessagePublisher messagePublisher, string region) where T : Message; 19 | void Start(); 20 | void Stop(); 21 | IMessagingConfig Config { get; } 22 | IMessageMonitor Monitor { get; set; } 23 | IMessageSerialisationRegister SerialisationRegister { get; } 24 | IMessageLock MessageLock { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingFluently/AddingMonitoring/WhenAddingACustomMonitor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.Monitoring; 3 | using NSubstitute; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.JustSayingFluently.AddingMonitoring 8 | { 9 | public class WhenAddingACustomMonitor : JustSayingFluentlyTestBase 10 | { 11 | readonly IMessageMonitor _monitor = Substitute.For(); 12 | private object _response; 13 | 14 | protected override void Given() { } 15 | 16 | protected override Task When() 17 | { 18 | _response = SystemUnderTest.WithMonitoring(_monitor); 19 | return Task.CompletedTask; 20 | } 21 | 22 | [Fact] 23 | public void ThatMonitorIsAddedToTheStack() 24 | { 25 | Bus.Received().Monitor = _monitor; 26 | } 27 | 28 | [Fact] 29 | public void ICanContinueConfiguringTheBus() 30 | { 31 | _response.ShouldBeAssignableTo(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenUpdatingRedrivePolicy.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.AwsTools.QueueCreation; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace JustSaying.IntegrationTests.AwsTools 7 | { 8 | [Collection(GlobalSetup.CollectionName)] 9 | public class WhenUpdatingRedrivePolicy : WhenCreatingQueuesByName 10 | { 11 | private int _newMaximumReceived; 12 | 13 | protected override void Given() 14 | { 15 | _newMaximumReceived = 2; 16 | 17 | base.Given(); 18 | } 19 | 20 | protected override async Task When() 21 | { 22 | 23 | SystemUnderTest.Create(new SqsBasicConfiguration()); 24 | 25 | await SystemUnderTest.UpdateRedrivePolicyAsync( 26 | new RedrivePolicy(_newMaximumReceived, SystemUnderTest.ErrorQueue.Arn)); 27 | } 28 | 29 | [Fact] 30 | public void TheRedrivePolicyIsUpdatedWithTheNewValue() 31 | { 32 | SystemUnderTest.RedrivePolicy.MaximumReceives.ShouldBe(_newMaximumReceived); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/JustSayingFluently/WhenAMessageIsPublishedViaSnsToSqsSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.IntegrationTests.TestHandlers; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.IntegrationTests.JustSayingFluently 8 | { 9 | [Collection(GlobalSetup.CollectionName)] 10 | public class WhenAMessageIsPublishedViaSnsToSqsSubscriber : GivenANotificationStack 11 | { 12 | private Future _handler; 13 | 14 | protected override void Given() 15 | { 16 | base.Given(); 17 | _handler = new Future(); 18 | RegisterSnsHandler(_handler); 19 | } 20 | 21 | protected override async Task When() 22 | { 23 | await ServiceBus.PublishAsync(new GenericMessage()); 24 | 25 | await _handler.DoneSignal; 26 | } 27 | 28 | [Fact] 29 | public void ThenItGetsHandled() 30 | { 31 | _handler.ReceivedMessageCount.ShouldBeGreaterThan(0); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenICreateAQueueByName.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.AwsTools.QueueCreation; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.IntegrationTests.AwsTools 8 | { 9 | [Collection(GlobalSetup.CollectionName)] 10 | public class WhenICreateAQueueByName : WhenCreatingQueuesByName 11 | { 12 | private bool _isQueueCreated; 13 | 14 | protected override Task When() 15 | { 16 | _isQueueCreated = SystemUnderTest.Create(new SqsBasicConfiguration(), attempt: 0); 17 | return Task.CompletedTask; 18 | } 19 | 20 | [Fact] 21 | public void TheQueueIsCreated() 22 | { 23 | _isQueueCreated.ShouldBeTrue(); 24 | } 25 | 26 | [Fact(Skip = "Extremely long running test")] 27 | public async Task DeadLetterQueueIsCreated() 28 | { 29 | await Patiently.AssertThatAsync( 30 | () => SystemUnderTest.ErrorQueue.Exists(), 31 | 40.Seconds()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/SqsQueueConfiguration/Validation/WhenPublishEndpointIsNotProvided.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.AwsTools; 3 | using JustSaying.AwsTools.QueueCreation; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.AwsTools.SqsQueueConfiguration.Validation 8 | { 9 | public class WhenPublishEndpointIsNotProvided : XBehaviourTest 10 | { 11 | protected override void Given() 12 | { 13 | RecordAnyExceptionsThrown(); 14 | } 15 | 16 | protected override void When() 17 | { 18 | SystemUnderTest.Validate(); 19 | } 20 | 21 | [Fact] 22 | public void ThrowsException() 23 | { 24 | ThrownException.ShouldNotBeNull(); 25 | } 26 | 27 | protected override SqsReadConfiguration CreateSystemUnderTest() 28 | { 29 | return new SqsReadConfiguration(SubscriptionType.ToTopic) { MessageRetentionSeconds = JustSayingConstants.MINIMUM_RETENTION_PERIOD +1, Topic = "ATopic", PublishEndpoint = null }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/JustSayingFluently/WhenAMessageIsPublishedViaSqsToSqsSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.IntegrationTests.TestHandlers; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.IntegrationTests.JustSayingFluently 8 | { 9 | [Collection(GlobalSetup.CollectionName)] 10 | public class WhenAMessageIsPublishedViaSqsToSqsSubscriber : GivenANotificationStack 11 | { 12 | private Future _handler; 13 | 14 | protected override void Given() 15 | { 16 | base.Given(); 17 | _handler = new Future(); 18 | RegisterSqsHandler(_handler); 19 | } 20 | 21 | protected override async Task When() 22 | { 23 | await ServiceBus.PublishAsync(new AnotherGenericMessage()); 24 | await _handler.DoneSignal; 25 | } 26 | 27 | [Fact] 28 | public void ThenItGetsHandled() 29 | { 30 | _handler.ReceivedMessageCount.ShouldBeGreaterThan(0); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /JustSaying/Messaging/Monitoring/StopwatchHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.Models; 5 | 6 | namespace JustSaying.Messaging.Monitoring 7 | { 8 | public class StopwatchHandler : IHandlerAsync where T : Message 9 | { 10 | private readonly IHandlerAsync _inner; 11 | private readonly IMeasureHandlerExecutionTime _monitoring; 12 | 13 | public StopwatchHandler(IHandlerAsync inner, IMeasureHandlerExecutionTime monitoring) 14 | { 15 | _inner = inner; 16 | _monitoring = monitoring; 17 | } 18 | 19 | public async Task Handle(T message) 20 | { 21 | var watch = Stopwatch.StartNew(); 22 | var result = await _inner.Handle(message).ConfigureAwait(false); 23 | 24 | watch.Stop(); 25 | 26 | _monitoring.HandlerExecutionTime(TypeName(_inner), TypeName(message), watch.Elapsed); 27 | return result; 28 | } 29 | 30 | private static string TypeName(object obj) => obj.GetType().Name.ToLower(); 31 | } 32 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenMessageHandlingFails.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SQS.Model; 2 | using JustSaying.TestingFramework; 3 | using NSubstitute; 4 | using Xunit; 5 | 6 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 7 | { 8 | public class WhenMessageHandlingFails : BaseQueuePollingTest 9 | { 10 | protected override void Given() 11 | { 12 | base.Given(); 13 | Handler.Handle(Arg.Any()).ReturnsForAnyArgs(false); 14 | } 15 | 16 | [Fact] 17 | public void MessageHandlerWasCalled() 18 | { 19 | Handler.ReceivedWithAnyArgs().Handle(Arg.Any()); 20 | } 21 | 22 | [Fact] 23 | public void FailedMessageIsNotRemovedFromQueue() 24 | { 25 | // The un-handled one is however. 26 | Sqs.DidNotReceiveWithAnyArgs().DeleteMessageAsync(Arg.Any()); 27 | } 28 | 29 | [Fact] 30 | public void ExceptionIsNotLoggedToMonitor() 31 | { 32 | Monitor.DidNotReceiveWithAnyArgs().HandleException(Arg.Any()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenCreatingTopicThatExistsAlready.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools.MessageHandling; 2 | using JustSaying.Messaging.MessageSerialisation; 3 | using Microsoft.Extensions.Logging; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.IntegrationTests.AwsTools 8 | { 9 | [Collection(GlobalSetup.CollectionName)] 10 | public class WhenCreatingTopicThatExistsAlready : WhenCreatingTopicByName 11 | { 12 | private bool _createWasSuccessful; 13 | private SnsTopicByName _topic; 14 | 15 | protected override void When() 16 | { 17 | _topic = new SnsTopicByName(UniqueName, Bus, new MessageSerialisationRegister(), new LoggerFactory()); 18 | _createWasSuccessful = _topic.Create(); 19 | } 20 | 21 | [Fact] 22 | public void CreateCallIsStillSuccessful() 23 | { 24 | _createWasSuccessful.ShouldBeTrue(); 25 | } 26 | 27 | [Fact] 28 | public void TopicArnIsPopulated() 29 | { 30 | _topic.Arn.ShouldNotBeNull(); 31 | _topic.Arn.ShouldEndWith(_topic.TopicName); 32 | _topic.Arn.ShouldBe(CreatedTopic.Arn); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/StructureMapHandlerResolver.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.Messaging.MessageHandling; 2 | using StructureMap; 3 | 4 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 5 | { 6 | public class StructureMapHandlerResolver : IHandlerResolver 7 | { 8 | private readonly IContainer _container; 9 | 10 | public StructureMapHandlerResolver(IContainer container) 11 | { 12 | _container = container; 13 | } 14 | 15 | public IHandlerAsync ResolveHandler(HandlerResolutionContext context) 16 | { 17 | var handler = _container.GetInstance>(); 18 | if (handler != null) 19 | { 20 | return handler; 21 | } 22 | 23 | // we use the obsolete interface"IHandler" here 24 | #pragma warning disable 618 25 | var syncHandler = _container.GetInstance>(); 26 | if (syncHandler != null) 27 | { 28 | return new BlockingHandler(syncHandler); 29 | } 30 | #pragma warning restore 618 31 | 32 | return null; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenStartingThenStopping.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging; 3 | using NSubstitute; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.JustSayingBus 8 | { 9 | public class WhenStartingThenStopping : GivenAServiceBus 10 | { 11 | private INotificationSubscriber _subscriber1; 12 | 13 | protected override void Given() 14 | { 15 | base.Given(); 16 | _subscriber1 = Substitute.For(); 17 | } 18 | 19 | protected override Task When() 20 | { 21 | SystemUnderTest.AddNotificationSubscriber("region1", _subscriber1); 22 | SystemUnderTest.Start(); 23 | SystemUnderTest.Stop(); 24 | 25 | return Task.CompletedTask; 26 | } 27 | 28 | [Fact] 29 | public void StateIsNotListening() 30 | { 31 | SystemUnderTest.Listening.ShouldBeFalse(); 32 | } 33 | 34 | [Fact] 35 | public void CallingStopTwiceDoesNotStopListeningTwice() 36 | { 37 | SystemUnderTest.Stop(); 38 | _subscriber1.Received(1).StopListening(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenUpdatingDeliveryDelay.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.AwsTools.QueueCreation; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace JustSaying.IntegrationTests.AwsTools 7 | { 8 | [Collection(GlobalSetup.CollectionName)] 9 | public class WhenUpdatingDeliveryDelay : WhenCreatingQueuesByName 10 | { 11 | private int _oldDeliveryDelay; 12 | private int _newDeliveryDelay; 13 | 14 | protected override void Given() 15 | { 16 | _oldDeliveryDelay = 120; 17 | _newDeliveryDelay = 300; 18 | 19 | base.Given(); 20 | } 21 | 22 | protected override Task When() 23 | { 24 | SystemUnderTest.Create(new SqsBasicConfiguration { DeliveryDelaySeconds = _oldDeliveryDelay }); 25 | 26 | SystemUnderTest.UpdateQueueAttribute( 27 | new SqsBasicConfiguration {DeliveryDelaySeconds = _newDeliveryDelay}); 28 | 29 | return Task.CompletedTask; 30 | } 31 | 32 | [Fact] 33 | public void TheDeliveryDelayIsUpdatedWithTheNewValue() 34 | { 35 | SystemUnderTest.DeliveryDelay.ShouldBe(_newDeliveryDelay); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/IMessageSerialisationRegister.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.Models; 2 | 3 | namespace JustSaying.Messaging.MessageSerialisation 4 | { 5 | public interface IMessageSerialisationRegister 6 | { 7 | /// 8 | /// Deserializes a message. 9 | /// 10 | /// Message must always have Subject and Message properties 11 | /// 12 | Message DeserializeMessage(string body); 13 | 14 | /// 15 | /// Serializes a message for publishing 16 | /// 17 | /// 18 | /// If set to false, then message will be wrapped in extra object with Subject and Message fields, e.g.: 19 | /// new { Subject = message.GetType().Name, Message = serializedMessage }; 20 | /// 21 | /// AWS SNS service adds these automatically, so for publishing to topics don't add these properties 22 | /// 23 | /// 24 | string Serialise(Message message, bool serializeForSnsPublishing); 25 | 26 | void AddSerialiser(IMessageSerialiser serialiser) where T : Message; 27 | } 28 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | version: 5.0.0-beta-{build} 3 | configuration: Release 4 | cache: 5 | - packages 6 | before_build: 7 | - dotnet restore 8 | clone_depth: 1 9 | build: 10 | project: JustSaying.sln 11 | parallel: true 12 | verbosity: minimal 13 | pull_requests: 14 | do_not_increment_build_number: true 15 | test_script: 16 | - dotnet test .\JustSaying.UnitTests\JustSaying.UnitTests.csproj 17 | after_build: 18 | - dotnet pack .\JustSaying\JustSaying.csproj -o ../output --no-build 19 | - dotnet pack .\JustSaying.Models\JustSaying.Models.csproj -o ../output --no-build 20 | artifacts: 21 | - path: 'output\*.nupkg' 22 | notifications: 23 | - provider: HipChat 24 | room: 'Eng :: Open Source' 25 | auth_token: 26 | secure: eJWABMRPoyfEF9iLzFaTcUEqTc7/64v0FtS1qQe4yhs= 27 | on_build_success: false 28 | on_build_failure: false 29 | on_build_status_changed: false 30 | - provider: Webhook 31 | url: https://webhooks.gitter.im/e/1d5903ab716f417802d0 32 | on_build_success: true 33 | on_build_failure: true 34 | on_build_status_changed: true 35 | deploy: 36 | - provider: NuGet 37 | api_key: 38 | secure: 6MzbzEs4YdJKS67Gio5gEO8mNKmwfC4UHTCmECZ1KOutI6ndm4vAECazmVNB6an7 39 | artifact: /.*nupkg/ 40 | on: 41 | APPVEYOR_REPO_TAG: true 42 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenUpdatingRetentionPeriod.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.AwsTools.QueueCreation; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace JustSaying.IntegrationTests.AwsTools 7 | { 8 | [Collection(GlobalSetup.CollectionName)] 9 | public class WhenUpdatingRetentionPeriod : WhenCreatingQueuesByName 10 | { 11 | private int _oldRetentionPeriod; 12 | private int _newRetentionPeriod; 13 | 14 | protected override void Given() 15 | { 16 | _oldRetentionPeriod = 600; 17 | _newRetentionPeriod = 700; 18 | 19 | base.Given(); 20 | } 21 | 22 | protected override Task When() 23 | { 24 | 25 | SystemUnderTest.Create(new SqsBasicConfiguration { MessageRetentionSeconds = _oldRetentionPeriod }); 26 | 27 | SystemUnderTest.UpdateQueueAttribute( 28 | new SqsBasicConfiguration {MessageRetentionSeconds = _newRetentionPeriod}); 29 | 30 | return Task.CompletedTask; 31 | } 32 | 33 | [Fact] 34 | public void TheRedrivePolicyIsUpdatedWithTheNewValue() 35 | { 36 | SystemUnderTest.MessageRetentionPeriod.ShouldBe(_newRetentionPeriod); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /JustSaying.TestingFramework/Patiently.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Shouldly; 4 | 5 | namespace JustSaying.TestingFramework 6 | { 7 | public static class Patiently 8 | { 9 | public static async Task AssertThatAsync(Func func) => await AssertThatAsync(func, 5.Seconds()); 10 | 11 | public static async Task AssertThatAsync(Func func, TimeSpan timeout) 12 | { 13 | var started = DateTime.Now; 14 | var timeoutAt = DateTime.Now + timeout; 15 | do 16 | { 17 | if (func.Invoke()) 18 | { 19 | return; 20 | } 21 | 22 | await Task.Delay(50.Milliseconds()); 23 | Console.WriteLine( 24 | $"Waiting for {(DateTime.Now - started).TotalMilliseconds} ms - Still Checking."); 25 | } while (DateTime.Now < timeoutAt); 26 | 27 | func.Invoke().ShouldBeTrue(); 28 | } 29 | } 30 | public static class Extensions 31 | { 32 | public static TimeSpan Seconds(this int n) => TimeSpan.FromSeconds(n); 33 | 34 | public static TimeSpan Milliseconds(this int n) => TimeSpan.FromMilliseconds(n); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageProcessingStrategies/IMessageProcessingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace JustSaying.Messaging.MessageProcessingStrategies 5 | { 6 | public interface IMessageProcessingStrategy 7 | { 8 | /// 9 | /// The maximum number of worker tasks that will be used to run messages handlers at any one time 10 | /// 11 | int MaxWorkers { get; } 12 | 13 | /// 14 | /// The number of worker tasks that are free to run messages handlers right now, 15 | /// Always in the range 0 to MaxWorkers 16 | /// the number of currently running workers will be = (MaxWorkers - AvailableWorkers) 17 | /// 18 | int AvailableWorkers { get; } 19 | 20 | /// 21 | /// Launch a worker to start processing a message. 22 | /// 23 | /// 24 | void StartWorker(Func action); 25 | 26 | /// 27 | /// After awaiting this, you should be in a position to start another worker 28 | /// i.e. AvailableWorkers should be above 0 29 | /// 30 | /// 31 | Task WaitForAvailableWorkers(); 32 | } 33 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/JustSayingFluently/WhenHandlersThrowAnException.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.IntegrationTests.TestHandlers; 3 | using JustSaying.TestingFramework; 4 | using NSubstitute; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace JustSaying.IntegrationTests.JustSayingFluently 9 | { 10 | [Collection(GlobalSetup.CollectionName)] 11 | public class WhenHandlersThrowAnException : GivenANotificationStack 12 | { 13 | private Future _handler; 14 | 15 | protected override void Given() 16 | { 17 | RecordAnyExceptionsThrown(); 18 | 19 | base.Given(); 20 | _handler = new Future(() => throw new TestException("Test Exception from WhenHandlersThrowAnException")); 21 | RegisterSnsHandler(_handler); 22 | } 23 | 24 | protected override async Task When() 25 | { 26 | await ServiceBus.PublishAsync(new GenericMessage()); 27 | await _handler.DoneSignal; 28 | } 29 | 30 | [Fact] 31 | public void ThenExceptionIsRecordedInMonitoring() 32 | { 33 | _handler.ReceivedMessageCount.ShouldBeGreaterThan(0); 34 | 35 | Monitoring.Received().HandleException(Arg.Any()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingFluently/ConfigValidation/WhenNoRegionIsProvided.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.JustSayingFluently.ConfigValidation 8 | { 9 | public class WhenNoRegionIsProvided : JustSayingFluentlyTestBase 10 | { 11 | protected override JustSaying.JustSayingFluently CreateSystemUnderTest() 12 | { 13 | return null; 14 | } 15 | 16 | protected override void Given() 17 | { 18 | RecordAnyExceptionsThrown(); 19 | } 20 | 21 | protected override Task When() 22 | { 23 | CreateMeABus 24 | .WithLogging(new LoggerFactory()).InRegion(null) 25 | .ConfigurePublisherWith(configuration => { }); 26 | return Task.CompletedTask; 27 | } 28 | 29 | [Fact] 30 | public void ConfigItemsAreRequired() 31 | { 32 | ThrownException.ShouldBeAssignableTo(); 33 | } 34 | 35 | [Fact] 36 | public void RegionIsRequested() 37 | { 38 | ((ArgumentException)ThrownException).ParamName.ShouldBe("config.Regions"); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenCreatingTopicByName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Amazon; 3 | using Amazon.SimpleNotificationService; 4 | using JustBehave; 5 | using JustSaying.AwsTools.MessageHandling; 6 | using JustSaying.Messaging.MessageSerialisation; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace JustSaying.IntegrationTests.AwsTools 10 | { 11 | public abstract class WhenCreatingTopicByName : XBehaviourTest 12 | { 13 | protected string UniqueName; 14 | protected SnsTopicByName CreatedTopic; 15 | protected IAmazonSimpleNotificationService Bus; 16 | 17 | protected override void Given() 18 | { } 19 | 20 | protected override SnsTopicByName CreateSystemUnderTest() 21 | { 22 | Bus = CreateMeABus.DefaultClientFactory().GetSnsClient(RegionEndpoint.EUWest1); 23 | UniqueName = "test" + DateTime.Now.Ticks; 24 | CreatedTopic = new SnsTopicByName(UniqueName, Bus , new MessageSerialisationRegister(), new LoggerFactory()); 25 | CreatedTopic.Create(); 26 | return CreatedTopic; 27 | } 28 | 29 | protected override void PostAssertTeardown() 30 | { 31 | Bus.DeleteTopicAsync(CreatedTopic.Arn).Wait(); 32 | base.PostAssertTeardown(); 33 | } 34 | 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingFluently/AddingHandlers/WhenAddingASubscriptionHandlerWithoutCustomConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.Models; 4 | using NSubstitute; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.JustSayingFluently.AddingHandlers 8 | { 9 | public class WhenAddingASubscriptionHandlerWithoutCustomConfig : JustSayingFluentlyTestBase 10 | { 11 | private readonly IHandlerAsync _handler = Substitute.For>(); 12 | private IFluentSubscription _bus; 13 | 14 | protected override void Given() { } 15 | 16 | protected override Task When() 17 | { 18 | _bus = SystemUnderTest 19 | .WithSqsTopicSubscriber() 20 | .IntoQueue("queuename"); 21 | 22 | return Task.CompletedTask; 23 | } 24 | 25 | [Fact] 26 | public void ConfigurationIsNotRequired() 27 | { 28 | // Tested by the fact that handlers can be added 29 | _bus.WithMessageHandler(_handler) 30 | .WithMessageHandler(_handler); 31 | } 32 | 33 | [Fact] 34 | public void ConfigurationCanBeProvided() 35 | { 36 | _bus.ConfigureSubscriptionWith(conf => conf.InstancePosition = 1); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenCreatingTopicAndNoPermission.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using JustSaying.AwsTools.MessageHandling; 3 | using JustSaying.Messaging.MessageSerialisation; 4 | using Microsoft.Extensions.Logging; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace JustSaying.IntegrationTests.AwsTools 9 | { 10 | [Collection(GlobalSetup.CollectionName)] 11 | public class WhenCreatingTopicAndNoPermission : WhenCreatingTopicByName 12 | { 13 | private SnsTopicByName _topic; 14 | 15 | private bool _createWasSuccessful; 16 | 17 | protected override void When() 18 | { 19 | var snsClient = new NoTopicCreationAwsClientFactory().GetSnsClient(RegionEndpoint.EUWest1); 20 | _topic = new SnsTopicByName(UniqueName, snsClient, new MessageSerialisationRegister(), new LoggerFactory()); 21 | _createWasSuccessful = _topic.Create(); 22 | } 23 | 24 | [Fact] 25 | public void TopicCreationWasUnsuccessful() 26 | { 27 | _createWasSuccessful.ShouldBeFalse(); 28 | } 29 | 30 | [Fact] 31 | public void FallbackToExistenceCheckStillPopulatesArn() 32 | { 33 | _topic.Arn.ShouldNotBeNull(); 34 | _topic.Arn.ShouldEndWith(_topic.TopicName); 35 | _topic.Arn.ShouldBe(CreatedTopic.Arn); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSaying.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/HandlersWithMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.TestingFramework; 4 | 5 | namespace JustSaying.UnitTests.AwsTools.MessageHandling 6 | { 7 | public class UnadornedHandlerAsync : IHandlerAsync 8 | { 9 | public Task Handle(GenericMessage message) 10 | { 11 | return Task.FromResult(true); 12 | } 13 | } 14 | 15 | [ExactlyOnce(TimeOut = 42)] 16 | public class OnceTestHandlerAsync : IHandlerAsync 17 | { 18 | public Task Handle(GenericMessage message) 19 | { 20 | return Task.FromResult(true); 21 | } 22 | } 23 | 24 | // we use the obsolete interface"IHandler" here 25 | #pragma warning disable 618 26 | [ExactlyOnce(TimeOut = 23)] 27 | public class OnceTestHandler : IHandler 28 | #pragma warning restore 618 29 | { 30 | public bool Handle(GenericMessage message) 31 | { 32 | return true; 33 | } 34 | } 35 | 36 | [ExactlyOnce] 37 | public class OnceHandlerWithImplicitTimeoutAsync : IHandlerAsync 38 | { 39 | public Task Handle(GenericMessage message) 40 | { 41 | return Task.FromResult(true); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenStopping.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging; 3 | using NSubstitute; 4 | using Xunit; 5 | 6 | namespace JustSaying.UnitTests.JustSayingBus 7 | { 8 | public class WhenStopping : GivenAServiceBus 9 | { 10 | private INotificationSubscriber _subscriber1; 11 | private INotificationSubscriber _subscriber2; 12 | 13 | protected override void Given() 14 | { 15 | base.Given(); 16 | _subscriber1 = Substitute.For(); 17 | _subscriber1.Queue.Returns("queue1"); 18 | _subscriber2 = Substitute.For(); 19 | _subscriber2.Queue.Returns("queue2"); 20 | } 21 | 22 | protected override Task When() 23 | { 24 | SystemUnderTest.AddNotificationSubscriber("region1", _subscriber1); 25 | SystemUnderTest.AddNotificationSubscriber("region1", _subscriber2); 26 | SystemUnderTest.Start(); 27 | SystemUnderTest.Stop(); 28 | 29 | return Task.CompletedTask; 30 | } 31 | 32 | [Fact] 33 | public void SubscribersAreToldToStopListening() 34 | { 35 | _subscriber1.Received().StopListening(); 36 | _subscriber2.Received().StopListening(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /JustSaying.TestingFramework/Tasks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NLog; 4 | 5 | namespace JustSaying.TestingFramework 6 | { 7 | public static class Tasks 8 | { 9 | private static readonly Logger Log = LogManager.GetCurrentClassLogger(); 10 | 11 | private const int DefaultTimeoutMillis = 5000; 12 | private const int DelaySendMillis = 200; 13 | 14 | public static async Task WaitWithTimeoutAsync(Task task) 15 | => await WaitWithTimeoutAsync(task, TimeSpan.FromMilliseconds(DefaultTimeoutMillis)); 16 | 17 | public static async Task WaitWithTimeoutAsync(Task task, TimeSpan timeoutDuration) 18 | { 19 | var timeoutTask = Task.Delay(timeoutDuration); 20 | var firstToComplete = await Task.WhenAny(task, timeoutTask).ConfigureAwait(false); 21 | 22 | if (firstToComplete != timeoutTask) return true; 23 | Log.Error("Task did not complete before timeout of " + timeoutDuration); 24 | return false; 25 | } 26 | public static void DelaySendDone(TaskCompletionSource doneSignal) 27 | { 28 | Task.Run(async () => 29 | { 30 | await Task.Yield(); 31 | await Task.Delay(DelaySendMillis); 32 | doneSignal.SetResult(null); 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/Support/ThrowingDuringMessageProcessingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageProcessingStrategies; 4 | using JustSaying.TestingFramework; 5 | 6 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support 7 | { 8 | public class ThrowingDuringMessageProcessingStrategy : IMessageProcessingStrategy 9 | { 10 | public int MaxWorkers => int.MaxValue; 11 | 12 | public int AvailableWorkers => int.MaxValue; 13 | 14 | private readonly TaskCompletionSource _doneSignal; 15 | private bool _firstTime = true; 16 | 17 | public ThrowingDuringMessageProcessingStrategy(TaskCompletionSource doneSignal) 18 | { 19 | _doneSignal = doneSignal; 20 | } 21 | 22 | public async Task WaitForAvailableWorkers() 23 | { 24 | await Task.Yield(); 25 | } 26 | 27 | public void StartWorker(Func action) 28 | { 29 | if (_firstTime) 30 | { 31 | _firstTime = false; 32 | Fail(); 33 | } 34 | } 35 | 36 | private void Fail() 37 | { 38 | Tasks.DelaySendDone(_doneSignal); 39 | throw new TestException("Thrown by test ProcessMessage"); 40 | } 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/WhenRegisteringAHandlerViaContainerWithMissingRegistration.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.IntegrationTests.TestHandlers; 3 | using Microsoft.Extensions.Logging; 4 | using Shouldly; 5 | using StructureMap; 6 | using Xunit; 7 | 8 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 9 | { 10 | [Collection(GlobalSetup.CollectionName)] 11 | public class WhenRegisteringAHandlerViaContainerWithMissingRegistration : GivenAPublisher 12 | { 13 | protected override void Given() 14 | { 15 | RecordAnyExceptionsThrown(); 16 | } 17 | 18 | protected override Task When() 19 | { 20 | var handlerResolver = new StructureMapHandlerResolver(new Container()); 21 | 22 | CreateMeABus.WithLogging(new LoggerFactory()) 23 | .InRegion("eu-west-1") 24 | .WithSqsTopicSubscriber() 25 | .IntoQueue("container-test") 26 | .WithMessageHandler(handlerResolver); 27 | 28 | return Task.FromResult(true); 29 | } 30 | 31 | [Fact] 32 | public void ExceptionIsThrownBecauseHandlerIsNotRegisteredInContainer() 33 | { 34 | ThrownException.ShouldBeAssignableTo(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringASqsSubscriber/WhenRegisteringLongNameMessageTypeTopicSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.Models; 4 | using NSubstitute; 5 | 6 | namespace JustSaying.IntegrationTests.WhenRegisteringASqsSubscriber 7 | { 8 | public class WhenRegisteringLongNameMessageTypeTopicSubscriber 9 | { 10 | public class LongLongLongLongLonggLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLonggLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongMessage : Message 11 | { 12 | } 13 | 14 | public class WhenRegisteringASqsGenericMessageTopicSubscriber : WhenRegisteringASqsTopicSubscriber 15 | { 16 | protected override Task When() 17 | { 18 | SystemUnderTest.WithSqsTopicSubscriber() 19 | .IntoQueue(QueueName) 20 | .WithMessageHandler(Substitute.For>()); 21 | 22 | return Task.CompletedTask; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/JustSaying.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/LocalTopicArnProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Amazon.SimpleNotificationService; 3 | 4 | namespace JustSaying.AwsTools.MessageHandling 5 | { 6 | public class LocalTopicArnProvider : ITopicArnProvider 7 | { 8 | private readonly IAmazonSimpleNotificationService _client; 9 | private readonly Lazy _lazyGetArn; 10 | private bool _exists; 11 | 12 | public LocalTopicArnProvider(IAmazonSimpleNotificationService client, string topicName) 13 | { 14 | _client = client; 15 | 16 | _lazyGetArn = new Lazy(() => GetArnInternal(topicName)); 17 | } 18 | 19 | private string GetArnInternal(string topicName) 20 | { 21 | try 22 | { 23 | var topic = _client.FindTopicAsync(topicName) 24 | .GetAwaiter().GetResult(); 25 | 26 | _exists = true; 27 | return topic.TopicArn; 28 | } 29 | catch 30 | { 31 | // ignored 32 | } 33 | return null; 34 | } 35 | 36 | public string GetArn() 37 | { 38 | return _lazyGetArn.Value; 39 | } 40 | 41 | public bool ArnExists() 42 | { 43 | // ReSharper disable once UnusedVariable 44 | var ignored = _lazyGetArn.Value; 45 | return _exists; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/NoTopicCreationAwsClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using Amazon.SimpleNotificationService; 3 | using Amazon.SimpleNotificationService.Model; 4 | using Amazon.SQS; 5 | using JustSaying.AwsTools; 6 | using NSubstitute; 7 | using NSubstitute.ExceptionExtensions; 8 | 9 | namespace JustSaying.IntegrationTests.AwsTools 10 | { 11 | public class NoTopicCreationAwsClientFactory : IAwsClientFactory 12 | { 13 | 14 | public IAmazonSimpleNotificationService GetSnsClient(RegionEndpoint region) 15 | { 16 | var innerClient = CreateMeABus.DefaultClientFactory().GetSnsClient(region); 17 | var client = Substitute.For(); 18 | 19 | client.CreateTopicAsync(Arg.Any()) 20 | .ThrowsForAnyArgs(x => new AuthorizationErrorException("Denied")); 21 | 22 | client.FindTopicAsync(Arg.Any()) 23 | .ReturnsForAnyArgs(r => innerClient.FindTopicAsync(r.Arg())); 24 | 25 | client.GetTopicAttributesAsync(Arg.Any()) 26 | .ReturnsForAnyArgs(r => innerClient.GetTopicAttributesAsync(r.Arg())); 27 | 28 | return client; 29 | } 30 | 31 | 32 | public IAmazonSQS GetSqsClient(RegionEndpoint region) 33 | { 34 | var innerClient = CreateMeABus.DefaultClientFactory().GetSqsClient(region); 35 | return innerClient; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingFluently/AddingHandlers/WhenAddingASubscriptionHandlerForAGenericMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.Models; 5 | using NSubstitute; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.JustSayingFluently.AddingHandlers 9 | { 10 | public class JustSayingMessage : Message 11 | { 12 | public T Contents { get; set; } 13 | } 14 | 15 | public class MyMessage { } 16 | 17 | public class WhenAddingASubscriptionHandlerForAGenericMessage : JustSayingFluentlyTestBase 18 | { 19 | private readonly IHandlerAsync> _handler = Substitute.For>>(); 20 | private object _response; 21 | 22 | protected override void Given() 23 | { 24 | } 25 | 26 | protected override Task When() 27 | { 28 | _response = SystemUnderTest 29 | .WithSqsTopicSubscriber() 30 | .IntoDefaultQueue() 31 | .ConfigureSubscriptionWith(cfg => { }) 32 | .WithMessageHandler(_handler); 33 | 34 | return Task.CompletedTask; 35 | } 36 | 37 | [Fact] 38 | public void HandlerIsAddedToBus() 39 | { 40 | Bus.Received().AddMessageHandler(Arg.Any(), Arg.Any(), Arg.Any>>>()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenPublishingFails.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging; 3 | using JustSaying.TestingFramework; 4 | using JustSaying.Models; 5 | using NSubstitute; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.JustSayingBus 9 | { 10 | public class WhenPublishingFails : GivenAServiceBus 11 | { 12 | private readonly IMessagePublisher _publisher = Substitute.For(); 13 | private const int PublishAttempts = 2; 14 | 15 | protected override void Given() 16 | { 17 | base.Given(); 18 | 19 | Config.PublishFailureReAttempts.Returns(PublishAttempts); 20 | Config.PublishFailureBackoffMilliseconds.Returns(0); 21 | RecordAnyExceptionsThrown(); 22 | 23 | _publisher.When(x => x.PublishAsync(Arg.Any())) 24 | .Do(x => { throw new TestException("Thrown by test WhenPublishingFails"); }); 25 | } 26 | 27 | protected override async Task When() 28 | { 29 | SystemUnderTest.AddMessagePublisher(_publisher, string.Empty); 30 | 31 | await SystemUnderTest.PublishAsync(new GenericMessage()); 32 | } 33 | 34 | [Fact] 35 | public void EventPublicationWasAttemptedTheConfiguredNumberOfTimes() 36 | { 37 | _publisher 38 | .Received(PublishAttempts) 39 | .PublishAsync(Arg.Any()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /JustSaying.Tools/CommandParser.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JustSaying.Tools.Commands; 3 | using Magnum.CommandLineParser; 4 | using Magnum.Monads.Parser; 5 | 6 | namespace JustSaying.Tools 7 | { 8 | public class CommandParser 9 | { 10 | public bool Parse(string commandText) 11 | { 12 | return CommandLine 13 | .Parse(commandText, InitializeCommandLineParser) 14 | .All(option => option.Execute()); 15 | } 16 | 17 | private static void InitializeCommandLineParser(ICommandLineElementParser x) 18 | { 19 | x.Add((from arg in x.Argument("exit") 20 | select (ICommand) new ExitCommand()) 21 | .Or(from arg in x.Argument("quit") 22 | select (ICommand) new ExitCommand()) 23 | .Or(from arg in x.Argument("help") 24 | select (ICommand) new HelpCommand()) 25 | 26 | .Or(from arg in x.Argument("move") 27 | from sourceQueueName in x.Definition("from") 28 | from destinationQueueName in x.Definition("to") 29 | from region in x.Definition("in") 30 | from count in (from d in x.Definition("count") select d).Optional("count", "1") 31 | select (ICommand) new MoveCommand(sourceQueueName.Value, destinationQueueName.Value, region.Value, int.Parse(count.Value))) 32 | ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenMessageHandlingSucceeds.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SQS.Model; 2 | using NSubstitute; 3 | using Xunit; 4 | 5 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 6 | { 7 | public class WhenMessageHandlingSucceeds : BaseQueuePollingTest 8 | { 9 | protected override void Given() 10 | { 11 | base.Given(); 12 | Handler.Handle(null).ReturnsForAnyArgs(true); 13 | } 14 | 15 | [Fact] 16 | public void MessagesGetDeserialisedByCorrectHandler() 17 | { 18 | SerialisationRegister.Received().DeserializeMessage(SqsMessageBody(MessageTypeString)); 19 | } 20 | 21 | [Fact] 22 | public void ProcessingIsPassedToTheHandlerForCorrectMessage() 23 | { 24 | Handler.Received().Handle(DeserialisedMessage); 25 | } 26 | 27 | [Fact] 28 | public void AllMessagesAreClearedFromQueue() 29 | { 30 | Sqs.Received(2).DeleteMessageAsync(Arg.Any()); 31 | } 32 | 33 | [Fact] 34 | public void ReceiveMessageTimeStatsSent() 35 | { 36 | Monitor.Received().ReceiveMessageTime(Arg.Any(), Arg.Any(), Arg.Any()); 37 | } 38 | 39 | [Fact] 40 | public void ExceptionIsNotLoggedToMonitor() 41 | { 42 | Monitor.DidNotReceiveWithAnyArgs().HandleException(Arg.Any()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenPublishingMessageWithoutMonitor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustBehave; 3 | using JustSaying.Messaging; 4 | using JustSaying.Messaging.Monitoring; 5 | using JustSaying.TestingFramework; 6 | using NSubstitute; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | namespace JustSaying.UnitTests.JustSayingBus 11 | { 12 | public class WhenPublishingMessageWithoutMonitor : GivenAServiceBusWithoutMonitoring 13 | { 14 | private readonly IMessagePublisher _publisher = Substitute.For(); 15 | 16 | protected override async Task When() 17 | { 18 | SystemUnderTest.AddMessagePublisher(_publisher, string.Empty); 19 | await SystemUnderTest.PublishAsync(new GenericMessage()); 20 | } 21 | 22 | [Fact] 23 | public void ANullMonitorIsProvidedByDefault() 24 | { 25 | SystemUnderTest.Monitor.ShouldBeAssignableTo(); 26 | } 27 | 28 | [Fact] 29 | public void SettingANullMonitorSetsTheMonitorToNullOpMonitor() 30 | { 31 | SystemUnderTest.Monitor = null; 32 | SystemUnderTest.Monitor.ShouldBeAssignableTo(); 33 | } 34 | 35 | [Fact] 36 | public void SettingANewMonitorIsAccepted() 37 | { 38 | SystemUnderTest.Monitor = new CustomMonitor(); 39 | SystemUnderTest.Monitor.ShouldBeAssignableTo(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenMessageHandlingThrows.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SQS.Model; 2 | using JustSaying.TestingFramework; 3 | using NSubstitute; 4 | using Xunit; 5 | 6 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 7 | { 8 | public class WhenMessageHandlingThrows : BaseQueuePollingTest 9 | { 10 | private bool _firstTime = true; 11 | 12 | protected override void Given() 13 | { 14 | base.Given(); 15 | Handler.Handle(Arg.Any()).Returns( 16 | _ => ExceptionOnFirstCall()); 17 | } 18 | 19 | private bool ExceptionOnFirstCall() 20 | { 21 | if (_firstTime) 22 | { 23 | _firstTime = false; 24 | throw new TestException("Thrown by test handler"); 25 | } 26 | 27 | return false; 28 | } 29 | 30 | [Fact] 31 | public void MessageHandlerWasCalled() 32 | { 33 | Handler.ReceivedWithAnyArgs().Handle(Arg.Any()); 34 | } 35 | 36 | [Fact] 37 | public void FailedMessageIsNotRemovedFromQueue() 38 | { 39 | Sqs.DidNotReceiveWithAnyArgs().DeleteMessageAsync(Arg.Any()); 40 | } 41 | 42 | [Fact] 43 | public void ExceptionIsLoggedToMonitor() 44 | { 45 | Monitor.ReceivedWithAnyArgs().HandleException(Arg.Any()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/NamedHandlerResolverTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Shouldly; 3 | using Xunit; 4 | 5 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 6 | { 7 | [Collection(GlobalSetup.CollectionName)] 8 | public class NamedHandlerResolverTests 9 | { 10 | private readonly IHandlerResolver _handlerResolver = new StructureMapNamedHandlerResolver(); 11 | 12 | [Fact] 13 | public void TestQueueAResolution() 14 | { 15 | var context = new HandlerResolutionContext("QueueA"); 16 | var handler = _handlerResolver.ResolveHandler(context); 17 | 18 | handler.ShouldNotBeNull(); 19 | handler.ShouldBeAssignableTo(); 20 | } 21 | 22 | [Fact] 23 | public void TestQueueBResolution() 24 | { 25 | var context = new HandlerResolutionContext("QueueB"); 26 | var handler = _handlerResolver.ResolveHandler(context); 27 | 28 | handler.ShouldNotBeNull(); 29 | handler.ShouldBeAssignableTo(); 30 | } 31 | 32 | [Fact] 33 | public void TestOtherQueueNameResolution() 34 | { 35 | var context = new HandlerResolutionContext("QueueWithAnyOtherName"); 36 | var handler = _handlerResolver.ResolveHandler(context); 37 | 38 | handler.ShouldNotBeNull(); 39 | handler.ShouldBeAssignableTo(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenRegisteringTheSamePublisherTwice.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging; 4 | using JustSaying.Models; 5 | using NSubstitute; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | namespace JustSaying.UnitTests.JustSayingBus 10 | { 11 | public class WhenRegisteringTheSamePublisherTwice : GivenAServiceBus 12 | { 13 | private IMessagePublisher _publisher; 14 | 15 | protected override void Given() 16 | { 17 | base.Given(); 18 | _publisher = Substitute.For(); 19 | RecordAnyExceptionsThrown(); 20 | } 21 | 22 | protected override Task When() 23 | { 24 | SystemUnderTest.AddMessagePublisher(_publisher, string.Empty); 25 | SystemUnderTest.AddMessagePublisher(_publisher, string.Empty); 26 | 27 | return Task.CompletedTask; 28 | } 29 | 30 | [Fact] 31 | public void NoExceptionIsThrown() 32 | { 33 | // Specifying failover regions mean that messages can be registered more than once. 34 | ThrownException.ShouldBeNull(); 35 | } 36 | 37 | [Fact] 38 | public void AndInterrogationShowsNonDuplicatedPublishers() 39 | { 40 | var response = SystemUnderTest.WhatDoIHave(); 41 | 42 | response.Publishers.Count().ShouldBe(1); 43 | response.Publishers.First(x => x.MessageType == typeof(Message)).ShouldNotBe(null); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/WhenRegisteringMultipleHandlersViaContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using StructureMap; 3 | using System.Threading.Tasks; 4 | using JustSaying.IntegrationTests.TestHandlers; 5 | using Microsoft.Extensions.Logging; 6 | using Shouldly; 7 | using Xunit; 8 | using Container = StructureMap.Container; 9 | 10 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 11 | { 12 | [Collection(GlobalSetup.CollectionName)] 13 | public class WhenRegisteringMultipleHandlersViaContainer : GivenAPublisher 14 | { 15 | private IContainer _container; 16 | 17 | protected override void Given() 18 | { 19 | RecordAnyExceptionsThrown(); 20 | 21 | _container = new Container(x => x.AddRegistry(new MultipleHandlerRegistry())); 22 | } 23 | 24 | protected override Task When() 25 | { 26 | var handlerResolver = new StructureMapHandlerResolver(_container); 27 | 28 | CreateMeABus.WithLogging(new LoggerFactory()) 29 | .InRegion("eu-west-1") 30 | .WithSqsTopicSubscriber() 31 | .IntoQueue("container-test") 32 | .WithMessageHandler(handlerResolver); 33 | 34 | return Task.FromResult(true); 35 | } 36 | 37 | [Fact] 38 | public void ThrowsNotSupportedException() 39 | { 40 | ThrownException.ShouldBeAssignableTo(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/ExactlyOnceReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using JustSaying.Messaging.MessageHandling; 5 | using JustSaying.Models; 6 | 7 | namespace JustSaying.AwsTools.MessageHandling 8 | { 9 | internal static class HandlerMetadata 10 | { 11 | // we use the obsolete interface"IHandler" here 12 | #pragma warning disable 618 13 | public static ExactlyOnceReader ReadExactlyOnce(IHandlerAsync handler) where T : Message 14 | { 15 | var asyncingHandler = handler as BlockingHandler; 16 | return asyncingHandler != null ? ReadExactlyOnce(asyncingHandler.Inner) : new ExactlyOnceReader(handler.GetType()); 17 | } 18 | 19 | public static ExactlyOnceReader ReadExactlyOnce(IHandler handler) where T : Message 20 | => new ExactlyOnceReader(handler.GetType()); 21 | } 22 | #pragma warning restore 618 23 | 24 | internal class ExactlyOnceReader 25 | { 26 | private const int DefaultTemporaryLockSeconds = 30; 27 | private readonly Type _type; 28 | 29 | public ExactlyOnceReader(Type type) 30 | { 31 | _type = type; 32 | } 33 | 34 | public bool Enabled => _type.GetTypeInfo().IsDefined(typeof(ExactlyOnceAttribute)); 35 | 36 | public int GetTimeOut() 37 | => _type.GetTypeInfo() 38 | .GetCustomAttributes(true) 39 | .OfType() 40 | .FirstOrDefault()?.TimeOut 41 | ?? DefaultTemporaryLockSeconds; 42 | } 43 | } -------------------------------------------------------------------------------- /JustSaying/MessagingConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using JustSaying.AwsTools; 5 | 6 | namespace JustSaying 7 | { 8 | public class MessagingConfig : IMessagingConfig 9 | { 10 | public MessagingConfig() 11 | { 12 | PublishFailureReAttempts = JustSayingConstants.DEFAULT_PUBLISHER_RETRY_COUNT; 13 | PublishFailureBackoffMilliseconds = JustSayingConstants.DEFAULT_PUBLISHER_RETRY_INTERVAL; 14 | AdditionalSubscriberAccounts = new List(); 15 | Regions = new List(); 16 | } 17 | 18 | public int PublishFailureReAttempts { get; set; } 19 | public int PublishFailureBackoffMilliseconds { get; set; } 20 | public IReadOnlyCollection AdditionalSubscriberAccounts { get; set; } 21 | public IList Regions { get; private set; } 22 | public Func GetActiveRegion { get; set; } 23 | 24 | public virtual void Validate() 25 | { 26 | if (!Regions.Any() || string.IsNullOrWhiteSpace(Regions.First())) 27 | { 28 | throw new ArgumentNullException("config.Regions", "Cannot have a blank entry for config.Regions"); 29 | } 30 | var duplicateRegion = Regions.GroupBy(x => x).FirstOrDefault(y => y.Count() > 1); 31 | if (duplicateRegion != null) 32 | { 33 | throw new ArgumentException($"Region {duplicateRegion.Key} was added multiple times"); 34 | } 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringAPublisher/WhenRegisteringAPublisherInANonDefaultRegion.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Amazon.SimpleNotificationService.Model; 3 | using JustSaying.Models; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.IntegrationTests.WhenRegisteringAPublisher 8 | { 9 | [Collection(GlobalSetup.CollectionName)] 10 | public class WhenRegisteringAPublisherInANonDefaultRegion : FluentNotificationStackTestBase 11 | { 12 | private string _topicName; 13 | private Topic _topic; 14 | 15 | protected override void Given() 16 | { 17 | base.Given(); 18 | 19 | _topicName = "message"; 20 | 21 | Configuration = new MessagingConfig(); 22 | 23 | DeleteTopicIfItAlreadyExists(TestEndpoint, _topicName).Wait(); 24 | 25 | } 26 | 27 | protected override Task When() 28 | { 29 | SystemUnderTest.WithSnsMessagePublisher(); 30 | return Task.CompletedTask; 31 | } 32 | 33 | [Fact] 34 | public async Task ASnsTopicIsCreatedInTheNonDefaultRegion() 35 | { 36 | bool topicExists; 37 | (topicExists, _topic) = await TryGetTopic(TestEndpoint, _topicName); 38 | topicExists.ShouldBeTrue(); 39 | } 40 | 41 | protected override void Teardown() 42 | { 43 | if (_topic != null) 44 | { 45 | DeleteTopic(TestEndpoint, _topic).Wait(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /release.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory=$true, HelpMessage="The version number to publish, eg 1.2.3. Set this in CI first.")] 3 | [string] $version, 4 | [Parameter(Mandatory=$false, HelpMessage="CI project owner")] 5 | [string] $owner = "justeattech" 6 | ) 7 | 8 | if (($version -eq $null) -or ($version -eq '')) { 9 | # TODO: validate that a tag like this doesn't exist already 10 | throw "Must supply version number in semver format eg 1.2.3" 11 | } 12 | $ci_name = "justsaying" 13 | $ci_uri = "https://ci.appveyor.com/project/$owner/$ci_name" 14 | $tag = "v$version" 15 | # $release = "release-$version" 16 | write-host "Your current status" -foregroundcolor green 17 | & git status 18 | write-host "Stashing any work and checking out master" -foregroundcolor green 19 | & git stash 20 | & git checkout master 21 | & git pull upstream master --tags 22 | write-host "We'll pause now while you remember to bump the version number in appveyor.yml to match the version you're releasing ($version) ;-)" 23 | write-host " TODO: bounty - do this in code against appveyor's api" -foregroundcolor red 24 | write-host " http://www.appveyor.com/docs/api/projects-builds#update-project" -foregroundcolor red 25 | read-host "hit enter when you've done that..." 26 | write-host "Tagging & branching. tag: $tag / branch: $release" -foregroundcolor green 27 | & git tag -a $tag -m "Release $tag" 28 | & git checkout $tag 29 | write-host "Pushing" -foregroundcolor green 30 | & git push --tags upstream 31 | write-host "Done." 32 | write-host "Check $ci_uri" 33 | & git checkout master 34 | write-host "Putting you back on master branch" -foregroundcolor green 35 | exit 0 -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/Serialisation/Newtonsoft/DealingWithPotentiallyMissingConversation.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.Messaging.MessageSerialisation; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.Messaging.Serialisation.Newtonsoft 8 | { 9 | public class DealingWithPotentiallyMissingConversation : XBehaviourTest 10 | { 11 | private MessageWithEnum _messageOut; 12 | private MessageWithEnum _messageIn; 13 | private string _jsonMessage; 14 | protected override void Given() 15 | { 16 | _messageOut = new MessageWithEnum(Values.Two); 17 | } 18 | 19 | protected override void When() 20 | { 21 | _jsonMessage = SystemUnderTest.Serialise(_messageOut, serializeForSnsPublishing:false); 22 | 23 | //add extra property to see what happens: 24 | _jsonMessage = _jsonMessage.Replace("{__", "{\"New\":\"Property\",__"); 25 | _messageIn = SystemUnderTest.Deserialise(_jsonMessage, typeof(MessageWithEnum)) as MessageWithEnum; 26 | } 27 | 28 | [Fact] 29 | public void 30 | ItDoesNotHaveConversationPropertySerialisedBecauseItIsNotSet_ThisIsForBackwardsCompatibilityWhenWeDeploy() 31 | { 32 | _jsonMessage.ShouldNotContain("Conversation"); 33 | } 34 | 35 | [Fact] 36 | public void DeserialisedMessageHasEmptyConversation_ThisIsForBackwardsCompatibilityWhenWeDeploy() 37 | { 38 | _messageIn.Conversation.ShouldBeNull(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/Serialisation/SerialisationRegister/WhenDeserializingMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustBehave; 3 | using JustSaying.Messaging.MessageSerialisation; 4 | using JustSaying.Models; 5 | using NSubstitute; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | namespace JustSaying.UnitTests.Messaging.Serialisation.SerialisationRegister 10 | { 11 | public class WhenDeserializingMessage : XBehaviourTest 12 | { 13 | private class CustomMessage : Message 14 | { 15 | } 16 | 17 | private string messageBody = "msgBody"; 18 | protected override void Given() 19 | { 20 | RecordAnyExceptionsThrown(); 21 | } 22 | 23 | protected override void When() 24 | { 25 | var messageSerialiser = Substitute.For(); 26 | messageSerialiser.GetMessageType(messageBody).Returns(typeof(CustomMessage).Name); 27 | messageSerialiser.Deserialise(messageBody, typeof (CustomMessage)).Returns(new CustomMessage()); 28 | SystemUnderTest.AddSerialiser(messageSerialiser); 29 | } 30 | 31 | [Fact] 32 | public void ThrowsMessageFormatNotSupportedWhenMessabeBodyIsUnserializable() 33 | { 34 | new Action(() => SystemUnderTest.DeserializeMessage(string.Empty)).ShouldThrow(); 35 | } 36 | 37 | [Fact] 38 | public void TheMappingContainsTheSerialiser() 39 | { 40 | SystemUnderTest.DeserializeMessage(messageBody).ShouldNotBeNull(); 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/Serialisation/Newtonsoft/WhenUsingCustomSettings.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.Messaging.MessageSerialisation; 3 | using JustSaying.TestingFramework; 4 | using Newtonsoft.Json; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.Messaging.Serialisation.Newtonsoft 9 | { 10 | public class WhenUsingCustomSettings : XBehaviourTest 11 | { 12 | private MessageWithEnum _messageOut; 13 | private string _jsonMessage; 14 | 15 | protected override NewtonsoftSerialiser CreateSystemUnderTest() 16 | { 17 | return new NewtonsoftSerialiser(new JsonSerializerSettings()); 18 | } 19 | 20 | protected override void Given() 21 | { 22 | _messageOut = new MessageWithEnum(Values.Two); 23 | } 24 | 25 | public string GetMessageInContext(MessageWithEnum message) 26 | { 27 | var context = new { Subject = message.GetType().Name, Message = SystemUnderTest.Serialise(message, false) }; 28 | return JsonConvert.SerializeObject(context); 29 | } 30 | 31 | protected override void When() 32 | { 33 | _jsonMessage = GetMessageInContext(_messageOut); 34 | } 35 | 36 | [Fact] 37 | public void MessageHasBeenCreated() 38 | { 39 | _messageOut.ShouldNotBeNull(); 40 | } 41 | 42 | [Fact] 43 | public void EnumsAreNotRepresentedAsStrings() 44 | { 45 | _jsonMessage.ShouldContain("EnumVal"); 46 | _jsonMessage.ShouldNotContain("Two"); } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/Support/ThrowingBeforeMessageProcessingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageProcessingStrategies; 4 | using JustSaying.TestingFramework; 5 | 6 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support 7 | { 8 | public class ThrowingBeforeMessageProcessingStrategy : IMessageProcessingStrategy 9 | { 10 | public int MaxWorkers => int.MaxValue; 11 | 12 | public int AvailableWorkers 13 | { 14 | get 15 | { 16 | if (_firstTime) 17 | { 18 | return 0; 19 | } 20 | 21 | return int.MaxValue; 22 | } 23 | } 24 | 25 | private readonly TaskCompletionSource _doneSignal; 26 | private bool _firstTime = true; 27 | 28 | public ThrowingBeforeMessageProcessingStrategy(TaskCompletionSource doneSignal) 29 | { 30 | _doneSignal = doneSignal; 31 | } 32 | 33 | public Task WaitForAvailableWorkers() 34 | { 35 | if (_firstTime) 36 | { 37 | _firstTime = false; 38 | Fail(); 39 | } 40 | return Task.FromResult(true); 41 | } 42 | 43 | public void StartWorker(Func action) 44 | { 45 | } 46 | 47 | private void Fail() 48 | { 49 | Tasks.DelaySendDone(_doneSignal); 50 | throw new TestException("Thrown by test ProcessMessage"); 51 | } 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/AwsTools/WhenCreatingErrorQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Amazon; 3 | using JustBehave; 4 | using JustSaying.AwsTools; 5 | using JustSaying.AwsTools.QueueCreation; 6 | using Microsoft.Extensions.Logging; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | namespace JustSaying.IntegrationTests.AwsTools 11 | { 12 | [Collection(GlobalSetup.CollectionName)] 13 | public class WhenCreatingErrorQueue : XBehaviourTest 14 | { 15 | protected string QueueUniqueKey; 16 | 17 | protected override void Given() 18 | { } 19 | protected override void When() 20 | { 21 | 22 | SystemUnderTest.Create(new SqsBasicConfiguration { ErrorQueueRetentionPeriodSeconds = JustSayingConstants.MAXIMUM_RETENTION_PERIOD, ErrorQueueOptOut = true}); 23 | 24 | SystemUnderTest.UpdateQueueAttribute( 25 | new SqsBasicConfiguration {ErrorQueueRetentionPeriodSeconds = 100}); 26 | } 27 | 28 | protected override ErrorQueue CreateSystemUnderTest() 29 | { 30 | QueueUniqueKey = "test" + DateTime.Now.Ticks; 31 | return new ErrorQueue(RegionEndpoint.EUWest1, QueueUniqueKey, CreateMeABus.DefaultClientFactory().GetSqsClient(RegionEndpoint.EUWest1), new LoggerFactory()); 32 | } 33 | 34 | protected override void PostAssertTeardown() 35 | { 36 | SystemUnderTest.Delete(); 37 | base.PostAssertTeardown(); 38 | } 39 | 40 | [Fact] 41 | public void TheRetentionPeriodOfTheErrorQueueStaysAsMaximum() 42 | { 43 | SystemUnderTest.MessageRetentionPeriod.ShouldBe(100); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/Serialisation/Newtonsoft/WhenSerialisingAndDeserialising.cs: -------------------------------------------------------------------------------- 1 | using JustBehave; 2 | using JustSaying.Messaging.MessageSerialisation; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.Messaging.Serialisation.Newtonsoft 8 | { 9 | public class WhenSerialisingAndDeserialising : XBehaviourTest 10 | { 11 | private MessageWithEnum _messageOut; 12 | private MessageWithEnum _messageIn; 13 | private string _jsonMessage; 14 | protected override void Given() 15 | { 16 | _messageOut = new MessageWithEnum(Values.Two); 17 | } 18 | 19 | protected override void When() 20 | { 21 | _jsonMessage = SystemUnderTest.Serialise(_messageOut, serializeForSnsPublishing: false); 22 | _messageIn = SystemUnderTest.Deserialise(_jsonMessage, typeof(MessageWithEnum)) as MessageWithEnum; 23 | } 24 | 25 | [Fact] 26 | public void MessageHasBeenCreated() 27 | { 28 | _messageOut.ShouldNotBeNull(); 29 | } 30 | 31 | [Fact] 32 | public void MessagesContainSameDetails() 33 | { 34 | _messageOut.EnumVal.ShouldBe(_messageIn.EnumVal); 35 | _messageOut.RaisingComponent.ShouldBe(_messageIn.RaisingComponent); 36 | _messageOut.TimeStamp.ShouldBe(_messageIn.TimeStamp); 37 | } 38 | 39 | [Fact] 40 | public void EnumsAreRepresentedAsStrings() 41 | { 42 | _jsonMessage.ShouldContain("EnumVal"); 43 | _jsonMessage.ShouldContain("Two"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /JustSaying/JustSaying.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6;net451 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | $(DefineConstants);AWS_SDK_HAS_SYNC 30 | 31 | 32 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/WhenRegisteringASingleHandlerViaContainer.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.IntegrationTests.TestHandlers; 2 | using Microsoft.Extensions.Logging; 3 | using Shouldly; 4 | using StructureMap; 5 | using Xunit; 6 | 7 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 8 | { 9 | [Collection(GlobalSetup.CollectionName)] 10 | public class WhenRegisteringASingleHandlerViaContainer : GivenAPublisher 11 | { 12 | private Future _handlerFuture; 13 | 14 | protected override void Given() 15 | { 16 | var container = new Container(x => x.AddRegistry(new SingleHandlerRegistry())); 17 | var resolutionContext = new HandlerResolutionContext("test"); 18 | 19 | var handlerResolver = new StructureMapHandlerResolver(container); 20 | var handler = handlerResolver.ResolveHandler(resolutionContext); 21 | handler.ShouldNotBeNull(); 22 | 23 | _handlerFuture = ((OrderProcessor)handler).Future; 24 | DoneSignal = _handlerFuture.DoneSignal; 25 | 26 | Subscriber = CreateMeABus.WithLogging(new LoggerFactory()) 27 | .InRegion("eu-west-1") 28 | .WithSqsTopicSubscriber() 29 | .IntoQueue("container-test") 30 | .WithMessageHandler(handlerResolver); 31 | 32 | Subscriber.StartListening(); 33 | } 34 | 35 | [Fact] 36 | public void ThenHandlerWillReceiveTheMessage() 37 | { 38 | _handlerFuture.ReceivedMessageCount.ShouldBeGreaterThan(0); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenPassingAHandledAndUnhandledMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Amazon.SQS.Model; 3 | using NSubstitute; 4 | using Xunit; 5 | 6 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 7 | { 8 | public class WhenPassingAHandledAndUnhandledMessage : BaseQueuePollingTest 9 | { 10 | protected override void Given() 11 | { 12 | base.Given(); 13 | Handler.Handle(null) 14 | .ReturnsForAnyArgs(info => true) 15 | .AndDoes(x => Thread.Sleep(1)); // Ensure at least one ms wait on processing 16 | } 17 | 18 | [Fact] 19 | public void MessagesGetDeserialisedByCorrectHandler() 20 | { 21 | SerialisationRegister.Received().DeserializeMessage( 22 | SqsMessageBody(MessageTypeString)); 23 | } 24 | 25 | [Fact] 26 | public void ProcessingIsPassedToTheHandlerForCorrectMessage() 27 | { 28 | Handler.Received().Handle(DeserialisedMessage); 29 | } 30 | 31 | [Fact] 32 | public void MonitoringToldMessageHandlingTime() 33 | { 34 | Monitor.Received().HandleTime(Arg.Is(x => x > 0)); 35 | } 36 | 37 | [Fact] 38 | public void AllMessagesAreClearedFromQueue() 39 | { 40 | SerialisationRegister.Received(2).DeserializeMessage(Arg.Any()); 41 | 42 | Sqs.Received().DeleteMessageAsync(Arg.Any()); 43 | } 44 | } 45 | 46 | /* 47 | Some more: 48 | * 1. Multiple handling of same message with different handlers 49 | * 2. etc 50 | */ 51 | } 52 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringASqsSubscriber/WhenHandlingMultipleTopics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Amazon; 4 | using JustSaying.AwsTools.MessageHandling; 5 | using JustSaying.Messaging.MessageHandling; 6 | using JustSaying.Models; 7 | using JustSaying.TestingFramework; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json.Linq; 10 | using NSubstitute; 11 | using Xunit; 12 | 13 | namespace JustSaying.IntegrationTests.WhenRegisteringASqsSubscriber 14 | { 15 | [Collection(GlobalSetup.CollectionName)] 16 | public class WhenHandlingMultipleTopics : WhenRegisteringASqsTopicSubscriber 17 | { 18 | public class TopicA : Message { } 19 | public class TopicB : Message { } 20 | 21 | protected override Task When() 22 | { 23 | SystemUnderTest.WithSqsTopicSubscriber() 24 | .IntoQueue(QueueName) 25 | .WithMessageHandler(Substitute.For>>()) 26 | .WithMessageHandler(Substitute.For>>()); 27 | 28 | return Task.CompletedTask; 29 | } 30 | 31 | [Fact] 32 | public async Task SqsPolicyWithAWildcardIsApplied() 33 | { 34 | var queue = new SqsQueueByName(RegionEndpoint.EUWest1, QueueName, Client, 0, Substitute.For()); 35 | await Patiently.AssertThatAsync(queue.Exists, TimeSpan.FromSeconds(60)); 36 | dynamic policyJson = JObject.Parse(queue.Policy); 37 | policyJson.Statement.Count.ShouldBe(1, $"Expecting 1 statement in Sqs policy but found {policyJson.Statement.Count}"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/TestHandlers/Future.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using JustSaying.Models; 6 | using JustSaying.TestingFramework; 7 | 8 | namespace JustSaying.IntegrationTests.TestHandlers 9 | { 10 | public class Future where TMessage : Message 11 | { 12 | private readonly TaskCompletionSource _doneSignal = new TaskCompletionSource(); 13 | 14 | private readonly Func _action; 15 | private readonly List _messages = new List(); 16 | 17 | public Future(): this(null) 18 | { 19 | } 20 | 21 | public Future(Func action) 22 | { 23 | _action = action; 24 | ExpectedMessageCount = 1; 25 | } 26 | 27 | public async Task Complete(TMessage message) 28 | { 29 | try 30 | { 31 | _messages.Add(message); 32 | 33 | if (_action != null) 34 | { 35 | await _action(); 36 | } 37 | } 38 | finally 39 | { 40 | if (ReceivedMessageCount >= ExpectedMessageCount) 41 | { 42 | Tasks.DelaySendDone(_doneSignal); 43 | } 44 | } 45 | } 46 | 47 | public Task DoneSignal => _doneSignal.Task; 48 | 49 | public int ExpectedMessageCount { get; set; } 50 | 51 | public int ReceivedMessageCount => _messages.Count; 52 | 53 | public Exception RecordedException { get; set; } 54 | 55 | public bool HasReceived(TMessage message) 56 | { 57 | return _messages.Any(m => m.Id == message.Id); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringAPublisher/WhenRegisteringAPublisherAPublisherIsAddedToTheNotificationStack.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging; 3 | using JustSaying.Messaging.MessageSerialisation; 4 | using JustSaying.Models; 5 | using NSubstitute; 6 | using Xunit; 7 | 8 | namespace JustSaying.IntegrationTests.WhenRegisteringAPublisher 9 | { 10 | [Collection(GlobalSetup.CollectionName)] 11 | public class WhenRegisteringAPublisher : FluentNotificationStackTestBase 12 | { 13 | private string _topicName; 14 | 15 | protected override void Given() 16 | { 17 | base.Given(); 18 | 19 | _topicName = "CustomerCommunication"; 20 | 21 | EnableMockedBus(); 22 | 23 | Configuration = new MessagingConfig(); 24 | 25 | DeleteTopicIfItAlreadyExists(TestEndpoint, _topicName).Wait(); 26 | } 27 | 28 | protected override Task When() 29 | { 30 | SystemUnderTest.WithSnsMessagePublisher(); 31 | return Task.CompletedTask; 32 | } 33 | 34 | [Fact] 35 | public void APublisherIsAddedToTheStack() 36 | { 37 | NotificationStack.Received().AddMessagePublisher(Arg.Any(), TestEndpoint.SystemName); 38 | } 39 | 40 | [Fact] 41 | public void SerialisationIsRegisteredForMessage() 42 | { 43 | NotificationStack.SerialisationRegister.Received() 44 | .AddSerialiser(Arg.Any()); 45 | } 46 | 47 | protected override void PostAssertTeardown() 48 | { 49 | DeleteTopicIfItAlreadyExists(TestEndpoint, _topicName).Wait(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/GivenAPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustBehave; 3 | using JustSaying.IntegrationTests.TestHandlers; 4 | using JustSaying.Messaging; 5 | using JustSaying.TestingFramework; 6 | using Microsoft.Extensions.Logging; 7 | using Shouldly; 8 | 9 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 10 | { 11 | public abstract class GivenAPublisher : XAsyncBehaviourTest 12 | { 13 | protected IHaveFulfilledPublishRequirements Publisher; 14 | protected IHaveFulfilledSubscriptionRequirements Subscriber; 15 | protected Task DoneSignal; 16 | 17 | protected override IMessagePublisher CreateSystemUnderTest() 18 | { 19 | Publisher = CreateMeABus.WithLogging(new LoggerFactory()) 20 | .InRegion("eu-west-1") 21 | .WithSnsMessagePublisher(); 22 | Publisher.StartListening(); 23 | return Publisher; 24 | } 25 | 26 | protected override async Task When() 27 | { 28 | await Publisher.PublishAsync(new OrderPlaced("1234")); 29 | 30 | await WaitForDone(); 31 | 32 | TearDownPubSub(); 33 | } 34 | 35 | private async Task WaitForDone() 36 | { 37 | if (DoneSignal == null) 38 | { 39 | return; 40 | } 41 | 42 | var done = await Tasks.WaitWithTimeoutAsync(DoneSignal); 43 | done.ShouldBe(true, "Done task timed out"); 44 | } 45 | 46 | private void TearDownPubSub() 47 | { 48 | Publisher?.StopListening(); 49 | Subscriber?.StopListening(); 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/JustSayingBus/WhenRegisteringPublishers.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging; 4 | using JustSaying.TestingFramework; 5 | using NSubstitute; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | namespace JustSaying.UnitTests.JustSayingBus 10 | { 11 | public class WhenRegisteringPublishers : GivenAServiceBus 12 | { 13 | private IMessagePublisher _publisher; 14 | 15 | protected override void Given() 16 | { 17 | base.Given(); 18 | _publisher = Substitute.For(); 19 | } 20 | 21 | protected override async Task When() 22 | { 23 | SystemUnderTest.AddMessagePublisher(_publisher, string.Empty); 24 | SystemUnderTest.AddMessagePublisher(_publisher, string.Empty); 25 | 26 | await SystemUnderTest.PublishAsync(new OrderAccepted()); 27 | await SystemUnderTest.PublishAsync(new OrderRejected()); 28 | await SystemUnderTest.PublishAsync(new OrderRejected()); 29 | } 30 | 31 | [Fact] 32 | public void AcceptedOrderWasPublishedOnce() 33 | { 34 | _publisher.Received(1).PublishAsync(Arg.Any()); 35 | } 36 | 37 | [Fact] 38 | public void RejectedOrderWasPublishedTwice() 39 | { 40 | _publisher.Received(2).PublishAsync(Arg.Any()); 41 | } 42 | 43 | [Fact] 44 | public void AndInterrogationShowsPublishersHaveBeenSet() 45 | { 46 | var response = SystemUnderTest.WhatDoIHave(); 47 | 48 | response.Publishers.Count().ShouldBe(2); 49 | response.Publishers.First(x => x.MessageType == typeof (OrderAccepted)).ShouldNotBe(null); 50 | response.Publishers.First(x => x.MessageType == typeof(OrderRejected)).ShouldNotBe(null); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to JustSaying 2 | 3 | ♥ JustSaying and want to get involved? 4 | Great! There are plenty of ways you can help! 5 | 6 | If you find a bug, have a feature request or even want to contribute an enhancement or fix, please follow the below guidelines so that we can help you out and/or get your code merged quickly. 7 | 8 | ## If you find what looks like a bug: 9 | 10 | * Check the [GitHub issue tracker](http://github.com/justeat/JustSaying/issues/) to see if anyone else has reported the issue. 11 | * Make sure you are using the latest version of JustSaying 12 | * If you are still having an issue, create an issue including: 13 | * Information you will need to reproduce and diagnose the problem 14 | 15 | ## If you want to contribute an enhancement or a fix: 16 | 17 | 0. Discuss any major enhancement with the project moderator. 18 | 0. Write your Spec and/or Functional tests. 19 | 0. Write or modify any accompanying documentation. 20 | 0. Ensure that your tests work and your documentation is complete. 21 | 22 | 23 | ### General Contribution Guidelines: 24 | We follow the standard GitHub fork & pull request approach to open source collaboration. 25 | You can find an awesome description on how this works on the [gun.io blog](https://gun.io/blog/how-to-github-fork-branch-and-pull-request/). 26 | 27 | In a nutshell: 28 | 29 | 0. Fork it. 30 | 0. Create your feature branch (`git checkout -b feature/my-new-feature`). 31 | 0. Commit your changes (`git commit -am 'Add some feature'`) and [reference any issues](https://github.com/blog/831-issues-2-0-the-next-generation). 32 | 0. Push to your branch (`git push origin my-new-feature`). 33 | 0. Send a new Pull Request describing what you have done and why, in detail. 34 | 35 | **IMPORTANT**: By submitting a patch, you agree to allow the project owners to 36 | license your work under the the terms of the Apache 2.0 license. A copy of this 37 | license is provided in the root of the repository. 38 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/MessageSerialisationRegister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using JustSaying.Models; 4 | 5 | namespace JustSaying.Messaging.MessageSerialisation 6 | { 7 | public class MessageSerialisationRegister : IMessageSerialisationRegister 8 | { 9 | private readonly Dictionary _map = new Dictionary(); 10 | 11 | public void AddSerialiser(IMessageSerialiser serialiser) where T : Message 12 | { 13 | var keyname = typeof(T).Name; 14 | if (!_map.ContainsKey(keyname)) 15 | { 16 | _map.Add(keyname, new TypeSerialiser(typeof(T), serialiser)); 17 | } 18 | } 19 | 20 | public Message DeserializeMessage(string body) 21 | { 22 | foreach (var formatter in _map) 23 | { 24 | var stringType = formatter.Value.Serialiser.GetMessageType(body); 25 | if (string.IsNullOrWhiteSpace(stringType)) 26 | { 27 | continue; 28 | } 29 | 30 | var matchedType = formatter.Value.Type; 31 | if (!string.Equals(matchedType.Name, stringType, StringComparison.CurrentCultureIgnoreCase)) 32 | { 33 | continue; 34 | } 35 | 36 | return formatter.Value.Serialiser.Deserialise(body, matchedType); 37 | } 38 | 39 | throw new MessageFormatNotSupportedException( 40 | $"Message can not be handled - type undetermined. Message body: '{body}'"); 41 | } 42 | 43 | public string Serialise(Message message, bool serializeForSnsPublishing) 44 | { 45 | var formatter = _map[message.GetType().Name]; 46 | return formatter.Serialiser.Serialise(message, serializeForSnsPublishing); 47 | } 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenMessageProcessingThrowsBefore.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Amazon.SQS.Model; 3 | using JustSaying.TestingFramework; 4 | using JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support; 5 | using NSubstitute; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 9 | { 10 | /// 11 | /// this test exercises different exception handlers to the "handler throws an exception" path in WhenMessageHandlingThrows 12 | /// 13 | public class WhenMessageProcessingThrowsBefore : BaseQueuePollingTest 14 | { 15 | protected override void Given() 16 | { 17 | base.Given(); 18 | Handler.Handle(null).ReturnsForAnyArgs(true); 19 | } 20 | 21 | protected override async Task When() 22 | { 23 | var doneSignal = new TaskCompletionSource(); 24 | SystemUnderTest.WithMessageProcessingStrategy(new ThrowingBeforeMessageProcessingStrategy(doneSignal)); 25 | 26 | SystemUnderTest.AddMessageHandler(() => Handler); 27 | SystemUnderTest.Listen(); 28 | 29 | await Tasks.WaitWithTimeoutAsync(doneSignal.Task); 30 | 31 | SystemUnderTest.StopListening(); 32 | await Task.Yield(); 33 | } 34 | 35 | [Fact] 36 | public void MessageHandlerWasNotCalled() 37 | { 38 | Handler.DidNotReceiveWithAnyArgs().Handle(Arg.Any()); 39 | } 40 | 41 | [Fact] 42 | public void FailedMessageIsNotRemovedFromQueue() 43 | { 44 | Sqs.DidNotReceiveWithAnyArgs().DeleteMessageAsync(Arg.Any()); 45 | } 46 | 47 | [Fact] 48 | public void ExceptionIsLoggedToMonitor() 49 | { 50 | Monitor.DidNotReceiveWithAnyArgs().HandleException(Arg.Any()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenMessageProcessingThrowsDuring.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Amazon.SQS.Model; 3 | using JustSaying.TestingFramework; 4 | using JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support; 5 | using NSubstitute; 6 | using Xunit; 7 | 8 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 9 | { 10 | /// 11 | /// this test exercises different exception handlers to the "handler throws an exception" path in WhenMessageHandlingThrows 12 | /// 13 | public class WhenMessageProcessingThrowsDuring : BaseQueuePollingTest 14 | { 15 | protected override void Given() 16 | { 17 | base.Given(); 18 | Handler.Handle(null).ReturnsForAnyArgs(true); 19 | } 20 | 21 | protected override async Task When() 22 | { 23 | var doneSignal = new TaskCompletionSource(); 24 | SystemUnderTest.WithMessageProcessingStrategy(new ThrowingDuringMessageProcessingStrategy(doneSignal)); 25 | 26 | SystemUnderTest.AddMessageHandler(() => Handler); 27 | SystemUnderTest.Listen(); 28 | 29 | await Tasks.WaitWithTimeoutAsync(doneSignal.Task); 30 | 31 | SystemUnderTest.StopListening(); 32 | await Task.Yield(); 33 | } 34 | 35 | [Fact] 36 | public void MessageHandlerWasNotCalled() 37 | { 38 | Handler.DidNotReceiveWithAnyArgs().Handle(Arg.Any()); 39 | } 40 | 41 | [Fact] 42 | public void FailedMessageIsNotRemovedFromQueue() 43 | { 44 | Sqs.DidNotReceiveWithAnyArgs().DeleteMessageAsync(Arg.Any()); 45 | } 46 | 47 | [Fact] 48 | public void ExceptionIsLoggedToMonitor() 49 | { 50 | Monitor.DidNotReceiveWithAnyArgs().HandleException(Arg.Any()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenOptingOutOfErrorQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Amazon; 3 | using Amazon.SQS; 4 | using JustSaying.AwsTools.MessageHandling; 5 | using JustSaying.Messaging.MessageHandling; 6 | using JustSaying.TestingFramework; 7 | using Microsoft.Extensions.Logging; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | namespace JustSaying.IntegrationTests 12 | { 13 | public class OrderPlacedHandler : IHandlerAsync 14 | { 15 | public Task Handle(GenericMessage message) 16 | { 17 | return Task.FromResult(true); 18 | } 19 | } 20 | 21 | public class WhenOptingOutOfErrorQueue 22 | { 23 | private readonly IAmazonSQS _client; 24 | 25 | public WhenOptingOutOfErrorQueue() 26 | { 27 | _client = CreateMeABus.DefaultClientFactory().GetSqsClient(RegionEndpoint.EUWest1); 28 | } 29 | 30 | [Fact] 31 | public void ErrorQueueShouldNotBeCreated() 32 | { 33 | var queueName = "test-queue-issue-191"; 34 | CreateMeABus.WithLogging(new LoggerFactory()) 35 | .InRegion("eu-west-1") 36 | .WithSnsMessagePublisher() 37 | 38 | .WithSqsTopicSubscriber() 39 | .IntoQueue(queueName) 40 | .ConfigureSubscriptionWith(policy => 41 | { 42 | policy.ErrorQueueOptOut = true; 43 | }) 44 | .WithMessageHandler(new OrderPlacedHandler()); 45 | 46 | AssertThatQueueDoesNotExist(queueName+ "_error"); 47 | } 48 | 49 | private void AssertThatQueueDoesNotExist(string name) 50 | { 51 | var sqsQueueByName = new SqsQueueByName(RegionEndpoint.EUWest1, name, _client, 1, new LoggerFactory()); 52 | sqsQueueByName.Exists().ShouldBeFalse($"Expecting queue '{name}' to not exist but it does."); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/WhenRegisteringABlockingHandlerViaContainer.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.IntegrationTests.TestHandlers; 2 | using JustSaying.Messaging.MessageHandling; 3 | using Microsoft.Extensions.Logging; 4 | 5 | using Shouldly; 6 | using StructureMap; 7 | using Xunit; 8 | 9 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 10 | { 11 | [Collection(GlobalSetup.CollectionName)] 12 | public class WhenRegisteringABlockingHandlerViaContainer : GivenAPublisher 13 | { 14 | private BlockingOrderProcessor _resolvedHandler; 15 | 16 | protected override void Given() 17 | { 18 | var container = new Container(x => x.AddRegistry(new BlockingHandlerRegistry())); 19 | var resolutionContext = new HandlerResolutionContext("test"); 20 | 21 | var handlerResolver = new StructureMapHandlerResolver(container); 22 | var handler = handlerResolver.ResolveHandler(resolutionContext); 23 | handler.ShouldNotBeNull(); 24 | 25 | // we use the obsolete interface"IHandler" here 26 | #pragma warning disable 618 27 | var blockingHandler = (BlockingHandler)handler; 28 | _resolvedHandler = (BlockingOrderProcessor)blockingHandler.Inner; 29 | #pragma warning restore 618 30 | 31 | DoneSignal = _resolvedHandler.DoneSignal.Task; 32 | 33 | Subscriber = CreateMeABus.WithLogging(new LoggerFactory()) 34 | .InRegion("eu-west-1") 35 | .WithSqsTopicSubscriber() 36 | .IntoQueue("container-test") 37 | .WithMessageHandler(handlerResolver); 38 | 39 | Subscriber.StartListening(); 40 | } 41 | 42 | [Fact] 43 | public void ThenHandlerWillReceiveTheMessage() 44 | { 45 | _resolvedHandler.ReceivedMessageCount.ShouldBeGreaterThan(0); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/WhenRegisteringHandlersViaResolver/StructuremapNamedHandlerResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.Models; 4 | using StructureMap; 5 | 6 | namespace JustSaying.IntegrationTests.WhenRegisteringHandlersViaResolver 7 | { 8 | public class TestMessage : Message 9 | { } 10 | 11 | public class HandlerA : IHandlerAsync 12 | { 13 | public Task Handle(TestMessage message) => Task.FromResult(true); 14 | } 15 | 16 | public class HandlerB : IHandlerAsync 17 | { 18 | public Task Handle(TestMessage message) => Task.FromResult(true); 19 | } 20 | 21 | public class HandlerC : IHandlerAsync 22 | { 23 | public Task Handle(TestMessage message) => Task.FromResult(true); 24 | } 25 | 26 | public class StructureMapNamedHandlerResolver : IHandlerResolver 27 | { 28 | private readonly IContainer _container; 29 | 30 | public StructureMapNamedHandlerResolver() 31 | { 32 | _container = new Container(ConfigureContainer); 33 | } 34 | 35 | private void ConfigureContainer(ConfigurationExpression config) 36 | { 37 | config.For>() 38 | .Use().Named("QueueA"); 39 | 40 | config.For>() 41 | .Use().Named("QueueB"); 42 | 43 | config.For>() 44 | .Use(); 45 | } 46 | 47 | public IHandlerAsync ResolveHandler(HandlerResolutionContext context) 48 | { 49 | var namedHandler = _container.TryGetInstance>(context.QueueName); 50 | if (namedHandler != null) 51 | { 52 | return namedHandler; 53 | } 54 | 55 | return _container.GetInstance>(); 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageHandling/ExactlyOnceHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Models; 4 | 5 | namespace JustSaying.Messaging.MessageHandling 6 | { 7 | public class ExactlyOnceHandler : IHandlerAsync where T : Message 8 | { 9 | private readonly IHandlerAsync _inner; 10 | private readonly IMessageLock _messageLock; 11 | private readonly int _timeOut; 12 | private readonly string _handlerName; 13 | 14 | public ExactlyOnceHandler(IHandlerAsync inner, IMessageLock messageLock, int timeOut, string handlerName) 15 | { 16 | _inner = inner; 17 | _messageLock = messageLock; 18 | _timeOut = timeOut; 19 | _handlerName = handlerName; 20 | } 21 | 22 | private const bool RemoveTheMessageFromTheQueue = true; 23 | private const bool LeaveItInTheQueue = false; 24 | 25 | public async Task Handle(T message) 26 | { 27 | var lockKey = $"{message.UniqueKey()}-{typeof(T).Name.ToLower()}-{_handlerName}"; 28 | var lockResponse = _messageLock.TryAquireLock(lockKey, TimeSpan.FromSeconds(_timeOut)); 29 | if (!lockResponse.DoIHaveExclusiveLock) 30 | { 31 | if (lockResponse.IsMessagePermanentlyLocked) 32 | { 33 | return RemoveTheMessageFromTheQueue; 34 | } 35 | 36 | return LeaveItInTheQueue; 37 | } 38 | 39 | try 40 | { 41 | var successfullyHandled = await _inner.Handle(message).ConfigureAwait(false); 42 | if (successfullyHandled) 43 | { 44 | _messageLock.TryAquireLockPermanently(lockKey); 45 | } 46 | return successfullyHandled; 47 | } 48 | catch 49 | { 50 | _messageLock.ReleaseLock(lockKey); 51 | throw; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /JustSaying.TestingFramework/IntegrationAwsClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using Amazon.Runtime; 3 | using Amazon.SimpleNotificationService; 4 | using Amazon.SQS; 5 | using JustSaying.AwsTools; 6 | 7 | namespace JustSaying.TestingFramework 8 | { 9 | /// 10 | /// AwsCustomClient factory for running integration tests in continuous integration mode. 11 | /// 12 | /// If environment variable "ci" is not defined, use default AWS profile name as specified by IntegrationTestConfig.AwsProfileName 13 | /// 14 | /// Otherwise, construct AWS Profile using access and secret key by looking at "ci-access_key" and "ci-secret_key" environment variables. 15 | /// 16 | public class IntegrationAwsClientFactory : IAwsClientFactory 17 | { 18 | private readonly AWSCredentials _credentials; 19 | private readonly string _ci = "ci"; 20 | private readonly string _ciAccesskey = "AWS_ACCESS_KEY_ID"; 21 | private readonly string _ciSecretkey = "AWS_SECRET_KEY"; 22 | 23 | public IntegrationAwsClientFactory() 24 | { 25 | FallbackCredentialsFactory.CredentialsGenerators.Insert(0, CredentialsFromEnvironment); 26 | _credentials = FallbackCredentialsFactory.GetCredentials(); 27 | } 28 | 29 | private AWSCredentials CredentialsFromEnvironment() 30 | { 31 | var ci = System.Environment.GetEnvironmentVariable(_ci); 32 | if (string.IsNullOrWhiteSpace(ci)) return null; 33 | var accessKey = System.Environment.GetEnvironmentVariable(_ciAccesskey); 34 | var secretKey = System.Environment.GetEnvironmentVariable(_ciSecretkey); 35 | return new BasicAWSCredentials(accessKey, secretKey); 36 | } 37 | 38 | public IAmazonSimpleNotificationService GetSnsClient(RegionEndpoint region) 39 | => new AmazonSimpleNotificationServiceClient(_credentials, region); 40 | 41 | public IAmazonSQS GetSqsClient(RegionEndpoint region) 42 | => new AmazonSQSClient(_credentials, region); 43 | } 44 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/Sqs/WhenPublishingDelayedMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Amazon; 4 | using Amazon.SQS; 5 | using Amazon.SQS.Model; 6 | using JustBehave; 7 | using JustSaying.AwsTools.MessageHandling; 8 | using JustSaying.Messaging.MessageSerialisation; 9 | using JustSaying.TestingFramework; 10 | using Microsoft.Extensions.Logging; 11 | using NSubstitute; 12 | using Xunit; 13 | 14 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.Sqs 15 | { 16 | public class WhenPublishingDelayedMessage : XAsyncBehaviourTest 17 | { 18 | private readonly IMessageSerialisationRegister _serialisationRegister = Substitute.For(); 19 | private readonly IAmazonSQS _sqs = Substitute.For(); 20 | private const string Url = "https://blablabla/" + QueueName; 21 | private readonly DelayedMessage _message = new DelayedMessage(delaySeconds: 1); 22 | private const string QueueName = "queuename"; 23 | 24 | protected override SqsPublisher CreateSystemUnderTest() 25 | { 26 | var sqs = new SqsPublisher(RegionEndpoint.EUWest1, QueueName, _sqs, 0, 27 | _serialisationRegister, Substitute.For()); 28 | sqs.Exists(); 29 | return sqs; 30 | } 31 | 32 | protected override void Given() 33 | { 34 | _sqs.ListQueuesAsync(Arg.Any()).Returns(new ListQueuesResponse { QueueUrls = new List { Url } }); 35 | _sqs.GetQueueAttributesAsync(Arg.Any()).Returns(new GetQueueAttributesResponse()); 36 | } 37 | 38 | protected override async Task When() 39 | { 40 | await SystemUnderTest.PublishAsync(_message); 41 | } 42 | 43 | [Fact] 44 | public void MessageIsPublishedWithDelaySecondsPropertySet() 45 | { 46 | _sqs.Received().SendMessageAsync(Arg.Is(x => x.DelaySeconds.Equals(1))); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/Sqs/WhenPublishingDelayedMessageAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Amazon; 4 | using Amazon.SQS; 5 | using Amazon.SQS.Model; 6 | using JustBehave; 7 | using JustSaying.AwsTools.MessageHandling; 8 | using JustSaying.Messaging.MessageSerialisation; 9 | using JustSaying.TestingFramework; 10 | using Microsoft.Extensions.Logging; 11 | using NSubstitute; 12 | using Xunit; 13 | 14 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.Sqs 15 | { 16 | public class WhenPublishingDelayedMessageAsync : XAsyncBehaviourTest 17 | { 18 | private readonly IMessageSerialisationRegister _serialisationRegister = Substitute.For(); 19 | private readonly IAmazonSQS _sqs = Substitute.For(); 20 | private const string Url = "https://blablabla/" + QueueName; 21 | private readonly DelayedMessage _message = new DelayedMessage(delaySeconds: 1); 22 | private const string QueueName = "queuename"; 23 | 24 | protected override SqsPublisher CreateSystemUnderTest() 25 | { 26 | var sqs = new SqsPublisher(RegionEndpoint.EUWest1, QueueName, _sqs, 0, 27 | _serialisationRegister, Substitute.For()); 28 | sqs.Exists(); 29 | return sqs; 30 | } 31 | 32 | protected override void Given() 33 | { 34 | _sqs.ListQueuesAsync(Arg.Any()).Returns(new ListQueuesResponse { QueueUrls = new List { Url } }); 35 | _sqs.GetQueueAttributesAsync(Arg.Any()).Returns(new GetQueueAttributesResponse()); 36 | } 37 | 38 | protected override async Task When() 39 | { 40 | await SystemUnderTest.PublishAsync(_message); 41 | } 42 | 43 | [Fact] 44 | public void MessageIsPublishedWithDelaySecondsPropertySet() 45 | { 46 | _sqs.Received().SendMessageAsync(Arg.Is(x => x.DelaySeconds.Equals(1))); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /JustSaying/AwsTools/MessageHandling/SqsPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Amazon.Auth.AccessControlPolicy; 4 | using Amazon.Auth.AccessControlPolicy.ActionIdentifiers; 5 | using Amazon.SQS; 6 | using Amazon.SQS.Model; 7 | 8 | namespace JustSaying.AwsTools.MessageHandling 9 | { 10 | public class SqsPolicy 11 | { 12 | private readonly string _policy; 13 | public SqsPolicy(string policy) 14 | { 15 | _policy = policy; 16 | } 17 | 18 | public static async Task SaveAsync(string sourceArn, string queueArn, string queueUrl, IAmazonSQS client) 19 | { 20 | var topicArnWildcard = CreateTopicArnWildcard(sourceArn); 21 | ActionIdentifier[] actions = { SQSActionIdentifiers.SendMessage }; 22 | 23 | var sqsPolicy = new Policy() 24 | .WithStatements(new Statement(Statement.StatementEffect.Allow) 25 | .WithPrincipals(Principal.AllUsers) 26 | .WithResources(new Resource(queueArn)) 27 | .WithConditions(ConditionFactory.NewSourceArnCondition(topicArnWildcard)) 28 | .WithActionIdentifiers(actions)); 29 | var setQueueAttributesRequest = new SetQueueAttributesRequest 30 | { 31 | QueueUrl = queueUrl, 32 | Attributes = { ["Policy"] = sqsPolicy.ToJson() } 33 | }; 34 | 35 | await client.SetQueueAttributesAsync(setQueueAttributesRequest); 36 | } 37 | 38 | 39 | public override string ToString() => _policy; 40 | 41 | private static string CreateTopicArnWildcard(string topicArn) 42 | { 43 | if (string.IsNullOrWhiteSpace(topicArn)) 44 | { 45 | // todo should not get here? 46 | return "*"; 47 | } 48 | 49 | var index = topicArn.LastIndexOf(":", StringComparison.OrdinalIgnoreCase); 50 | if (index > 0) 51 | { 52 | topicArn = topicArn.Substring(0, index + 1); 53 | } 54 | 55 | return topicArn + "*"; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/HandlerMetadataTest.cs: -------------------------------------------------------------------------------- 1 | using JustSaying.AwsTools.MessageHandling; 2 | using JustSaying.Messaging.MessageHandling; 3 | using JustSaying.TestingFramework; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace JustSaying.UnitTests.AwsTools.MessageHandling 8 | { 9 | public class HandlerMetadataTests 10 | { 11 | [Fact] 12 | public void UnadornedHandler_DoesNotHaveExactlyOnce() 13 | { 14 | var handler = new UnadornedHandlerAsync(); 15 | var reader = HandlerMetadata.ReadExactlyOnce(handler); 16 | 17 | reader.Enabled.ShouldBeFalse(); 18 | } 19 | 20 | [Fact] 21 | public void OnceTestHandlerAsync_DoesHaveExactlyOnce() 22 | { 23 | var handler = new OnceTestHandlerAsync(); 24 | var reader = HandlerMetadata.ReadExactlyOnce(handler); 25 | 26 | reader.Enabled.ShouldBeTrue(); 27 | } 28 | 29 | [Fact] 30 | public void OnceTestHandlerAsync_HasCorrectTimeout() 31 | { 32 | var handler = new OnceTestHandlerAsync(); 33 | var reader = HandlerMetadata.ReadExactlyOnce(handler); 34 | 35 | reader.GetTimeOut().ShouldBe(42); 36 | } 37 | 38 | [Fact] 39 | public void OnceTestHandler_DoesHaveExactlyOnce() 40 | { 41 | var handler = new OnceTestHandler(); 42 | var reader = HandlerMetadata.ReadExactlyOnce(handler); 43 | 44 | reader.Enabled.ShouldBeTrue(); 45 | } 46 | 47 | [Fact] 48 | public void OnceTestHandler_HasCorrectTimeout() 49 | { 50 | var handler = new OnceTestHandler(); 51 | var reader = HandlerMetadata.ReadExactlyOnce(handler); 52 | 53 | reader.GetTimeOut().ShouldBe(23); 54 | } 55 | 56 | [Fact] 57 | public void WrappedHandler_DoesHaveExactlyOnce() 58 | { 59 | #pragma warning disable 618 60 | var wrapped = new BlockingHandler(new OnceTestHandler()); 61 | #pragma warning restore 618 62 | 63 | var reader = HandlerMetadata.ReadExactlyOnce(wrapped); 64 | 65 | reader.Enabled.ShouldBeTrue(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /JustSaying.IntegrationTests/JustSayingFluently/WhenPublishingWithoutAMonitor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Amazon; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.TestingFramework; 5 | using Microsoft.Extensions.Logging; 6 | using NSubstitute; 7 | using Xunit; 8 | 9 | namespace JustSaying.IntegrationTests.JustSayingFluently 10 | { 11 | [Collection(GlobalSetup.CollectionName)] 12 | public class WhenPublishingWithoutAMonitor 13 | { 14 | private IAmJustSayingFluently _bus; 15 | private readonly IHandlerAsync _handler = Substitute.For>(); 16 | 17 | private async Task Given() 18 | { 19 | // Setup 20 | var doneSignal = new TaskCompletionSource(); 21 | 22 | // Given 23 | _handler.Handle(Arg.Any()) 24 | .Returns(true) 25 | .AndDoes(_ => Tasks.DelaySendDone(doneSignal)); 26 | 27 | var bus = CreateMeABus.WithLogging(new LoggerFactory()) 28 | .InRegion(RegionEndpoint.EUWest1.SystemName) 29 | .ConfigurePublisherWith(c => 30 | { 31 | c.PublishFailureBackoffMilliseconds = 1; 32 | c.PublishFailureReAttempts = 1; 33 | 34 | }) 35 | .WithSnsMessagePublisher() 36 | .WithSqsTopicSubscriber() 37 | .IntoQueue("queuename") 38 | .ConfigureSubscriptionWith(cfg => cfg.InstancePosition = 1) 39 | .WithMessageHandler(_handler); 40 | 41 | _bus = bus; 42 | 43 | // When 44 | _bus.StartListening(); 45 | await _bus.PublishAsync(new GenericMessage()); 46 | 47 | // Teardown 48 | await doneSignal.Task; 49 | bus.StopListening(); 50 | } 51 | 52 | [Fact] 53 | public async Task AMessageCanStillBePublishedAndPopsOutTheOtherEnd() 54 | { 55 | await Given(); 56 | Received.InOrder(async () => await _handler.Handle(Arg.Any())); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | *.idea 16 | project.lock.json 17 | .vs/ 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Rr]elease/ 22 | x64/ 23 | *_i.c 24 | *_p.c 25 | *.ilk 26 | *.meta 27 | *.obj 28 | *.pch 29 | *.pdb 30 | *.pgc 31 | *.pgd 32 | *.rsp 33 | *.sbr 34 | *.tlb 35 | *.tli 36 | *.tlh 37 | *.tmp 38 | *.log 39 | *.vspscc 40 | *.vssscc 41 | .builds 42 | out/ 43 | 44 | # Visual C++ cache files 45 | ipch/ 46 | *.aps 47 | *.ncb 48 | *.opensdf 49 | *.sdf 50 | 51 | # Visual Studio profiler 52 | *.psess 53 | *.vsp 54 | *.vspx 55 | 56 | # Guidance Automation Toolkit 57 | *.gpState 58 | 59 | # ReSharper is a .NET coding add-in 60 | _ReSharper* 61 | 62 | # NCrunch 63 | *.ncrunch* 64 | .*crunch*.local.xml 65 | 66 | # Installshield output folder 67 | [Ee]xpress 68 | 69 | # DocProject is a documentation generator add-in 70 | DocProject/buildhelp/ 71 | DocProject/Help/*.HxT 72 | DocProject/Help/*.HxC 73 | DocProject/Help/*.hhc 74 | DocProject/Help/*.hhk 75 | DocProject/Help/*.hhp 76 | DocProject/Help/Html2 77 | DocProject/Help/html 78 | 79 | # Click-Once directory 80 | publish 81 | 82 | # Publish Web Output 83 | *.Publish.xml 84 | 85 | # NuGet Packages Directory 86 | packages/*/ 87 | 88 | # Windows Azure Build Output 89 | csx 90 | *.build.csdef 91 | 92 | # Windows Store app package directory 93 | AppPackages/ 94 | 95 | # Others 96 | [Bb]in 97 | [Oo]bj 98 | sql 99 | TestResults 100 | [Tt]est[Rr]esult* 101 | *.Cache 102 | ClientBin 103 | [Ss]tyle[Cc]op.* 104 | ~$* 105 | *.dbmdl 106 | Generated_Code #added for RIA/Silverlight projects 107 | *_NCrunch_* 108 | 109 | # Backup & report files from converting an old project file to a newer 110 | # Visual Studio version. Backup files are not needed, because we have git ;-) 111 | _UpgradeReport_Files/ 112 | Backup*/ 113 | UpgradeLog*.XML 114 | AWSSDK.1.5.25.0.nupkg 115 | AWSSDK.1.5.25.0.nuspec 116 | 117 | Gemfile.lock 118 | *.orig -------------------------------------------------------------------------------- /JustSaying.UnitTests/CreateMe/WhenCreatingABus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Xunit; 4 | 5 | namespace JustSaying.UnitTests.CreateMe 6 | { 7 | public class WhenCreatingABus 8 | { 9 | private readonly Action _config; 10 | private readonly string _region; 11 | 12 | public WhenCreatingABus() 13 | { 14 | _region = "region-1"; 15 | _config = x => 16 | { 17 | x.PublishFailureBackoffMilliseconds = 50; 18 | x.PublishFailureReAttempts = 2; 19 | }; 20 | } 21 | 22 | [Fact] 23 | public void PublishConfigurationIsOptional() 24 | { 25 | // Enforced by the fact we can do other configurations on the bus. 26 | CreateMeABus.WithLogging(new LoggerFactory()).InRegion(_region).StopListening(); 27 | } 28 | 29 | [Fact] 30 | public void PublishConfigurationCanBeProvided() 31 | { 32 | CreateMeABus.WithLogging(new LoggerFactory()).InRegion(_region).ConfigurePublisherWith(_config); 33 | } 34 | 35 | [Fact] 36 | public void ThenICanProvideMonitoring() 37 | { 38 | CreateMeABus.WithLogging(new LoggerFactory()).InRegion(_region).WithMonitoring(null).ConfigurePublisherWith(_config); 39 | } 40 | 41 | [Fact] 42 | public void MonitoringIsNotEnforced() 43 | { 44 | // Enforced by the fact we can do other configurations on the bus. 45 | CreateMeABus.WithLogging(new LoggerFactory()).InRegion(_region).ConfigurePublisherWith(_config).StopListening(); 46 | } 47 | 48 | [Fact] 49 | public void ThenICanProvideCustomSerialisation() 50 | { 51 | CreateMeABus.WithLogging(new LoggerFactory()).InRegion(_region).WithSerialisationFactory(null); 52 | } 53 | 54 | [Fact] 55 | public void CustomSerialisationIsNotEnforced() 56 | { 57 | // Enforced by the fact we can do other configurations on the bus. 58 | CreateMeABus.WithLogging(new LoggerFactory()).InRegion(_region).WithSerialisationFactory(null).StopListening(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenExactlyOnceIsAppliedToHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.TestingFramework; 5 | using JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support; 6 | using NSubstitute; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 11 | { 12 | public class WhenExactlyOnceIsAppliedToHandler : BaseQueuePollingTest 13 | { 14 | private int _expectedtimeout; 15 | private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); 16 | private ExplicitExactlyOnceSignallingHandler _handler; 17 | 18 | protected override void Given() 19 | { 20 | base.Given(); 21 | _expectedtimeout = 5; 22 | 23 | var messageLockResponse = new MessageLockResponse 24 | { 25 | DoIHaveExclusiveLock = true 26 | }; 27 | 28 | MessageLock = Substitute.For(); 29 | MessageLock.TryAquireLock(Arg.Any(), Arg.Any()) 30 | .Returns(messageLockResponse); 31 | 32 | _handler = new ExplicitExactlyOnceSignallingHandler(_tcs); 33 | Handler = _handler; 34 | } 35 | 36 | protected override async Task When() 37 | { 38 | SystemUnderTest.AddMessageHandler(() => Handler); 39 | SystemUnderTest.Listen(); 40 | 41 | // wait until it's done 42 | await Tasks.WaitWithTimeoutAsync(_tcs.Task); 43 | SystemUnderTest.StopListening(); 44 | await Task.Yield(); 45 | } 46 | 47 | [Fact] 48 | public void ProcessingIsPassedToTheHandler() 49 | { 50 | _handler.HandleWasCalled.ShouldBeTrue(); 51 | } 52 | 53 | [Fact] 54 | public void MessageIsLocked() 55 | { 56 | var messageId = DeserialisedMessage.Id.ToString(); 57 | 58 | MessageLock.Received().TryAquireLock( 59 | Arg.Is(a => a.Contains(messageId)), 60 | TimeSpan.FromSeconds(_expectedtimeout)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/Sqs/WhenPublishing.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Amazon; 3 | using Amazon.SQS; 4 | using Amazon.SQS.Model; 5 | using JustBehave; 6 | using JustSaying.AwsTools.MessageHandling; 7 | using JustSaying.Messaging.MessageSerialisation; 8 | using JustSaying.TestingFramework; 9 | using Microsoft.Extensions.Logging; 10 | using NSubstitute; 11 | using Xunit; 12 | 13 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.Sqs 14 | { 15 | public class WhenPublishing : XAsyncBehaviourTest 16 | { 17 | private readonly IMessageSerialisationRegister _serialisationRegister = Substitute.For(); 18 | private readonly IAmazonSQS _sqs = Substitute.For(); 19 | private const string Url = "https://blablabla/" + QueueName; 20 | private readonly GenericMessage _message = new GenericMessage {Content = "Hello"}; 21 | private const string QueueName = "queuename"; 22 | 23 | protected override SqsPublisher CreateSystemUnderTest() 24 | { 25 | var sqs = new SqsPublisher(RegionEndpoint.EUWest1, QueueName, _sqs, 0, _serialisationRegister, Substitute.For()); 26 | sqs.Exists(); 27 | return sqs; 28 | } 29 | 30 | protected override void Given() 31 | { 32 | _sqs.GetQueueUrlAsync(Arg.Any()).Returns(new GetQueueUrlResponse {QueueUrl = Url}); 33 | _sqs.GetQueueAttributesAsync(Arg.Any()).Returns(new GetQueueAttributesResponse()); 34 | _serialisationRegister.Serialise(_message, false).Returns("serialized_contents"); 35 | } 36 | 37 | protected override async Task When() 38 | { 39 | await SystemUnderTest.PublishAsync(_message); 40 | } 41 | 42 | [Fact] 43 | public void MessageIsPublishedToQueue() 44 | { 45 | // ToDo: Can be better... 46 | _sqs.Received().SendMessageAsync(Arg.Is(x => x.MessageBody.Equals("serialized_contents"))); 47 | } 48 | 49 | [Fact] 50 | public void MessageIsPublishedToCorrectLocation() 51 | { 52 | _sqs.Received().SendMessageAsync(Arg.Is(x => x.QueueUrl == Url)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /JustSaying/Messaging/MessageSerialisation/NewtonsoftSerialiser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JustSaying.Models; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace JustSaying.Messaging.MessageSerialisation 7 | { 8 | public class NewtonsoftSerialiser : IMessageSerialiser 9 | { 10 | private readonly JsonSerializerSettings _settings; 11 | 12 | public NewtonsoftSerialiser() 13 | { 14 | _settings = null; 15 | } 16 | 17 | public NewtonsoftSerialiser(JsonSerializerSettings settings) : this() 18 | { 19 | _settings = settings; 20 | } 21 | 22 | public Message Deserialise(string message, Type type) 23 | { 24 | var jsqsMessage = JObject.Parse(message); 25 | var messageBody = jsqsMessage["Message"].ToString(); 26 | return (Message)JsonConvert.DeserializeObject(messageBody, type, GetJsonSettings()); 27 | } 28 | 29 | public string Serialise(Message message, bool serializeForSnsPublishing) 30 | { 31 | var settings = GetJsonSettings(); 32 | 33 | var msg = JsonConvert.SerializeObject(message, settings); 34 | 35 | // AWS SNS service will add Subject and Message properties automatically, 36 | // so just return plain message 37 | if (serializeForSnsPublishing) 38 | { 39 | return msg; 40 | } 41 | 42 | // for direct publishing to SQS, add Subject and Message properties manually 43 | var context = new { Subject = message.GetType().Name, Message = msg }; 44 | return JsonConvert.SerializeObject(context); 45 | } 46 | 47 | private JsonSerializerSettings GetJsonSettings() 48 | { 49 | return _settings ?? new JsonSerializerSettings 50 | { 51 | NullValueHandling = NullValueHandling.Ignore, 52 | Converters = new JsonConverter[] {new Newtonsoft.Json.Converters.StringEnumConverter()} 53 | }; 54 | } 55 | 56 | public string GetMessageType(string sqsMessge) 57 | { 58 | var body = JObject.Parse(sqsMessge); 59 | 60 | var type = body["Subject"] ?? string.Empty; 61 | return type.ToString(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/Sqs/WhenFetchingQueueByName.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Amazon; 3 | using Amazon.SQS; 4 | using Amazon.SQS.Model; 5 | using JustSaying.AwsTools.MessageHandling; 6 | using Microsoft.Extensions.Logging; 7 | using NSubstitute; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.Sqs 12 | { 13 | public class WhenFetchingQueueByName 14 | { 15 | private readonly IAmazonSQS _client; 16 | private readonly ILoggerFactory _log; 17 | private const int RetryCount = 3; 18 | 19 | 20 | public WhenFetchingQueueByName() 21 | { 22 | _client = Substitute.For(); 23 | 24 | _client.GetQueueUrlAsync(Arg.Any()) 25 | .Returns(x => 26 | { 27 | if (x.Arg() == "some-queue-name") 28 | return new GetQueueUrlResponse {QueueUrl = "some-queue-name"}; 29 | throw new QueueDoesNotExistException("some-queue-name not found"); 30 | }); 31 | _client.GetQueueAttributesAsync(Arg.Any()) 32 | .Returns(new GetQueueAttributesResponse() 33 | { 34 | Attributes = new Dictionary { { "QueueArn", "something:some-queue-name" } } 35 | }); 36 | _log = Substitute.For(); 37 | } 38 | 39 | [Fact] 40 | public void IncorrectQueueNameDoNotMatch() 41 | { 42 | var sqsQueueByName = new SqsQueueByName(RegionEndpoint.EUWest1, "some-queue-name1", _client, RetryCount, _log); 43 | sqsQueueByName.Exists().ShouldBeFalse(); 44 | } 45 | 46 | [Fact] 47 | public void IncorrectPartialQueueNameDoNotMatch() 48 | { 49 | var sqsQueueByName = new SqsQueueByName(RegionEndpoint.EUWest1, "some-queue", _client, RetryCount, _log); 50 | sqsQueueByName.Exists().ShouldBeFalse(); 51 | } 52 | 53 | [Fact] 54 | public void CorrectQueueNameShouldMatch() 55 | { 56 | var sqsQueueByName = new SqsQueueByName(RegionEndpoint.EUWest1, "some-queue-name", _client, RetryCount, _log); 57 | sqsQueueByName.Exists().ShouldBeTrue(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenExactlyOnceIsAppliedToHandlerWithoutExplicitTimeout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.TestingFramework; 5 | using JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener.Support; 6 | using NSubstitute; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 11 | { 12 | public class WhenExactlyOnceIsAppliedWithoutSpecificTimeout : BaseQueuePollingTest 13 | { 14 | //private readonly int _maximumTimeout = int.MaxValue; 15 | private readonly int _maximumTimeout = (int)TimeSpan.MaxValue.TotalSeconds; 16 | private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); 17 | private ExactlyOnceSignallingHandler _handler; 18 | 19 | protected override void Given() 20 | { 21 | base.Given(); 22 | 23 | var messageLockResponse = new MessageLockResponse 24 | { 25 | DoIHaveExclusiveLock = true 26 | }; 27 | 28 | MessageLock = Substitute.For(); 29 | MessageLock.TryAquireLock(Arg.Any(), Arg.Any()) 30 | .Returns(messageLockResponse); 31 | 32 | _handler = new ExactlyOnceSignallingHandler(_tcs); 33 | Handler = _handler; 34 | } 35 | 36 | protected override async Task When() 37 | { 38 | SystemUnderTest.AddMessageHandler(() => Handler); 39 | SystemUnderTest.Listen(); 40 | 41 | // wait until it's done 42 | await Tasks.WaitWithTimeoutAsync(_tcs.Task); 43 | SystemUnderTest.StopListening(); 44 | await Task.Yield(); 45 | } 46 | 47 | [Fact] 48 | public void MessageIsLocked() 49 | { 50 | var messageId = DeserialisedMessage.Id.ToString(); 51 | 52 | MessageLock.Received().TryAquireLock( 53 | Arg.Is(a => a.Contains(messageId)), 54 | TimeSpan.FromSeconds(_maximumTimeout)); 55 | } 56 | 57 | [Fact] 58 | public void ProcessingIsPassedToTheHandler() 59 | { 60 | _handler.HandleWasCalled.ShouldBeTrue(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/SqsNotificationListener/WhenThereAreNoMessagesToProcess.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Amazon; 5 | using Amazon.SQS; 6 | using Amazon.SQS.Model; 7 | using JustBehave; 8 | using JustSaying.AwsTools.MessageHandling; 9 | using JustSaying.Messaging.Monitoring; 10 | using Microsoft.Extensions.Logging; 11 | using NSubstitute; 12 | using Shouldly; 13 | using Xunit; 14 | 15 | namespace JustSaying.UnitTests.AwsTools.MessageHandling.SqsNotificationListener 16 | { 17 | public class WhenThereAreNoMessagesToProcess : XAsyncBehaviourTest 18 | { 19 | private readonly IAmazonSQS _sqs = Substitute.For(); 20 | private int _callCount; 21 | 22 | protected override JustSaying.AwsTools.MessageHandling.SqsNotificationListener CreateSystemUnderTest() 23 | { 24 | return new JustSaying.AwsTools.MessageHandling.SqsNotificationListener( 25 | new SqsQueueByUrl(RegionEndpoint.EUWest1, "", _sqs), 26 | null, 27 | Substitute.For(), 28 | Substitute.For()); 29 | } 30 | 31 | protected override void Given() 32 | { 33 | _sqs.ReceiveMessageAsync( 34 | Arg.Any(), 35 | Arg.Any()) 36 | .Returns(x => Task.FromResult(GenerateEmptyMessage())); 37 | 38 | _sqs.When(x => x.ReceiveMessageAsync( 39 | Arg.Any(), 40 | Arg.Any())) 41 | .Do(x => _callCount++); 42 | } 43 | 44 | protected override async Task When() 45 | { 46 | SystemUnderTest.Listen(); 47 | await Task.Delay(100); 48 | SystemUnderTest.StopListening(); 49 | await Task.Yield(); 50 | } 51 | 52 | [Fact] 53 | public void ListenLoopDoesNotDie() 54 | { 55 | _callCount.ShouldBeGreaterThan(3); 56 | } 57 | 58 | private ReceiveMessageResponse GenerateEmptyMessage() 59 | { 60 | return new ReceiveMessageResponse { Messages = new List() }; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/Messaging/MessageHandling/BlockingHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.TestingFramework; 5 | using NSubstitute; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | // we use the obsolete interface"IHandler" here 10 | #pragma warning disable 618 11 | 12 | namespace JustSaying.UnitTests.Messaging.MessageHandling 13 | { 14 | public class BlockingHandlerTests 15 | { 16 | [Fact] 17 | public void WhenInnerIsNull_ExcpetionIsThrown() 18 | { 19 | // ReSharper disable once ObjectCreationAsStatement 20 | new Action(() => new BlockingHandler(null)).ShouldThrow(); 21 | } 22 | 23 | [Fact] 24 | public async Task WhenAMessageIsHandled_TheInnerIsCalled() 25 | { 26 | var inner = Substitute.For>(); 27 | inner.Handle(Arg.Any()) 28 | .Returns(false); 29 | 30 | var handler = new BlockingHandler(inner); 31 | 32 | var message = new OrderAccepted(); 33 | 34 | await handler.Handle(message); 35 | 36 | inner.Received().Handle(message); 37 | } 38 | 39 | [Fact] 40 | public async Task WhenAMessageIsHandled_TheInnerResultFalseIsReturned() 41 | { 42 | var inner = Substitute.For>(); 43 | inner.Handle(Arg.Any()) 44 | .Returns(false); 45 | 46 | var handler = new BlockingHandler(inner); 47 | 48 | var message = new OrderAccepted(); 49 | 50 | var result = await handler.Handle(message); 51 | 52 | result.ShouldBeFalse(); 53 | } 54 | 55 | [Fact] 56 | public async Task WhenAMessageIsHandled_TheInnerResultTrueIsReturned() 57 | { 58 | var inner = Substitute.For>(); 59 | inner.Handle(Arg.Any()) 60 | .Returns(true); 61 | 62 | var handler = new BlockingHandler(inner); 63 | 64 | var message = new OrderAccepted(); 65 | 66 | var result = await handler.Handle(message); 67 | 68 | result.ShouldBeTrue(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /JustSaying.UnitTests/AwsTools/MessageHandling/MessageHandlerWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JustSaying.AwsTools.MessageHandling; 3 | using JustSaying.Messaging.MessageHandling; 4 | using JustSaying.Messaging.Monitoring; 5 | using JustSaying.TestingFramework; 6 | using NSubstitute; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | namespace JustSaying.UnitTests.AwsTools.MessageHandling 11 | { 12 | public class MessageHandlerWrapperTests 13 | { 14 | [Fact] 15 | public void WrapperReturnsAFunction() 16 | { 17 | var messageLock = Substitute.For(); 18 | var handlerWrapper = new MessageHandlerWrapper(messageLock, new NullOpMessageMonitor()); 19 | 20 | var wrapped = handlerWrapper.WrapMessageHandler(() => new UnadornedHandlerAsync()); 21 | 22 | wrapped.ShouldNotBeNull(); 23 | } 24 | 25 | [Fact] 26 | public async Task ReturnedFunctionIsCallable() 27 | { 28 | // arrange 29 | var messageLock = Substitute.For(); 30 | var handlerWrapper = new MessageHandlerWrapper(messageLock, new NullOpMessageMonitor()); 31 | 32 | var mockHandler = Substitute.For>(); 33 | mockHandler.Handle(Arg.Any()).Returns(Task.FromResult(true)); 34 | 35 | // act 36 | var wrapped = handlerWrapper.WrapMessageHandler(() => mockHandler); 37 | 38 | var result = await wrapped(new GenericMessage()); 39 | 40 | result.ShouldBeTrue(); 41 | } 42 | 43 | [Fact] 44 | public async Task ReturnedFunctionCallsInner() 45 | { 46 | // arrange 47 | var messageLock = Substitute.For(); 48 | var handlerWrapper = new MessageHandlerWrapper(messageLock, new NullOpMessageMonitor()); 49 | 50 | var mockHandler = Substitute.For>(); 51 | mockHandler.Handle(Arg.Any()).Returns(Task.FromResult(true)); 52 | 53 | var testMessage = new GenericMessage(); 54 | 55 | // act 56 | var wrapped = handlerWrapper.WrapMessageHandler(() => mockHandler); 57 | 58 | await wrapped(testMessage); 59 | 60 | await mockHandler.Received().Handle(testMessage); 61 | } 62 | } 63 | } 64 | --------------------------------------------------------------------------------