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
|
image: redis
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq
|
||||||
|
ports:
|
||||||
|
- "5672:5672"
|
||||||
|
@ -14,7 +14,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||||
{
|
{
|
||||||
public class EventBusRabbitMQ : IEventBus
|
public class EventBusRabbitMQ : IEventBus, IDisposable
|
||||||
{
|
{
|
||||||
private readonly string _brokerName = "eshop_event_bus";
|
private readonly string _brokerName = "eshop_event_bus";
|
||||||
private readonly string _connectionString;
|
private readonly string _connectionString;
|
||||||
@ -31,6 +31,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
|||||||
_handlers = new Dictionary<string, List<IIntegrationEventHandler>>();
|
_handlers = new Dictionary<string, List<IIntegrationEventHandler>>();
|
||||||
_eventTypes = new List<Type>();
|
_eventTypes = new List<Type>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Publish(IntegrationEvent @event)
|
public void Publish(IntegrationEvent @event)
|
||||||
{
|
{
|
||||||
var eventName = @event.GetType().Name;
|
var eventName = @event.GetType().Name;
|
||||||
@ -92,14 +93,24 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
|||||||
if (_handlers.Keys.Count == 0)
|
if (_handlers.Keys.Count == 0)
|
||||||
{
|
{
|
||||||
_queueName = string.Empty;
|
_queueName = string.Empty;
|
||||||
_connection.Item1.Close();
|
_connection.Item1.Dispose();
|
||||||
_connection.Item2.Close();
|
_connection.Item2.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_connection != null)
|
||||||
|
{
|
||||||
|
_handlers.Clear();
|
||||||
|
_connection.Item1.Dispose();
|
||||||
|
_connection.Item2.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private IModel GetChannel()
|
private IModel GetChannel()
|
||||||
{
|
{
|
||||||
if (_connection != null)
|
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)
|
foreach (var id in userIds)
|
||||||
{
|
{
|
||||||
var basket = await _repository.GetBasket(id);
|
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)
|
foreach (var item in itemsToUpdate)
|
||||||
{
|
{
|
||||||
var originalPrice = item.UnitPrice;
|
if(item.UnitPrice != newPrice)
|
||||||
item.UnitPrice = newPrice;
|
{
|
||||||
item.OldUnitPrice = originalPrice;
|
var originalPrice = item.UnitPrice;
|
||||||
|
item.UnitPrice = newPrice;
|
||||||
|
item.OldUnitPrice = originalPrice;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await _repository.UpdateBasket(basket);
|
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.Events;
|
||||||
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
|
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||||
{
|
{
|
||||||
@ -80,13 +81,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
|||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
|
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
|
||||||
var eventBus = new EventBusRabbitMQ(configuration.EventBusConnection);
|
services.AddSingleton<IEventBus>(provider => new EventBusRabbitMQ(configuration.EventBusConnection));
|
||||||
services.AddSingleton<IEventBus>(eventBus);
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
// 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
|
// Use frameworks
|
||||||
app.UseCors("CorsPolicy");
|
app.UseCors("CorsPolicy");
|
||||||
|
|
||||||
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
|
ConfigureAuth(app);
|
||||||
|
|
||||||
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
|
||||||
{
|
|
||||||
Authority = identityUrl.ToString(),
|
|
||||||
ScopeName = "basket",
|
|
||||||
RequireHttpsMetadata = false
|
|
||||||
});
|
|
||||||
|
|
||||||
app.UseMvcWithDefaultRoute();
|
app.UseMvcWithDefaultRoute();
|
||||||
|
|
||||||
app.UseSwagger()
|
app.UseSwagger()
|
||||||
.UseSwaggerUi();
|
.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>
|
||||||
|
|
||||||
<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\Catalog\Catalog.API\Catalog.API.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
|
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
|
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="settings.json">
|
<None Update="settings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</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 Orders = "api/v1/orders";
|
||||||
|
|
||||||
|
public static string Items = "api/v1/catalog/items";
|
||||||
|
|
||||||
public static string ProductByName(string name)
|
public static string ProductByName(string name)
|
||||||
{
|
{
|
||||||
return $"api/v1/catalog/items/withname/{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