From e996593d327dcc01b332c4bd7a0500edd8e97917 Mon Sep 17 00:00:00 2001 From: dsanz Date: Fri, 17 Mar 2017 15:57:57 +0100 Subject: [PATCH] Add functional test for integration events. Implement IDisposable for EventBusRabbitMQ. --- docker-compose-external.yml | 5 + .../EventBusRabbitMQ/EventBusRabbitMQ.cs | 18 ++- .../ProductPriceChangedEventHandler.cs | 11 +- src/Services/Basket/Basket.API/Startup.cs | 32 +++-- .../FunctionalTests/FunctionalTests.csproj | 4 + .../Services/Basket/BasketScenariosBase.cs | 34 +++++ .../Services/Basket/BasketTestsStartup.cs | 29 ++++ .../Services/Catalog/CatalogScenarios.cs | 55 -------- .../Services/Catalog/CatalogScenariosBase.cs | 2 + .../Services/Catalog/CatalogTestsStartup.cs | 15 -- .../Services/IntegrationEventsScenarios.cs | 132 ++++++++++++++++++ .../Services/FunctionalTests/appsettings.json | 6 + 12 files changed, 252 insertions(+), 91 deletions(-) create mode 100644 test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs create mode 100644 test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs delete mode 100644 test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs delete mode 100644 test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs create mode 100644 test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs create mode 100644 test/Services/FunctionalTests/appsettings.json diff --git a/docker-compose-external.yml b/docker-compose-external.yml index 6bce32a3e..ff1641cdd 100644 --- a/docker-compose-external.yml +++ b/docker-compose-external.yml @@ -8,3 +8,8 @@ services: image: redis ports: - "6379:6379" + + rabbitmq: + image: rabbitmq + ports: + - "5672:5672" diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 3582e8cc4..e31c43396 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { - public class EventBusRabbitMQ : IEventBus + public class EventBusRabbitMQ : IEventBus, IDisposable { private readonly string _brokerName = "eshop_event_bus"; private readonly string _connectionString; @@ -31,6 +31,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _handlers = new Dictionary>(); _eventTypes = new List(); } + public void Publish(IntegrationEvent @event) { var eventName = @event.GetType().Name; @@ -92,14 +93,24 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ if (_handlers.Keys.Count == 0) { _queueName = string.Empty; - _connection.Item1.Close(); - _connection.Item2.Close(); + _connection.Item1.Dispose(); + _connection.Item2.Dispose(); } } } } + public void Dispose() + { + if (_connection != null) + { + _handlers.Clear(); + _connection.Item1.Dispose(); + _connection.Item2.Dispose(); + } + } + private IModel GetChannel() { if (_connection != null) @@ -151,5 +162,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } } } + } } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs index 1a7599162..f32b71af3 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs @@ -20,7 +20,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even foreach (var id in userIds) { var basket = await _repository.GetBasket(id); - await UpdateBasket(@event.ProductId, @event.NewPrice, basket); + await UpdateBasket(@event.ProductId, @event.NewPrice, basket); } } @@ -31,9 +31,12 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even { foreach (var item in itemsToUpdate) { - var originalPrice = item.UnitPrice; - item.UnitPrice = newPrice; - item.OldUnitPrice = originalPrice; + if(item.UnitPrice != newPrice) + { + var originalPrice = item.UnitPrice; + item.UnitPrice = newPrice; + item.OldUnitPrice = originalPrice; + } } await _repository.UpdateBasket(basket); } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index b96029718..49197782f 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; +using System; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -80,13 +81,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; - var eventBus = new EventBusRabbitMQ(configuration.EventBusConnection); - services.AddSingleton(eventBus); + services.AddSingleton(provider => new EventBusRabbitMQ(configuration.EventBusConnection)); - - var catalogPriceHandler = serviceProvider.GetService>(); - eventBus.Subscribe(catalogPriceHandler); - } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -100,20 +96,28 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // Use frameworks app.UseCors("CorsPolicy"); - var identityUrl = Configuration.GetValue("IdentityUrl"); - - app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions - { - Authority = identityUrl.ToString(), - ScopeName = "basket", - RequireHttpsMetadata = false - }); + ConfigureAuth(app); app.UseMvcWithDefaultRoute(); app.UseSwagger() .UseSwaggerUi(); + var catalogPriceHandler = app.ApplicationServices.GetService>(); + var eventBus = app.ApplicationServices.GetRequiredService(); + eventBus.Subscribe(catalogPriceHandler); + } + + protected virtual void ConfigureAuth(IApplicationBuilder app) + { + var identityUrl = Configuration.GetValue("IdentityUrl"); + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = identityUrl.ToString(), + ScopeName = "basket", + RequireHttpsMetadata = false + }); + } } } diff --git a/test/Services/FunctionalTests/FunctionalTests.csproj b/test/Services/FunctionalTests/FunctionalTests.csproj index 0e24f233d..34fa68d42 100644 --- a/test/Services/FunctionalTests/FunctionalTests.csproj +++ b/test/Services/FunctionalTests/FunctionalTests.csproj @@ -13,12 +13,16 @@ + + + PreserveNewest + PreserveNewest diff --git a/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs b/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs new file mode 100644 index 000000000..5de55abb3 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace FunctionalTests.Services.Basket +{ + public class BasketScenariosBase + { + public TestServer CreateServer() + { + var webHostBuilder = new WebHostBuilder(); + webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory()); + webHostBuilder.UseStartup(); + + return new TestServer(webHostBuilder); + } + + public static class Get + { + public static string GetBasketByCustomer(string customerId) + { + return $"/{customerId}"; + } + } + + public static class Post + { + public static string CreateBasket = "/"; + } + } +} diff --git a/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs b/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs new file mode 100644 index 000000000..a0e625d96 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs @@ -0,0 +1,29 @@ +using Microsoft.eShopOnContainers.Services.Basket.API; +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; +using FunctionalTests.Middleware; + +namespace FunctionalTests.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/FunctionalTests/Services/Catalog/CatalogScenarios.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs deleted file mode 100644 index ec4643ce7..000000000 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.eShopOnContainers.Services.Catalog.API.Model; -using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace FunctionalTests.Services.Catalog -{ - public class CatalogScenarios : CatalogScenariosBase - { - [Fact] - public async Task Post_update_a_catalogitem_price_and_catalogitem_is_returned_modified() - { - using (var server = CreateServer()) - { - var client = server.CreateClient(); - - // Arrange - var itemToModify = GetCatalogItem(); - var newPrice = new Random().Next(1, 200); - itemToModify.Price = newPrice; - - // Act - var postRes = await client.PostAsync(Post.UpdateCatalogProduct, - new StringContent(JsonConvert.SerializeObject(itemToModify), - UTF8Encoding.UTF8, "application/json")); - var response = await client.GetAsync(Get.ProductByName(itemToModify.Name)); - var result = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - var item = result.Data.First(); - - // Assert - Assert.Equal(result.Count, 1); - Assert.Equal(itemToModify.Id, item.Id); - Assert.Equal(newPrice, item.Price); - } - - } - - private CatalogItem GetCatalogItem() - { - return new CatalogItem() - { - Id = 1, - Price = 12.5M, - Name = ".NET Bot Black Sweatshirt" - }; - } - - } -} diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs index b4107b3bc..727e58a00 100644 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs @@ -24,6 +24,8 @@ namespace FunctionalTests.Services.Catalog { public static string Orders = "api/v1/orders"; + public static string Items = "api/v1/catalog/items"; + public static string ProductByName(string name) { return $"api/v1/catalog/items/withname/{name}"; diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs deleted file mode 100644 index f06f9b248..000000000 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.eShopOnContainers.Services.Catalog.API; -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.AspNetCore.Hosting; - -namespace FunctionalTests.Services.Catalog -{ - public class CatalogTestsStartup : Startup - { - public CatalogTestsStartup(IHostingEnvironment env) : base(env) - { - } - } -} diff --git a/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs b/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs new file mode 100644 index 000000000..57254db1f --- /dev/null +++ b/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs @@ -0,0 +1,132 @@ +using FunctionalTests.Services.Basket; +using FunctionalTests.Services.Catalog; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.eShopOnContainers.Services.Catalog.API.Model; +using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; +using Newtonsoft.Json; +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using System.Net.Http; +using System.Threading; + +namespace FunctionalTests.Services +{ + public class IntegrationEventsScenarios + { + [Fact] + public async Task Post_update_product_price_and_catalog_and_basket_list_modified() + { + decimal priceModification = 0.15M; + string userId = "JohnId"; + + using (var catalogServer = new CatalogScenariosBase().CreateServer()) + using (var basketServer = new BasketScenariosBase().CreateServer()) + { + var catalogClient = catalogServer.CreateClient(); + var basketClient = basketServer.CreateClient(); + + // GIVEN a product catalog list + var originalCatalogProducts = await GetCatalogAsync(catalogClient); + + // AND a user basket filled with products + var basket = ComposeBasket(userId, originalCatalogProducts.Data.Take(3)); + var res = await basketClient.PostAsync( + BasketScenariosBase.Post.CreateBasket, + new StringContent(JsonConvert.SerializeObject(basket), UTF8Encoding.UTF8, "application/json") + ); + + // WHEN the price of one product is modified in the catalog + var itemToModify = basket.Items[2]; + var oldPrice = itemToModify.UnitPrice; + var newPrice = oldPrice + priceModification; + var pRes = await catalogClient.PostAsync(CatalogScenariosBase.Post.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice), UTF8Encoding.UTF8, "application/json")); + + var modifiedCatalogProducts = await GetCatalogAsync(catalogClient); + + var itemUpdated = await GetUpdatedBasketItem(newPrice, itemToModify.ProductId, userId, basketClient); + + if (itemUpdated == null) + { + Assert.False(true, $"The basket service has not been updated."); + } + else + { + //THEN the product price changes in the catalog + Assert.Equal(newPrice, modifiedCatalogProducts.Data.Single(it => it.Id == int.Parse(itemToModify.ProductId)).Price); + + // AND the products in the basket reflects the changed priced and the original price + Assert.Equal(newPrice, itemUpdated.UnitPrice); + Assert.Equal(oldPrice, itemUpdated.OldUnitPrice); + } + } + } + + private async Task GetUpdatedBasketItem(decimal newPrice, string productId, string userId, HttpClient basketClient) + { + bool continueLoop = true; + var counter = 0; + BasketItem itemUpdated = null; + + while (continueLoop && counter < 20) + { + //get the basket and verify that the price of the modified product is updated + var basketGetResponse = await basketClient.GetAsync(BasketScenariosBase.Get.GetBasketByCustomer(userId)); + var basketUpdated = JsonConvert.DeserializeObject(await basketGetResponse.Content.ReadAsStringAsync()); + + itemUpdated = basketUpdated.Items.Single(pr => pr.ProductId == productId); + + if (itemUpdated.UnitPrice == newPrice) + { + continueLoop = false; + } + else + { + counter++; + await Task.Delay(100); + } + } + + return itemUpdated; + } + + private async Task> GetCatalogAsync(HttpClient catalogClient) + { + var response = await catalogClient.GetAsync(CatalogScenariosBase.Get.Items); + var items = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(items); + } + + private string ChangePrice(BasketItem itemToModify, decimal newPrice) + { + var item = new CatalogItem() + { + Id = int.Parse(itemToModify.ProductId), + Price = newPrice + }; + return JsonConvert.SerializeObject(item); + } + + private CustomerBasket ComposeBasket(string customerId, IEnumerable items) + { + var basket = new CustomerBasket(customerId); + foreach (var item in items) + { + basket.Items.Add(new BasketItem() + { + Id = Guid.NewGuid().ToString(), + UnitPrice = item.Price, + PictureUrl = item.PictureUri, + ProductId = item.Id.ToString(), + OldUnitPrice = 0, + ProductName = item.Name, + Quantity = 1 + }); + } + return basket; + } + } +} \ No newline at end of file diff --git a/test/Services/FunctionalTests/appsettings.json b/test/Services/FunctionalTests/appsettings.json new file mode 100644 index 000000000..2b1bdb46a --- /dev/null +++ b/test/Services/FunctionalTests/appsettings.json @@ -0,0 +1,6 @@ +{ + "ConnectionString": "127.0.0.1", + "IdentityUrl": "http://localhost:5105", + "isTest": "true", + "EventBusConnection": "localhost" +}