Merge branch 'order-processflow-redesign' of https://github.com/dotnet-architecture/eShopOnContainers into order-processflow-redesign

# Conflicts:
#	src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
This commit is contained in:
Christian Arenas 2017-05-16 11:11:09 +02:00
commit 94ee95f32b
53 changed files with 743 additions and 236 deletions
src
test/Services

@ -52,15 +52,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
} }
[Route("checkout")] [Route("checkout")]
[HttpPut] [HttpPost]
public async Task<IActionResult> Checkout([FromBody]BasketCheckout value) public async Task<IActionResult> Checkout([FromBody]BasketCheckout value, [FromHeader(Name = "x-requestid")] string requestId)
{ {
var userId = _identitySvc.GetUserIdentity(); var userId = _identitySvc.GetUserIdentity();
value.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ?
guid : value.RequestId;
var basket = await _repository.GetBasketAsync(userId); var basket = await _repository.GetBasketAsync(userId);
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, value.City, value.Street, var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, value.City, value.Street,
value.State, value.Country, value.ZipCode, value.CardNumber, value.CardHolderName, value.State, value.Country, value.ZipCode, value.CardNumber, value.CardHolderName,
value.CardExpiration, value.CardSecurityNumber, value.CardTypeId, value.Buyer, value.RequestId, basket); value.CardExpiration, value.CardSecurityNumber, value.CardTypeId, value.Buyer, value.RequestId, basket);
// Once basket is checkout, sends an integration event to
// ordering.api to convert basket to order and proceeds with
// order creation process
_eventBus.Publish(eventMessage); _eventBus.Publish(eventMessage);
if (basket == null) if (basket == null)

@ -23,20 +23,11 @@
foreach (var orderStockItem in @event.OrderStockItems) foreach (var orderStockItem in @event.OrderStockItems)
{ {
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
CheckValidcatalogItemId(catalogItem);
catalogItem.RemoveStock(orderStockItem.Units); catalogItem.RemoveStock(orderStockItem.Units);
} }
await _catalogContext.SaveChangesAsync(); await _catalogContext.SaveChangesAsync();
} }
private void CheckValidcatalogItemId(CatalogItem catalogItem)
{
if (catalogItem is null)
{
throw new CatalogDomainException("Not able to process catalog event. Reason: no valid catalogItemId");
}
}
} }
} }

@ -0,0 +1,21 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class ShipOrderCommand : IAsyncRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
public ShipOrderCommand(int orderNumber)
{
OrderNumber = orderNumber;
}
}
}

@ -26,8 +26,6 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
{ {
var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1;
//var userGuid = _identityService.GetUserIdentity();
var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId); var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId);
bool buyerOriginallyExisted = (buyer == null) ? false : true; bool buyerOriginallyExisted = (buyer == null) ? false : true;

@ -3,7 +3,6 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationEvents.Events; using Ordering.API.Application.IntegrationEvents.Events;
using Ordering.Domain.Exceptions;
using System.Threading.Tasks; using System.Threading.Tasks;
public class OrderPaymentFailedIntegrationEventHandler : public class OrderPaymentFailedIntegrationEventHandler :

@ -3,7 +3,6 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationEvents.Events; using Ordering.API.Application.IntegrationEvents.Events;
using Ordering.Domain.Exceptions;
using System.Threading.Tasks; using System.Threading.Tasks;
public class OrderPaymentSuccededIntegrationEventHandler : public class OrderPaymentSuccededIntegrationEventHandler :

@ -4,8 +4,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Events; using Events;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.Domain.Exceptions;
public class OrderStockConfirmedIntegrationEventHandler : public class OrderStockConfirmedIntegrationEventHandler :
IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent> IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>

@ -4,11 +4,9 @@ using Ordering.API.Application.IntegrationCommands.Commands;
namespace Ordering.API.Application.IntegrationEvents.EventHandling namespace Ordering.API.Application.IntegrationEvents.EventHandling
{ {
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Events; using Events;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Domain.Exceptions;
public class OrderStockNotConfirmedIntegrationEventHandler : IIntegrationEventHandler<OrderStockNotConfirmedIntegrationEvent> public class OrderStockNotConfirmedIntegrationEventHandler : IIntegrationEventHandler<OrderStockNotConfirmedIntegrationEvent>
{ {

@ -1,5 +1,4 @@
using Autofac.Features.OwnedInstances; using MediatR;
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
@ -7,11 +6,9 @@ using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Ordering.API.Application.Commands; using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationCommands.Commands; using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.Domain.Exceptions;
using System;
using System.Linq;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationEvents; using Ordering.API.Application.IntegrationEvents;
using Ordering.Domain.Exceptions;
using System.Threading.Tasks;
namespace Ordering.API.Application.Sagas namespace Ordering.API.Application.Sagas
{ {
@ -25,20 +22,14 @@ namespace Ordering.API.Application.Sagas
/// </summary> /// </summary>
public class OrderProcessSaga : OrderSaga, public class OrderProcessSaga : OrderSaga,
IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>, IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>,
IAsyncRequestHandler<CancelOrderCommand, bool> IAsyncRequestHandler<CancelOrderCommand, bool>,
IAsyncRequestHandler<ShipOrderCommand, bool>
{ {
private readonly IMediator _mediator;
private readonly Func<Owned<OrderingContext>> _dbContextFactory;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderProcessSaga( public OrderProcessSaga(
Func<Owned<OrderingContext>> dbContextFactory, OrderingContext orderingContext, OrderingContext orderingContext)
IMediator mediator, IOrderingIntegrationEventService orderingIntegrationEventService)
: base(orderingContext) : base(orderingContext)
{ {
_dbContextFactory = dbContextFactory;
_mediator = mediator;
_orderingIntegrationEventService = orderingIntegrationEventService;
} }
/// <summary> /// <summary>
@ -73,12 +64,41 @@ namespace Ordering.API.Application.Sagas
/// <returns></returns> /// <returns></returns>
public async Task<bool> Handle(CancelOrderCommand command) public async Task<bool> Handle(CancelOrderCommand command)
{ {
var result = false;
var orderSaga = FindSagaById(command.OrderNumber); var orderSaga = FindSagaById(command.OrderNumber);
CheckValidSagaId(orderSaga); CheckValidSagaId(orderSaga);
// Set order status tu cancelled // Not possible to cancel order when
// it has already been shipped
if (orderSaga.GetOrderStatusId() != OrderStatus.Cancelled.Id
|| orderSaga.GetOrderStatusId() != OrderStatus.Shipped.Id)
{
orderSaga.SetCancelStatus();
result = await SaveChangesAsync();
}
return result;
}
return true; /// <summary>
/// Handler which processes the command when
/// administrator executes ship order from app
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(ShipOrderCommand command)
{
var result = false;
var orderSaga = FindSagaById(command.OrderNumber);
CheckValidSagaId(orderSaga);
// Only ship order when
// its status is paid
if (orderSaga.GetOrderStatusId() == OrderStatus.Paid.Id)
{
orderSaga.SetShippedStatus();
result = await SaveChangesAsync();
}
return result;
} }
private void CheckValidSagaId(Order orderSaga) private void CheckValidSagaId(Order orderSaga)
@ -103,6 +123,18 @@ namespace Ordering.API.Application.Sagas
} }
} }
public class ShipOrderCommandIdentifiedHandler : IdentifierCommandHandler<ShipOrderCommand, bool>
{
public ShipOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
#endregion #endregion
} }
} }

@ -28,7 +28,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
} }
[Route("cancel")] [Route("cancel")]
[HttpPost] [HttpPut]
public async Task<IActionResult> CancelOrder([FromBody]CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) public async Task<IActionResult> CancelOrder([FromBody]CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
{ {
bool commandResult = false; bool commandResult = false;
@ -42,6 +42,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
} }
[Route("ship")]
[HttpPut]
public async Task<IActionResult> ShipOrder([FromBody]ShipOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
{
bool commandResult = false;
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{
var requestShipOrder = new IdentifiedCommand<ShipOrderCommand, bool>(command, guid);
commandResult = await _mediator.SendAsync(requestShipOrder);
}
return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest();
}
[Route("{orderId:int}")] [Route("{orderId:int}")]
[HttpGet] [HttpGet]
public async Task<IActionResult> GetOrder(int orderId) public async Task<IActionResult> GetOrder(int orderId)

@ -3,8 +3,10 @@
using AspNetCore.Http; using AspNetCore.Http;
using Autofac; using Autofac;
using Autofac.Extensions.DependencyInjection; using Autofac.Extensions.DependencyInjection;
using global::Ordering.API.Application.IntegrationCommands.Commands;
using global::Ordering.API.Application.IntegrationEvents; using global::Ordering.API.Application.IntegrationEvents;
using global::Ordering.API.Application.IntegrationEvents.Events; using global::Ordering.API.Application.IntegrationEvents.Events;
using global::Ordering.API.Application.Sagas;
using global::Ordering.API.Infrastructure.Middlewares; using global::Ordering.API.Infrastructure.Middlewares;
using global::Ordering.API.Application.IntegrationCommands.Commands; using global::Ordering.API.Application.IntegrationCommands.Commands;
using global::Ordering.API.Application.Sagas; using global::Ordering.API.Application.Sagas;

@ -168,28 +168,45 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public void SetCancelledStatus() public void SetCancelledStatus()
{ {
switch(OrderStatus.From(_orderStatusId)) if (_orderStatusId == OrderStatus.Submited.Id)
{ {
//case OrderStatus.Submited: _description = "";
// _description = ""; }
// break; else if (_orderStatusId == OrderStatus.AwaitingValidation.Id)
//case OrderStatus.StockConfirmed: {
// _description = ""; _description = "";
// break; }
//case OrderStatus.Paid: else if (_orderStatusId == OrderStatus.StockConfirmed.Id)
// _description = ""; {
// break; _description = "";
//case OrderStatus.Shipped: }
// _description = ""; else if (_orderStatusId == OrderStatus.Paid.Id)
// break; {
_description = "";
}
else if(_orderStatusId == OrderStatus.Shipped.Id)
{
throw new OrderingDomainException("Not possible to change order status. Reason: cannot cancel order it is already shipped");
} }
_orderStatusId = OrderStatus.Cancelled.Id; _orderStatusId = OrderStatus.Cancelled.Id;
} }
public void SetCancelStatus()
{
if (_orderStatusId == OrderStatus.Shipped.Id)
{
throw new OrderingDomainException("Not possible to change order status. Reason: cannot cancel order it is already shipped");
}
_orderStatusId = OrderStatus.Cancelled.Id;
}
#endregion #endregion
public int GetOrderStatusId()
{
return _orderStatusId;
}
private void AddOrderStartedDomainEvent(string userId, int cardTypeId, string cardNumber, private void AddOrderStartedDomainEvent(string userId, int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
{ {

@ -33,8 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor
public async Task<Order> GetAsync(int orderId) public async Task<Order> GetAsync(int orderId)
{ {
return await _context.Orders.FindAsync(orderId) return await _context.Orders.FindAsync(orderId);
?? throw new OrderingDomainException($"Not able to get the order. Reason: no valid orderId: {orderId}");
} }
public void Update(Order order) public void Update(Order order)

@ -4,16 +4,25 @@
using Payment.API.IntegrationCommands.Commands; using Payment.API.IntegrationCommands.Commands;
using System.Threading.Tasks; using System.Threading.Tasks;
using System; using System;
using Payment.API.IntegrationEvents;
using Payment.API.IntegrationEvents.Events;
public class PayOrderCommandMsgHandler : IIntegrationEventHandler<PayOrderCommandMsg> public class PayOrderCommandMsgHandler : IIntegrationEventHandler<PayOrderCommandMsg>
{ {
public PayOrderCommandMsgHandler() private readonly IPaymentIntegrationEventService _paymentIntegrationEventService;
{
} public PayOrderCommandMsgHandler(IPaymentIntegrationEventService paymentIntegrationEventService)
=> _paymentIntegrationEventService = paymentIntegrationEventService;
public async Task Handle(PayOrderCommandMsg @event) public async Task Handle(PayOrderCommandMsg @event)
{ {
throw new NotImplementedException(); //PAYMENT SUCCESSED
var orderPaymentSuccededIntegrationEvent = new OrderPaymentSuccededIntegrationEvent(@event.OrderId);
_paymentIntegrationEventService.PublishThroughEventBus(orderPaymentSuccededIntegrationEvent);
//PAYMENT FAILED
//var orderPaymentFailedIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId);
//_paymentIntegrationEventService.PublishThroughEventBus(orderPaymentFailedIntegrationEvent);
} }
} }
} }

@ -0,0 +1,9 @@
namespace Payment.API.IntegrationEvents
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public interface IPaymentIntegrationEventService
{
void PublishThroughEventBus(IntegrationEvent evt);
}
}

@ -0,0 +1,21 @@
namespace Payment.API.IntegrationEvents
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
public class PaymentIntegrationEventService : IPaymentIntegrationEventService
{
private readonly IEventBus _eventBus;
public PaymentIntegrationEventService(IEventBus eventBus)
{
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
}
public void PublishThroughEventBus(IntegrationEvent evt)
{
_eventBus.Publish(evt); ;
}
}
}

@ -67,8 +67,6 @@ namespace Payment.API
return new AutofacServiceProvider(container.Build()); return new AutofacServiceProvider(container.Build());
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {

@ -59,18 +59,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
return View(model); return View(model);
} }
[HttpPut] public async Task<IActionResult> Cancel(string orderId)
public async Task<IActionResult> Cancel(Order model)
{ {
if (ModelState.IsValid) await _orderSvc.CancelOrder(orderId);
{
var user = _appUserParser.Parse(HttpContext.User);
await _orderSvc.CancelOrder(model);
//Redirect to historic list. //Redirect to historic list.
return RedirectToAction("Index"); return RedirectToAction("Index");
}
return View(model);
} }
public async Task<IActionResult> Detail(string orderId) public async Task<IActionResult> Detail(string orderId)

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Authorization;
namespace WebMVC.Controllers
{
[Authorize]
public class OrderManagementController : Controller
{
private IOrderingService _orderSvc;
private readonly IIdentityParser<ApplicationUser> _appUserParser;
public OrderManagementController(IOrderingService orderSvc, IIdentityParser<ApplicationUser> appUserParser)
{
_appUserParser = appUserParser;
_orderSvc = orderSvc;
}
public async Task<IActionResult> Index()
{
var user = _appUserParser.Parse(HttpContext.User);
var vm = await _orderSvc.GetMyOrders(user);
return View(vm);
}
[HttpPost]
public async Task<IActionResult> OrderProcess(string orderId, string actionCode)
{
if (OrderProcessAction.Ship.Code == actionCode)
{
await _orderSvc.ShipOrder(orderId);
}
return RedirectToAction("Index");
}
}
}

@ -46,6 +46,11 @@
{ {
return $"{baseUri}/cancel"; return $"{baseUri}/cancel";
} }
public static string ShipOrder(string baseUri)
{
return $"{baseUri}/ship";
}
} }
public static class Catalog public static class Catalog

