Browse Source

Refactor to event bus to support dynamic subscriptions to events

Checkout HTTP entrypoint in Basket API
pull/223/head
Eduard Tomas 7 years ago
parent
commit
f297f8b6fc
18 changed files with 347 additions and 75 deletions
  1. +13
    -0
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IDynamicIntegrationEventHandler.cs
  2. +6
    -0
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
  3. +0
    -4
      src/BuildingBlocks/EventBus/EventBus/EventBus.csproj
  4. +13
    -5
      src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs
  5. +80
    -33
      src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs
  6. +28
    -0
      src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs
  7. +3
    -1
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/CommandBusRabbitMQ.cs
  8. +37
    -23
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  9. +29
    -2
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  10. +21
    -0
      src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAccepted.cs
  11. +1
    -1
      src/Services/Basket/Basket.API/Model/Basket.cs
  12. +12
    -0
      src/Services/Basket/Basket.API/Services/IIdentityService.cs
  13. +24
    -0
      src/Services/Basket/Basket.API/Services/IdentityService.cs
  14. +16
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs
  15. +0
    -1
      src/Services/Ordering/Ordering.API/Ordering.API.csproj
  16. +14
    -1
      src/Services/Ordering/Ordering.API/Startup.cs
  17. +49
    -4
      test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs
  18. +1
    -0
      test/Services/UnitTest/UnitTest.csproj

+ 13
- 0
src/BuildingBlocks/EventBus/EventBus/Abstractions/IDynamicIntegrationEventHandler.cs View File

@ -0,0 +1,13 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IDynamicIntegrationEventHandler
{
Task Handle(dynamic eventData);
}
}

+ 6
- 0
src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs View File

@ -8,6 +8,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
void Subscribe<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void SubscribeDynamic<TH>(string eventName, Func<TH> handler)
where TH : IDynamicIntegrationEventHandler;
void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;


+ 0
- 4
src/BuildingBlocks/EventBus/EventBus/EventBus.csproj View File

@ -6,10 +6,6 @@
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBus</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Folder Include="Abstractions\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>


+ 13
- 5
src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using static Microsoft.eShopOnContainers.BuildingBlocks.EventBus.InMemoryEventBusSubscriptionsManager;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
@ -9,18 +10,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
bool IsEmpty { get; }
event EventHandler<string> OnEventRemoved;
void AddDynamicSubscription<TH>(string eventName, Func<TH> handler)
where TH : IDynamicIntegrationEventHandler;
void AddSubscription<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
bool HasSubscriptionsForEvent(string eventName);
Type GetEventTypeByName(string eventName);
void Clear();
IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<Delegate> GetHandlersForEvent(string eventName);
IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName);
string GetEventKey<T>();
}
}

+ 80
- 33
src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs View File

@ -8,64 +8,99 @@ using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{
private readonly Dictionary<string, List<Delegate>> _handlers;
private readonly Dictionary<string, List<SubscriptionInfo>> _handlers;
private readonly List<Type> _eventTypes;
public event EventHandler<string> OnEventRemoved;
public InMemoryEventBusSubscriptionsManager()
{
_handlers = new Dictionary<string, List<Delegate>>();
_handlers = new Dictionary<string, List<SubscriptionInfo>>();
_eventTypes = new List<Type>();
}
public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear();
public void AddSubscription<T, TH>(Func<TH> handler)
public void AddDynamicSubscription<TH>(string eventName, Func<TH> handler)
where TH : IDynamicIntegrationEventHandler
{
DoAddSubscription(handler, eventName, isDynamic: true);
}
public void AddSubscription<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var key = GetEventKey<T>();
if (!HasSubscriptionsForEvent<T>())
var eventName = GetEventKey<T>();
DoAddSubscription(handler, eventName, isDynamic: false);
_eventTypes.Add(typeof(T));
}
private void DoAddSubscription(Delegate handler, string eventName, bool isDynamic)
{
if (!HasSubscriptionsForEvent(eventName))
{
_handlers.Add(key, new List<Delegate>());
_handlers.Add(eventName, new List<SubscriptionInfo>());
}
_handlers[key].Add(handler);
_eventTypes.Add(typeof(T));
if (isDynamic)
{
_handlers[eventName].Add(SubscriptionInfo.Dynamic(handler));
}
else
{
_handlers[eventName].Add(SubscriptionInfo.Typed(handler));
}
}
public void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName);
DoRemoveHandler(eventName, handlerToRemove);
}
public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent
{
var handlerToRemove = FindHandlerToRemove<T, TH>();
if (handlerToRemove != null)
var handlerToRemove = FindSubscriptionToRemove<T, TH>();
var eventName = GetEventKey<T>();
DoRemoveHandler(eventName, handlerToRemove);
}
private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove)
{
if (subsToRemove != null)
{
var key = GetEventKey<T>();
_handlers[key].Remove(handlerToRemove);
if (!_handlers[key].Any())
_handlers[eventName].Remove(subsToRemove);
if (!_handlers[eventName].Any())
{
_handlers.Remove(key);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key);
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_eventTypes.Remove(eventType);
RaiseOnEventRemoved(eventType.Name);
}
RaiseOnEventRemoved(eventName);
}
}
}
public IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent
public IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return GetHandlersForEvent(key);
}
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName];
public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName];
private void RaiseOnEventRemoved(string eventName)
{
@ -76,22 +111,34 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
}
}
private Delegate FindHandlerToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
private SubscriptionInfo FindDynamicSubscriptionToRemove<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
if (!HasSubscriptionsForEvent<T>())
return DoFindHandlerToRemove(eventName, typeof(TH));
}
private SubscriptionInfo FindSubscriptionToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = GetEventKey<T>();
return DoFindHandlerToRemove(eventName, typeof(TH));
}
private SubscriptionInfo DoFindHandlerToRemove(string eventName, Type handlerType)
{
if (!HasSubscriptionsForEvent(eventName))
{
return null;
}
var key = GetEventKey<T>();
foreach (var func in _handlers[key])
foreach (var subscription in _handlers[eventName])
{
var genericArgs = func.GetType().GetGenericArguments();
if (genericArgs.SingleOrDefault() == typeof(TH))
var genericArgs = subscription.Factory.GetType().GetGenericArguments();
if (genericArgs.SingleOrDefault() == handlerType)
{
return func;
return subscription;
}
}
@ -104,10 +151,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
return HasSubscriptionsForEvent(key);
}
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName);
private string GetEventKey<T>()
public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName);
public string GetEventKey<T>()
{
return typeof(T).Name;
}


+ 28
- 0
src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs View File

@ -0,0 +1,28 @@
using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{
public class SubscriptionInfo
{
public bool IsDynamic { get; }
public Delegate Factory { get; }
private SubscriptionInfo(bool isDynamic, Delegate factory)
{
IsDynamic = isDynamic;
Factory = factory;
}
public static SubscriptionInfo Dynamic(Delegate factory)
{
return new SubscriptionInfo(true, factory);
}
public static SubscriptionInfo Typed(Delegate factory)
{
return new SubscriptionInfo(false, factory);
}
}
}
}

+ 3
- 1
src/BuildingBlocks/EventBus/EventBusRabbitMQ/CommandBusRabbitMQ.cs View File

@ -1,4 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.CommandBus;
//using Microsoft.eShopOnContainers.BuildingBlocks.CommandBus;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Polly;
@ -12,6 +12,7 @@ using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
/*
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class CommandBusRabbitMQ : ICommandBus, IDisposable
@ -141,3 +142,4 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
}
}
*/

