public async Task HandleSubmitOrderForProductionAsyncSubmitsTheOrder()
        {
            var mockClock            = new Mock <IClock>();
            var mockSubmitter        = new Mock <IOrderSubmitter>();
            var mockSubmitPublisher  = new Mock <ICommandPublisher <SubmitOrderForProduction> >();
            var mockFailurePublisher = new Mock <ICommandPublisher <NotifyOfFatalFailure> >();
            var mockEventPublisher   = new Mock <IEventPublisher <EventBase> >();
            var mockLogger           = new Mock <ILogger>();
            var mockLifetimeScope    = new Mock <IDisposable>();
            var serializerSettings   = new JsonSerializerSettings();
            var serializer           = new JsonSerializer {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };
            var processorFunctions = new TestOrderSubmitterFunctions(serializer, new CommandRetryThresholds(-1, 0, 0), mockClock.Object, mockSubmitter.Object, mockSubmitPublisher.Object, mockFailurePublisher.Object, mockEventPublisher.Object, mockLogger.Object, mockLifetimeScope.Object);
            var partner            = "That guy";
            var orderId            = "ABC123";
            var correlationId      = "Hello";
            var emulation          = new DependencyEmulation {
                OrderDetails = new OperationResult {
                    Payload = "Yay!"
                }
            };

            serializer.Converters.Add(new StringEnumConverter());
            serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            serializerSettings.Converters.Add(new StringEnumConverter());

            mockLogger
            .Setup(logger => logger.ForContext(It.IsAny <string>(), It.IsAny <object>(), It.IsAny <bool>()))
            .Returns(mockLogger.Object);

            mockSubmitter
            .Setup(processor => processor.SubmitOrderForProductionAsync(It.Is <string>(partnerCode => partnerCode == partner),
                                                                        It.Is <string>(order => order == orderId),
                                                                        It.Is <DependencyEmulation>(emu => emu.OrderDetails.Payload == emulation.OrderDetails.Payload),
                                                                        It.Is <string>(correlation => correlation == correlationId)))
            .ReturnsAsync(new OperationResult {
                Outcome = Outcome.Success
            })
            .Verifiable("The order should have been submitted");

            var command = new SubmitOrderForProduction
            {
                Id              = Guid.NewGuid(),
                CorrelationId   = correlationId,
                PartnerCode     = partner,
                OrderId         = orderId,
                Emulation       = emulation,
                OccurredTimeUtc = new DateTime(2017, 12, 09, 9, 0, 0, DateTimeKind.Utc)
            };

            using (var memStream = new MemoryStream())
                using (var writer = new StreamWriter(memStream))
                    using (var jsonWriter = new JsonTextWriter(writer))
                    {
                        serializer.Serialize(jsonWriter, command);
                        jsonWriter.Flush();

                        memStream.Seek(0, SeekOrigin.Begin);

                        using (var message = new BrokeredMessage(memStream))
                        {
                            message.ContentType   = MimeTypes.Json;
                            message.CorrelationId = Guid.NewGuid().ToString();
                            message.MessageId     = Guid.NewGuid().ToString();

                            await processorFunctions.HandleSubmitOrderForProductionAsync(message);
                        }

                        jsonWriter.Close();
                        writer.Close();
                        memStream.Close();
                    };

            mockSubmitter.VerifyAll();
        }
