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(); }
/// <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); } }
/// <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); } }