Browse Source

Merge pull request #1884 from szukuro/dev

Adding various language enhancements, removing unused/duplicated extension
pull/1892/merge
Tarun Jain 2 years ago
committed by GitHub
parent
commit
59805331cd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 440 additions and 664 deletions
  1. +1
    -1
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs
  2. +1
    -1
      src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs
  3. +46
    -53
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  4. +18
    -21
      src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs
  5. +3
    -5
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Utilities/ResilientTransaction.cs
  6. +34
    -36
      src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs
  7. +1
    -1
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/AuthorizeCheckOperationFilter.cs
  8. +17
    -23
      src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs
  9. +22
    -26
      src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs
  10. +11
    -33
      src/Services/Catalog/Catalog.API/Controllers/PicController.cs
  11. +0
    -122
      src/Services/Catalog/Catalog.API/Extensions/HostExtensions.cs
  12. +33
    -35
      src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs
  13. +21
    -21
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs
  14. +50
    -72
      src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarios.cs
  15. +2
    -2
      src/Services/Catalog/Catalog.FunctionalTests/GlobalUsings.cs
  16. +10
    -12
      src/Services/Catalog/Catalog.UnitTests/Application/CatalogControllerTest.cs
  17. +7
    -11
      src/Services/Identity/Identity.API/Certificate/Certificate.cs
  18. +11
    -13
      src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs
  19. +35
    -37
      src/Services/Identity/Identity.API/IWebHostExtensions.cs
  20. +1
    -1
      src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs
  21. +15
    -21
      src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs
  22. +1
    -1
      src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs
  23. +13
    -15
      src/Services/Ordering/Ordering.BackgroundTasks/Services/GracePeriodManagerService.cs
  24. +14
    -20
      src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs
  25. +1
    -1
      src/Services/Webhooks/Webhooks.API/Infrastructure/AuthorizeCheckOperationFilter.cs
  26. +30
    -32
      src/Tests/Services/Application.FunctionalTests/Services/IntegrationEventsScenarios.cs
  27. +20
    -22
      src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenarios.cs
  28. +11
    -13
      src/Web/WebMVC/Infrastructure/WebContextSeed.cs
  29. +11
    -13
      src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs

+ 1
- 1
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs View File

@ -22,7 +22,7 @@
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
new()
{
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" }
}


+ 1
- 1
src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs View File

@ -22,7 +22,7 @@
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
new()
{
[ oAuthScheme ] = new[] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" }
}


+ 46
- 53
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -34,17 +34,15 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
_persistentConnection.TryConnect();
}
using (var channel = _persistentConnection.CreateModel())
{
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
using var channel = _persistentConnection.CreateModel();
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
if (_subsManager.IsEmpty)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
if (_subsManager.IsEmpty)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
}
@ -66,32 +64,30 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
_logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, eventName);
using (var channel = _persistentConnection.CreateModel())
{
_logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id);
using var channel = _persistentConnection.CreateModel();
_logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id);
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
var body = JsonSerializer.SerializeToUtf8Bytes(@event, @event.GetType(), new JsonSerializerOptions
{
WriteIndented = true
});
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
policy.Execute(() =>
{
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; // persistent
var body = JsonSerializer.SerializeToUtf8Bytes(@event, @event.GetType(), new JsonSerializerOptions
{
WriteIndented = true
});
policy.Execute(() =>
{
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; // persistent
_logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);
channel.BasicPublish(
exchange: BROKER_NAME,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: body);
});
}
channel.BasicPublish(
exchange: BROKER_NAME,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: body);
});
}
public void SubscribeDynamic<TH>(string eventName)
@ -244,30 +240,27 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
using var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME);
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
{
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
if (subscription.IsDynamic)
{
if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
using dynamic eventData = JsonDocument.Parse(message);
await Task.Yield();
await handler.Handle(eventData);
}
else
{
if (subscription.IsDynamic)
{
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
using dynamic eventData = JsonDocument.Parse(message);
await Task.Yield();
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive= true});
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await Task.Yield();
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await Task.Yield();
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}
}


+ 18
- 21
src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs View File

@ -155,32 +155,29 @@ public class EventBusServiceBus : IEventBus, IDisposable
var processed = false;
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME);
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
{
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
if (subscription.IsDynamic)
{
if (subscription.IsDynamic)
{
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
using dynamic eventData = JsonDocument.Parse(message);
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonSerializer.Deserialize(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
using dynamic eventData = JsonDocument.Parse(message);
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonSerializer.Deserialize(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}
processed = true;
}
processed = true;
return processed;
}


+ 3
- 5
src/BuildingBlocks/EventBus/IntegrationEventLogEF/Utilities/ResilientTransaction.cs View File

@ -15,11 +15,9 @@ public class ResilientTransaction
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = _context.Database.BeginTransaction())
{
await action();
transaction.Commit();
}
using var transaction = _context.Database.BeginTransaction();
await action();
transaction.Commit();
});
}
}

+ 34
- 36
src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs View File

@ -21,48 +21,46 @@ namespace Microsoft.AspNetCore.Hosting
{
var underK8s = webHost.IsInKubernetes();
using (var scope = webHost.Services.CreateScope())
using var scope = webHost.Services.CreateScope();
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
try
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
try
if (underK8s)
{
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
InvokeSeeder(seeder, context, services);
}
else
{
var retries = 10;
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(
retryCount: retries,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
});
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
InvokeSeeder(seeder, context, services);
}
else
{
var retries = 10;
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(
retryCount: retries,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
});
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
catch (Exception ex)
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
}


+ 1
- 1
src/Services/Basket/Basket.API/Infrastructure/Middlewares/AuthorizeCheckOperationFilter.cs View File

@ -20,7 +20,7 @@ public class AuthorizeCheckOperationFilter : IOperationFilter
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
new()
{
[ oAuthScheme ] = new [] { "basketapi" }
}


+ 17
- 23
src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs View File

@ -6,45 +6,39 @@ public class BasketScenarios
[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);
using var server = CreateServer();
var content = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateClient()
.PostAsync(Post.Basket, content);
response.EnsureSuccessStatusCode();
}
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));
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.GetBasket(1));
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Send_Checkout_basket_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
using var server = CreateServer();
var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
await server.CreateClient()
.PostAsync(Post.Basket, contentBasket);
await server.CreateClient()
.PostAsync(Post.Basket, contentBasket);
var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json");
var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateIdempotentClient()
.PostAsync(Post.CheckoutOrder, contentCheckout);
var response = await server.CreateIdempotentClient()
.PostAsync(Post.CheckoutOrder, contentCheckout);
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
string BuildBasket()


+ 22
- 26
src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs View File

@ -9,21 +9,19 @@ namespace Basket.FunctionalTests
[Fact]
public async Task UpdateBasket_return_and_add_basket()
{
using (var server = CreateServer())
{
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
using var server = CreateServer();
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
var redisBasketRepository = BuildBasketRepository(redis);
var redisBasketRepository = BuildBasketRepository(redis);
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
{
BuyerId = "buyerId",
Items = BuildBasketItems()
});
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
{
BuyerId = "buyerId",
Items = BuildBasketItems()
});
Assert.NotNull(basket);
Assert.Single(basket.Items);
}
Assert.NotNull(basket);
Assert.Single(basket.Items);
}
@ -32,25 +30,23 @@ namespace Basket.FunctionalTests
public async Task Delete_Basket_return_null()
{
using (var server = CreateServer())
{
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
using var server = CreateServer();
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
var redisBasketRepository = BuildBasketRepository(redis);
var redisBasketRepository = BuildBasketRepository(redis);
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
{
BuyerId = "buyerId",
Items = BuildBasketItems()
});
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
{
BuyerId = "buyerId",
Items = BuildBasketItems()
});
var deleteResult = await redisBasketRepository.DeleteBasketAsync("buyerId");
var deleteResult = await redisBasketRepository.DeleteBasketAsync("buyerId");
var result = await redisBasketRepository.GetBasketAsync(basket.BuyerId);
var result = await redisBasketRepository.GetBasketAsync(basket.BuyerId);
Assert.True(deleteResult);
Assert.Null(result);
}
Assert.True(deleteResult);
Assert.Null(result);
}
RedisBasketRepository BuildBasketRepository(ConnectionMultiplexer connMux)


+ 11
- 33
src/Services/Catalog/Catalog.API/Controllers/PicController.cs View File

@ -47,40 +47,18 @@ public class PicController : ControllerBase
private string GetImageMimeTypeFromImageFileExtension(string extension)
{
string mimetype;
switch (extension)
string mimetype = extension switch
{
case ".png":
mimetype = "image/png";
break;
case ".gif":
mimetype = "image/gif";
break;
case ".jpg":
case ".jpeg":
mimetype = "image/jpeg";
break;
case ".bmp":
mimetype = "image/bmp";
break;
case ".tiff":
mimetype = "image/tiff";
break;
case ".wmf":
mimetype = "image/wmf";
break;
case ".jp2":
mimetype = "image/jp2";
break;
case ".svg":
mimetype = "image/svg+xml";
break;
default:
mimetype = "application/octet-stream";
break;
}
".png" => "image/png",
".gif" => "image/gif",
".jpg" or ".jpeg" => "image/jpeg",
".bmp" => "image/bmp",
".tiff" => "image/tiff",
".wmf" => "image/wmf",
".jp2" => "image/jp2",
".svg" => "image/svg+xml",
_ => "application/octet-stream",
};
return mimetype;
}
}

+ 0
- 122
src/Services/Catalog/Catalog.API/Extensions/HostExtensions.cs View File

@ -1,122 +0,0 @@
namespace Catalog.API.Extensions;
public static class HostExtensions
{
public static bool IsInKubernetes(this IHost host)
{
var cfg = host.Services.GetService<IConfiguration>();
var orchestratorType = cfg.GetValue<string>("OrchestratorType");
return orchestratorType?.ToUpper() == "K8S";
}
public static IHost MigrateDbContext<TContext>(this IHost host, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
{
var underK8s = host.IsInKubernetes();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
try
{
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
InvokeSeeder(seeder, context, services);
}
else
{
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(8),
});
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
}
}
return host;
}
public static IWebHost MigrateDbContext<TContext>(this IWebHost host, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
{
var underK8s = host.IsInKubernetes();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
try
{
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
InvokeSeeder(seeder, context, services);
}
else
{
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(8),
});
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
}
}
return host;
}
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)
where TContext : DbContext
{
context.Database.Migrate();
seeder(context, services);
}
}

+ 33
- 35
src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs View File

@ -13,48 +13,46 @@ public static class WebHostExtensions
{
var underK8s = host.IsInKubernetes();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
var context = services.GetService<TContext>();
var logger = services.GetRequiredService<ILogger<TContext>>();
try
{
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
var context = services.GetService<TContext>();
if (underK8s)
{
InvokeSeeder(seeder, context, services);
}
else
{
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(8),
});
try
{
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
if (underK8s)
{
InvokeSeeder(seeder, context, services);
}
else
{
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(8),
});
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
catch (Exception ex)
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
}


+ 21
- 21
src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs View File

@ -92,11 +92,11 @@ public class CatalogContextSeed
{
return new List<CatalogBrand>()
{
new CatalogBrand() { Brand = "Azure"},
new CatalogBrand() { Brand = ".NET" },
new CatalogBrand() { Brand = "Visual Studio" },
new CatalogBrand() { Brand = "SQL Server" },
new CatalogBrand() { Brand = "Other" }
new() { Brand = "Azure"},
new() { Brand = ".NET" },
new() { Brand = "Visual Studio" },
new() { Brand = "SQL Server" },
new() { Brand = "Other" }
};
}
@ -147,10 +147,10 @@ public class CatalogContextSeed
{
return new List<CatalogType>()
{
new CatalogType() { Type = "Mug"},
new CatalogType() { Type = "T-Shirt" },
new CatalogType() { Type = "Sheet" },
new CatalogType() { Type = "USB Memory Stick" }
new() { Type = "Mug"},
new() { Type = "T-Shirt" },
new() { Type = "Sheet" },
new() { Type = "USB Memory Stick" }
};
}
@ -297,18 +297,18 @@ public class CatalogContextSeed
{
return new List<CatalogItem>()
{
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" },
new CatalogItem { CatalogTypeId = 1, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureFileName = "2.png" },
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" },
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" },
new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" },
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" },
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" },
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" },
new CatalogItem { CatalogTypeId = 1, CatalogBrandId = 5, AvailableStock = 100, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureFileName = "9.png" },
new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" },
new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureFileName = "11.png" },
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" },
new() { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" },
new() { CatalogTypeId = 1, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureFileName = "2.png" },
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" },
new() { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" },
new() { CatalogTypeId = 3, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" },
new() { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" },
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" },
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" },
new() { CatalogTypeId = 1, CatalogBrandId = 5, AvailableStock = 100, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureFileName = "9.png" },
new() { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" },
new() { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureFileName = "11.png" },
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" },
};
}


+ 50
- 72
src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarios.cs View File

@ -6,135 +6,113 @@ public class CatalogScenarios
[Fact]
public async Task Get_get_all_catalogitems_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.Items());
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.Items());
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_get_catalogitem_by_id_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.ItemById(1));
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.ItemById(1));
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_get_catalogitem_by_id_and_response_bad_request_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.ItemById(int.MinValue));
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.ItemById(int.MinValue));
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task Get_get_catalogitem_by_id_and_response_not_found_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.ItemById(int.MaxValue));
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.ItemById(int.MaxValue));
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task Get_get_catalogitem_by_name_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.ItemByName(".NET"));
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.ItemByName(".NET"));
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_get_paginated_catalogitem_by_name_and_response_ok_status_code()
{
using (var server = CreateServer())
{
const bool paginated = true;
var response = await server.CreateClient()
.GetAsync(Get.ItemByName(".NET", paginated));
response.EnsureSuccessStatusCode();
}
using var server = CreateServer();
const bool paginated = true;
var response = await server.CreateClient()
.GetAsync(Get.ItemByName(".NET", paginated));
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_get_paginated_catalog_items_and_response_ok_status_code()
{
using (var server = CreateServer())
{
const bool paginated = true;
var response = await server.CreateClient()
.GetAsync(Get.Items(paginated));
response.EnsureSuccessStatusCode();
}
using var server = CreateServer();
const bool paginated = true;
var response = await server.CreateClient()
.GetAsync(Get.Items(paginated));
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_get_filtered_catalog_items_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.Filtered(1, 1));
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.Filtered(1, 1));
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_get_paginated_filtered_catalog_items_and_response_ok_status_code()
{
using (var server = CreateServer())
{
const bool paginated = true;
var response = await server.CreateClient()
.GetAsync(Get.Filtered(1, 1, paginated));
response.EnsureSuccessStatusCode();
}
using var server = CreateServer();
const bool paginated = true;
var response = await server.CreateClient()
.GetAsync(Get.Filtered(1, 1, paginated));
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_catalog_types_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.Types);
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.Types);
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Get_catalog_brands_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.Brands);
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.Brands);
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
}

+ 2
- 2
src/Services/Catalog/Catalog.FunctionalTests/GlobalUsings.cs View File

@ -1,7 +1,7 @@
global using Catalog.API.Extensions;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.TestHost;
global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
global using Microsoft.eShopOnContainers.Services.Catalog.API.Extensions;
global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
global using Microsoft.eShopOnContainers.Services.Catalog.API;
global using Microsoft.Extensions.Configuration;


