mirror of https://github.com/EasyAbp/EShop.git
41 changed files with 961 additions and 580 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,93 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; |
||||
|
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
||||
|
using EasyAbp.Eshop.Products.Products; |
||||
|
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 IFlashSalePlanRepository FlashSalePlanRepository { get; } |
||||
|
protected IProductAppService ProductAppService { get; } |
||||
|
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
||||
|
protected IObjectMapper ObjectMapper { get; } |
||||
|
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; } |
||||
|
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
||||
|
|
||||
|
public FlashSaleOrderCreationResultEventHandler( |
||||
|
ILogger<FlashSaleOrderCreationResultEventHandler> logger, |
||||
|
IFlashSaleInventoryManager flashSaleInventoryManager, |
||||
|
IFlashSalePlanRepository flashSalePlanRepository, |
||||
|
IProductAppService productAppService, |
||||
|
IUnitOfWorkManager unitOfWorkManager, |
||||
|
IObjectMapper objectMapper, |
||||
|
IFlashSaleCurrentResultCache flashSaleCurrentResultCache, |
||||
|
IFlashSaleResultRepository flashSaleResultRepository) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
FlashSaleInventoryManager = flashSaleInventoryManager; |
||||
|
FlashSalePlanRepository = flashSalePlanRepository; |
||||
|
ProductAppService = productAppService; |
||||
|
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 |
||||
|
{ |
||||
|
var plan = await FlashSalePlanRepository.GetAsync(eventData.PlanId); |
||||
|
var product = await ProductAppService.GetAsync(plan.ProductId); |
||||
|
|
||||
|
flashSaleResult.MarkAsFailed(eventData.Reason); |
||||
|
|
||||
|
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true); |
||||
|
|
||||
|
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync( |
||||
|
plan.TenantId, product.InventoryProviderName, |
||||
|
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true |
||||
|
)) |
||||
|
{ |
||||
|
Logger.LogWarning("Try roll back inventory failed."); |
||||
|
return; // Avoid to remove cache on the UOW completed.
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
UnitOfWorkManager.Current.OnCompleted(async () => |
||||
|
{ |
||||
|
if (flashSaleResult.Status is FlashSaleResultStatus.Failed && eventData.AllowToTryAgain) |
||||
|
{ |
||||
|
await FlashSaleCurrentResultCache.RemoveAsync(flashSaleResult.PlanId, flashSaleResult.UserId); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await FlashSaleCurrentResultCache.SetAsync(flashSaleResult.PlanId, flashSaleResult.UserId, |
||||
|
new FlashSaleCurrentResultCacheItem |
||||
|
{ |
||||
|
TenantId = flashSaleResult.TenantId, |
||||
|
ResultDto = ObjectMapper.Map<FlashSaleResult, FlashSaleResultDto>(flashSaleResult) |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
||||
|
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos; |
||||
|
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.Guids; |
||||
|
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 IGuidGenerator GuidGenerator { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected ILogger<CreateFlashSaleResultEventHandler> Logger { get; } |
||||
|
protected IAbpDistributedLock AbpDistributedLock { get; } |
||||
|
protected IDistributedEventBus DistributedEventBus { get; } |
||||
|
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; } |
||||
|
protected IFlashSaleResultRepository FlashSaleResultRepository { get; } |
||||
|
|
||||
|
public CreateFlashSaleResultEventHandler( |
||||
|
IObjectMapper objectMapper, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
ICurrentTenant currentTenant, |
||||
|
ILogger<CreateFlashSaleResultEventHandler> logger, |
||||
|
IAbpDistributedLock abpDistributedLock, |
||||
|
IDistributedEventBus distributedEventBus, |
||||
|
IFlashSaleCurrentResultCache flashSaleCurrentResultCache, |
||||
|
IFlashSaleResultRepository flashSaleResultRepository) |
||||
|
{ |
||||
|
ObjectMapper = objectMapper; |
||||
|
GuidGenerator = guidGenerator; |
||||
|
CurrentTenant = currentTenant; |
||||
|
Logger = logger; |
||||
|
AbpDistributedLock = abpDistributedLock; |
||||
|
DistributedEventBus = distributedEventBus; |
||||
|
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) |
||||
|
}); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
} |
||||
6
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
6
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,24 @@ |
|||||
|
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 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,229 @@ |
|||||
|
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, |
||||
|
AllowToTryAgain = false |
||||
|
}; |
||||
|
|
||||
|
FlashSaleInventoryManager |
||||
|
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true) |
||||
|
.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(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true); |
||||
|
} |
||||
|
|
||||
|
[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, |
||||
|
AllowToTryAgain = false |
||||
|
}; |
||||
|
|
||||
|
FlashSaleInventoryManager |
||||
|
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true) |
||||
|
.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(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true); |
||||
|
} |
||||
|
|
||||
|
[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, |
||||
|
AllowToTryAgain = true |
||||
|
}; |
||||
|
|
||||
|
FlashSaleInventoryManager |
||||
|
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true) |
||||
|
.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(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true); |
||||
|
} |
||||
|
|
||||
|
[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, |
||||
|
AllowToTryAgain = true |
||||
|
}; |
||||
|
|
||||
|
FlashSaleInventoryManager |
||||
|
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true) |
||||
|
.Returns(Task.FromResult(false)); |
||||
|
|
||||
|
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto); |
||||
|
|
||||
|
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(plan.Id, CurrentUser.GetId()); |
||||
|
flashSaleCurrentResultCache.ShouldNotBeNull(); |
||||
|
|
||||
|
await FlashSaleInventoryManager.Received() |
||||
|
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; |
||||
|
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; } |
||||
|
|
||||
|
public CreateFlashSaleResultEventHandlerTests() |
||||
|
{ |
||||
|
FlashSaleResultAppService = GetRequiredService<IFlashSaleResultAppService>(); |
||||
|
CreateFlashSaleResultEventHandler = GetRequiredService<CreateFlashSaleResultEventHandler>(); |
||||
|
} |
||||
|
|
||||
|
[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); |
||||
|
|
||||
|
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); |
||||
|
|
||||
|
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); |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
public 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" |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public 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