mirror of https://github.com/EasyAbp/EShop.git
committed by
GitHub
46 changed files with 1071 additions and 603 deletions
2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/CreateOrderInput.cs → plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/OrderFlashSalePlanInput.cs
2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/CreateOrderInput.cs → plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/OrderFlashSalePlanInput.cs
@ -1,71 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class CreateFlashSaleOrderFailedEventHandler : IDistributedEventHandler<CreateFlashSaleOrderCompleteEto>, ITransientDependency |
|||
{ |
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } |
|||
protected IDistributedCache DistributedCache { get; } |
|||
protected IFlashSalePlanRepository FlashSalePlanRepository { get; } |
|||
protected IProductAppService ProductAppService { get; } |
|||
protected ILogger<CreateFlashSaleOrderFailedEventHandler > Logger { get; } |
|||
|
|||
public CreateFlashSaleOrderFailedEventHandler ( |
|||
IFlashSaleInventoryManager flashSaleInventoryManager, |
|||
IDistributedCache distributedCache, |
|||
IFlashSalePlanRepository flashSalePlanRepository, |
|||
IProductAppService productAppService, |
|||
ILogger<CreateFlashSaleOrderFailedEventHandler > logger) |
|||
{ |
|||
FlashSaleInventoryManager = flashSaleInventoryManager; |
|||
DistributedCache = distributedCache; |
|||
FlashSalePlanRepository = flashSalePlanRepository; |
|||
ProductAppService = productAppService; |
|||
Logger = logger; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(CreateFlashSaleOrderCompleteEto eventData) |
|||
{ |
|||
if (eventData.Success) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (eventData.Reason != FlashSaleResultFailedReason.InvalidHashToken) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var plan = await FlashSalePlanRepository.GetAsync(eventData.PlanId); |
|||
var product = await ProductAppService.GetAsync(plan.ProductId); |
|||
|
|||
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync( |
|||
plan.TenantId, product.InventoryProviderName, |
|||
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true |
|||
)) |
|||
{ |
|||
Logger.LogWarning("Try roll back inventory failed."); |
|||
return; |
|||
} |
|||
|
|||
await RemoveUserFlashSaleResultCacheAsync(plan, eventData.UserId); |
|||
} |
|||
|
|||
protected virtual Task<string> GetUserFlashSaleResultCacheKeyAsync(FlashSalePlan plan, Guid userId) |
|||
{ |
|||
return Task.FromResult(string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, userId)); |
|||
} |
|||
|
|||
protected virtual async Task RemoveUserFlashSaleResultCacheAsync(FlashSalePlan plan, Guid userId) |
|||
{ |
|||
await DistributedCache.RemoveAsync(await GetUserFlashSaleResultCacheKeyAsync(plan, userId)); |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class FlashSaleOrderCreationResultEventHandler : IDistributedEventHandler<FlashSaleOrderCreationResultEto>, |
|||
ITransientDependency |
|||
{ |
|||
protected ILogger<FlashSaleOrderCreationResultEventHandler> Logger { get; } |
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } |
|||
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
|||
protected IObjectMapper ObjectMapper { get; } |
|||
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; } |
|||
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
|||
|
|||
public FlashSaleOrderCreationResultEventHandler( |
|||
ILogger<FlashSaleOrderCreationResultEventHandler> logger, |
|||
IFlashSaleInventoryManager flashSaleInventoryManager, |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
IObjectMapper objectMapper, |
|||
IFlashSaleCurrentResultCache flashSaleCurrentResultCache, |
|||
IFlashSaleResultRepository flashSaleResultRepository) |
|||
{ |
|||
Logger = logger; |
|||
FlashSaleInventoryManager = flashSaleInventoryManager; |
|||
UnitOfWorkManager = unitOfWorkManager; |
|||
ObjectMapper = objectMapper; |
|||
FlashSaleCurrentResultCache = flashSaleCurrentResultCache; |
|||
FlashSaleResultRepository = flashSaleResultRepository; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(FlashSaleOrderCreationResultEto eventData) |
|||
{ |
|||
var flashSaleResult = await FlashSaleResultRepository.GetAsync(eventData.ResultId); |
|||
|
|||
if (eventData.Success) |
|||
{ |
|||
flashSaleResult.MarkAsSuccessful(eventData.OrderId!.Value); |
|||
|
|||
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true); |
|||
} |
|||
else |
|||
{ |
|||
flashSaleResult.MarkAsFailed(eventData.Reason); |
|||
|
|||
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true); |
|||
} |
|||
|
|||
UnitOfWorkManager.Current.OnCompleted(async () => |
|||
{ |
|||
if (eventData.Success) |
|||
{ |
|||
await ResetFlashSaleCurrentResultCacheAsync(flashSaleResult); |
|||
} |
|||
else |
|||
{ |
|||
// try to roll back the inventory.
|
|||
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync( |
|||
eventData.TenantId, eventData.ProductInventoryProviderName, eventData.StoreId, |
|||
eventData.ProductId, eventData.ProductSkuId)) |
|||
{ |
|||
Logger.LogWarning("Failed to roll back the flash sale inventory."); |
|||
return; // avoid to remove cache if the rollback failed.
|
|||
} |
|||
|
|||
// remove the cache so the user can try to order again.
|
|||
if (eventData.AllowToTryAgain) |
|||
{ |
|||
await FlashSaleCurrentResultCache.RemoveAsync(flashSaleResult.PlanId, flashSaleResult.UserId); |
|||
} |
|||
else |
|||
{ |
|||
await ResetFlashSaleCurrentResultCacheAsync(flashSaleResult); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected virtual async Task ResetFlashSaleCurrentResultCacheAsync(FlashSaleResult flashSaleResult) |
|||
{ |
|||
await FlashSaleCurrentResultCache.SetAsync(flashSaleResult.PlanId, flashSaleResult.UserId, |
|||
new FlashSaleCurrentResultCacheItem |
|||
{ |
|||
TenantId = flashSaleResult.TenantId, |
|||
ResultDto = ObjectMapper.Map<FlashSaleResult, FlashSaleResultDto>(flashSaleResult) |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.DistributedLocking; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectExtending; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class CreateFlashSaleResultEventHandler : IDistributedEventHandler<CreateFlashSaleResultEto>, |
|||
ITransientDependency |
|||
{ |
|||
protected IObjectMapper ObjectMapper { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
|||
protected ILogger<CreateFlashSaleResultEventHandler> Logger { get; } |
|||
protected IAbpDistributedLock AbpDistributedLock { get; } |
|||
protected IDistributedEventBus DistributedEventBus { get; } |
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } |
|||
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; } |
|||
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
|||
|
|||
public CreateFlashSaleResultEventHandler( |
|||
IObjectMapper objectMapper, |
|||
ICurrentTenant currentTenant, |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
ILogger<CreateFlashSaleResultEventHandler> logger, |
|||
IAbpDistributedLock abpDistributedLock, |
|||
IDistributedEventBus distributedEventBus, |
|||
IFlashSaleInventoryManager flashSaleInventoryManager, |
|||
IFlashSaleCurrentResultCache flashSaleCurrentResultCache, |
|||
IFlashSaleResultRepository flashSaleResultRepository) |
|||
{ |
|||
ObjectMapper = objectMapper; |
|||
CurrentTenant = currentTenant; |
|||
UnitOfWorkManager = unitOfWorkManager; |
|||
Logger = logger; |
|||
AbpDistributedLock = abpDistributedLock; |
|||
DistributedEventBus = distributedEventBus; |
|||
FlashSaleInventoryManager = flashSaleInventoryManager; |
|||
FlashSaleCurrentResultCache = flashSaleCurrentResultCache; |
|||
FlashSaleResultRepository = flashSaleResultRepository; |
|||
} |
|||
|
|||
[UnitOfWork(true)] |
|||
public virtual async Task HandleEventAsync(CreateFlashSaleResultEto eventData) |
|||
{ |
|||
await using var handle = await AbpDistributedLock.TryAcquireAsync(await GetLockKeyAsync(eventData)); |
|||
|
|||
if (handle is null) |
|||
{ |
|||
throw new AbpException("Concurrent flash sale result creation"); |
|||
} |
|||
|
|||
var ongoingResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x => |
|||
x.PlanId == eventData.Plan.Id && |
|||
x.UserId == eventData.UserId && |
|||
x.Status != FlashSaleResultStatus.Failed); |
|||
|
|||
if (ongoingResult is not null) |
|||
{ |
|||
Logger.LogWarning("Duplicate ongoing flash sale result creation"); |
|||
|
|||
await FlashSaleCurrentResultCache.SetAsync(eventData.Plan.Id, eventData.UserId, |
|||
new FlashSaleCurrentResultCacheItem |
|||
{ |
|||
TenantId = ongoingResult.TenantId, |
|||
ResultDto = ObjectMapper.Map<FlashSaleResult, FlashSaleResultDto>(ongoingResult) |
|||
}); |
|||
|
|||
// try to roll back the inventory.
|
|||
UnitOfWorkManager.Current.OnCompleted(async () => |
|||
{ |
|||
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync(eventData.TenantId, |
|||
eventData.ProductInventoryProviderName, eventData.Plan.StoreId, |
|||
eventData.Plan.ProductId, eventData.Plan.ProductSkuId)) |
|||
{ |
|||
Logger.LogWarning("Failed to roll back the flash sale inventory."); |
|||
} |
|||
}); |
|||
|
|||
return; // avoid to create a result entity and an order.
|
|||
} |
|||
|
|||
var result = new FlashSaleResult( |
|||
id: eventData.ResultId, |
|||
tenantId: CurrentTenant.Id, |
|||
storeId: eventData.Plan.StoreId, |
|||
planId: eventData.Plan.Id, |
|||
userId: eventData.UserId, |
|||
reducedInventoryTime: eventData.ReducedInventoryTime |
|||
); |
|||
|
|||
var eto = new CreateFlashSaleOrderEto |
|||
{ |
|||
TenantId = eventData.TenantId, |
|||
ResultId = eventData.ResultId, |
|||
UserId = eventData.UserId, |
|||
CustomerRemark = eventData.CustomerRemark, |
|||
Plan = eventData.Plan, |
|||
HashToken = eventData.HashToken |
|||
}; |
|||
|
|||
eventData.MapExtraPropertiesTo(eto, MappingPropertyDefinitionChecks.None); |
|||
|
|||
await DistributedEventBus.PublishAsync(eto); |
|||
|
|||
await FlashSaleResultRepository.InsertAsync(result, autoSave: true); |
|||
} |
|||
|
|||
protected virtual Task<string> GetLockKeyAsync(CreateFlashSaleResultEto eventData) |
|||
{ |
|||
return Task.FromResult($"eshopflashsales-creating-result_{eventData.Plan.Id}-{eventData.UserId}"); |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.Options; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class FlashSaleCurrentResultCache : IFlashSaleCurrentResultCache, ITransientDependency |
|||
{ |
|||
/// <summary>
|
|||
/// The <see cref="GetKeyAsync(Guid,Guid)"/> cache key format.
|
|||
/// <para>{0}: FlashSalePlan ID</para>
|
|||
/// <para>{1}: User ID</para>
|
|||
/// </summary>
|
|||
public const string FlashSaleCurrentResultCacheKeyFormat = "eshopflashsales-current-result_{0}_{1}"; |
|||
|
|||
protected IDistributedCache<FlashSaleCurrentResultCacheItem> Cache { get; } |
|||
protected FlashSalesOptions Options { get; } |
|||
|
|||
public FlashSaleCurrentResultCache( |
|||
IDistributedCache<FlashSaleCurrentResultCacheItem> cache, |
|||
IOptions<FlashSalesOptions> options) |
|||
{ |
|||
Cache = cache; |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public virtual async Task<FlashSaleCurrentResultCacheItem> GetAsync(Guid planId, Guid userId) |
|||
{ |
|||
var flashSaleCurrentResultCacheKey = await GetKeyAsync(planId, userId); |
|||
return await Cache.GetAsync(flashSaleCurrentResultCacheKey); |
|||
} |
|||
|
|||
public virtual async Task SetAsync(Guid planId, Guid userId, FlashSaleCurrentResultCacheItem cacheItem) |
|||
{ |
|||
var flashSaleCurrentResultCacheKey = await GetKeyAsync(planId, userId); |
|||
|
|||
await Cache.SetAsync(flashSaleCurrentResultCacheKey, cacheItem, |
|||
new DistributedCacheEntryOptions |
|||
{ |
|||
AbsoluteExpiration = DateTimeOffset.Now.Add(Options.FlashSaleCurrentResultCacheExpires) |
|||
}); |
|||
} |
|||
|
|||
public virtual async Task RemoveAsync(Guid planId, Guid userId) |
|||
{ |
|||
var flashSaleCurrentResultCacheKey = await GetKeyAsync(planId, userId); |
|||
|
|||
await Cache.RemoveAsync(flashSaleCurrentResultCacheKey); |
|||
} |
|||
|
|||
protected virtual Task<string> GetKeyAsync(Guid planId, Guid userId) |
|||
{ |
|||
return Task.FromResult(string.Format(FlashSaleCurrentResultCacheKeyFormat, planId, userId)); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
[Serializable] |
|||
public class FlashSaleCurrentResultCacheItem : IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public FlashSaleResultDto ResultDto { get; set; } |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public interface IFlashSaleCurrentResultCache |
|||
{ |
|||
Task<FlashSaleCurrentResultCacheItem> GetAsync(Guid planId, Guid userId); |
|||
|
|||
Task SetAsync(Guid planId, Guid userId, FlashSaleCurrentResultCacheItem cacheItem); |
|||
|
|||
Task RemoveAsync(Guid planId, Guid userId); |
|||
} |
|||
12
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs → plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEto.cs
12
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs → plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEto.cs
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
[Serializable] |
|||
public class CreateFlashSaleResultEto : ExtensibleObject, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public Guid ResultId { get; set; } |
|||
|
|||
public Guid UserId { get; set; } |
|||
|
|||
public DateTime ReducedInventoryTime { get; set; } |
|||
|
|||
public string CustomerRemark { get; set; } |
|||
|
|||
public FlashSalePlanEto Plan { get; set; } |
|||
|
|||
public string ProductInventoryProviderName { get; set; } |
|||
|
|||
public string HashToken { get; set; } |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class CreateFlashSaleOrderCompleteEventHandler : IDistributedEventHandler<CreateFlashSaleOrderCompleteEto>, ITransientDependency |
|||
{ |
|||
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
|||
protected IGuidGenerator GuidGenerator { get; } |
|||
|
|||
public CreateFlashSaleOrderCompleteEventHandler(IFlashSaleResultRepository flashSaleResultRepository, IGuidGenerator guidGenerator) |
|||
{ |
|||
FlashSaleResultRepository = flashSaleResultRepository; |
|||
GuidGenerator = guidGenerator; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(CreateFlashSaleOrderCompleteEto eventData) |
|||
{ |
|||
var flashSaleResult = await FlashSaleResultRepository.GetAsync(eventData.PendingResultId); |
|||
|
|||
if (eventData.Success) |
|||
{ |
|||
flashSaleResult.MarkAsSuccessful(eventData.OrderId.Value); |
|||
} |
|||
else |
|||
{ |
|||
flashSaleResult.MarkAsFailed(eventData.Reason); |
|||
} |
|||
|
|||
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Stores.Stores; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public interface IFlashSaleResult : IMultiStore |
|||
{ |
|||
Guid PlanId { get; } |
|||
|
|||
FlashSaleResultStatus Status { get; } |
|||
|
|||
string Reason { get; } |
|||
|
|||
Guid UserId { get; } |
|||
|
|||
Guid? OrderId { get; } |
|||
} |
|||
@ -1,165 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using NSubstitute; |
|||
using NSubstitute.ReceivedExtensions; |
|||
using Shouldly; |
|||
using Volo.Abp.Users; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class CreateFlashSaleOrderFailedEventHandlerTest : FlashSalesApplicationTestBase |
|||
{ |
|||
protected CreateFlashSaleOrderFailedEventHandler EventHandler { get; } |
|||
|
|||
protected IDistributedCache DistributedCache { get; } |
|||
|
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; set; } |
|||
|
|||
private ProductDto Product1 { get; set; } |
|||
|
|||
public CreateFlashSaleOrderFailedEventHandlerTest() |
|||
{ |
|||
EventHandler = GetRequiredService<CreateFlashSaleOrderFailedEventHandler >(); |
|||
DistributedCache = GetRequiredService<IDistributedCache>(); |
|||
FlashSaleInventoryManager = GetRequiredService<IFlashSaleInventoryManager>(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
Product1 = CreateMockProductDto(); |
|||
|
|||
var productAppService = Substitute.For<IProductAppService>(); |
|||
productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1)); |
|||
services.Replace(ServiceDescriptor.Singleton(productAppService)); |
|||
|
|||
FlashSaleInventoryManager = Substitute.For<IFlashSaleInventoryManager>(); |
|||
services.Replace(ServiceDescriptor.Singleton(FlashSaleInventoryManager)); |
|||
base.AfterAddApplication(services); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId()); |
|||
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString()); |
|||
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = pendingFlashResult.TenantId, |
|||
PendingResultId = pendingFlashResult.Id, |
|||
Success = false, |
|||
StoreId = pendingFlashResult.StoreId, |
|||
PlanId = pendingFlashResult.PlanId, |
|||
OrderId = null, |
|||
Reason = FlashSaleResultFailedReason.InvalidHashToken, |
|||
UserId = pendingFlashResult.UserId |
|||
}; |
|||
FlashSaleInventoryManager |
|||
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true) |
|||
.Returns(Task.FromResult(true)); |
|||
|
|||
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto); |
|||
|
|||
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey); |
|||
userFlashSaleResultCache.ShouldBeNull(); |
|||
|
|||
await FlashSaleInventoryManager.Received() |
|||
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_Should_Not_Remove_UserFlashSaleResultCache_When_TryRollBackInventory_Failed() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId()); |
|||
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString()); |
|||
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = pendingFlashResult.TenantId, |
|||
PendingResultId = pendingFlashResult.Id, |
|||
Success = false, |
|||
StoreId = pendingFlashResult.StoreId, |
|||
PlanId = pendingFlashResult.PlanId, |
|||
OrderId = null, |
|||
Reason = FlashSaleResultFailedReason.InvalidHashToken, |
|||
UserId = pendingFlashResult.UserId |
|||
}; |
|||
FlashSaleInventoryManager |
|||
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true) |
|||
.Returns(Task.FromResult(false)); |
|||
|
|||
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto); |
|||
|
|||
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey); |
|||
userFlashSaleResultCache.ShouldBe(pendingFlashResult.Id.ToString()); |
|||
|
|||
await FlashSaleInventoryManager.Received() |
|||
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_Should_Ignore_When_Success_Is_True() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId()); |
|||
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString()); |
|||
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = pendingFlashResult.TenantId, |
|||
PendingResultId = pendingFlashResult.Id, |
|||
Success = true, |
|||
StoreId = pendingFlashResult.StoreId, |
|||
PlanId = pendingFlashResult.PlanId, |
|||
OrderId = Guid.NewGuid(), |
|||
Reason = null, |
|||
UserId = pendingFlashResult.UserId |
|||
}; |
|||
|
|||
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto); |
|||
|
|||
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey); |
|||
userFlashSaleResultCache.ShouldBe(pendingFlashResult.Id.ToString()); |
|||
|
|||
await FlashSaleInventoryManager.DidNotReceiveWithAnyArgs() |
|||
.TryRollBackInventoryAsync(default, default, Guid.Empty, Guid.Empty, Guid.Empty, default, default); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_Should_Ignore_When_Reason_Not_InvalidHashToken() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId()); |
|||
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString()); |
|||
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = pendingFlashResult.TenantId, |
|||
PendingResultId = pendingFlashResult.Id, |
|||
Success = false, |
|||
StoreId = pendingFlashResult.StoreId, |
|||
PlanId = pendingFlashResult.PlanId, |
|||
OrderId = null, |
|||
Reason = "Other", |
|||
UserId = pendingFlashResult.UserId |
|||
}; |
|||
|
|||
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto); |
|||
|
|||
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey); |
|||
userFlashSaleResultCache.ShouldBe(pendingFlashResult.Id.ToString()); |
|||
|
|||
await FlashSaleInventoryManager.DidNotReceiveWithAnyArgs() |
|||
.TryRollBackInventoryAsync(default, default, Guid.Empty, Guid.Empty, Guid.Empty, default, default); |
|||
} |
|||
} |
|||
@ -0,0 +1,257 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
|||
using EasyAbp.EShop.Products.ProductDetails; |
|||
using EasyAbp.EShop.Products.ProductDetails.Dtos; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using NSubstitute; |
|||
using Shouldly; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.Users; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
|
|||
public class FlashSaleOrderCreationResultEventHandlerTests : FlashSalesApplicationTestBase |
|||
{ |
|||
protected IObjectMapper ObjectMapper { get; } |
|||
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; } |
|||
protected FlashSaleOrderCreationResultEventHandler FlashSaleOrderCreationResultEventHandler { get; } |
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; set; } |
|||
|
|||
private ProductDto Product1 { get; set; } |
|||
|
|||
public FlashSaleOrderCreationResultEventHandlerTests() |
|||
{ |
|||
ObjectMapper = GetRequiredService<IObjectMapper>(); |
|||
FlashSaleCurrentResultCache = GetRequiredService<IFlashSaleCurrentResultCache>(); |
|||
FlashSaleOrderCreationResultEventHandler = GetRequiredService<FlashSaleOrderCreationResultEventHandler>(); |
|||
FlashSaleInventoryManager = GetRequiredService<IFlashSaleInventoryManager>(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
Product1 = CreateMockProductDto(); |
|||
|
|||
var productAppService = Substitute.For<IProductAppService>(); |
|||
productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1)); |
|||
services.Replace(ServiceDescriptor.Singleton(productAppService)); |
|||
|
|||
var productDetailAppService = Substitute.For<IProductDetailAppService>(); |
|||
services.Replace(ServiceDescriptor.Singleton(productDetailAppService)); |
|||
productDetailAppService.GetAsync(FlashSalesTestData.ProductDetail1Id).Returns(Task.FromResult( |
|||
new ProductDetailDto |
|||
{ |
|||
Id = FlashSalesTestData.ProductDetail1Id, |
|||
CreationTime = FlashSalesTestData.ProductDetailLastModificationTime, |
|||
LastModificationTime = FlashSalesTestData.ProductDetailLastModificationTime, |
|||
StoreId = FlashSalesTestData.Store1Id, |
|||
Description = "My Details 1" |
|||
})); |
|||
productDetailAppService.GetAsync(FlashSalesTestData.ProductDetail2Id).Returns(Task.FromResult( |
|||
new ProductDetailDto |
|||
{ |
|||
Id = FlashSalesTestData.ProductDetail2Id, |
|||
StoreId = FlashSalesTestData.Store1Id, |
|||
Description = "My Details 2" |
|||
})); |
|||
|
|||
var flashSaleInventoryManager = Substitute.For<IFlashSaleInventoryManager>(); |
|||
services.Replace(ServiceDescriptor.Singleton(flashSaleInventoryManager)); |
|||
|
|||
base.AfterAddApplication(services); |
|||
} |
|||
|
|||
protected async Task SetFlashSaleCurrentResultCacheAsync(FlashSaleResult flashSaleResult) |
|||
{ |
|||
await FlashSaleCurrentResultCache.SetAsync(flashSaleResult.PlanId, flashSaleResult.UserId, |
|||
new FlashSaleCurrentResultCacheItem |
|||
{ |
|||
TenantId = CurrentTenant.Id, |
|||
ResultDto = ObjectMapper.Map<FlashSaleResult, FlashSaleResultDto>(flashSaleResult) |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_When_Create_Order_Success() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult); |
|||
|
|||
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto |
|||
{ |
|||
TenantId = flashSaleResult.TenantId, |
|||
ResultId = flashSaleResult.Id, |
|||
Success = true, |
|||
StoreId = flashSaleResult.StoreId, |
|||
PlanId = flashSaleResult.PlanId, |
|||
OrderId = GuidGenerator.Create(), |
|||
Reason = null, |
|||
UserId = flashSaleResult.UserId, |
|||
ProductInventoryProviderName = Product1.InventoryProviderName, |
|||
ProductId = plan.ProductId, |
|||
ProductSkuId = plan.ProductSkuId, |
|||
AllowToTryAgain = false |
|||
}; |
|||
|
|||
FlashSaleInventoryManager |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId) |
|||
.Returns(Task.FromResult(true)); |
|||
|
|||
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto); |
|||
|
|||
var flashResult = await FlashSaleResultRepository.GetAsync(flashSaleResult.Id); |
|||
flashResult.Status.ShouldBe(FlashSaleResultStatus.Successful); |
|||
flashResult.OrderId.ShouldBe(flashSaleOrderCreationResultEto.OrderId); |
|||
flashResult.Reason.ShouldBe(null); |
|||
|
|||
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(flashResult.PlanId, flashResult.UserId); |
|||
flashSaleCurrentResultCache.ShouldNotBeNull(); |
|||
flashSaleCurrentResultCache.ResultDto.Id.ShouldBe(flashSaleResult.Id); |
|||
flashSaleCurrentResultCache.ResultDto.Status.ShouldBe(FlashSaleResultStatus.Successful); |
|||
|
|||
await FlashSaleInventoryManager.DidNotReceive() |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_When_Create_Order_Failed_And_Not_Allow_To_Try_Again() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult); |
|||
|
|||
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto |
|||
{ |
|||
TenantId = flashSaleResult.TenantId, |
|||
ResultId = flashSaleResult.Id, |
|||
Success = false, |
|||
StoreId = FlashSalesTestData.Store1Id, |
|||
PlanId = flashSaleResult.PlanId, |
|||
OrderId = null, |
|||
Reason = "Failed reason", |
|||
UserId = flashSaleResult.UserId, |
|||
ProductInventoryProviderName = Product1.InventoryProviderName, |
|||
ProductId = plan.ProductId, |
|||
ProductSkuId = plan.ProductSkuId, |
|||
AllowToTryAgain = false |
|||
}; |
|||
|
|||
FlashSaleInventoryManager |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId) |
|||
.Returns(Task.FromResult(true)); |
|||
|
|||
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto); |
|||
|
|||
var flashResult = await FlashSaleResultRepository.GetAsync(flashSaleResult.Id); |
|||
flashResult.Status.ShouldBe(FlashSaleResultStatus.Failed); |
|||
flashResult.OrderId.ShouldBe(null); |
|||
flashResult.Reason.ShouldBe("Failed reason"); |
|||
|
|||
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(flashResult.PlanId, flashResult.UserId); |
|||
flashSaleCurrentResultCache.ShouldNotBeNull(); |
|||
flashSaleCurrentResultCache.ResultDto.Id.ShouldBe(flashSaleResult.Id); |
|||
flashSaleCurrentResultCache.ResultDto.Status.ShouldBe(FlashSaleResultStatus.Failed); |
|||
|
|||
await FlashSaleInventoryManager.Received() |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_When_Create_Order_Failed_And_Allow_To_Try_Again() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult); |
|||
|
|||
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto |
|||
{ |
|||
TenantId = flashSaleResult.TenantId, |
|||
ResultId = flashSaleResult.Id, |
|||
Success = false, |
|||
StoreId = FlashSalesTestData.Store1Id, |
|||
PlanId = flashSaleResult.PlanId, |
|||
OrderId = null, |
|||
Reason = "Failed reason", |
|||
UserId = flashSaleResult.UserId, |
|||
ProductInventoryProviderName = Product1.InventoryProviderName, |
|||
ProductId = plan.ProductId, |
|||
ProductSkuId = plan.ProductSkuId, |
|||
AllowToTryAgain = true |
|||
}; |
|||
|
|||
FlashSaleInventoryManager |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId) |
|||
.Returns(Task.FromResult(true)); |
|||
|
|||
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto); |
|||
|
|||
var flashResult = await FlashSaleResultRepository.GetAsync(flashSaleResult.Id); |
|||
flashResult.Status.ShouldBe(FlashSaleResultStatus.Failed); |
|||
flashResult.OrderId.ShouldBe(null); |
|||
flashResult.Reason.ShouldBe("Failed reason"); |
|||
|
|||
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(flashResult.PlanId, flashResult.UserId); |
|||
flashSaleCurrentResultCache.ShouldBeNull(); |
|||
|
|||
await FlashSaleInventoryManager.Received() |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Remove_FlashSaleCurrentResultCache_When_TryRollBackInventory_Failed() |
|||
{ |
|||
var plan = await CreateFlashSalePlanAsync(); |
|||
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId()); |
|||
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult); |
|||
|
|||
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto |
|||
{ |
|||
TenantId = flashSaleResult.TenantId, |
|||
ResultId = flashSaleResult.Id, |
|||
Success = false, |
|||
StoreId = flashSaleResult.StoreId, |
|||
PlanId = flashSaleResult.PlanId, |
|||
OrderId = null, |
|||
Reason = FlashSaleResultFailedReason.InvalidHashToken, |
|||
UserId = flashSaleResult.UserId, |
|||
ProductInventoryProviderName = Product1.InventoryProviderName, |
|||
ProductId = plan.ProductId, |
|||
ProductSkuId = plan.ProductSkuId, |
|||
AllowToTryAgain = true |
|||
}; |
|||
|
|||
FlashSaleInventoryManager |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId) |
|||
.Returns(Task.FromResult(false)); |
|||
|
|||
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto); |
|||
|
|||
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(plan.Id, CurrentUser.GetId()); |
|||
flashSaleCurrentResultCache.ShouldNotBeNull(); |
|||
|
|||
await FlashSaleInventoryManager.Received() |
|||
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId, |
|||
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId, |
|||
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId); |
|||
} |
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using EasyAbp.Eshop.Products.Products; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using NSubstitute; |
|||
using Shouldly; |
|||
using Volo.Abp.Users; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class CreateFlashSaleResultEventHandlerTests : FlashSalesApplicationTestBase |
|||
{ |
|||
protected IFlashSaleResultAppService FlashSaleResultAppService { get; } |
|||
protected CreateFlashSaleResultEventHandler CreateFlashSaleResultEventHandler { get; } |
|||
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } |
|||
|
|||
public CreateFlashSaleResultEventHandlerTests() |
|||
{ |
|||
FlashSaleResultAppService = GetRequiredService<IFlashSaleResultAppService>(); |
|||
CreateFlashSaleResultEventHandler = GetRequiredService<CreateFlashSaleResultEventHandler>(); |
|||
FlashSaleInventoryManager = GetRequiredService<IFlashSaleInventoryManager>(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
var flashSaleInventoryManager = Substitute.For<IFlashSaleInventoryManager>(); |
|||
services.Replace(ServiceDescriptor.Singleton(flashSaleInventoryManager)); |
|||
|
|||
base.AfterAddApplication(services); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Create_Result() |
|||
{ |
|||
var reducedInventoryTime = DateTime.Now; |
|||
var createFlashSaleResultEto = await CreateCreateFlashSaleResultEtoAsync(reducedInventoryTime); |
|||
|
|||
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto); |
|||
|
|||
var flashResult = await FlashSaleResultRepository.GetAsync(FlashSalesTestData.Result1Id); |
|||
flashResult.Status.ShouldBe(FlashSaleResultStatus.Pending); |
|||
flashResult.OrderId.ShouldBeNull(); |
|||
flashResult.Reason.ShouldBe(null); |
|||
flashResult.ReducedInventoryTime.ShouldBe(reducedInventoryTime); |
|||
|
|||
(await FlashSaleResultAppService.GetCurrentAsync(createFlashSaleResultEto.Plan.Id)).Id.ShouldBe(flashResult.Id); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Create_Result_If_Duplicate() |
|||
{ |
|||
var existFlashResult1 = await CreateFlashSaleResultAsync(); |
|||
var createFlashSaleResultEto1 = await CreateCreateFlashSaleResultEtoAsync(DateTime.Now); |
|||
var planId = createFlashSaleResultEto1.Plan.Id; |
|||
|
|||
existFlashResult1.MarkAsFailed("some reason"); |
|||
await FlashSaleResultRepository.UpdateAsync(existFlashResult1, true); |
|||
|
|||
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldBe(existFlashResult1.Id); |
|||
|
|||
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto1); |
|||
|
|||
await FlashSaleInventoryManager.DidNotReceive() |
|||
.TryRollBackInventoryAsync(createFlashSaleResultEto1.TenantId, |
|||
createFlashSaleResultEto1.ProductInventoryProviderName, createFlashSaleResultEto1.Plan.StoreId, |
|||
createFlashSaleResultEto1.Plan.ProductId, createFlashSaleResultEto1.Plan.ProductSkuId); |
|||
|
|||
var flashResultList = await FlashSaleResultRepository.GetListAsync(); |
|||
flashResultList.Count.ShouldBe(2); |
|||
flashResultList.ShouldContain(x => x.Id == existFlashResult1.Id); |
|||
|
|||
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldNotBe(existFlashResult1.Id); |
|||
|
|||
var existFlashResult2 = flashResultList.First(x => x.Id != existFlashResult1.Id); |
|||
var createFlashSaleResultEto2 = await CreateCreateFlashSaleResultEtoAsync(DateTime.Now); |
|||
|
|||
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto2); |
|||
|
|||
await FlashSaleInventoryManager.Received() |
|||
.TryRollBackInventoryAsync(createFlashSaleResultEto2.TenantId, |
|||
createFlashSaleResultEto2.ProductInventoryProviderName, createFlashSaleResultEto2.Plan.StoreId, |
|||
createFlashSaleResultEto2.Plan.ProductId, createFlashSaleResultEto2.Plan.ProductSkuId); |
|||
|
|||
flashResultList = await FlashSaleResultRepository.GetListAsync(); |
|||
flashResultList.Count.ShouldBe(2); |
|||
flashResultList.ShouldContain(x => x.Id == existFlashResult1.Id); |
|||
flashResultList.ShouldContain(x => x.Id == existFlashResult2.Id); |
|||
|
|||
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldBe(existFlashResult2.Id); |
|||
|
|||
existFlashResult2.MarkAsSuccessful(GuidGenerator.Create()); |
|||
await FlashSaleResultRepository.UpdateAsync(existFlashResult2, true); |
|||
|
|||
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto2); |
|||
|
|||
await FlashSaleInventoryManager.Received() |
|||
.TryRollBackInventoryAsync(createFlashSaleResultEto2.TenantId, |
|||
createFlashSaleResultEto2.ProductInventoryProviderName, createFlashSaleResultEto2.Plan.StoreId, |
|||
createFlashSaleResultEto2.Plan.ProductId, createFlashSaleResultEto2.Plan.ProductSkuId); |
|||
|
|||
flashResultList = await FlashSaleResultRepository.GetListAsync(); |
|||
flashResultList.Count.ShouldBe(2); |
|||
flashResultList.ShouldContain(x => x.Id == existFlashResult1.Id); |
|||
flashResultList.ShouldContain(x => x.Id == existFlashResult2.Id); |
|||
|
|||
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldBe(existFlashResult2.Id); |
|||
} |
|||
|
|||
protected Task<CreateFlashSaleResultEto> CreateCreateFlashSaleResultEtoAsync(DateTime reducedInventoryTime) |
|||
{ |
|||
return Task.FromResult(new CreateFlashSaleResultEto |
|||
{ |
|||
ResultId = FlashSalesTestData.Result1Id, |
|||
UserId = CurrentUser.GetId(), |
|||
ReducedInventoryTime = reducedInventoryTime, |
|||
Plan = new FlashSalePlanEto |
|||
{ |
|||
Id = FlashSalesTestData.Plan1Id, |
|||
TenantId = null, |
|||
StoreId = FlashSalesTestData.Store1Id, |
|||
BeginTime = reducedInventoryTime, |
|||
EndTime = DateTime.Now.AddMinutes(30), |
|||
ProductId = FlashSalesTestData.Product1Id, |
|||
ProductSkuId = FlashSalesTestData.ProductSku1Id, |
|||
IsPublished = true |
|||
}, |
|||
HashToken = "My Hash Token" |
|||
}); |
|||
} |
|||
|
|||
protected async Task<FlashSaleResult> CreateFlashSaleResultAsync() |
|||
{ |
|||
return await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
var flashSaleResult = new FlashSaleResult( |
|||
GuidGenerator.Create(), |
|||
null, |
|||
FlashSalesTestData.Store1Id, |
|||
FlashSalesTestData.Plan1Id, |
|||
CurrentUser.GetId(), |
|||
DateTime.Now); |
|||
await FlashSaleResultRepository.InsertAsync(flashSaleResult); |
|||
|
|||
return flashSaleResult; |
|||
}); |
|||
} |
|||
} |
|||
@ -1,85 +0,0 @@ |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
|||
using Shouldly; |
|||
using Volo.Abp.Guids; |
|||
using Xunit; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
|||
|
|||
public class CreateFlashSaleOrderCompleteEventHandlerTests : FlashSalesDomainTestBase |
|||
{ |
|||
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
|||
protected CreateFlashSaleOrderCompleteEventHandler CreateFlashSaleOrderCompleteEventHandler { get; } |
|||
protected IGuidGenerator GuidGenerator { get; } |
|||
|
|||
public CreateFlashSaleOrderCompleteEventHandlerTests() |
|||
{ |
|||
FlashSaleResultRepository = GetRequiredService<IFlashSaleResultRepository>(); |
|||
CreateFlashSaleOrderCompleteEventHandler = GetRequiredService<CreateFlashSaleOrderCompleteEventHandler>(); |
|||
GuidGenerator = GetRequiredService<IGuidGenerator>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_When_Create_Order_Success() |
|||
{ |
|||
var existFlashResult = await CreateFlashSaleResultAsync(); |
|||
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = existFlashResult.TenantId, |
|||
PendingResultId = existFlashResult.Id, |
|||
Success = true, |
|||
StoreId = existFlashResult.StoreId, |
|||
PlanId = existFlashResult.PlanId, |
|||
OrderId = GuidGenerator.Create(), |
|||
Reason = null, |
|||
UserId = existFlashResult.UserId, |
|||
}; |
|||
|
|||
await CreateFlashSaleOrderCompleteEventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto); |
|||
|
|||
var flashResult = await FlashSaleResultRepository.GetAsync(existFlashResult.Id); |
|||
flashResult.Status.ShouldBe(FlashSaleResultStatus.Successful); |
|||
flashResult.OrderId.ShouldBe(createFlashSaleOrderCompleteEto.OrderId); |
|||
flashResult.Reason.ShouldBe(null); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleEventAsync_When_Create_Order_Failed() |
|||
{ |
|||
var existFlashResult = await CreateFlashSaleResultAsync(); |
|||
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto() |
|||
{ |
|||
TenantId = existFlashResult.TenantId, |
|||
PendingResultId = existFlashResult.Id, |
|||
Success = false, |
|||
StoreId = FlashSalesTestData.Store1Id, |
|||
PlanId = existFlashResult.PlanId, |
|||
OrderId = null, |
|||
Reason = "Failed reason", |
|||
UserId = existFlashResult.UserId, |
|||
}; |
|||
|
|||
await CreateFlashSaleOrderCompleteEventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto); |
|||
|
|||
var flashResult = await FlashSaleResultRepository.GetAsync(existFlashResult.Id); |
|||
flashResult.Status.ShouldBe(FlashSaleResultStatus.Failed); |
|||
flashResult.OrderId.ShouldBe(null); |
|||
flashResult.Reason.ShouldBe("Failed reason"); |
|||
} |
|||
|
|||
public async Task<FlashSaleResult> CreateFlashSaleResultAsync() |
|||
{ |
|||
return await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
var flashSaleResult = new FlashSaleResult( |
|||
GuidGenerator.Create(), |
|||
null, |
|||
FlashSalesTestData.Store1Id, |
|||
FlashSalesTestData.Plan1Id, |
|||
GuidGenerator.Create()); |
|||
await FlashSaleResultRepository.InsertAsync(flashSaleResult); |
|||
|
|||
return flashSaleResult; |
|||
}); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue