Browse Source

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

# Conflicts:
#	src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
pull/223/head
Christian Arenas 7 years ago
parent
commit
94ee95f32b
53 changed files with 748 additions and 241 deletions
  1. +8
    -2
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  2. +0
    -9
      src/Services/Catalog/Catalog.API/IntegrationCommands/CommandHandlers/DecrementOrderStockCommandMsgHandler.cs
  3. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs
  4. +0
    -2
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs
  5. +0
    -1
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs
  6. +0
    -1
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs
  7. +0
    -2
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs
  8. +0
    -2
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs
  9. +48
    -16
      src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs
  10. +16
    -1
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  11. +2
    -0
      src/Services/Ordering/Ordering.API/Startup.cs
  12. +32
    -15
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  13. +1
    -2
      src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs
  14. +13
    -4
      src/Services/Payment/Payment.API/IntegrationCommands/CommandHandlers/PayOrderCommandMsgHandler.cs
  15. +9
    -0
      src/Services/Payment/Payment.API/IntegrationEvents/IPaymentIntegrationEventService.cs
  16. +21
    -0
      src/Services/Payment/Payment.API/IntegrationEvents/PaymentIntegrationEventService.cs
  17. +0
    -2
      src/Services/Payment/Payment.API/Startup.cs
  18. +4
    -10
      src/Web/WebMVC/Controllers/OrderController.cs
  19. +43
    -0
      src/Web/WebMVC/Controllers/OrderManagementController.cs
  20. +5
    -0
      src/Web/WebMVC/Infrastructure/API.cs
  21. +11
    -0
      src/Web/WebMVC/Models/OrderDTO.cs
  22. +25
    -0
      src/Web/WebMVC/Models/OrderProcessAction.cs
  23. +1
    -1
      src/Web/WebMVC/Services/BasketService.cs
  24. +2
    -1
      src/Web/WebMVC/Services/IOrderingService.cs
  25. +27
    -3
      src/Web/WebMVC/Services/OrderingService.cs
  26. +25
    -1
      src/Web/WebMVC/ViewModels/Order.cs
  27. +2
    -1
      src/Web/WebMVC/Views/Cart/Index.cshtml
  28. +2
    -2
      src/Web/WebMVC/Views/Order/Create.cshtml
  29. +2
    -1
      src/Web/WebMVC/Views/Order/Detail.cshtml
  30. +10
    -2
      src/Web/WebMVC/Views/Order/Index.cshtml
  31. +43
    -0
      src/Web/WebMVC/Views/OrderManagement/Index.cshtml
  32. +6
    -2
      src/Web/WebMVC/Views/Shared/_Header.cshtml
  33. +14
    -0
      src/Web/WebMVC/wwwroot/css/shared/components/header/header.css
  34. +28
    -1
      src/Web/WebSPA/Client/modules/basket/basket.service.ts
  35. +7
    -6
      src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts
  36. +3
    -2
      src/Web/WebSPA/Client/modules/orders/orders.module.ts
  37. +1
    -7
      src/Web/WebSPA/Client/modules/orders/orders.service.ts
  38. +16
    -0
      src/Web/WebSPA/Client/modules/shared/models/basketCheckout.model.ts
  39. +22
    -0
      src/Web/WebSPA/Client/modules/shared/services/data.service.ts
  40. +2
    -2
      src/Web/WebSPA/WebSPA.csproj
  41. +3
    -6
      test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs
  42. +4
    -0
      test/Services/IntegrationTests/IntegrationTests.csproj
  43. +36
    -0
      test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs
  44. +80
    -0
      test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs
  45. +26
    -0
      test/Services/IntegrationTests/Services/Basket/BasketTestsStartup.cs
  46. +8
    -0
      test/Services/IntegrationTests/Services/Basket/appsettings.json
  47. +3
    -2
      test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs
  48. +47
    -98
      test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs
  49. +3
    -2
      test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs
  50. +2
    -1
      test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs
  51. +3
    -2
      test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs
  52. +56
    -24
      test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs
  53. +5
    -5
      test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs

+ 8
- 2
src/Services/Basket/Basket.API/Controllers/BasketController.cs View File

@ -52,15 +52,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
}
[Route("checkout")]
[HttpPut]
public async Task<IActionResult> Checkout([FromBody]BasketCheckout value)
[HttpPost]
public async Task<IActionResult> 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)


+ 0
- 9
src/Services/Catalog/Catalog.API/IntegrationCommands/CommandHandlers/DecrementOrderStockCommandMsgHandler.cs View File

@ -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");
}
}
}
}

+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs View File

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

+ 0
- 2
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs View File

@ -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;


+ 0
- 1
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs View File

@ -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 :


+ 0
- 1
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs View File

@ -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 :


+ 0
- 2
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs View File

@ -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<OrderStockConfirmedIntegrationEvent>


+ 0
- 2
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs View File