+ 10
- 12
src/Services/Catalog/Catalog.UnitTests/Application/CatalogControllerTest.cs View File

@ -25,11 +25,9 @@ public class CatalogControllerTest
.UseInMemoryDatabase(databaseName: "in-memory")
.Options;
using (var dbContext = new CatalogContext(_dbOptions))
{
dbContext.AddRange(GetFakeCatalog());
dbContext.SaveChanges();
}
using var dbContext = new CatalogContext(_dbOptions);
dbContext.AddRange(GetFakeCatalog());
dbContext.SaveChanges();
}
[Fact]
@ -66,7 +64,7 @@ public class CatalogControllerTest
{
return new List<CatalogItem>()
{
new CatalogItem()
new()
{
Id = 1,
Name = "fakeItemA",
@ -74,7 +72,7 @@ public class CatalogControllerTest
CatalogBrandId = 1,
PictureFileName = "fakeItemA.png"
},
new CatalogItem()
new()
{
Id = 2,
Name = "fakeItemB",
@ -82,7 +80,7 @@ public class CatalogControllerTest
CatalogBrandId = 1,
PictureFileName = "fakeItemB.png"
},
new CatalogItem()
new()
{
Id = 3,
Name = "fakeItemC",
@ -90,7 +88,7 @@ public class CatalogControllerTest
CatalogBrandId = 1,
PictureFileName = "fakeItemC.png"
},
new CatalogItem()
new()
{
Id = 4,
Name = "fakeItemD",
@ -98,7 +96,7 @@ public class CatalogControllerTest
CatalogBrandId = 1,
PictureFileName = "fakeItemD.png"
},
new CatalogItem()
new()
{
Id = 5,
Name = "fakeItemE",
@ -106,7 +104,7 @@ public class CatalogControllerTest
CatalogBrandId = 1,
PictureFileName = "fakeItemE.png"
},
new CatalogItem()
new()
{
Id = 6,
Name = "fakeItemF",
@ -120,7 +118,7 @@ public class CatalogControllerTest
public class TestCatalogSettings : IOptionsSnapshot<CatalogSettings>
{
public CatalogSettings Value => new CatalogSettings
public CatalogSettings Value => new()
{
PicBaseUrl = "http://image-server.com/",
AzureStorageEnabled = true


+ 7
- 11
src/Services/Identity/Identity.API/Certificate/Certificate.cs View File

@ -12,24 +12,20 @@
* real environment the certificate should be created and stored in a secure way, which is out
* of the scope of this project.
**********************************************************************************************/
using (var stream = assembly.GetManifestResourceStream("Identity.API.Certificate.idsrv3test.pfx"))
{
return new X509Certificate2(ReadStream(stream), "idsrv3test");
}
using var stream = assembly.GetManifestResourceStream("Identity.API.Certificate.idsrv3test.pfx");
return new X509Certificate2(ReadStream(stream), "idsrv3test");
}
private static byte[] ReadStream(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
using MemoryStream ms = new MemoryStream();
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}

+ 11
- 13
src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs View File

@ -191,23 +191,21 @@
string imagePath = Path.Combine(webroot, "images");
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read);
foreach (ZipArchiveEntry entry in zip.Entries)
{
foreach (ZipArchiveEntry entry in zip.Entries)
if (imageFiles.Contains(entry.Name))
{
if (imageFiles.Contains(entry.Name))
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
}
}
}


+ 35
- 37
src/Services/Identity/Identity.API/IWebHostExtensions.cs View File

@ -13,52 +13,50 @@ namespace Microsoft.AspNetCore.Hosting
{
var underK8s = webHost.IsInKubernetes();
using (var scope = webHost.Services.CreateScope())
using var scope = webHost.Services.CreateScope();
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
try
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
try
if (underK8s)
{
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
InvokeSeeder(seeder, context, services);
}
else
{
var retries = 10;
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(
retryCount: retries,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
});
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
InvokeSeeder(seeder, context, services);
}
else
{
var retries = 10;
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(
retryCount: retries,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
});
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
catch (Exception ex)
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
}
return webHost;
return webHost;
}
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)