@ -0,0 +1,11 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace WebMVC.Models
{
public class OrderDTO
{
[Required]
public string OrderNumber { get; set; }
}
}

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebMVC.Models
{
public class OrderProcessAction
{
public string Code { get; private set; }
public string Name { get; private set; }
public static OrderProcessAction Ship = new OrderProcessAction(nameof(Ship).ToLowerInvariant(), "Ship");
protected OrderProcessAction()
{
}
public OrderProcessAction(string code, string name)
{
Code = code;
Name = name;
}
}
}

@ -60,7 +60,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
var token = await GetUserTokenAsync(); var token = await GetUserTokenAsync();
var updateBasketUri = API.Basket.CheckoutBasket(_remoteServiceBaseUrl); var updateBasketUri = API.Basket.CheckoutBasket(_remoteServiceBaseUrl);
var response = await _apiClient.PutAsync(updateBasketUri, basket, token); var response = await _apiClient.PostAsync(updateBasketUri, basket, token);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }

@ -11,7 +11,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
Task<List<Order>> GetMyOrders(ApplicationUser user); Task<List<Order>> GetMyOrders(ApplicationUser user);
Task<Order> GetOrder(ApplicationUser user, string orderId); Task<Order> GetOrder(ApplicationUser user, string orderId);
Task CancelOrder(Order order); Task CancelOrder(string orderId);
Task ShipOrder(string orderId);
Order MapUserInfoIntoOrder(ApplicationUser user, Order order); Order MapUserInfoIntoOrder(ApplicationUser user, Order order);
BasketDTO MapOrderToBasket(Order order); BasketDTO MapOrderToBasket(Order order);
void OverrideUserInfoIntoOrder(Order original, Order destination); void OverrideUserInfoIntoOrder(Order original, Order destination);