@ -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<OrderStockNotConfirmedIntegrationEvent>
{


+ 48
- 16
src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs View File

@ -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
/// </summary>
public class OrderProcessSaga : OrderSaga,
IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>,
IAsyncRequestHandler<CancelOrderCommand, bool>
IAsyncRequestHandler<CancelOrderCommand, bool>,
IAsyncRequestHandler<ShipOrderCommand, bool>
{
private readonly IMediator _mediator;
private readonly Func<Owned<OrderingContext>> _dbContextFactory;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderProcessSaga(
Func<Owned<OrderingContext>> dbContextFactory, OrderingContext orderingContext,
IMediator mediator, IOrderingIntegrationEventService orderingIntegrationEventService)
OrderingContext orderingContext)
: base(orderingContext)
{
_dbContextFactory = dbContextFactory;
_mediator = mediator;
_orderingIntegrationEventService = orderingIntegrationEventService;
}
/// <summary>
@ -73,12 +64,41 @@ namespace Ordering.API.Application.Sagas
/// <returns></returns>
public async Task<bool> 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;
}
/// <summary>
/// Handler which processes the command when
/// administrator executes ship order from app
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(ShipOrderCommand command)
{
var result = false;
var orderSaga = FindSagaById(command.OrderNumber);
CheckValidSagaId(orderSaga);
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<ShipOrderCommand, bool>
{
public ShipOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
#endregion
}
}

+ 16
- 1
src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs View File

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


+ 2
- 0
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -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;


+ 32
- 15
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -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)
{


+ 1
- 2
src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs View File

@ -33,8 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor
public async Task<Order> 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)


+ 13
- 4
src/Services/Payment/Payment.API/IntegrationCommands/CommandHandlers/PayOrderCommandMsgHandler.cs View File

@ -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<PayOrderCommandMsg>
{
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);
}
}
}

+ 9
- 0
src/Services/Payment/Payment.API/IntegrationEvents/IPaymentIntegrationEventService.cs View File

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

+ 21
- 0
src/Services/Payment/Payment.API/IntegrationEvents/PaymentIntegrationEventService.cs View File

@ -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); ;
}
}
}

+ 0
- 2
src/Services/Payment/Payment.API/Startup.cs View File

@ -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)
{


+ 4
- 10
src/Web/WebMVC/Controllers/OrderController.cs View File

@ -59,18 +59,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
return View(model);
}
[HttpPut]
public async Task<IActionResult> Cancel(Order model)
public async Task<IActionResult> 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<IActionResult> Detail(string orderId)


+ 43
- 0
src/Web/WebMVC/Controllers/OrderManagementController.cs View File

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

+ 5
- 0
src/Web/WebMVC/Infrastructure/API.cs View File

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


+ 11
- 0
src/Web/WebMVC/Models/OrderDTO.cs View File

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

+ 25
- 0
src/Web/WebMVC/Models/OrderProcessAction.cs View File

@ -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;
}
}
}

+ 1
- 1
src/Web/WebMVC/Services/BasketService.cs View File

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


+ 2
- 1
src/Web/WebMVC/Services/IOrderingService.cs View File

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


+ 27
- 3
src/Web/WebMVC/Services/OrderingService.cs View File

@ -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;


+ 25
- 1
src/Web/WebMVC/ViewModels/Order.cs View File

@ -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<SelectListItem> 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<SelectListItem> GetActionCodesByCurrentState()
{
var actions = new List<OrderProcessAction>();
switch (Status?.ToLower())
{
case "paid":
actions.Add(OrderProcessAction.Ship);
break;
}
var result = new List<SelectListItem>();
actions.ForEach(action =>
{
result.Add(new SelectListItem { Text = action.Name, Value = action.Code });
});
return result;
}
}
public enum CardType


+ 2
- 1
src/Web/WebMVC/Views/Cart/Index.cshtml View File

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


+ 2
- 2
src/Web/WebMVC/Views/Order/Create.cshtml View File

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


+ 2
- 1
src/Web/WebMVC/Views/Order/Detail.cshtml View File

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


+ 10
- 2
src/Web/WebMVC/Views/Order/Index.cshtml View File

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


+ 43
- 0
src/Web/WebMVC/Views/OrderManagement/Index.cshtml View File

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

+ 6
- 2
src/Web/WebMVC/Views/Shared/_Header.cshtml View File

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

+ 14
- 0
src/Web/WebMVC/wwwroot/css/shared/components/header/header.css View File

@ -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;


+ 28
- 1
src/Web/WebSPA/Client/modules/basket/basket.service.ts View File