+ 1
- 1
src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs View File

@ -35,7 +35,7 @@ public class TransactionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequ
{
Guid transactionId;
using (>n class="kt">var transaction = await _dbContext.BeginTransactionAsync())
using var transaction = await _dbContext.BeginTransactionAsync();
using (LogContext.PushProperty("TransactionContext", transaction.TransactionId))
{
_logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request);


+ 15
- 21
src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs View File

@ -13,12 +13,11 @@ public class OrderQueries
public async Task<Order> GetOrderAsync(int id)
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
using var connection = new SqlConnection(_connectionString);
connection.Open();
var result = await connection.QueryAsync<dynamic>(
@"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
var result = await connection.QueryAsync<dynamic>(
@"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
o.Address_City as city, o.Address_Country as country, o.Address_State as state, o.Address_Street as street, o.Address_ZipCode as zipcode,
os.Name as status,
oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl
@ -26,23 +25,21 @@ public class OrderQueries
LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid
LEFT JOIN ordering.orderstatus os on o.OrderStatusId = os.Id
WHERE o.Id=@id"
, new { id }
);
, new { id }
);
if (result.AsList().Count == 0)
throw new KeyNotFoundException();
if (result.AsList().Count == 0)
throw new KeyNotFoundException();
return MapOrderItems(result);
}
return MapOrderItems(result);
}
public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId)
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
using var connection = new SqlConnection(_connectionString);
connection.Open();
return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
FROM [ordering].[Orders] o
LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid
LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
@ -50,17 +47,14 @@ public class OrderQueries
WHERE ob.IdentityGuid = @userId
GROUP BY o.[Id], o.[OrderDate], os.[Name]
ORDER BY o.[Id]", new { userId });
}
}
public async Task<IEnumerable<CardType>> GetCardTypesAsync()
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
using var connection = new SqlConnection(_connectionString);
connection.Open();
return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes");
}
return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes");
}
private Order MapOrderItems(dynamic result)


+ 1
- 1
src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs View File

@ -20,7 +20,7 @@ public class AuthorizeCheckOperationFilter : IOperationFilter
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
new()
{
[ oAuthScheme ] = new [] { "orderingapi" }
}


+ 13
- 15
src/Services/Ordering/Ordering.BackgroundTasks/Services/GracePeriodManagerService.cs View File

@ -63,24 +63,22 @@ namespace Ordering.BackgroundTasks.Services
{
IEnumerable<int> orderIds = new List<int>();
using (var conn = new SqlConnection(_settings.ConnectionString))
using var conn = new SqlConnection(_settings.ConnectionString);
try
{
try
{
conn.Open();
orderIds = conn.Query<int>(
@"SELECT Id FROM [ordering].[orders]
WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime
AND [OrderStatusId] = 1",
new { _settings.GracePeriodTime });
}
catch (SqlException exception)
{
_logger.LogCritical(exception, "FATAL ERROR: Database connections could not be opened: {Message}", exception.Message);
}
conn.Open();
orderIds = conn.Query<int>(
@"SELECT Id FROM [ordering].[orders]
WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime
AND [OrderStatusId] = 1",
new { _settings.GracePeriodTime });
}
catch (SqlException exception)
{
_logger.LogCritical(exception, "FATAL ERROR: Database connections could not be opened: {Message}", exception.Message);
}
return orderIds;
}
}


+ 14
- 20
src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs View File