@ -66,13 +66,17 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
return order; return order;
} }
async public Task CancelOrder(Order order) async public Task CancelOrder(string orderId)
{ {
var token = await GetUserTokenAsync(); var token = await GetUserTokenAsync();
var requestId = order.RequestId.ToString(); var order = new OrderDTO()
{
OrderNumber = orderId
};
var cancelOrderUri = API.Order.CancelOrder(_remoteServiceBaseUrl); var cancelOrderUri = API.Order.CancelOrder(_remoteServiceBaseUrl);
var response = await _apiClient.PutAsync(cancelOrderUri, order, token, requestId); var response = await _apiClient.PutAsync(cancelOrderUri, order, token, Guid.NewGuid().ToString());
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{ {
@ -82,6 +86,26 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
async public Task ShipOrder(string orderId)
{
var token = await GetUserTokenAsync();
var order = new OrderDTO()
{
OrderNumber = orderId
};
var shipOrderUri = API.Order.ShipOrder(_remoteServiceBaseUrl);
var response = await _apiClient.PutAsync(shipOrderUri, order, token, Guid.NewGuid().ToString());
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{
throw new Exception("Error in ship order process, try later.");
}
response.EnsureSuccessStatusCode();
}
public void OverrideUserInfoIntoOrder(Order original, Order destination) public void OverrideUserInfoIntoOrder(Order original, Order destination)
{ {
destination.City = original.City; destination.City = original.City;

@ -1,4 +1,5 @@
using Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -6,6 +7,7 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Models;
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{ {
@ -51,6 +53,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
public string Buyer { get; set; } public string Buyer { get; set; }
public List<SelectListItem> ActionCodeSelectList =>
GetActionCodesByCurrentState();
// See the property initializer syntax below. This // See the property initializer syntax below. This
// initializes the compiler generated field for this // initializes the compiler generated field for this
// auto-implemented property. // auto-implemented property.
@ -72,6 +77,25 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
CardExpiration = new DateTime(int.Parse(year), int.Parse(month), 1); CardExpiration = new DateTime(int.Parse(year), int.Parse(month), 1);
} }
private List<SelectListItem> GetActionCodesByCurrentState()
{
var actions = new List<OrderProcessAction>();
switch (Status?.ToLower())
{
case "paid":
actions.Add(OrderProcessAction.Ship);
break;
}
var result = new List<SelectListItem>();
actions.ForEach(action =>
{
result.Add(new SelectListItem { Text = action.Name, Value = action.Code });
});
return result;
}
} }
public enum CardType public enum CardType

@ -10,7 +10,8 @@
<form method="post" id="cartForm"> <form method="post" id="cartForm">
<div class="esh-basket"> <div class="esh-basket">
@Html.Partial("_Header", new Header(){ Controller = "Catalog", Text = "Back to catalog" }) @Html.Partial("_Header", new List<Header>() {
new Header() { Controller = "Catalog", Text = "Back to catalog" } })
@await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) }) @await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })
</div> </div>

@ -6,8 +6,8 @@
ViewData["Title"] = "New Order"; ViewData["Title"] = "New Order";
} }
@Html.Partial("_Header", new Header() { Controller = "Cart", Text = "Back to cart" }) @Html.Partial("_Header", new List<Header>() {
new Header() { Controller = "Cart", Text = "Back to cart" } })
<div class="container"> <div class="container">
<form method="post" asp-controller="Order" asp-action="Checkout"> <form method="post" asp-controller="Order" asp-action="Checkout">
<section class="esh-orders_new-section"> <section class="esh-orders_new-section">

@ -7,7 +7,8 @@
} }
<div class="esh-orders_detail"> <div class="esh-orders_detail">
@Html.Partial("_Header", new Header() { Controller = "Order", Text = "Back to list" }) @Html.Partial("_Header", new List<Header>() {
new Header() { Controller = "Catalog", Text = "Back to catalog" } })
<div class="container"> <div class="container">
<section class="esh-orders_detail-section"> <section class="esh-orders_detail-section">

@ -7,7 +7,9 @@
} }
<div class="esh-orders"> <div class="esh-orders">
@Html.Partial("_Header", new Header() { Controller = "Catalog", Text = "Back to catalog" }) @Html.Partial("_Header", new List<Header>() {
new Header() { Controller = "Catalog", Text = "Back to catalog" },
new Header() { Controller = "OrderManagement", Text = "Orders Management" } })
<div class="container"> <div class="container">
<article class="esh-orders-titles row"> <article class="esh-orders-titles row">
@ -25,9 +27,15 @@
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.Date)</section> <section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.Date)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section> <section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section> <section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-xs-2"> <section class="esh-orders-item col-xs-1">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a> <a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section> </section>
<section class="esh-orders-item col-xs-1">
@if ((item.Status.ToLower() != "shipped") && (item.Status.ToLower() != "cancelled"))
{
<a class="esh-orders-link" asp-controller="Order" asp-action="Cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>
}
</section>
</article> </article>
} }
</div> </div>

@ -0,0 +1,43 @@
@using Microsoft.eShopOnContainers.WebMVC.ViewModels
@model IEnumerable<Microsoft.eShopOnContainers.WebMVC.ViewModels.Order>
@{
ViewData["Title"] = "My Orders";
}
<div class="esh-orders">
@Html.Partial("_Header", new List<Header>() {
new Header() { Controller = "Catalog", Text = "Back to catalog" } })
<div class="container">
<article class="esh-orders-titles row">
<section class="esh-orders-title col-xs-2">Order number</section>
<section class="esh-orders-title col-xs-4">Date</section>
<section class="esh-orders-title col-xs-2">Total</section>
<section class="esh-orders-title col-xs-2">Status</section>
<section class="esh-orders-title col-xs-2"></section>
</article>
@foreach (var item in Model)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.Date)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-xs-2">
<form asp-action="OrderProcess" id="orderForm+@item.OrderNumber" method="post">
<input type="hidden" name="orderId" value="@item.OrderNumber" />
<select name="actionCode" asp-items="@item.ActionCodeSelectList"
disabled=@(item.Status != "paid")
onchange="document.getElementById('orderForm+@item.OrderNumber').submit()">
<option value="">&nbsp;&nbsp;Select Action</option>
<option value="">------------------</option>
</select>
</form>
</section>
</article>
}
</div>
</div>

@ -1,7 +1,11 @@
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Header
@model IEnumerable<Microsoft.eShopOnContainers.WebMVC.ViewModels.Header>
<div class="esh-header"> <div class="esh-header">
<div class="container"> <div class="container">
<a class="esh-header-back" asp-area="" asp-controller="@Model.Controller" asp-action="Index">@Model.Text</a> @foreach (var header in @Model)
{
<a class="esh-header-title" asp-area="" asp-controller="@header.Controller" asp-action="Index">@header.Text</a>
}
</div> </div>
</div> </div>

