Merge branch 'dev' of https://github.com/dotnet/eShopOnContainers into dev
This commit is contained in:
commit
cfecbc5dba
@ -8,3 +8,8 @@ services:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq
|
||||
ports:
|
||||
- "5672:5672"
|
||||
|
@ -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<string, List<IIntegrationEventHandler>>();
|
||||
_eventTypes = new List<Type>();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<IOptionsSnapshot<BasketSettings>>().Value;
|
||||
var eventBus = new EventBusRabbitMQ(configuration.EventBusConnection);
|
||||
services.AddSingleton<IEventBus>(eventBus);
|
||||
services.AddSingleton<IEventBus>(provider => new EventBusRabbitMQ(configuration.EventBusConnection));
|
||||
|
||||
|
||||
var catalogPriceHandler = serviceProvider.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
|
||||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(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<string>("IdentityUrl");
|
||||
|
||||
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
||||
{
|
||||
Authority = identityUrl.ToString(),
|
||||
ScopeName = "basket",
|
||||
RequireHttpsMetadata = false
|
||||
});
|
||||
ConfigureAuth(app);
|
||||
|
||||
app.UseMvcWithDefaultRoute();
|
||||
|
||||
app.UseSwagger()
|
||||
.UseSwaggerUi();
|
||||
|
||||
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
|
||||
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
||||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
|
||||
|
||||
}
|
||||
|
||||
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||
{
|
||||
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
|
||||
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
||||
{
|
||||
Authority = identityUrl.ToString(),
|
||||
ScopeName = "basket",
|
||||
RequireHttpsMetadata = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,16 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
@ -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<BasketTestsStartup>();
|
||||
|
||||
return new TestServer(webHostBuilder);
|
||||
}
|
||||
|
||||
public static class Get
|
||||
{
|
||||
public static string GetBasketByCustomer(string customerId)
|
||||
{
|
||||
return $"/{customerId}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class Post
|
||||
{
|
||||
public static string CreateBasket = "/";
|
||||
}
|
||||
}
|
||||
}
|
@ -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<AutoAuthorizeMiddleware>();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ConfigureAuth(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<PaginatedItemsViewModel<CatalogItem>>(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"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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}";
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -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<BasketItem> 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<CustomerBasket>(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<PaginatedItemsViewModel<CatalogItem>> GetCatalogAsync(HttpClient catalogClient)
|
||||
{
|
||||
var response = await catalogClient.GetAsync(CatalogScenariosBase.Get.Items);
|
||||
var items = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<PaginatedItemsViewModel<CatalogItem>>(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<CatalogItem> 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;
|
||||
}
|
||||
}
|
||||
}
|
6
test/Services/FunctionalTests/appsettings.json
Normal file
6
test/Services/FunctionalTests/appsettings.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ConnectionString": "127.0.0.1",
|
||||
"IdentityUrl": "http://localhost:5105",
|
||||
"isTest": "true",
|
||||
"EventBusConnection": "localhost"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user