@ -14,39 +14,33 @@ namespace Ordering.FunctionalTests
[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);
using var server = CreateServer();
var response = await server.CreateClient()
.GetAsync(Get.Orders);
response.EnsureSuccessStatusCode();
}
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Cancel_order_no_order_created_bad_request_response()
{
using (var server = CreateServer())
{
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateIdempotentClient()
.PutAsync(Put.CancelOrder, content);
using var server = CreateServer();
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateIdempotentClient()
.PutAsync(Put.CancelOrder, content);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task Ship_order_no_order_created_bad_request_response()
{
using (var server = CreateServer())
{
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateIdempotentClient()
.PutAsync(Put.ShipOrder, content);
using var server = CreateServer();
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
var response = await server.CreateIdempotentClient()
.PutAsync(Put.ShipOrder, content);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
string BuildOrder()


+ 1
- 1
src/Services/Webhooks/Webhooks.API/Infrastructure/AuthorizeCheckOperationFilter.cs View File

@ -20,7 +20,7 @@ public class AuthorizeCheckOperationFilter : IOperationFilter
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
new()
{
[ oAuthScheme ] = new [] { "webhooksapi" }
}


+ 30
- 32
src/Tests/Services/Application.FunctionalTests/Services/IntegrationEventsScenarios.cs View File

@ -11,45 +11,43 @@ public class IntegrationEventsScenarios
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();
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);
// 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(JsonSerializer.Serialize(basket), UTF8Encoding.UTF8, "application/json")
);
// 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(JsonSerializer.Serialize(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.PutAsync(CatalogScenariosBase.Put.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), 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.PutAsync(CatalogScenariosBase.Put.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), UTF8Encoding.UTF8, "application/json"));
var modifiedCatalogProducts = await GetCatalogAsync(catalogClient);
var modifiedCatalogProducts = await GetCatalogAsync(catalogClient);
var itemUpdated = await GetUpdatedBasketItem(newPrice, itemToModify.ProductId, userId, basketClient);
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 == itemToModify.ProductId).Price);
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 == 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);
}
// AND the products in the basket reflects the changed priced and the original price
Assert.Equal(newPrice, itemUpdated.UnitPrice);
Assert.Equal(oldPrice, itemUpdated.OldUnitPrice);
}
}


+ 20
- 22
src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenarios.cs View File

@ -5,35 +5,33 @@ public class OrderingScenarios : OrderingScenariosBase
[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";
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();
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);
// 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.CheckoutOrder, new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json"));
// AND basket checkout is sent
await basketClient.PostAsync(BasketScenariosBase.Post.CheckoutOrder, new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json"));
// WHEN Order is created in Ordering.api
var newOrder = await TryGetNewOrderCreated(cityExpected, orderClient);
// 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 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
var order = await TryGetOrder(newOrder.OrderNumber, orderClient);
// AND the requested order is retrieved
var order = await TryGetOrder(newOrder.OrderNumber, orderClient);
// THEN check status
Assert.Equal(orderStatusExpected, order.Status);
}
// THEN check status
Assert.Equal(orderStatusExpected, order.Status);
}
async Task<Order> TryGetOrder(string orderNumber, HttpClient orderClient)


+ 11
- 13
src/Web/WebMVC/Infrastructure/WebContextSeed.cs View File

@ -56,23 +56,21 @@ public class WebContextSeed
string imagePath = Path.Combine(webroot, "images");
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read);
foreach (ZipArchiveEntry entry in zip.Entries)
{
foreach (ZipArchiveEntry entry in zip.Entries)
if (imageFiles.Contains(entry.Name))
{
if (imageFiles.Contains(entry.Name))
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
log.Warning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
log.Warning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
}
}
}


+ 11
- 13
src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs View File

@ -39,23 +39,21 @@ public class WebContextSeed
}
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read);
foreach (ZipArchiveEntry entry in zip.Entries)
{
foreach (ZipArchiveEntry entry in zip.Entries)
if (!imageFiles.Contains(entry.Name))
{
if (!imageFiles.Contains(entry.Name))
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
log.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
log.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
}
}
}


Loading…
Cancel
Save