@ -3,6 +3,20 @@
height: 4rem; height: 4rem;
} }
.esh-header-title {
color: rgba(255, 255, 255, 0.5) !important;
line-height: 4rem;
text-transform: uppercase;
text-decoration: none;
transition: color 0.35s;
margin-right: 15px;
}
.esh-header-title:hover {
color: #FFFFFF !important;
transition: color 0.35s;
}
.esh-header-back { .esh-header-back {
color: rgba(255, 255, 255, 0.5) !important; color: rgba(255, 255, 255, 0.5) !important;
line-height: 4rem; line-height: 4rem;

@ -4,7 +4,9 @@ import { Router } from '@angular/router';
import { DataService } from '../shared/services/data.service'; import { DataService } from '../shared/services/data.service';
import { SecurityService } from '../shared/services/security.service'; import { SecurityService } from '../shared/services/security.service';
import { IBasket } from '../shared/models/basket.model'; import { IBasket } from '../shared/models/basket.model';
import { IOrder } from '../shared/models/order.model';
import { IBasketCheckout } from '../shared/models/basketCheckout.model';
import { IBasketItem } from '../shared/models/basketItem.model'; import { IBasketItem } from '../shared/models/basketItem.model';
import { BasketWrapperService } from '../shared/services/basket.wrapper.service'; import { BasketWrapperService } from '../shared/services/basket.wrapper.service';
import { ConfigurationService } from '../shared/services/configuration.service'; import { ConfigurationService } from '../shared/services/configuration.service';
@ -67,6 +69,12 @@ export class BasketService {
}); });
} }
setBasketCheckout(basketCheckout): Observable<boolean> {
return this.service.postWithId(this.basketUrl + '/checkout', basketCheckout).map((response: Response) => {
return true;
});
}
getBasket(): Observable<IBasket> { getBasket(): Observable<IBasket> {
return this.service.get(this.basketUrl + '/' + this.basket.buyerId).map((response: Response) => { return this.service.get(this.basketUrl + '/' + this.basket.buyerId).map((response: Response) => {
if (response.status === 204) { if (response.status === 204) {
@ -83,6 +91,25 @@ export class BasketService {
this.basketDropedSource.next(); this.basketDropedSource.next();
} }
mapBasketInfoCheckout(order: IOrder): IBasketCheckout {
let basketCheckout = <IBasketCheckout>{};
basketCheckout.street = order.street
basketCheckout.city = order.city;
basketCheckout.country = order.country;
basketCheckout.state = order.state;
basketCheckout.zipcode = order.zipcode;
basketCheckout.cardexpiration = order.cardexpiration;
basketCheckout.cardnumber = order.cardnumber;
basketCheckout.cardsecuritynumber = order.cardsecuritynumber;
basketCheckout.cardtypeid = order.cardtypeid;
basketCheckout.cardholdername = order.cardholdername;
basketCheckout.total = 0;
basketCheckout.expiration = order.expiration;
return basketCheckout;
}
private loadData() { private loadData() {
this.getBasket().subscribe(basket => { this.getBasket().subscribe(basket => {
if (basket != null) if (basket != null)

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { OrdersService } from '../orders.service'; import { OrdersService } from '../orders.service';
import { BasketService } from '../../basket/basket.service';
import { IOrder } from '../../shared/models/order.model'; import { IOrder } from '../../shared/models/order.model';
import { BasketWrapperService } from '../../shared/services/basket.wrapper.service'; import { BasketWrapperService } from '../../shared/services/basket.wrapper.service';
@ -18,9 +19,9 @@ export class OrdersNewComponent implements OnInit {
errorReceived: boolean; errorReceived: boolean;
order: IOrder; order: IOrder;
constructor(private service: OrdersService, fb: FormBuilder, private router: Router) { constructor(private orderService: OrdersService, private basketService: BasketService, fb: FormBuilder, private router: Router) {
// Obtain user profile information // Obtain user profile information
this.order = service.mapBasketAndIdentityInfoNewOrder(); this.order = orderService.mapOrderAndIdentityInfoNewOrder();
this.newOrderForm = fb.group({ this.newOrderForm = fb.group({
'street': [this.order.street, Validators.required], 'street': [this.order.street, Validators.required],
'city': [this.order.city, Validators.required], 'city': [this.order.city, Validators.required],
@ -36,7 +37,7 @@ export class OrdersNewComponent implements OnInit {
ngOnInit() { ngOnInit() {
} }
submitForm(value: any) { submitForm(value: any) {
this.order.street = this.newOrderForm.controls['street'].value; this.order.street = this.newOrderForm.controls['street'].value;
this.order.city = this.newOrderForm.controls['city'].value; this.order.city = this.newOrderForm.controls['city'].value;
this.order.state = this.newOrderForm.controls['state'].value; this.order.state = this.newOrderForm.controls['state'].value;
@ -46,8 +47,8 @@ export class OrdersNewComponent implements OnInit {
this.order.cardholdername = this.newOrderForm.controls['cardholdername'].value; this.order.cardholdername = this.newOrderForm.controls['cardholdername'].value;
this.order.cardexpiration = new Date(20 + this.newOrderForm.controls['expirationdate'].value.split('/')[1], this.newOrderForm.controls['expirationdate'].value.split('/')[0]); this.order.cardexpiration = new Date(20 + this.newOrderForm.controls['expirationdate'].value.split('/')[1], this.newOrderForm.controls['expirationdate'].value.split('/')[0]);
this.order.cardsecuritynumber = this.newOrderForm.controls['securitycode'].value; this.order.cardsecuritynumber = this.newOrderForm.controls['securitycode'].value;
let basketCheckout = this.basketService.mapBasketInfoCheckout(this.order);
this.service.postOrder(this.order) this.basketService.setBasketCheckout(basketCheckout)
.catch((errMessage) => { .catch((errMessage) => {
this.errorReceived = true; this.errorReceived = true;
this.isOrderProcessing = false; this.isOrderProcessing = false;

@ -5,12 +5,13 @@ import { SharedModule } from '../shared/shared.module';
import { OrdersComponent } from './orders.component'; import { OrdersComponent } from './orders.component';
import { OrdersDetailComponent } from './orders-detail/orders-detail.component'; import { OrdersDetailComponent } from './orders-detail/orders-detail.component';
import { OrdersNewComponent } from './orders-new/orders-new.component'; import { OrdersNewComponent } from './orders-new/orders-new.component';
import { OrdersService } from './orders.service'; import { OrdersService } from './orders.service';
import { BasketService } from '../basket/basket.service';
import { Header } from '../shared/components/header/header'; import { Header } from '../shared/components/header/header';
@NgModule({ @NgModule({
imports: [BrowserModule, SharedModule], imports: [BrowserModule, SharedModule],
declarations: [OrdersComponent, OrdersDetailComponent, OrdersNewComponent], declarations: [OrdersComponent, OrdersDetailComponent, OrdersNewComponent],
providers: [OrdersService] providers: [OrdersService, BasketService]
}) })
export class OrdersModule { } export class OrdersModule { }

@ -44,13 +44,7 @@ export class OrdersService {
}); });
} }
postOrder(item): Observable<boolean> { mapOrderAndIdentityInfoNewOrder(): IOrder {
return this.service.postWithId(this.ordersUrl + '/api/v1/orders/new', item).map((response: Response) => {
return true;
});
}
mapBasketAndIdentityInfoNewOrder(): IOrder {
let order = <IOrder>{}; let order = <IOrder>{};
let basket = this.basketService.basket; let basket = this.basketService.basket;
let identityInfo = this.identityService.UserData; let identityInfo = this.identityService.UserData;

@ -0,0 +1,16 @@
export interface IBasketCheckout {
city: number;
street: string;
state: string;
country: number;
zipcode: string;
cardnumber: string;
cardexpiration: Date;
expiration: string;
cardsecuritynumber: string;
cardholdername: string;
cardtypeid: number;
buyer: string;
ordernumber: string;
total: number;
}

@ -39,6 +39,10 @@ export class DataService {
return this.doPost(url, data, false, params); return this.doPost(url, data, false, params);
} }
putWithId(url: string, data: any, params?: any): Observable<Response> {
return this.doPut(url, data, true, params);
}
private doPost(url: string, data: any, needId: boolean, params?: any): Observable<Response> { private doPost(url: string, data: any, needId: boolean, params?: any): Observable<Response> {
let options: RequestOptionsArgs = {}; let options: RequestOptionsArgs = {};
@ -57,6 +61,24 @@ export class DataService {
}).catch(this.handleError); }).catch(this.handleError);
} }
private doPut(url: string, data: any, needId: boolean, params?: any): Observable<Response> {
let options: RequestOptionsArgs = {};
options.headers = new Headers();
if (this.securityService) {
options.headers.append('Authorization', 'Bearer ' + this.securityService.GetToken());
}
if (needId) {
let guid = Guid.newGuid();
options.headers.append('x-requestid', guid);
}
return this.http.put(url, data, options).map(
(res: Response) => {
return res;
}).catch(this.handleError);
}
delete(url: string, params?: any) { delete(url: string, params?: any) {
let options: RequestOptionsArgs = {}; let options: RequestOptionsArgs = {};

@ -72,12 +72,12 @@
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0-msbuild3-final" /> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0-msbuild3-final" />
</ItemGroup> </ItemGroup>
<!-- workaround for https://github.com/aspnet/websdk/issues/114 --> <!-- workaround for https://github.com/aspnet/websdk/issues/114 --><!--
<Target Name="AddGeneratedContentItems" BeforeTargets="AssignTargetPaths" DependsOnTargets="PrepareForPublish"> <Target Name="AddGeneratedContentItems" BeforeTargets="AssignTargetPaths" DependsOnTargets="PrepareForPublish">
<ItemGroup> <ItemGroup>
<Content Include="wwwroot/**" CopyToPublishDirectory="PreserveNewest" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);@(Content)" /> <Content Include="wwwroot/**" CopyToPublishDirectory="PreserveNewest" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);@(Content)" />
</ItemGroup> </ItemGroup>
</Target> </Target>-->
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" /> <ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />

@ -1,10 +1,7 @@
using Microsoft.eShopOnContainers.Services.Basket.API; using FunctionalTests.Middleware;
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using FunctionalTests.Middleware; using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.Services.Basket.API;
namespace FunctionalTests.Services.Basket namespace FunctionalTests.Services.Basket
{ {

@ -18,6 +18,9 @@
<ItemGroup> <ItemGroup>
<!--<Content Include="settings.json;web.config">--> <!--<Content Include="settings.json;web.config">-->
<Content Include="Services\Basket\appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Services\Catalog\settings.json"> <Content Include="Services\Catalog\settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -30,6 +33,7 @@
<ProjectReference Include="..\..\..\src\Services\Basket\Basket.API\Basket.API.csproj" /> <ProjectReference Include="..\..\..\src\Services\Basket\Basket.API\Basket.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" /> <ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" /> <ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.eShopOnContainers.Services.Basket.API;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IntegrationTests.Services.Basket
{
public class BasketScenarioBase
{
public TestServer CreateServer()
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\basket");
webHostBuilder.UseStartup<BasketTestsStartup>();
return new TestServer(webHostBuilder);
}
public static class Get
{
public static string GetBasket(int id)
{
return $"api/v1/basket/{id}";
}
}
public static class Post
{
public static string Basket = "api/v1/basket";
public static string CheckoutOrder = "api/v1/basket/checkout";
}
}
}

@ -0,0 +1,80 @@
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using WebMVC.Models;
using Xunit;
namespace IntegrationTests.Services.Basket
{
public class BasketScenarios
: BasketScenarioBase
{
[Fact]
public async Task Post_basket_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var content = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateClient()
.PostAsync(Post.Basket, content);
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Get_basket_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.GetBasket(1));
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Send_Checkout_basket_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var content = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateClient()
.PostAsync(Post.CheckoutOrder, content);
response.EnsureSuccessStatusCode();
}
}
string BuildBasket()
{
var order = new CustomerBasket("1");
return JsonConvert.SerializeObject(order);
}
string BuildCheckout()
{
var checkoutBasket = new BasketDTO()
{
City = "city",
Street = "street",
State = "state",
Country = "coutry",
ZipCode = "zipcode",
CardNumber = "CardNumber",
CardHolderName = "CardHolderName",
CardExpiration = DateTime.UtcNow,
CardSecurityNumber = "1234",
CardTypeId = 1,
Buyer = "Buyer",
RequestId = Guid.NewGuid()
};
return JsonConvert.SerializeObject(checkoutBasket);
}
}
}

@ -0,0 +1,26 @@
using IntegrationTests.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.Services.Basket.API;
namespace IntegrationTests.Services.Basket
{
public class BasketTestsStartup : Startup
{
public BasketTestsStartup(IHostingEnvironment env) : base(env)
{
}
protected override void ConfigureAuth(IApplicationBuilder app)
{
if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant())
{
app.UseMiddleware<AutoAuthorizeMiddleware>();
}
else
{
base.ConfigureAuth(app);
}
}
}
}

@ -0,0 +1,8 @@
{
"ConnectionString": "127.0.0.1",
"IdentityUrl": "http://localhost:5105",
"isTest": "true",
"EventBusConnection": "localhost"
}

@ -26,9 +26,10 @@
} }
} }
public static class Post public static class Put
{ {
public static string AddNewOrder = "api/v1/orders/new"; public static string CancelOrder = "api/v1/orders/cancel";
public static string ShipOrder = "api/v1/orders/ship";
} }
} }
} }

@ -1,112 +1,61 @@
namespace IntegrationTests.Services.Ordering namespace IntegrationTests.Services.Ordering
{ {
using IntegrationTests.Services.Extensions; using IntegrationTests.Services.Extensions;
using Microsoft.AspNetCore.TestHost;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Models;
using Xunit; using Xunit;
using System.Collections;
using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand;
using System.Collections.Generic;
public class OrderingScenarios public class OrderingScenarios
: OrderingScenarioBase : OrderingScenarioBase
{ {
// [Fact] [Fact]
// public async Task Get_get_all_stored_orders_and_response_ok_status_code() public async Task Get_get_all_stored_orders_and_response_ok_status_code()
// { {
// using (var server = CreateServer()) using (var server = CreateServer())
// { {
// var response = await server.CreateClient() var response = await server.CreateClient()
// .GetAsync(Get.Orders); .GetAsync(Get.Orders);
// response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
// } }
// } }
// [Fact] [Fact]
// public async Task AddNewOrder_add_new_order_and_response_ok_status_code() public async Task Cancel_order_and_response_ok_status_code()
// { {
// using (var server = CreateServer()) using (var server = CreateServer())
// { {
// var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json"); var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
// var response = await server.CreateIdempotentClient() var response = await server.CreateIdempotentClient()
// .PostAsync(Post.AddNewOrder, content); .PutAsync(Put.CancelOrder, content);
// response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
// } }
// } }
// [Fact] [Fact]
// public async Task AddNewOrder_response_bad_request_if_card_expiration_is_invalid() public async Task Ship_order_and_response_bad_status_code()
// { {
// using (var server = CreateServer()) using (var server = CreateServer())
// { {
// var content = new StringContent(BuildOrderWithInvalidExperationTime(), UTF8Encoding.UTF8, "application/json"); var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateIdempotentClient()
.PutAsync(Put.ShipOrder, content);
// var response = await server.CreateIdempotentClient() response.EnsureSuccessStatusCode();
// .PostAsync(Post.AddNewOrder, content); }
}
// Assert.True(response.StatusCode == System.Net.HttpStatusCode.BadRequest); string BuildOrder()
// } {
// } var order = new OrderDTO()
{
// //public CreateOrderCommand(string city, string street, string state, string country, string zipcode, OrderNumber = "1"
// // string cardNumber, string cardHolderName, DateTime cardExpiration, };
// // string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this() return JsonConvert.SerializeObject(order);
}
// string BuildOrder()
// {
// List<OrderItemDTO> orderItemsList = new List<OrderItemDTO>();
// orderItemsList.Add(new OrderItemDTO()
// {
// ProductId = 1,
// Discount = 10M,
// UnitPrice = 10,
// Units = 1,
// ProductName = "Some name"
// }
// );
// var order = new CreateOrderCommand(
// orderItemsList,
// cardExpiration: DateTime.UtcNow.AddYears(1),
// cardNumber: "5145-555-5555",
// cardHolderName: "Jhon Senna",
// cardSecurityNumber: "232",
// cardTypeId: 1,
// city: "Redmon",
// country: "USA",
// state: "WA",
// street: "One way",
// zipcode: "zipcode"
// );
// return JsonConvert.SerializeObject(order);
// }
// string BuildOrderWithInvalidExperationTime()
// {
// var order = new CreateOrderCommand(
// null,
// cardExpiration: DateTime.UtcNow.AddYears(-1),
// cardNumber: "5145-555-5555",
// cardHolderName: "Jhon Senna",
// cardSecurityNumber: "232",
// cardTypeId: 1,
// city: "Redmon",
// country: "USA",
// state: "WA",
// street: "One way",
// zipcode: "zipcode",
// buyerId: 1,
// paymentId:1
// );
// return JsonConvert.SerializeObject(order);
// }
} }
} }

@ -5,6 +5,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; using Microsoft.eShopOnContainers.Services.Basket.API.Controllers;
using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Moq; using Moq;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
@ -80,7 +81,7 @@ namespace UnitTest.Basket.Application
var basketController = new BasketController( var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
var result = await basketController.Checkout(new BasketCheckout()) as BadRequestResult; var result = await basketController.Checkout(new BasketCheckout(), Guid.NewGuid().ToString()) as BadRequestResult;
Assert.NotNull(result); Assert.NotNull(result);
} }
@ -96,7 +97,7 @@ namespace UnitTest.Basket.Application
var basketController = new BasketController( var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
var result = await basketController.Checkout(new BasketCheckout()) as AcceptedResult; var result = await basketController.Checkout(new BasketCheckout(), Guid.NewGuid().ToString()) as AcceptedResult;
_serviceBusMock.Verify(mock => mock.Publish(It.IsAny<UserCheckoutAcceptedIntegrationEvent>()), Times.Once); _serviceBusMock.Verify(mock => mock.Publish(It.IsAny<UserCheckoutAcceptedIntegrationEvent>()), Times.Once);
Assert.NotNull(result); Assert.NotNull(result);
} }

@ -4,6 +4,7 @@ using System.Text;
namespace UnitTest.Ordering.Application namespace UnitTest.Ordering.Application
{ {
using global::Ordering.API.Application.Models;
using MediatR; using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
@ -70,7 +71,7 @@ namespace UnitTest.Ordering.Application
private CreateOrderCommand FakeOrderRequest(Dictionary<string, object> args = null) private CreateOrderCommand FakeOrderRequest(Dictionary<string, object> args = null)
{ {
return new CreateOrderCommand( return new CreateOrderCommand(
null, new List<BasketItem>(),
userId: args != null && args.ContainsKey("userId") ? (string)args["userId"] : null, userId: args != null && args.ContainsKey("userId") ? (string)args["userId"] : null,
city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null,
street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null,

@ -10,6 +10,7 @@ using System.Threading.Tasks;
namespace UnitTest.Ordering.Application namespace UnitTest.Ordering.Application
{ {
using global::Ordering.API.Application.Models;
using MediatR; using MediatR;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -66,13 +67,13 @@ namespace UnitTest.Ordering.Application
private Order FakeOrder() private Order FakeOrder()
{ {
return new Order(new Guid().ToString(), new Address("street", "city", "state", "country", "zipcode"), 1, "12", "111", "fakeName", DateTime.Now.AddYears(1)); return new Order("1", new Address("street", "city", "state", "country", "zipcode"), 1, "12", "111", "fakeName", DateTime.Now.AddYears(1));
} }
private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary<string, object> args = null) private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary<string, object> args = null)
{ {
return new CreateOrderCommand( return new CreateOrderCommand(
null, new List<BasketItem>(),
userId: args != null && args.ContainsKey("userId") ? (string)args["userId"] : null, userId: args != null && args.ContainsKey("userId") ? (string)args["userId"] : null,
city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null,
street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null,

@ -5,6 +5,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Moq; using Moq;
using Ordering.API.Application.Commands;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -25,36 +26,67 @@ namespace UnitTest.Ordering.Application
_identityServiceMock = new Mock<IIdentityService>(); _identityServiceMock = new Mock<IIdentityService>();
} }
//[Fact] [Fact]
//public async Task Create_order_with_requestId_success() public async Task Create_order_with_requestId_success()
//{ {
// //Arrange //Arrange
// _mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<CreateOrderCommand, bool>>())) _mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<CancelOrderCommand, bool>>()))
// .Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
// //Act //Act
// var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
// var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), Guid.NewGuid().ToString()) as OkResult; var actionResult = await orderController.CancelOrder(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
// //Assert //Assert
// Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK);
//} }
//[Fact] [Fact]
//public async Task Create_order_bad_request() public async Task Cancel_order_bad_request()
//{ {
// //Arrange //Arrange
// _mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<CreateOrderCommand, bool>>())) _mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<CancelOrderCommand, bool>>()))
// .Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
// //Act //Act
// var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
// var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), String.Empty) as BadRequestResult; var actionResult = await orderController.CancelOrder(new CancelOrderCommand(1), String.Empty) as BadRequestResult;
// //Assert //Assert
// Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest);
//} }
[Fact]
public async Task Ship_order_with_requestId_success()
{
//Arrange
_mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<ShipOrderCommand, bool>>()))
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var actionResult = await orderController.ShipOrder(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
//Assert
Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK);
}
[Fact]
public async Task Ship_order_bad_request()
{
//Arrange
_mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<ShipOrderCommand, bool>>()))
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var actionResult = await orderController.ShipOrder(new ShipOrderCommand(1), String.Empty) as BadRequestResult;
//Assert
Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest);
}
[Fact] [Fact]
public async Task Get_orders_success() public async Task Get_orders_success()

@ -111,7 +111,7 @@ public class OrderAggregateTest
var expectedResult = 1; var expectedResult = 1;
//Act //Act
var fakeOrder = new Order(userId.ToString(), new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); var fakeOrder = new Order("1", new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
//Assert //Assert
Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult); Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult);
@ -135,8 +135,8 @@ public class OrderAggregateTest
var expectedResult = 2; var expectedResult = 2;
//Act //Act
var fakeOrder = new Order(userId.ToString(), new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); var fakeOrder = new Order("1", new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
fakeOrder.AddDomainEvent(new OrderStartedDomainEvent(fakeOrder, userId.ToString(), cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration)); fakeOrder.AddDomainEvent(new OrderStartedDomainEvent(fakeOrder, "1", cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration));
//Assert //Assert
Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult); Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult);
} }
@ -156,8 +156,8 @@ public class OrderAggregateTest
var cardSecurityNumber = "123"; var cardSecurityNumber = "123";
var cardHolderName = "FakeName"; var cardHolderName = "FakeName";
var cardExpiration = DateTime.Now.AddYears(1); var cardExpiration = DateTime.Now.AddYears(1);
var fakeOrder = new Order(userId.ToString(), new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); var fakeOrder = new Order("1", new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
var @fakeEvent = new OrderStartedDomainEvent(fakeOrder, userId.ToString(), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); var @fakeEvent = new OrderStartedDomainEvent(fakeOrder, "1", cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
var expectedResult = 1; var expectedResult = 1;
//Act //Act