예제 #2
0
        /// <summary>
        ///   Performs the actions needed to process an order in preparation for submission.
        /// </summary>
        ///
        /// <param name="partner">The partner associated with the order.</param>
        /// <param name="orderId">The unique identifier of the order.</param>
        /// <param name="orderAssets">The set of assets associated with the order.</param>
        /// <param name="emulation">The set of emulation requirements for processing; this will override the associated external communication an, instead, use the emulated result.</param>
        /// <param name="correlationId">An optional identifier used to correlate activities across the disparate parts of processing, including external interations.</param>
        ///
        /// <returns>The result of the operation.</returns>
        ///
        public async Task <OperationResult> ProcessOrderAsync(string partner,
                                                              string orderId,
                                                              IReadOnlyDictionary <string, string> orderAssets,
                                                              DependencyEmulation emulation = null,
                                                              string correlationId          = null)
        {
            if (String.IsNullOrEmpty(partner))
            {
                throw new ArgumentException("The partner must be provided.", nameof(partner));
            }

            if (String.IsNullOrEmpty(orderId))
            {
                throw new ArgumentException("The order identifier must be provided.", nameof(orderId));
            }

            if ((orderAssets == null) || (orderAssets.Count < 1))
            {
                throw new ArgumentException("At least one asset is expected to be associated with the order.", nameof(orderAssets));
            }

            // Begin processing

            var log = this.Log.WithCorrelationId(correlationId);

            log.Information("Processing for {Partner}//{Order} has begun.", partner, orderId);

            try
            {
                var config      = this.configuration;
                var retryPolicy = this.CreateRetryPolicy(this.rng, config.OperationRetryMaxCount, config.OperationRetryExponentialSeconds, config.OperationRetryJitterSeconds);

                // Retrieve the details for the order.  If the result was not successful, return it as the result of processing.  This will allow
                // the caller to process the result and understand whether or not it should be retried.

                var policyResult = await retryPolicy.ExecuteAndCaptureAsync(() => this.RetrieveOrderDetailsAsync(log, this.ecommerceClient, partner, orderId, correlationId, emulation?.OrderDetails));

                var detailsResult = policyResult.Result ?? policyResult.FinalHandledResult ?? OperationResult.ExceptionResult;

                if (detailsResult.Outcome != Outcome.Success)
                {
                    return(detailsResult);
                }

                // Translate the order details into a CreateOrderMessage.  This is the format that the order will need to be submitted in to be produced.

                var details                  = JsonConvert.DeserializeObject <OrderDetails>(detailsResult.Payload, this.jsonSerializerSettings);
                var firstAssetUrl            = orderAssets.First().Value;
                var lineItemAssets           = details.LineItems.ToDictionary(item => item.LineItemId, item => firstAssetUrl);
                var createOrderMessageResult = await this.BuildCreateOrderMessageFromDetailsAsync(config, log, this.skuMetadataProcessor, partner, orderId, lineItemAssets, details, this.jsonSerializerSettings, correlationId, emulation?.CreateOrderMessage);

                if (createOrderMessageResult.Outcome != Outcome.Success)
                {
                    return(createOrderMessageResult);
                }

                // Store the CreateOrderMessage so that it can be retrieved for submission at a later point.

                policyResult = await retryPolicy.ExecuteAndCaptureAsync(() => this.StoreOrderForSubmissionAsync(log, this.orderStorage, partner, orderId, JsonConvert.DeserializeObject <CreateOrderMessage>(createOrderMessageResult.Payload), correlationId));

                var storageResult = policyResult.Result ?? policyResult.FinalHandledResult ?? OperationResult.ExceptionResult;

                if (storageResult.Outcome != Outcome.Success)
                {
                    return(storageResult);
                }

                log.Information("Processing for {Partner}//{Order} was successful.  The order has been staged for submission.", partner, orderId);

                return(new OperationResult
                {
                    Outcome = Outcome.Success,
                    Reason = String.Empty,
                    Recoverable = Recoverability.Final,
                    Payload = storageResult.Payload
                });
            }

            catch (Exception ex)
            {
                log.Error(ex, "An exception occurred at an indeterminite point of processing for {Partner}//{Order}", partner, orderId);
                return(OperationResult.ExceptionResult);
            }
        }
