diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs index 19ae1b594..f6e7de1b0 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs @@ -1,5 +1,6 @@ using Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.Services.Basket.API.Model; using System; using System.Threading.Tasks; diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs b/src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs index a32ad0beb..b78d10c05 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs @@ -7,9 +7,9 @@ namespace Basket.API.IntegrationEvents.Events // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. public class OrderStartedIntegrationEvent : IntegrationEvent { - public string UserId { get; } + public string UserId { get; set; } - public OrderStartedIntegrationEvent(string userId) => - UserId = userId; + public OrderStartedIntegrationEvent(string userId) + => UserId = userId; } } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 2c1e70645..953406f18 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -1,8 +1,11 @@ -using Basket.API.Infrastructure.Filters; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Basket.API.Infrastructure.Filters; using Basket.API.IntegrationEvents.EventHandling; using Basket.API.IntegrationEvents.Events; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; @@ -10,6 +13,7 @@ using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.eShopOnContainers.Services.Basket.API.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.HealthChecks; @@ -17,14 +21,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using RabbitMQ.Client; using StackExchange.Redis; +using System; using System.Linq; using System.Net; using System.Threading.Tasks; -using System; -using Microsoft.eShopOnContainers.Services.Basket.API.Services; -using Microsoft.AspNetCore.Http; -using Autofac; -using Autofac.Extensions.DependencyInjection; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -114,6 +114,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddTransient(); services.AddTransient(); RegisterServiceBus(services); + services.AddOptions(); var container = new ContainerBuilder(); container.Populate(services); @@ -127,7 +128,6 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddTransient(); services.AddTransient(); - } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -165,14 +165,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API protected virtual void ConfigureEventBus(IApplicationBuilder app) { - var catalogPriceHandler = app.ApplicationServices - .GetService>(); + //var catalogPriceHandler = app.ApplicationServices + // .GetService>(); - var orderStartedHandler = app.ApplicationServices - .GetService>(); + //var orderStartedHandler = app.ApplicationServices + // .GetService>(); var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.Subscribe(); eventBus.Subscribe(); } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs index 808b43a1d..4a5d7db38 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs @@ -11,9 +11,9 @@ namespace Ordering.API.Application.IntegrationEvents.Events // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. public class OrderStartedIntegrationEvent : IntegrationEvent { - public string UserId { get; } + public string UserId { get; set; } - public OrderStartedIntegrationEvent(string userId) => - UserId = userId; + public OrderStartedIntegrationEvent(string userId) + => UserId = userId; } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs index 92c32069c..c23e59332 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs @@ -1,9 +1,6 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Ordering.API.Application.Models; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Ordering.API.Application.IntegrationEvents.Events { diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs new file mode 100644 index 000000000..5c619586f --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using Ordering.API.Application.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Validations +{ + public class CancelOrderCommandValidator : AbstractValidator + { + public CancelOrderCommandValidator() + { + RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs new file mode 100644 index 000000000..72a16d319 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using Ordering.API.Application.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Validations +{ + public class ShipOrderCommandValidator : AbstractValidator + { + public ShipOrderCommandValidator() + { + RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 95014dd5b..a6d20eb64 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -5,11 +5,10 @@ using Autofac.Extensions.DependencyInjection; using global::Ordering.API.Application.IntegrationCommands.Commands; using global::Ordering.API.Application.IntegrationEvents; + using global::Ordering.API.Application.IntegrationEvents.EventHandling; 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; using Infrastructure; using Infrastructure.Auth; using Infrastructure.AutofacModules; @@ -127,10 +126,10 @@ services.AddSingleton(); services.AddSingleton(); - services.AddTransient>(); + services.AddTransient(); services.AddTransient, OrderProcessSaga>(); - services.AddTransient>(); - services.AddTransient>(); + services.AddTransient(); + services.AddTransient(); services.AddOptions(); //configure autofac @@ -175,13 +174,13 @@ { var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.Subscribe>(); + eventBus.Subscribe(); eventBus.Subscribe>(); - eventBus.Subscribe>(); + eventBus.Subscribe(); - eventBus.Subscribe>(); + eventBus.Subscribe(); } protected virtual void ConfigureAuth(IApplicationBuilder app) diff --git a/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs b/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs index 5de55abb3..2f3a22d10 100644 --- a/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs @@ -29,6 +29,7 @@ namespace FunctionalTests.Services.Basket public static class Post { public static string CreateBasket = "/"; + public static string Checkout = "/checkout"; } } } diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs index 736adaf15..156d0f187 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs @@ -1,5 +1,6 @@ using FunctionalTests.Extensions; -using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using FunctionalTests.Services.Basket; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Newtonsoft.Json; using System; @@ -8,69 +9,159 @@ using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using WebMVC.Models; using Xunit; -using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; namespace FunctionalTests.Services.Ordering { - //public class OrderingScenarios : OrderingScenariosBase - //{ - // [Fact] - // public async Task Create_order_and_return_the_order_by_id() - // { - // using (var server = CreateServer()) - // { - // var client = server.CreateIdempotentClient(); - - // // GIVEN an order is created - // await client.PostAsync(Post.AddNewOrder, new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json")); - - // var ordersResponse = await client.GetAsync(Get.Orders); - // var responseBody = await ordersResponse.Content.ReadAsStringAsync(); - // var orders = JsonConvert.DeserializeObject>(responseBody); - // string orderId = orders.OrderByDescending(o => o.Date).First().OrderNumber; - - // //WHEN we request the order bit its id - // var order= await client.GetAsync(Get.OrderBy(int.Parse(orderId))); - // var orderBody = await order.Content.ReadAsStringAsync(); - // var result = JsonConvert.DeserializeObject(orderBody); - - // //THEN the requested order is returned - // Assert.Equal(orderId, result.OrderNumber); - // Assert.Equal("inprocess", result.Status); - // Assert.Equal(1, result.OrderItems.Count); - // Assert.Equal(10, result.OrderItems[0].UnitPrice); - // } - // } - - // string BuildOrder() - // { - // List orderItemsList = new List(); - // orderItemsList.Add(new BasketItem() - // { - // ProductId = "1", - // Discount = 8M, - // 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); - // } - //} + public class OrderingScenarios : OrderingScenariosBase + { + [Fact] + public async Task Checkout_basket_and_check_order_status_submited() + { + using (var orderServer = new OrderingScenariosBase().CreateServer()) + using (var basketServer = new BasketScenariosBase().CreateServer()) + { + // Expected data + var cityExpected = $"city-{Guid.NewGuid()}"; + var orderStatusExpected = "submited"; + + var basketClient = basketServer.CreateIdempotentClient(); + var orderClient = orderServer.CreateIdempotentClient(); + + // GIVEN a basket is created + var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json"); + await basketClient.PostAsync(BasketScenariosBase.Post.CreateBasket, contentBasket); + + // AND basket checkout is sent + await basketClient.PostAsync(BasketScenariosBase.Post.Checkout, new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json")); + + // AND the requested order is retrieved and removed + var newOrder = await TryGetNewOrderCreated(cityExpected, orderClient); + await orderClient.DeleteAsync(OrderingScenariosBase.Delete.OrderBy(int.TryParse(newOrder.OrderNumber, out int id) ? id : 0)); + + // THEN check status + Assert.Equal(orderStatusExpected, newOrder.Status); + } + } + + [Fact] + public async Task Cancel_basket_and_check_order_status_cancelled() + { + using (var orderServer = new OrderingScenariosBase().CreateServer()) + using (var basketServer = new BasketScenariosBase().CreateServer()) + { + // Expected data + var cityExpected = $"city-{Guid.NewGuid()}"; + var orderStatusExpected = "cancelled"; + + var basketClient = basketServer.CreateIdempotentClient(); + var orderClient = orderServer.CreateIdempotentClient(); + + // GIVEN a basket is created + var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json"); + await basketClient.PostAsync(BasketScenariosBase.Post.CreateBasket, contentBasket); + + // AND basket checkout is sent + await basketClient.PostAsync(BasketScenariosBase.Post.Checkout, new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json")); + + // WHEN Order is created in Ordering.api + var newOrder = await TryGetNewOrderCreated(cityExpected, orderClient); + + // AND Order is cancelled in Ordering.api + await orderClient.PutAsync(OrderingScenariosBase.Put.CancelOrder, new StringContent(BuildCancelOrder(newOrder.OrderNumber), UTF8Encoding.UTF8, "application/json")); + + // AND the requested order is retrieved and removed + var order = await TryGetNewOrderCreated(cityExpected, orderClient); + await orderClient.DeleteAsync(OrderingScenariosBase.Delete.OrderBy(int.TryParse(newOrder.OrderNumber, out int id) ? id : 0)); + + // THEN check status + Assert.Equal(orderStatusExpected, order.Status); + } + } + + private async Task TryGetNewOrderCreated(string city, HttpClient orderClient) + { + var counter = 0; + Order order = null; + + while (counter < 20) + { + //get the orders and verify that the new order has been created + var ordersGetResponse = await orderClient.GetStringAsync(OrderingScenariosBase.Get.Orders); + var orders = JsonConvert.DeserializeObject>(ordersGetResponse); + + if (orders != null && orders.Any()) { + var lastOrder = orders.OrderByDescending(o => o.Date).First(); + int.TryParse(lastOrder.OrderNumber, out int id); + var orderDetails = await orderClient.GetStringAsync(OrderingScenariosBase.Get.OrderBy(id)); + order = JsonConvert.DeserializeObject(orderDetails); + } + + if (IsOrderCreated(order, city)) + { + break; + } + else + { + counter++; + await Task.Delay(1000); + } + } + + return order; + } + + private bool IsOrderCreated(Order order, string city) + { + return order.City == city; + } + + string BuildBasket() + { + var order = new CustomerBasket("1234"); + order.Items = new List() + { + new Microsoft.eShopOnContainers.Services.Basket.API.Model.BasketItem() + { + Id = "1", + ProductName = "ProductName", + ProductId = "1", + UnitPrice = 10, + Quantity = 1 + } + }; + return JsonConvert.SerializeObject(order); + } + + string BuildCancelOrder(string orderId) + { + var order = new OrderDTO() + { + OrderNumber = orderId + }; + return JsonConvert.SerializeObject(order); + } + + string BuildCheckout(string cityExpected) + { + var checkoutBasket = new BasketDTO() + { + City = cityExpected, + Street = "street", + State = "state", + Country = "coutry", + ZipCode = "zipcode", + CardNumber = "CardNumber", + CardHolderName = "CardHolderName", + CardExpiration = DateTime.Now.AddYears(1), + CardSecurityNumber = "1234", + CardTypeId = 1, + Buyer = "Buyer", + RequestId = Guid.NewGuid() + }; + + return JsonConvert.SerializeObject(checkoutBasket); + } + } } diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenariosBase.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenariosBase.cs index 8c3c554e5..93391121d 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenariosBase.cs @@ -32,5 +32,18 @@ namespace FunctionalTests.Services.Ordering { public static string AddNewOrder = "api/v1/orders/new"; } + + public static class Put + { + public static string CancelOrder = "api/v1/orders/cancel"; + } + + public static class Delete + { + public static string OrderBy(int id) + { + return $"api/v1/orders/{id}"; + } + } } } diff --git a/test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs b/test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs index 725b58206..cca3bf306 100644 --- a/test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs +++ b/test/Services/IntegrationTests/Services/Basket/BasketScenarioBase.cs @@ -23,14 +23,14 @@ namespace IntegrationTests.Services.Basket { public static string GetBasket(int id) { - return $"api/v1/basket/{id}"; + return $"{id}"; } } public static class Post { - public static string Basket = "api/v1/basket"; - public static string CheckoutOrder = "api/v1/basket/checkout"; + public static string Basket = ""; + public static string CheckoutOrder = "checkout"; } } } diff --git a/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs b/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs index ecc8d60be..451b1fbf7 100644 --- a/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs +++ b/test/Services/IntegrationTests/Services/Basket/BasketScenarios.cs @@ -1,4 +1,5 @@ -using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using IntegrationTests.Services.Extensions; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Newtonsoft.Json; using System; using System.Net.Http; @@ -42,9 +43,13 @@ namespace IntegrationTests.Services.Basket { using (var server = CreateServer()) { - var content = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json"); - var response = await server.CreateClient() - .PostAsync(Post.CheckoutOrder, content); + var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json"); + await server.CreateClient() + .PostAsync(Post.Basket, contentBasket); + + var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json"); + var response = await server.CreateIdempotentClient() + .PostAsync(Post.CheckoutOrder, contentCheckout); response.EnsureSuccessStatusCode(); } @@ -52,7 +57,7 @@ namespace IntegrationTests.Services.Basket string BuildBasket() { - var order = new CustomerBasket("1"); + var order = new CustomerBasket("1234"); return JsonConvert.SerializeObject(order); } diff --git a/test/Services/IntegrationTests/Services/Basket/appsettings.json b/test/Services/IntegrationTests/Services/Basket/appsettings.json index e669ab532..0d84f580e 100644 --- a/test/Services/IntegrationTests/Services/Basket/appsettings.json +++ b/test/Services/IntegrationTests/Services/Basket/appsettings.json @@ -1,6 +1,14 @@ { - "ConnectionString": "127.0.0.1", + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, "IdentityUrl": "http://localhost:5105", + "ConnectionString": "127.0.0.1", "isTest": "true", "EventBusConnection": "localhost" } diff --git a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs index 9b4cade3f..d1381a92f 100644 --- a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs @@ -2,6 +2,7 @@ { using IntegrationTests.Services.Extensions; using Newtonsoft.Json; + using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -24,7 +25,7 @@ } [Fact] - public async Task Cancel_order_and_response_ok_status_code() + public async Task Cancel_order_no_order_created_response_bad_status_code() { using (var server = CreateServer()) { @@ -32,12 +33,12 @@ var response = await server.CreateIdempotentClient() .PutAsync(Put.CancelOrder, content); - response.EnsureSuccessStatusCode(); + Assert.Equal(response.StatusCode, HttpStatusCode.InternalServerError); } } [Fact] - public async Task Ship_order_and_response_bad_status_code() + public async Task Ship_order_no_order_created_response_bad_status_code() { using (var server = CreateServer()) { @@ -45,7 +46,7 @@ var response = await server.CreateIdempotentClient() .PutAsync(Put.ShipOrder, content); - response.EnsureSuccessStatusCode(); + Assert.Equal(response.StatusCode, HttpStatusCode.InternalServerError); } }