diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 9a120efc0..75efe4740 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -52,15 +52,21 @@ 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, 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/Catalog/Catalog.API/IntegrationCommands/CommandHandlers/DecrementOrderStockCommandMsgHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationCommands/CommandHandlers/DecrementOrderStockCommandMsgHandler.cs index 962f5222d..9268aa0ba 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationCommands/CommandHandlers/DecrementOrderStockCommandMsgHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationCommands/CommandHandlers/DecrementOrderStockCommandMsgHandler.cs @@ -23,20 +23,11 @@ foreach (var orderStockItem in @event.OrderStockItems) { var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); - CheckValidcatalogItemId(catalogItem); catalogItem.RemoveStock(orderStockItem.Units); } 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"); - } - } } } \ No newline at end of file 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/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs index 91ad55226..1e4e79978 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs @@ -3,7 +3,6 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Ordering.API.Application.IntegrationEvents.Events; - using Ordering.Domain.Exceptions; using System.Threading.Tasks; public class OrderPaymentFailedIntegrationEventHandler : diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs index 095a4c7b1..f3075eeec 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs @@ -3,7 +3,6 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Ordering.API.Application.IntegrationEvents.Events; - using Ordering.Domain.Exceptions; using System.Threading.Tasks; public class OrderPaymentSuccededIntegrationEventHandler : diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs index 8d71be501..60c8bb65d 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs @@ -4,8 +4,6 @@ using System.Threading.Tasks; using Events; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; - using Ordering.API.Application.IntegrationCommands.Commands; - using Ordering.Domain.Exceptions; public class OrderStockConfirmedIntegrationEventHandler : IIntegrationEventHandler diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs index 8e8540708..68a89885c 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs @@ -4,11 +4,9 @@ using Ordering.API.Application.IntegrationCommands.Commands; namespace Ordering.API.Application.IntegrationEvents.EventHandling { using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; - using System; using System.Threading.Tasks; using Events; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; - using Domain.Exceptions; public class OrderStockNotConfirmedIntegrationEventHandler : IIntegrationEventHandler { diff --git a/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs b/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs index 9ebeb21b7..260b0c651 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,11 +6,9 @@ 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; using Ordering.Domain.Exceptions; -using System; -using System.Linq; using System.Threading.Tasks; -using Ordering.API.Application.IntegrationEvents; namespace Ordering.API.Application.Sagas { @@ -25,20 +22,14 @@ namespace Ordering.API.Application.Sagas /// public class OrderProcessSaga : OrderSaga, 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) : base(orderingContext) { - _dbContextFactory = dbContextFactory; - _mediator = mediator; - _orderingIntegrationEventService = orderingIntegrationEventService; } /// @@ -73,12 +64,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.SetCancelStatus(); + 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.SetShippedStatus(); + result = await SaveChangesAsync(); + } + return result; } private void CheckValidSagaId(Order orderSaga) @@ -103,6 +123,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.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 4f3ca9dfa..95014dd5b 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -3,8 +3,10 @@ using AspNetCore.Http; using Autofac; using Autofac.Extensions.DependencyInjection; + 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 global::Ordering.API.Application.IntegrationCommands.Commands; using global::Ordering.API.Application.Sagas; diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index 5da725b63..32dd43bc5 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -168,28 +168,45 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O public void SetCancelledStatus() { - switch(OrderStatus.From(_orderStatusId)) + if (_orderStatusId == OrderStatus.Submited.Id) { - //case OrderStatus.Submited: - // _description = ""; - // break; - //case OrderStatus.StockConfirmed: - // _description = ""; - // break; - //case OrderStatus.Paid: - // _description = ""; - // break; - //case OrderStatus.Shipped: - // _description = ""; - // break; - + _description = ""; + } + else if (_orderStatusId == OrderStatus.AwaitingValidation.Id) + { + _description = ""; + } + else if (_orderStatusId == OrderStatus.StockConfirmed.Id) + { + _description = ""; + } + else if (_orderStatusId == OrderStatus.Paid.Id) + { + _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; } + 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() + { + return _orderStatusId; + } + private void AddOrderStartedDomainEvent(string userId, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) { diff --git a/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs b/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs index 5c0c3ef8c..84ef46dfb 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs @@ -33,8 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor public async Task GetAsync(int orderId) { - return await _context.Orders.FindAsync(orderId) - ?? throw new OrderingDomainException($"Not able to get the order. Reason: no valid orderId: {orderId}"); + return await _context.Orders.FindAsync(orderId); } public void Update(Order order) diff --git a/src/Services/Payment/Payment.API/IntegrationCommands/CommandHandlers/PayOrderCommandMsgHandler.cs b/src/Services/Payment/Payment.API/IntegrationCommands/CommandHandlers/PayOrderCommandMsgHandler.cs index 0a41c133c..c0023909c 100644 --- a/src/Services/Payment/Payment.API/IntegrationCommands/CommandHandlers/PayOrderCommandMsgHandler.cs +++ b/src/Services/Payment/Payment.API/IntegrationCommands/CommandHandlers/PayOrderCommandMsgHandler.cs @@ -4,16 +4,25 @@ using Payment.API.IntegrationCommands.Commands; using System.Threading.Tasks; using System; + using Payment.API.IntegrationEvents; + using Payment.API.IntegrationEvents.Events; public class PayOrderCommandMsgHandler : IIntegrationEventHandler { - public PayOrderCommandMsgHandler() - { - } + private readonly IPaymentIntegrationEventService _paymentIntegrationEventService; + + public PayOrderCommandMsgHandler(IPaymentIntegrationEventService paymentIntegrationEventService) + => _paymentIntegrationEventService = paymentIntegrationEventService; 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); } } } diff --git a/src/Services/Payment/Payment.API/IntegrationEvents/IPaymentIntegrationEventService.cs b/src/Services/Payment/Payment.API/IntegrationEvents/IPaymentIntegrationEventService.cs new file mode 100644 index 000000000..f34315763 --- /dev/null +++ b/src/Services/Payment/Payment.API/IntegrationEvents/IPaymentIntegrationEventService.cs @@ -0,0 +1,9 @@ +namespace Payment.API.IntegrationEvents +{ + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + + public interface IPaymentIntegrationEventService + { + void PublishThroughEventBus(IntegrationEvent evt); + } +} diff --git a/src/Services/Payment/Payment.API/IntegrationEvents/PaymentIntegrationEventService.cs b/src/Services/Payment/Payment.API/IntegrationEvents/PaymentIntegrationEventService.cs new file mode 100644 index 000000000..7b2f37814 --- /dev/null +++ b/src/Services/Payment/Payment.API/IntegrationEvents/PaymentIntegrationEventService.cs @@ -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); ; + } + } +} \ No newline at end of file diff --git a/src/Services/Payment/Payment.API/Startup.cs b/src/Services/Payment/Payment.API/Startup.cs index ac970d82d..7899d9aa6 100644 --- a/src/Services/Payment/Payment.API/Startup.cs +++ b/src/Services/Payment/Payment.API/Startup.cs @@ -67,8 +67,6 @@ namespace Payment.API return new AutofacServiceProvider(container.Build()); } - - // 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) { 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/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/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/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/IdentifierCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs index b2f2f6a5d..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,7 @@ 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, diff --git a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs index 98f5292ef..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,13 @@ namespace UnitTest.Ordering.Application 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 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, 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() diff --git a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs index 28ee18078..dfa31eb62 100644 --- a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs @@ -111,7 +111,7 @@ public class OrderAggregateTest var expectedResult = 1; //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.Equal(fakeOrder.DomainEvents.Count, expectedResult); @@ -135,8 +135,8 @@ public class OrderAggregateTest var expectedResult = 2; //Act - var fakeOrder = new Order(userId.ToString(), new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - fakeOrder.AddDomainEvent(new OrderStartedDomainEvent(fakeOrder, userId.ToString(), 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); } @@ -156,8 +156,8 @@ public class OrderAggregateTest var cardSecurityNumber = "123"; var cardHolderName = "FakeName"; 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 @fakeEvent = new OrderStartedDomainEvent(fakeOrder, userId.ToString(), 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