예제 #3
0
        /// <summary>
        ///   Performs the actions needed to submit an order for production.
        /// </summary>
        ///
        /// <param name="partner">The partner associated with the order.</param>
        /// <param name="orderId">The unique identifier of the order.</param>
        /// <param name="emulation">The set of emulation requirements for processing; this will override the associated external communication an, instead, use the emulated result.</param>
        /// <param name="correlationId">An optional identifier used to correlate activities across the disparate parts of processing, including external interations.</param>
        ///
        /// <returns>The result of the operation.</returns>
        ///
        public async Task <OperationResult> SubmitOrderForProductionAsync(string partner,
                                                                          string orderId,
                                                                          DependencyEmulation emulation = null,
                                                                          string correlationId          = null)
        {
            if (String.IsNullOrEmpty(partner))
            {
                throw new ArgumentException("The partner must be provided.", nameof(partner));
            }

            if (String.IsNullOrEmpty(orderId))
            {
                throw new ArgumentException("The order identifier must be provided.", nameof(orderId));
            }

            // Begin processing

            var log = this.Log.WithCorrelationId(correlationId);

            log.Information("Submission for {Partner}//{Order} has begun.", partner, orderId);

            try
            {
                var config      = this.configuration;
                var retryPolicy = this.CreateRetryPolicy <OperationResult, string>(this.rng, config.OperationRetryMaxCount, config.OperationRetryExponentialSeconds, config.OperationRetryJitterSeconds);

                // Retrieve the details for the order.  If the result was not successful, return it as the result of processing.  This will allow
                // the caller to process the result and understand whether or not it should be retried.

                var orderRetryPolicy  = this.CreateRetryPolicy <OperationResult <CreateOrderMessage>, CreateOrderMessage>(this.rng, config.OperationRetryMaxCount, config.OperationRetryExponentialSeconds, config.OperationRetryJitterSeconds);
                var orderPolicyResult = await orderRetryPolicy.ExecuteAndCaptureAsync(() => this.RetrievePendingOrderAsync(log, this.orderStorage, this.jsonSerializerSettings, partner, orderId, correlationId, emulation?.CreateOrderMessage));

                var orderResult = orderPolicyResult.Result ?? orderPolicyResult.FinalHandledResult ?? OperationResult <CreateOrderMessage> .ExceptionResult;

                if (orderResult.Outcome != Outcome.Success)
                {
                    return(new OperationResult
                    {
                        Outcome = orderResult.Outcome,
                        Reason = orderResult.Reason,
                        Recoverable = orderResult.Recoverable,
                        Payload = String.Empty
                    });
                }

                // Submit the order for production.

                var policyResult = await retryPolicy.ExecuteAndCaptureAsync(() => this.SendOrderToProductionAsync(log, this.orderProductionClient, orderResult.Payload, correlationId, emulation?.OrderSubmission));

                var submissionResult = policyResult.Result ?? policyResult.FinalHandledResult ?? OperationResult.ExceptionResult;

                if (submissionResult.Outcome != Outcome.Success)
                {
                    return(submissionResult);
                }

                // Store the CreateOrderMessage as complated, so that it can be used for any troubleshooting in the future.

                policyResult = await retryPolicy.ExecuteAndCaptureAsync(() => this.StoreOrderAsCompletedAsync(log, this.orderStorage, orderResult.Payload, correlationId, null));

                var completedStorageResult = policyResult.Result ?? policyResult.FinalHandledResult ?? OperationResult.ExceptionResult;

                if (completedStorageResult.Outcome != Outcome.Success)
                {
                    return(completedStorageResult);
                }

                // Remove the pending order from storage, as it is now complete.  If there was an issue deleting, log a warning and continue;  this is
                // a non-critical operation.

                OperationResult deletePendingResult;

                try
                {
                    policyResult = await retryPolicy.ExecuteAndCaptureAsync(() => this.DeletePendingOrderAsync(log, this.orderStorage, partner, orderId, correlationId, null));

                    deletePendingResult = policyResult.Result ?? policyResult.FinalHandledResult ?? OperationResult.ExceptionResult;
                }

                catch
                {
                    deletePendingResult = OperationResult.ExceptionResult;
                }

                if (deletePendingResult.Outcome != Outcome.Success)
                {
                    log.Warning("Could not remove {Partner}//{Order} from the pending order storage.  This should be cleaned up.", partner, orderId);
                }

                // Submission is complete.

                log.Information("Submission for {Partner}//{Order} was successful.", partner, orderId);

                return(new OperationResult
                {
                    Outcome = Outcome.Success,
                    Reason = String.Empty,
                    Recoverable = Recoverability.Final,
                    Payload = String.Empty
                });
            }
            catch (Exception ex)
            {
                log.Error(ex, "An exception occurred at an indeterminite point of submission for {Partner}//{Order}", partner, orderId);
                return(OperationResult.ExceptionResult);
            }
        }