From e104da9f8562c1432867886d6fa660839e065feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Sun, 14 May 2017 14:48:37 +0200 Subject: [PATCH 1/3] Created Ship order process in WebMVC app Create Ship order command and handler in Ordering.api Create Order management page in WebMVC app --- .../Controllers/BasketController.cs | 3 + .../Commands/CreateOrderCommand.cs | 2 +- .../Application/Commands/ShipOrderCommand.cs | 21 ++++++ ...egateWhenOrderStartedDomainEventHandler.cs | 2 - .../Application/Sagas/OrderProcessSaga.cs | 66 ++++++++++++++----- .../Controllers/OrdersController.cs | 17 ++++- .../AggregatesModel/OrderAggregate/Order.cs | 5 ++ src/Web/WebMVC/Controllers/OrderController.cs | 14 ++-- .../Controllers/OrderManagementController.cs | 43 ++++++++++++ src/Web/WebMVC/Infrastructure/API.cs | 5 ++ src/Web/WebMVC/Models/OrderDTO.cs | 11 ++++ src/Web/WebMVC/Models/OrderProcessAction.cs | 25 +++++++ src/Web/WebMVC/Services/IOrderingService.cs | 3 +- src/Web/WebMVC/Services/OrderingService.cs | 30 ++++++++- src/Web/WebMVC/ViewModels/Order.cs | 26 +++++++- src/Web/WebMVC/Views/Cart/Index.cshtml | 3 +- src/Web/WebMVC/Views/Order/Create.cshtml | 4 +- src/Web/WebMVC/Views/Order/Detail.cshtml | 3 +- src/Web/WebMVC/Views/Order/Index.cshtml | 12 +++- .../WebMVC/Views/OrderManagement/Index.cshtml | 43 ++++++++++++ src/Web/WebMVC/Views/Shared/_Header.cshtml | 8 ++- .../css/shared/components/header/header.css | 14 ++++ .../IdentifierCommandHandlerTest.cs | 4 +- .../Application/NewOrderCommandHandlerTest.cs | 6 +- .../Ordering/Domain/OrderAggregateTest.cs | 10 +-- 25 files changed, 328 insertions(+), 52 deletions(-) create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs create mode 100644 src/Web/WebMVC/Controllers/OrderManagementController.cs create mode 100644 src/Web/WebMVC/Models/OrderDTO.cs create mode 100644 src/Web/WebMVC/Models/OrderProcessAction.cs create mode 100644 src/Web/WebMVC/Views/OrderManagement/Index.cshtml diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 9a120efc0..68e323187 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -61,6 +61,9 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers value.State, value.Country, value.ZipCode, value.CardNumber, value.CardHolderName, 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); if (basket == null) diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs index 19fa3b818..fc6fd97d0 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs @@ -88,7 +88,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands var result = new List(); basketItems.ForEach((item) => { result.Add(new OrderItemDTO() { - ProductId = int.TryParse(item.Id, out int id) ? id : -1, + ProductId = int.TryParse(item.ProductId, out int id) ? id : -1, ProductName = item.ProductName, PictureUrl = item.PictureUrl, UnitPrice = item.UnitPrice, diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs new file mode 100644 index 000000000..d56123ba7 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs @@ -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 + { + + [DataMember] + public int OrderNumber { get; private set; } + + public ShipOrderCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs index b2120904c..77714d1b0 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs @@ -26,8 +26,6 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent { var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; - //var userGuid = _identityService.GetUserIdentity(); - var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId); bool buyerOriginallyExisted = (buyer == null) ? false : true; diff --git a/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs b/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs index 8a4c51795..47dcbe6f5 100644 --- a/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs +++ b/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs @@ -1,5 +1,4 @@ -using Autofac.Features.OwnedInstances; -using MediatR; +using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; @@ -7,15 +6,10 @@ using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationCommands.Commands; -using Ordering.API.Application.IntegrationEvents.Events; +using Ordering.API.Application.IntegrationEvents; using Ordering.Domain.Exceptions; -using System; -using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; -using Ordering.API.Application.IntegrationEvents; -using Ordering.API.Application.IntegrationEvents.Events; namespace Ordering.API.Application.Sagas { @@ -29,19 +23,16 @@ namespace Ordering.API.Application.Sagas /// public class OrderProcessSaga : Saga, IIntegrationEventHandler, - IAsyncRequestHandler + IAsyncRequestHandler, + IAsyncRequestHandler { - private readonly IMediator _mediator; - private readonly Func> _dbContextFactory; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; public OrderProcessSaga( - Func> dbContextFactory, OrderingContext orderingContext, - IMediator mediator, IOrderingIntegrationEventService orderingIntegrationEventService) + OrderingContext orderingContext, + IOrderingIntegrationEventService orderingIntegrationEventService) : base(orderingContext) { - _dbContextFactory = dbContextFactory; - _mediator = mediator; _orderingIntegrationEventService = orderingIntegrationEventService; } @@ -85,12 +76,41 @@ namespace Ordering.API.Application.Sagas /// public async Task Handle(CancelOrderCommand command) { + var result = false; var orderSaga = FindSagaById(command.OrderNumber); 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.SetOrderStatusId(OrderStatus.Cancelled.Id); + result = await SaveChangesAsync(); + } + return result; + } + + /// + /// Handler which processes the command when + /// administrator executes ship order from app + /// + /// + /// + public async Task Handle(ShipOrderCommand command) + { + var result = false; + var orderSaga = FindSagaById(command.OrderNumber); + CheckValidSagaId(orderSaga); - return true; + // Only ship order when + // its status is paid + if (orderSaga.GetOrderStatusId() == OrderStatus.Paid.Id) + { + orderSaga.SetOrderStatusId(OrderStatus.Shipped.Id); + result = await SaveChangesAsync(); + } + return result; } private void CheckValidSagaId(Order orderSaga) @@ -115,6 +135,18 @@ namespace Ordering.API.Application.Sagas } } + public class ShipOrderCommandIdentifiedHandler : IdentifierCommandHandler + { + public ShipOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } + #endregion } } diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index 2d4ce4059..d8e658f16 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -28,7 +28,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers } [Route("cancel")] - [HttpPost] + [HttpPut] public async Task CancelOrder([FromBody]CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) { bool commandResult = false; @@ -42,6 +42,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers } + [Route("ship")] + [HttpPut] + public async Task 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(command, guid); + commandResult = await _mediator.SendAsync(requestShipOrder); + } + + return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest(); + + } + [Route("{orderId:int}")] [HttpGet] public async Task GetOrder(int orderId) diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index 9cc8c06bd..d9468a342 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -120,6 +120,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O } } + public int GetOrderStatusId() + { + return _orderStatusId; + } + private void AddOrderStartedDomainEvent(string userId, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) { diff --git a/src/Web/WebMVC/Controllers/OrderController.cs b/src/Web/WebMVC/Controllers/OrderController.cs index b8efd930e..8f34c2282 100644 --- a/src/Web/WebMVC/Controllers/OrderController.cs +++ b/src/Web/WebMVC/Controllers/OrderController.cs @@ -59,18 +59,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers return View(model); } - [HttpPut] - public async Task Cancel(Order model) + public async Task Cancel(string orderId) { - if (ModelState.IsValid) - { - var user = _appUserParser.Parse(HttpContext.User); - await _orderSvc.CancelOrder(model); + await _orderSvc.CancelOrder(orderId); - //Redirect to historic list. - return RedirectToAction("Index"); - } - return View(model); + //Redirect to historic list. + return RedirectToAction("Index"); } public async Task Detail(string orderId) diff --git a/src/Web/WebMVC/Controllers/OrderManagementController.cs b/src/Web/WebMVC/Controllers/OrderManagementController.cs new file mode 100644 index 000000000..abd0b21ab --- /dev/null +++ b/src/Web/WebMVC/Controllers/OrderManagementController.cs @@ -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 _appUserParser; + public OrderManagementController(IOrderingService orderSvc, IIdentityParser appUserParser) + { + _appUserParser = appUserParser; + _orderSvc = orderSvc; + } + + public async Task Index() + { + var user = _appUserParser.Parse(HttpContext.User); + var vm = await _orderSvc.GetMyOrders(user); + + return View(vm); + } + + [HttpPost] + public async Task OrderProcess(string orderId, string actionCode) + { + if (OrderProcessAction.Ship.Code == actionCode) + { + await _orderSvc.ShipOrder(orderId); + } + + return RedirectToAction("Index"); + } + } +} diff --git a/src/Web/WebMVC/Infrastructure/API.cs b/src/Web/WebMVC/Infrastructure/API.cs index f23b94288..fc170ffcc 100644 --- a/src/Web/WebMVC/Infrastructure/API.cs +++ b/src/Web/WebMVC/Infrastructure/API.cs @@ -46,6 +46,11 @@ { return $"{baseUri}/cancel"; } + + public static string ShipOrder(string baseUri) + { + return $"{baseUri}/ship"; + } } public static class Catalog diff --git a/src/Web/WebMVC/Models/OrderDTO.cs b/src/Web/WebMVC/Models/OrderDTO.cs new file mode 100644 index 000000000..13646ea38 --- /dev/null +++ b/src/Web/WebMVC/Models/OrderDTO.cs @@ -0,0 +1,11 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace WebMVC.Models +{ + public class OrderDTO + { + [Required] + public string OrderNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/Web/WebMVC/Models/OrderProcessAction.cs b/src/Web/WebMVC/Models/OrderProcessAction.cs new file mode 100644 index 000000000..bd746bb36 --- /dev/null +++ b/src/Web/WebMVC/Models/OrderProcessAction.cs @@ -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; + } + } +} diff --git a/src/Web/WebMVC/Services/IOrderingService.cs b/src/Web/WebMVC/Services/IOrderingService.cs index fca0151f1..1de2c631c 100644 --- a/src/Web/WebMVC/Services/IOrderingService.cs +++ b/src/Web/WebMVC/Services/IOrderingService.cs @@ -11,7 +11,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services { Task> GetMyOrders(ApplicationUser user); Task GetOrder(ApplicationUser user, string orderId); - Task CancelOrder(Order order); + Task CancelOrder(string orderId); + Task ShipOrder(string orderId); Order MapUserInfoIntoOrder(ApplicationUser user, Order order); BasketDTO MapOrderToBasket(Order order); void OverrideUserInfoIntoOrder(Order original, Order destination); diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs index 7140b0eb3..d9eba7392 100644 --- a/src/Web/WebMVC/Services/OrderingService.cs +++ b/src/Web/WebMVC/Services/OrderingService.cs @@ -66,13 +66,17 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services return order; } - async public Task CancelOrder(Order order) + async public Task CancelOrder(string orderId) { var token = await GetUserTokenAsync(); - var requestId = order.RequestId.ToString(); + var order = new OrderDTO() + { + OrderNumber = orderId + }; + 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) { @@ -82,6 +86,26 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services 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) { destination.City = original.City; diff --git a/src/Web/WebMVC/ViewModels/Order.cs b/src/Web/WebMVC/ViewModels/Order.cs index 213914208..28dbe9968 100644 --- a/src/Web/WebMVC/ViewModels/Order.cs +++ b/src/Web/WebMVC/ViewModels/Order.cs @@ -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 System; using System.Collections.Generic; @@ -6,6 +7,7 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; +using WebMVC.Models; namespace Microsoft.eShopOnContainers.WebMVC.ViewModels { @@ -51,6 +53,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels public string Buyer { get; set; } + public List ActionCodeSelectList => + GetActionCodesByCurrentState(); + // See the property initializer syntax below. This // initializes the compiler generated field for this // auto-implemented property. @@ -72,6 +77,25 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels CardExpiration = new DateTime(int.Parse(year), int.Parse(month), 1); } + + private List GetActionCodesByCurrentState() + { + var actions = new List(); + switch (Status?.ToLower()) + { + case "paid": + actions.Add(OrderProcessAction.Ship); + break; + } + + var result = new List(); + actions.ForEach(action => + { + result.Add(new SelectListItem { Text = action.Name, Value = action.Code }); + }); + + return result; + } } public enum CardType diff --git a/src/Web/WebMVC/Views/Cart/Index.cshtml b/src/Web/WebMVC/Views/Cart/Index.cshtml index 1111df0cf..d9386a1fd 100644 --- a/src/Web/WebMVC/Views/Cart/Index.cshtml +++ b/src/Web/WebMVC/Views/Cart/Index.cshtml @@ -10,7 +10,8 @@
- @Html.Partial("_Header", new Header(){ Controller = "Catalog", Text = "Back to catalog" }) + @Html.Partial("_Header", new List
() { + new Header() { Controller = "Catalog", Text = "Back to catalog" } }) @await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })
diff --git a/src/Web/WebMVC/Views/Order/Create.cshtml b/src/Web/WebMVC/Views/Order/Create.cshtml index 79ced5b3a..24a987fa9 100644 --- a/src/Web/WebMVC/Views/Order/Create.cshtml +++ b/src/Web/WebMVC/Views/Order/Create.cshtml @@ -6,8 +6,8 @@ ViewData["Title"] = "New Order"; } -@Html.Partial("_Header", new Header() { Controller = "Cart", Text = "Back to cart" }) - +@Html.Partial("_Header", new List
() { + new Header() { Controller = "Cart", Text = "Back to cart" } })
diff --git a/src/Web/WebMVC/Views/Order/Detail.cshtml b/src/Web/WebMVC/Views/Order/Detail.cshtml index ef112a1c2..c17ffb2a9 100644 --- a/src/Web/WebMVC/Views/Order/Detail.cshtml +++ b/src/Web/WebMVC/Views/Order/Detail.cshtml @@ -7,7 +7,8 @@ }
- @Html.Partial("_Header", new Header() { Controller = "Order", Text = "Back to list" }) + @Html.Partial("_Header", new List
() { + new Header() { Controller = "Catalog", Text = "Back to catalog" } })
diff --git a/src/Web/WebMVC/Views/Order/Index.cshtml b/src/Web/WebMVC/Views/Order/Index.cshtml index 83cc8b992..227b95686 100644 --- a/src/Web/WebMVC/Views/Order/Index.cshtml +++ b/src/Web/WebMVC/Views/Order/Index.cshtml @@ -7,7 +7,9 @@ }
- @Html.Partial("_Header", new Header() { Controller = "Catalog", Text = "Back to catalog" }) + @Html.Partial("_Header", new List
() { + new Header() { Controller = "Catalog", Text = "Back to catalog" }, + new Header() { Controller = "OrderManagement", Text = "Orders Management" } })
@@ -25,9 +27,15 @@
@Html.DisplayFor(modelItem => item.Date)
$ @Html.DisplayFor(modelItem => item.Total)
@Html.DisplayFor(modelItem => item.Status)
-
+
Detail
+
+ @if ((item.Status.ToLower() != "shipped") && (item.Status.ToLower() != "cancelled")) + { + Cancel + } +
}
diff --git a/src/Web/WebMVC/Views/OrderManagement/Index.cshtml b/src/Web/WebMVC/Views/OrderManagement/Index.cshtml new file mode 100644 index 000000000..d108f2281 --- /dev/null +++ b/src/Web/WebMVC/Views/OrderManagement/Index.cshtml @@ -0,0 +1,43 @@ +@using Microsoft.eShopOnContainers.WebMVC.ViewModels + +@model IEnumerable + +@{ + ViewData["Title"] = "My Orders"; +} + +
+ @Html.Partial("_Header", new List
() { + new Header() { Controller = "Catalog", Text = "Back to catalog" } }) + +
+
+
Order number
+
Date
+
Total
+
Status
+
+
+ + @foreach (var item in Model) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.Date)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ + + + +
+
+ } +
+
\ No newline at end of file diff --git a/src/Web/WebMVC/Views/Shared/_Header.cshtml b/src/Web/WebMVC/Views/Shared/_Header.cshtml index 894a92af2..9c06eabd3 100644 --- a/src/Web/WebMVC/Views/Shared/_Header.cshtml +++ b/src/Web/WebMVC/Views/Shared/_Header.cshtml @@ -1,7 +1,11 @@ -@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Header + +@model IEnumerable
- @Model.Text + @foreach (var header in @Model) + { + @header.Text + }
diff --git a/src/Web/WebMVC/wwwroot/css/shared/components/header/header.css b/src/Web/WebMVC/wwwroot/css/shared/components/header/header.css index 89fd203bd..737c18dbb 100644 --- a/src/Web/WebMVC/wwwroot/css/shared/components/header/header.css +++ b/src/Web/WebMVC/wwwroot/css/shared/components/header/header.css @@ -3,6 +3,20 @@ 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 { color: rgba(255, 255, 255, 0.5) !important; line-height: 4rem; diff --git a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs index d9cc878a5..a7eddfc0d 100644 --- a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs @@ -4,6 +4,7 @@ using System.Text; namespace UnitTest.Ordering.Application { + using global::Ordering.API.Application.Models; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; @@ -70,7 +71,8 @@ namespace UnitTest.Ordering.Application private CreateOrderCommand FakeOrderRequest(Dictionary args = null) { return new CreateOrderCommand( - null, + new List(), + userId: args != null && args.ContainsKey("userId") ? (string)args["userId"] : null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, state: args != null && args.ContainsKey("state") ? (string)args["state"] : null, diff --git a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs index 2979fa561..87725f669 100644 --- a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; namespace UnitTest.Ordering.Application { + using global::Ordering.API.Application.Models; using MediatR; using System.Collections; using System.Collections.Generic; @@ -66,13 +67,14 @@ namespace UnitTest.Ordering.Application private Order FakeOrder() { - return new Order(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 args = null) { return new CreateOrderCommand( - null, + new List(), + userId: args != null && args.ContainsKey("userId") ? (string)args["userId"] : null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, state: args != null && args.ContainsKey("state") ? (string)args["state"] : null, diff --git a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs index 40bc66431..90bf412ef 100644 --- a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs @@ -110,7 +110,7 @@ public class OrderAggregateTest var expectedResult = 1; //Act - var fakeOrder = new Order(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.Equal(fakeOrder.DomainEvents.Count, expectedResult); @@ -133,8 +133,8 @@ public class OrderAggregateTest var expectedResult = 2; //Act - var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - fakeOrder.AddDomainEvent(new OrderStartedDomainEvent(fakeOrder,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, "1", cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration)); //Assert Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult); } @@ -153,8 +153,8 @@ public class OrderAggregateTest var cardSecurityNumber = "123"; var cardHolderName = "FakeName"; var cardExpiration = DateTime.Now.AddYears(1); - var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - var @fakeEvent = new OrderStartedDomainEvent(fakeOrder, 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, "1", cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); var expectedResult = 1; //Act From b2b01bae062b60efadd8a8d7e35db6ad50f03aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 16 May 2017 09:23:35 +0200 Subject: [PATCH 2/3] Change SPA app to call basket for order checkout --- .../Controllers/BasketController.cs | 7 +- src/Services/Ordering/Ordering.API/Startup.cs | 6 +- src/Web/WebMVC/Services/BasketService.cs | 2 +- .../Client/modules/basket/basket.service.ts | 29 +++- .../orders/orders-new/orders-new.component.ts | 13 +- .../Client/modules/orders/orders.module.ts | 5 +- .../Client/modules/orders/orders.service.ts | 8 +- .../shared/models/basketCheckout.model.ts | 16 ++ .../modules/shared/services/data.service.ts | 22 +++ src/Web/WebSPA/WebSPA.csproj | 4 +- .../Services/Basket/BasketTestsStartup.cs | 9 +- .../IntegrationTests/IntegrationTests.csproj | 4 + .../Services/Basket/BasketScenarioBase.cs | 36 +++++ .../Services/Basket/BasketScenarios.cs | 80 ++++++++++ .../Services/Basket/BasketTestsStartup.cs | 26 ++++ .../Services/Basket/appsettings.json | 8 + .../Services/Ordering/OrderingScenarioBase.cs | 5 +- .../Services/Ordering/OrderingScenarios.cs | 145 ++++++------------ .../Basket/Application/BasketWebApiTest.cs | 5 +- .../Ordering/Application/OrdersWebApiTest.cs | 80 +++++++--- 20 files changed, 353 insertions(+), 157 deletions(-) create mode 100644 src/Web/WebSPA/Client/modules/shared/models/basketCheckout.model.ts create mode 100644 test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs create mode 100644 test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs create mode 100644 test/Services/IntegrationTests/Services/Basket/BasketTestsStartup.cs create mode 100644 test/Services/IntegrationTests/Services/Basket/appsettings.json diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 68e323187..75efe4740 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -52,10 +52,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers } [Route("checkout")] - [HttpPut] - public async Task Checkout([FromBody]BasketCheckout value) + [HttpPost] + public async Task Checkout([FromBody]BasketCheckout value, [FromHeader(Name = "x-requestid")] string requestId) { 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 eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, value.City, value.Street, value.State, value.Country, value.ZipCode, value.CardNumber, value.CardHolderName, diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index b5b01e068..5d649f8a2 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -3,12 +3,11 @@ using AspNetCore.Http; using Autofac; using Autofac.Extensions.DependencyInjection; - using global::Ordering.API.Application.IntegrationEvents; - using global::Ordering.API.Application.IntegrationEvents.Events; - using global::Ordering.API.Infrastructure.Middlewares; using global::Ordering.API.Application.IntegrationCommands.Commands; + using global::Ordering.API.Application.IntegrationEvents; using global::Ordering.API.Application.IntegrationEvents.Events; using global::Ordering.API.Application.Sagas; + using global::Ordering.API.Infrastructure.Middlewares; using Infrastructure; using Infrastructure.Auth; using Infrastructure.AutofacModules; @@ -31,7 +30,6 @@ using System; using System.Data.Common; using System.Reflection; - using global::Ordering.API.Application.IntegrationEvents.EventHandling; public class Startup { diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index 538980052..84c3756a7 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -60,7 +60,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var token = await GetUserTokenAsync(); var updateBasketUri = API.Basket.CheckoutBasket(_remoteServiceBaseUrl); - var response = await _apiClient.PutAsync(updateBasketUri, basket, token); + var response = await _apiClient.PostAsync(updateBasketUri, basket, token); response.EnsureSuccessStatusCode(); } diff --git a/src/Web/WebSPA/Client/modules/basket/basket.service.ts b/src/Web/WebSPA/Client/modules/basket/basket.service.ts index b7fbb60d5..37cecdc5d 100644 --- a/src/Web/WebSPA/Client/modules/basket/basket.service.ts +++ b/src/Web/WebSPA/Client/modules/basket/basket.service.ts @@ -4,7 +4,9 @@ import { Router } from '@angular/router'; import { DataService } from '../shared/services/data.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 { BasketWrapperService } from '../shared/services/basket.wrapper.service'; import { ConfigurationService } from '../shared/services/configuration.service'; @@ -67,6 +69,12 @@ export class BasketService { }); } + setBasketCheckout(basketCheckout): Observable { + return this.service.postWithId(this.basketUrl + '/checkout', basketCheckout).map((response: Response) => { + return true; + }); + } + getBasket(): Observable { return this.service.get(this.basketUrl + '/' + this.basket.buyerId).map((response: Response) => { if (response.status === 204) { @@ -83,6 +91,25 @@ export class BasketService { this.basketDropedSource.next(); } + mapBasketInfoCheckout(order: IOrder): IBasketCheckout { + let basketCheckout = {}; + + 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() { this.getBasket().subscribe(basket => { if (basket != null) diff --git a/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts b/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts index 4f6f82a9f..b1e4e49da 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; 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 { BasketWrapperService } from '../../shared/services/basket.wrapper.service'; @@ -18,9 +19,9 @@ export class OrdersNewComponent implements OnInit { errorReceived: boolean; 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 - this.order = service.mapBasketAndIdentityInfoNewOrder(); + this.order = orderService.mapOrderAndIdentityInfoNewOrder(); this.newOrderForm = fb.group({ 'street': [this.order.street, Validators.required], 'city': [this.order.city, Validators.required], @@ -36,7 +37,7 @@ export class OrdersNewComponent implements OnInit { ngOnInit() { } - submitForm(value: any) { + submitForm(value: any) { this.order.street = this.newOrderForm.controls['street'].value; this.order.city = this.newOrderForm.controls['city'].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.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.service.postOrder(this.order) + let basketCheckout = this.basketService.mapBasketInfoCheckout(this.order); + this.basketService.setBasketCheckout(basketCheckout) .catch((errMessage) => { this.errorReceived = true; this.isOrderProcessing = false; diff --git a/src/Web/WebSPA/Client/modules/orders/orders.module.ts b/src/Web/WebSPA/Client/modules/orders/orders.module.ts index 1ecd0e9f5..9b43eb207 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders.module.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders.module.ts @@ -5,12 +5,13 @@ import { SharedModule } from '../shared/shared.module'; import { OrdersComponent } from './orders.component'; import { OrdersDetailComponent } from './orders-detail/orders-detail.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'; @NgModule({ imports: [BrowserModule, SharedModule], declarations: [OrdersComponent, OrdersDetailComponent, OrdersNewComponent], - providers: [OrdersService] + providers: [OrdersService, BasketService] }) export class OrdersModule { } diff --git a/src/Web/WebSPA/Client/modules/orders/orders.service.ts b/src/Web/WebSPA/Client/modules/orders/orders.service.ts index 355670cdd..5eda7c8ce 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders.service.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders.service.ts @@ -44,13 +44,7 @@ export class OrdersService { }); } - postOrder(item): Observable { - return this.service.postWithId(this.ordersUrl + '/api/v1/orders/new', item).map((response: Response) => { - return true; - }); - } - - mapBasketAndIdentityInfoNewOrder(): IOrder { + mapOrderAndIdentityInfoNewOrder(): IOrder { let order = {}; let basket = this.basketService.basket; let identityInfo = this.identityService.UserData; diff --git a/src/Web/WebSPA/Client/modules/shared/models/basketCheckout.model.ts b/src/Web/WebSPA/Client/modules/shared/models/basketCheckout.model.ts new file mode 100644 index 000000000..2f7cfbf62 --- /dev/null +++ b/src/Web/WebSPA/Client/modules/shared/models/basketCheckout.model.ts @@ -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; +} \ No newline at end of file diff --git a/src/Web/WebSPA/Client/modules/shared/services/data.service.ts b/src/Web/WebSPA/Client/modules/shared/services/data.service.ts index 3b121f4e7..64288e127 100644 --- a/src/Web/WebSPA/Client/modules/shared/services/data.service.ts +++ b/src/Web/WebSPA/Client/modules/shared/services/data.service.ts @@ -39,6 +39,10 @@ export class DataService { return this.doPost(url, data, false, params); } + putWithId(url: string, data: any, params?: any): Observable { + return this.doPut(url, data, true, params); + } + private doPost(url: string, data: any, needId: boolean, params?: any): Observable { let options: RequestOptionsArgs = {}; @@ -57,6 +61,24 @@ export class DataService { }).catch(this.handleError); } + private doPut(url: string, data: any, needId: boolean, params?: any): Observable { + 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) { let options: RequestOptionsArgs = {}; diff --git a/src/Web/WebSPA/WebSPA.csproj b/src/Web/WebSPA/WebSPA.csproj index 03c3cd442..976ed59c0 100644 --- a/src/Web/WebSPA/WebSPA.csproj +++ b/src/Web/WebSPA/WebSPA.csproj @@ -72,12 +72,12 @@ - + diff --git a/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs b/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs index a0e625d96..2beb73bd1 100644 --- a/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs +++ b/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs @@ -1,10 +1,7 @@ -using Microsoft.eShopOnContainers.Services.Basket.API; -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.AspNetCore.Hosting; +using FunctionalTests.Middleware; using Microsoft.AspNetCore.Builder; -using FunctionalTests.Middleware; +using Microsoft.AspNetCore.Hosting; +using Microsoft.eShopOnContainers.Services.Basket.API; namespace FunctionalTests.Services.Basket { diff --git a/test/Services/IntegrationTests/IntegrationTests.csproj b/test/Services/IntegrationTests/IntegrationTests.csproj index 436731593..a93364c4d 100644 --- a/test/Services/IntegrationTests/IntegrationTests.csproj +++ b/test/Services/IntegrationTests/IntegrationTests.csproj @@ -18,6 +18,9 @@ + + PreserveNewest + PreserveNewest @@ -30,6 +33,7 @@ + diff --git a/test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs b/test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs new file mode 100644 index 000000000..725b58206 --- /dev/null +++ b/test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs @@ -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(); + + 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"; + } + } +} diff --git a/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs b/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs new file mode 100644 index 000000000..ecc8d60be --- /dev/null +++ b/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs @@ -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); + } + } +} diff --git a/test/Services/IntegrationTests/Services/Basket/BasketTestsStartup.cs b/test/Services/IntegrationTests/Services/Basket/BasketTestsStartup.cs new file mode 100644 index 000000000..788c9621d --- /dev/null +++ b/test/Services/IntegrationTests/Services/Basket/BasketTestsStartup.cs @@ -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(); + } + else + { + base.ConfigureAuth(app); + } + } + } +} diff --git a/test/Services/IntegrationTests/Services/Basket/appsettings.json b/test/Services/IntegrationTests/Services/Basket/appsettings.json new file mode 100644 index 000000000..e669ab532 --- /dev/null +++ b/test/Services/IntegrationTests/Services/Basket/appsettings.json @@ -0,0 +1,8 @@ +{ + "ConnectionString": "127.0.0.1", + "IdentityUrl": "http://localhost:5105", + "isTest": "true", + "EventBusConnection": "localhost" +} + + diff --git a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs index b4909e5a6..a815886e8 100644 --- a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs +++ b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs @@ -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"; } } } diff --git a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs index 46bb05c79..9b4cade3f 100644 --- a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs @@ -1,112 +1,61 @@ namespace IntegrationTests.Services.Ordering { using IntegrationTests.Services.Extensions; - using Microsoft.AspNetCore.TestHost; - using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Newtonsoft.Json; - using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; + using WebMVC.Models; using Xunit; - using System.Collections; - using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; - using System.Collections.Generic; public class OrderingScenarios : OrderingScenarioBase { - // [Fact] - // public async Task Get_get_all_stored_orders_and_response_ok_status_code() - // { - // using (var server = CreateServer()) - // { - // var response = await server.CreateClient() - // .GetAsync(Get.Orders); - - // response.EnsureSuccessStatusCode(); - // } - // } - - // [Fact] - // public async Task AddNewOrder_add_new_order_and_response_ok_status_code() - // { - // using (var server = CreateServer()) - // { - // var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json"); - // var response = await server.CreateIdempotentClient() - // .PostAsync(Post.AddNewOrder, content); - - // response.EnsureSuccessStatusCode(); - // } - // } - - // [Fact] - // public async Task AddNewOrder_response_bad_request_if_card_expiration_is_invalid() - // { - // using (var server = CreateServer()) - // { - // var content = new StringContent(BuildOrderWithInvalidExperationTime(), UTF8Encoding.UTF8, "application/json"); - - // var response = await server.CreateIdempotentClient() - // .PostAsync(Post.AddNewOrder, content); - - // Assert.True(response.StatusCode == System.Net.HttpStatusCode.BadRequest); - // } - // } - - // //public CreateOrderCommand(string city, string street, string state, string country, string zipcode, - // // string cardNumber, string cardHolderName, DateTime cardExpiration, - // // string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this() - - // string BuildOrder() - // { - // List orderItemsList = new List(); - // 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); - // } + [Fact] + public async Task Get_get_all_stored_orders_and_response_ok_status_code() + { + using (var server = CreateServer()) + { + var response = await server.CreateClient() + .GetAsync(Get.Orders); + + response.EnsureSuccessStatusCode(); + } + } + + [Fact] + public async Task Cancel_order_and_response_ok_status_code() + { + using (var server = CreateServer()) + { + var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json"); + var response = await server.CreateIdempotentClient() + .PutAsync(Put.CancelOrder, content); + + response.EnsureSuccessStatusCode(); + } + } + + [Fact] + public async Task Ship_order_and_response_bad_status_code() + { + using (var server = CreateServer()) + { + var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json"); + var response = await server.CreateIdempotentClient() + .PutAsync(Put.ShipOrder, content); + + response.EnsureSuccessStatusCode(); + } + } + + string BuildOrder() + { + var order = new OrderDTO() + { + OrderNumber = "1" + }; + return JsonConvert.SerializeObject(order); + } } } diff --git a/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs b/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs index be551115f..7e17a9980 100644 --- a/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs +++ b/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs @@ -5,6 +5,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Moq; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -80,7 +81,7 @@ namespace UnitTest.Basket.Application var basketController = new BasketController( _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); } @@ -96,7 +97,7 @@ namespace UnitTest.Basket.Application var basketController = new BasketController( _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()), Times.Once); Assert.NotNull(result); } diff --git a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs index 22142dfbf..6a2c532ec 100644 --- a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs +++ b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs @@ -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.Infrastructure.Services; using Moq; +using Ordering.API.Application.Commands; using System; using System.Linq; using System.Threading.Tasks; @@ -25,36 +26,67 @@ namespace UnitTest.Ordering.Application _identityServiceMock = new Mock(); } - //[Fact] - //public async Task Create_order_with_requestId_success() - //{ - // //Arrange - // _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) - // .Returns(Task.FromResult(true)); + [Fact] + public async Task Create_order_with_requestId_success() + { + //Arrange + _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) + .Returns(Task.FromResult(true)); - // //Act - // var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - // var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), Guid.NewGuid().ToString()) as OkResult; + //Act + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var actionResult = await orderController.CancelOrder(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; - // //Assert - // Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + //Assert + Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); - //} + } - //[Fact] - //public async Task Create_order_bad_request() - //{ - // //Arrange - // _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) - // .Returns(Task.FromResult(true)); + [Fact] + public async Task Cancel_order_bad_request() + { + //Arrange + _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) + .Returns(Task.FromResult(true)); - // //Act - // var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - // var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), String.Empty) as BadRequestResult; + //Act + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var actionResult = await orderController.CancelOrder(new CancelOrderCommand(1), String.Empty) as BadRequestResult; - // //Assert - // Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); - //} + //Assert + 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>())) + .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>())) + .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] public async Task Get_orders_success() From d3d88def2e6f747d530316c73b5a08cb5454aa50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 16 May 2017 09:58:06 +0200 Subject: [PATCH 3/3] Update order saga cancel and ship commands --- .../Application/Sagas/OrderProcessSaga.cs | 12 +++--------- .../AggregatesModel/OrderAggregate/Order.cs | 9 +++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs b/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs index 3b5c8606b..260b0c651 100644 --- a/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs +++ b/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs @@ -8,10 +8,7 @@ using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationCommands.Commands; using Ordering.API.Application.IntegrationEvents; using Ordering.Domain.Exceptions; -using System; -using System.Linq; using System.Threading.Tasks; -using Ordering.API.Application.IntegrationEvents; namespace Ordering.API.Application.Sagas { @@ -28,14 +25,11 @@ namespace Ordering.API.Application.Sagas IAsyncRequestHandler, IAsyncRequestHandler { - private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; public OrderProcessSaga( - OrderingContext orderingContext, - IOrderingIntegrationEventService orderingIntegrationEventService) + OrderingContext orderingContext) : base(orderingContext) { - _orderingIntegrationEventService = orderingIntegrationEventService; } /// @@ -79,7 +73,7 @@ namespace Ordering.API.Application.Sagas if (orderSaga.GetOrderStatusId() != OrderStatus.Cancelled.Id || orderSaga.GetOrderStatusId() != OrderStatus.Shipped.Id) { - orderSaga.SetOrderStatusId(OrderStatus.Cancelled.Id); + orderSaga.SetCancelStatus(); result = await SaveChangesAsync(); } return result; @@ -101,7 +95,7 @@ namespace Ordering.API.Application.Sagas // its status is paid if (orderSaga.GetOrderStatusId() == OrderStatus.Paid.Id) { - orderSaga.SetOrderStatusId(OrderStatus.Shipped.Id); + orderSaga.SetShippedStatus(); result = await SaveChangesAsync(); } return result; diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index e25f285f9..58f1fe5f2 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -166,6 +166,15 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O //Call Domain Event } + 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 public int GetOrderStatusId()