+ 37
- 23
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -3,6 +3,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Polly;
using Polly.Retry;
using RabbitMQ.Client;
@ -96,12 +97,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
}
}
public void SubscribeDynamic<TH>(string eventName, Func<TH> handler)
where TH: IDynamicIntegrationEventHandler
{
DoInternalSubscription(eventName);
_subsManager.AddDynamicSubscription<TH>(eventName,handler);
}
public void Subscribe<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = typeof(T).Name;
var containsKey = _subsManager.HasSubscriptionsForEvent<T>();
var eventName = _subsManager.GetEventKey<T>();
DoInternalSubscription(eventName);
_subsManager.AddSubscription<T, TH>(handler);
}
private void DoInternalSubscription(string eventName)
{
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
if (!containsKey)
{
if (!_persistentConnection.IsConnected)
@ -116,9 +130,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
routingKey: eventName);
}
}
_subsManager.AddSubscription<T, TH>(handler);
}
public void Unsubscribe<T, TH>()
@ -128,19 +139,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_subsManager.RemoveSubscription<T, TH>();
}
private static Func<IIntegrationEventHandler> FindHandlerByType(Type handlerType, IEnumerable<Func<IIntegrationEventHandler>> handlers)
public void UnsubscribeDynamic<TH>(string eventName)
where TH: IDynamicIntegrationEventHandler
{
foreach (var func in handlers)
{
if (func.GetMethodInfo().ReturnType == handlerType)
{
return func;
}
}
return null;
_subsManager.RemoveDynamicSubscription<TH>(eventName);
}
public void Dispose()
{
if (_consumerChannel != null)
@ -191,16 +196,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handlers = _subsManager.GetHandlersForEvent(eventName);
{
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var handlerfactory in handlers)
foreach (var subscription in subscriptions)
{
var handler = handlerfactory.DynamicInvoke();
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
if (subscription.IsDynamic)
{
var handler = subscription.Factory.DynamicInvoke() as IDynamicIntegrationEventHandler;
dynamic eventData = JObject.Parse(message);
await handler.Handle(eventData);
}
else
{
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handler = subscription.Factory.DynamicInvoke();
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}
}
}


+ 29
- 2
src/Services/Basket/Basket.API/Controllers/BasketController.cs View File

@ -5,6 +5,10 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Services;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
{
@ -16,11 +20,17 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
[Authorize]
public class BasketController : Controller
{
private IBasketRepository _repository;
private readonly IBasketRepository _repository;
private readonly IIdentityService _identitySvc;
private readonly IEventBus _eventBus;
public BasketController(IBasketRepository repository)
public BasketController(IBasketRepository repository,
IIdentityService identityService,
IEventBus eventBus)
{
_repository = repository;
_identitySvc = identityService;
_eventBus = eventBus;
}
// GET api/values/5
[HttpGet("{id}")]
@ -40,11 +50,28 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
return Ok(basket);
}
[HttpPost]
public async Task<IActionResult> Checkout()
{
var userId = _identitySvc.GetUserIdentity();
var basket = await _repository.GetBasketAsync(userId);
_eventBus.Publish(new UserCheckoutAccepted(userId, basket));
if (basket == null)
{
return BadRequest();
}
return Accepted();
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(string id)
{
_repository.DeleteBasketAsync(id);
}
}
}

+ 21
- 0
src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAccepted.cs View File

@ -0,0 +1,21 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basket.API.IntegrationEvents.Events
{
public class UserCheckoutAccepted : IntegrationEvent
{
public string UserId {get; }
CustomerBasket Basket { get; }
public UserCheckoutAccepted(string userId, CustomerBasket basket)
{
UserId = userId;
Basket = basket;
}
}
}

+ 1
- 1
src/Services/Basket/Basket.API/Model/Basket.cs View File

@ -13,7 +13,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
public CustomerBasket(string customerId)
{
BuyerId = customerId;
Items = new List<Model.BasketItem>();
Items = new List<BasketItem>();
}
}
}

+ 12
- 0
src/Services/Basket/Basket.API/Services/IIdentityService.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services
{
public interface IIdentityService
{
string GetUserIdentity();
}
}

+ 24
- 0
src/Services/Basket/Basket.API/Services/IdentityService.cs View File

@ -0,0 +1,24 @@

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services
{
public class IdentityService : IIdentityService
{
private IHttpContextAccessor _context;
public IdentityService(IHttpContextAccessor context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public string GetUserIdentity()
{
return _context.HttpContext.User.FindFirst("sub").Value;
}
}
}

+ 16
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs View File

@ -0,0 +1,16 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
public class UserCheckoutAcceptedIntegrationEventHandler : IDynamicIntegrationEventHandler
{
public async Task Handle(dynamic eventData)
{
int i = 0;
}
}
}

+ 0
- 1
src/Services/Ordering/Ordering.API/Ordering.API.csproj View File

@ -80,7 +80,6 @@
<ItemGroup>
<Folder Include="Application\IntegrationCommands\CommandHandlers\" />
<Folder Include="Application\IntegrationEvents\EventHandling\" />
<Folder Include="Infrastructure\IntegrationEventMigrations\" />
</ItemGroup>


+ 14
- 1
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -4,6 +4,7 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using global::Ordering.API.Application.IntegrationEvents;
using global::Ordering.API.Application.IntegrationEvents.EventHandling;
using global::Ordering.API.Infrastructure.Middlewares;
using Infrastructure;
using Infrastructure.Auth;
@ -120,9 +121,10 @@
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddTransient<UserCheckoutAcceptedIntegrationEventHandler>();
services.AddOptions();
//configure autofac
@ -136,6 +138,7 @@
return new AutofacServiceProvider(container.Build());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
@ -146,6 +149,7 @@
app.UseFailingMiddleware();
ConfigureAuth(app);
ConfigureEventBus(app);
app.UseMvcWithDefaultRoute();
@ -161,6 +165,15 @@
integrationEventLogContext.Database.Migrate();
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.SubscribeDynamic(
"UserCheckoutAccepted",
() => app.ApplicationServices.GetRequiredService<UserCheckoutAcceptedIntegrationEventHandler>());
}
protected virtual void ConfigureAuth(IApplicationBuilder app)
{
var identityUrl = Configuration.GetValue<string>("IdentityUrl");


+ 49
- 4
test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs View File

@ -1,20 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Basket.API.IntegrationEvents.Events;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.Controllers;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Moq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
using IBasketIdentityService = Microsoft.eShopOnContainers.Services.Basket.API.Services.IIdentityService;
namespace UnitTest.Basket.Application
{
public class BasketWebApiTest
{
private readonly Mock<IBasketRepository> _basketRepositoryMock;
private readonly Mock<IBasketIdentityService> _identityServiceMock;
private readonly Mock<IEventBus> _serviceBusMock;
public BasketWebApiTest()
{
_basketRepositoryMock = new Mock<IBasketRepository>();
_identityServiceMock = new Mock<IBasketIdentityService>();
_serviceBusMock = new Mock<IEventBus>();
}
[Fact]
@ -26,9 +33,12 @@ namespace UnitTest.Basket.Application
_basketRepositoryMock.Setup(x => x.GetBasketAsync(It.IsAny<string>()))
.Returns(Task.FromResult(fakeCustomerBasket));
_identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId);
_serviceBusMock.Setup(x => x.Publish(It.IsAny<UserCheckoutAccepted>()));
//Act
var basketController = new BasketController(_basketRepositoryMock.Object);
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
var actionResult = await basketController.Get(fakeCustomerId) as OkObjectResult;
//Assert
@ -45,9 +55,12 @@ namespace UnitTest.Basket.Application
_basketRepositoryMock.Setup(x => x.UpdateBasketAsync(It.IsAny<CustomerBasket>()))
.Returns(Task.FromResult(fakeCustomerBasket));
_identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId);
_serviceBusMock.Setup(x => x.Publish(It.IsAny<UserCheckoutAccepted>()));
//Act
var basketController = new BasketController(_basketRepositoryMock.Object);
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
var actionResult = await basketController.Post(fakeCustomerBasket) as OkObjectResult;
//Assert
@ -55,6 +68,38 @@ namespace UnitTest.Basket.Application
Assert.Equal(((CustomerBasket)actionResult.Value).BuyerId, fakeCustomerId);
}
[Fact]
public async Task Doing_Checkout_Without_Basket_Should_Return_Bad_Request()
{
var fakeCustomerId = "2";
_basketRepositoryMock.Setup(x => x.GetBasketAsync(It.IsAny<string>()))
.Returns(Task.FromResult((CustomerBasket)null));
_identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId);
//Act
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
var result = await basketController.Checkout() as BadRequestResult;
Assert.NotNull(result);
}
[Fact]
public async Task Doing_Checkout_Wit_Basket_Should_Publish_UserCheckoutAccepted_Integration_Event()
{
var fakeCustomerId = "1";
var fakeCustomerBasket = GetCustomerBasketFake(fakeCustomerId);
_basketRepositoryMock.Setup(x => x.GetBasketAsync(It.IsAny<string>()))
.Returns(Task.FromResult(fakeCustomerBasket));
_identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId);
//Act
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
var result = await basketController.Checkout() as AcceptedResult;
_serviceBusMock.Verify(mock => mock.Publish(It.IsAny<UserCheckoutAccepted>()), Times.Once);
Assert.NotNull(result);
}
private CustomerBasket GetCustomerBasketFake(string fakeCustomerId)
{
return new CustomerBasket(fakeCustomerId)


+ 1
- 0
test/Services/UnitTest/UnitTest.csproj View File

@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<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" />


Loading…
Cancel
Save