@ -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<boolean> {
return this.service.postWithId(this.basketUrl + '/checkout', basketCheckout).map((response: Response) => {
return true;
});
}
getBasket(): Observable<IBasket> {
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 = <IBasketCheckout>{};
basketCheckout.street = order.street
basketCheckout.city = order.city;
basketCheckout.country = order.country;
basketCheckout.state = order.state;
basketCheckout.zipcode = order.zipcode;
basketCheckout.cardexpiration = order.cardexpiration;
basketCheckout.cardnumber = order.cardnumber;
basketCheckout.cardsecuritynumber = order.cardsecuritynumber;
basketCheckout.cardtypeid = order.cardtypeid;
basketCheckout.cardholdername = order.cardholdername;
basketCheckout.total = 0;
basketCheckout.expiration = order.expiration;
return basketCheckout;
}
private loadData() {
this.getBasket().subscribe(basket => {
if (basket != null)


+ 7
- 6
src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts View File

@ -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;


+ 3
- 2
src/Web/WebSPA/Client/modules/orders/orders.module.ts View File

@ -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 { }

+ 1
- 7
src/Web/WebSPA/Client/modules/orders/orders.service.ts View File

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


+ 16
- 0
src/Web/WebSPA/Client/modules/shared/models/basketCheckout.model.ts View File

@ -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;
}

+ 22
- 0
src/Web/WebSPA/Client/modules/shared/services/data.service.ts View File

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


+ 2
- 2
src/Web/WebSPA/WebSPA.csproj View File

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


+ 3
- 6
test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs View File

@ -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
{


+ 4
- 0
test/Services/IntegrationTests/IntegrationTests.csproj View File

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


+ 36
- 0
test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs View File

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

+ 80
- 0
test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs View File

@ -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);
}
}
}

+ 26
- 0
test/Services/IntegrationTests/Services/Basket/BasketTestsStartup.cs View File

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

+ 8
- 0
test/Services/IntegrationTests/Services/Basket/appsettings.json View File

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

+ 3
- 2
test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs View File

@ -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";
}
}
}

+ 47
- 98
test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs View File

@ -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<OrderItemDTO> orderItemsList = new List<OrderItemDTO>();
// orderItemsList.Add(new OrderItemDTO()
// {
// ProductId = 1,
// Discount = 10M,
// UnitPrice = 10,
// Units = 1,
// ProductName = "Some name"
// }
// );
// var order = new CreateOrderCommand(
// orderItemsList,
// cardExpiration: DateTime.UtcNow.AddYears(1),
// cardNumber: "5145-555-5555",
// cardHolderName: "Jhon Senna",
// cardSecurityNumber: "232",
// cardTypeId: 1,
// city: "Redmon",
// country: "USA",
// state: "WA",
// street: "One way",
// zipcode: "zipcode"
// );
// return JsonConvert.SerializeObject(order);
// }
// string BuildOrderWithInvalidExperationTime()
// {
// var order = new CreateOrderCommand(
// null,
// cardExpiration: DateTime.UtcNow.AddYears(-1),
// cardNumber: "5145-555-5555",
// cardHolderName: "Jhon Senna",
// cardSecurityNumber: "232",
// cardTypeId: 1,
// city: "Redmon",
// country: "USA",
// state: "WA",
// street: "One way",
// zipcode: "zipcode",
// buyerId: 1,
// paymentId:1
// );
// return JsonConvert.SerializeObject(order);
// }
[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);
}
}
}

+ 3
- 2
test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs View File

@ -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<UserCheckoutAcceptedIntegrationEvent>()), Times.Once);
Assert.NotNull(result);
}


+ 2
- 1
test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs View File

@ -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<string, object> args = null)
{
return new CreateOrderCommand(
null,
new List<BasketItem>(),
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,


+ 3
- 2
test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs View File

@ -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<string, object> args = null)
{
return new CreateOrderCommand(
null,
new List<BasketItem>(),
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,


+ 56
- 24
test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs View File

@ -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<IIdentityService>();
}
//[Fact]
//public async Task Create_order_with_requestId_success()
//{
// //Arrange
// _mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<CreateOrderCommand, bool>>()))
// .Returns(Task.FromResult(true));
[Fact]
public async Task Create_order_with_requestId_success()
{
//Arrange
_mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<CancelOrderCommand, bool>>()))
.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<IdentifiedCommand<CreateOrderCommand, bool>>()))
// .Returns(Task.FromResult(true));
[Fact]
public async Task Cancel_order_bad_request()
{
//Arrange
_mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<CancelOrderCommand, bool>>()))
.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<IdentifiedCommand<ShipOrderCommand, bool>>()))
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var actionResult = await orderController.ShipOrder(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
//Assert
Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK);
}
[Fact]
public async Task Ship_order_bad_request()
{
//Arrange
_mediatorMock.Setup(x => x.SendAsync(It.IsAny<IdentifiedCommand<ShipOrderCommand, bool>>()))
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var actionResult = await orderController.ShipOrder(new ShipOrderCommand(1), String.Empty) as BadRequestResult;
//Assert
Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest);
}
[Fact]
public async Task Get_orders_success()


+ 5
- 5
test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs View File

@ -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


Loading…
Cancel
Save