diff --git a/EShop.sln b/EShop.sln
index 44c76155..9031a10d 100644
--- a/EShop.sln
+++ b/EShop.sln
@@ -423,6 +423,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plug
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.Client", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.Client\EasyAbp.EShop.Products.Plugins.FlashSales.HttpApi.Client.csproj", "{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests", "plugins\FlashSales\test\EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests\EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.csproj", "{17A3486C-1845-4B4E-B1A6-752106F0C309}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1101,6 +1103,10 @@ Global
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {17A3486C-1845-4B4E-B1A6-752106F0C309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17A3486C-1845-4B4E-B1A6-752106F0C309}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {17A3486C-1845-4B4E-B1A6-752106F0C309}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17A3486C-1845-4B4E-B1A6-752106F0C309}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1312,6 +1318,7 @@ Global
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{B137BF4B-8C0A-4CE2-AF22-BD9BD29C86B7} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{274769DC-5DD6-4CFD-8078-5E0E0CE8D6D8} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
+ {17A3486C-1845-4B4E-B1A6-752106F0C309} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.abppkg.json b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.abppkg.json
new file mode 100644
index 00000000..a686451f
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.abppkg.json
@@ -0,0 +1,3 @@
+{
+ "role": "lib.test"
+}
\ No newline at end of file
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.csproj b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.csproj
new file mode 100644
index 00000000..bd18bfdc
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs
new file mode 100644
index 00000000..b7643685
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.FlashSales;
+using EasyAbp.EShop.Products.ProductDetails.Dtos;
+using EasyAbp.EShop.Products.ProductDetails;
+using EasyAbp.EShop.Products.Products;
+using EasyAbp.EShop.Products.Products.Dtos;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using NSubstitute;
+using Xunit;
+using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
+using Volo.Abp.Users;
+using Volo.Abp.EventBus.Distributed;
+using Volo.Abp.MultiTenancy;
+using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
+
+namespace EasyAbp.EShop.Orders.Orders;
+
+public class CreateFlashSaleOrderEventHandlerTests : OrdersPluginsFlashSalesApplicationTestBase
+{
+ protected CreateFlashSaleOrderEventHandler EventHandler { get; }
+
+ protected ICurrentUser CurrentUser { get; }
+
+ protected ICurrentTenant CurrentTenant { get; }
+
+ protected IDistributedEventBus DistributedEventBus { get; }
+
+ protected IFlashSalePlanHasher FlashSalePlanHasher { get; }
+
+ protected IOrderRepository OrderRepository { get; }
+
+ protected ProductDto Product1 { get; set; }
+
+ public CreateFlashSaleOrderEventHandlerTests()
+ {
+ EventHandler = GetRequiredService();
+ CurrentUser = GetRequiredService();
+ CurrentTenant = GetRequiredService();
+ DistributedEventBus = GetRequiredService();
+ FlashSalePlanHasher = GetRequiredService();
+ OrderRepository = GetRequiredService();
+ }
+
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ Product1 = CreateMockProductDto();
+
+ var productAppService = Substitute.For();
+ productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1));
+ services.Replace(ServiceDescriptor.Singleton(productAppService));
+
+ var productDetailAppService = Substitute.For();
+ 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 distributedEventBus = Substitute.For();
+ services.Replace(ServiceDescriptor.Singleton(distributedEventBus));
+
+ var flashSalePlanHasher = Substitute.For();
+ services.Replace(ServiceDescriptor.Singleton(flashSalePlanHasher));
+
+ var orderRepository = Substitute.For();
+ services.Replace(ServiceDescriptor.Singleton(orderRepository));
+
+ base.AfterAddApplication(services);
+ }
+
+ [Fact]
+ public async Task HandleEventAsync()
+ {
+ FlashSalePlanHasher.HashAsync(default, default, default)
+ .ReturnsForAnyArgs("My Hash Token");
+ OrderRepository.InsertAsync(default, default, default)
+ .ReturnsForAnyArgs(callInfo => callInfo.Arg());
+
+ var createFlashSaleOrderEto = new CreateFlashSaleOrderEto()
+ {
+ TenantId = CurrentTenant.Id,
+ PlanId = FlashSalesTestData.Plan1Id,
+ StoreId = FlashSalesTestData.Store1Id,
+ UserId = CurrentUser.GetId(),
+ PendingResultId = FlashSalesTestData.Result1Id,
+ CreateTime = DateTime.Now,
+ CustomerRemark = "My Remark",
+ HashToken = "My Hash Token",
+ Plan = new FlashSalePlanEto
+ {
+ Id = FlashSalesTestData.Plan1Id,
+ TenantId = CurrentTenant.Id,
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ }
+ };
+
+ await EventHandler.HandleEventAsync(createFlashSaleOrderEto);
+
+ await DistributedEventBus.Received()
+ .PublishAsync(Arg.Is(eto =>
+ eto.TenantId == CurrentTenant.Id &&
+ eto.PendingResultId == FlashSalesTestData.Result1Id &&
+ eto.Success &&
+ eto.StoreId == FlashSalesTestData.Store1Id &&
+ eto.PlanId == FlashSalesTestData.Plan1Id &&
+ eto.UserId == CurrentUser.GetId() &&
+ eto.OrderId != null &&
+ eto.Reason == null
+ ));
+ }
+
+ [Fact]
+ public async Task HandleEventAsync_Should_Publish_False_When_ValidateHashToken_Failed()
+ {
+ FlashSalePlanHasher.HashAsync(default, default, default)
+ .ReturnsForAnyArgs("My Hash Token");
+
+ var createFlashSaleOrderEto = new CreateFlashSaleOrderEto()
+ {
+ TenantId = CurrentTenant.Id,
+ PlanId = FlashSalesTestData.Plan1Id,
+ StoreId = FlashSalesTestData.Store1Id,
+ UserId = CurrentUser.GetId(),
+ PendingResultId = FlashSalesTestData.Result1Id,
+ CreateTime = DateTime.Now,
+ CustomerRemark = "My Remark",
+ HashToken = "My Hash Token Failed",
+ Plan = new FlashSalePlanEto
+ {
+ Id = FlashSalesTestData.Plan1Id,
+ TenantId = CurrentTenant.Id,
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ }
+ };
+
+ await EventHandler.HandleEventAsync(createFlashSaleOrderEto);
+
+ await DistributedEventBus.Received()
+ .PublishAsync(Arg.Is(eto =>
+ eto.TenantId == CurrentTenant.Id &&
+ eto.PendingResultId == FlashSalesTestData.Result1Id &&
+ !eto.Success &&
+ eto.StoreId == FlashSalesTestData.Store1Id &&
+ eto.PlanId == FlashSalesTestData.Plan1Id &&
+ eto.UserId == CurrentUser.GetId() &&
+ eto.OrderId == null &&
+ eto.Reason == FlashSaleResultFailedReason.InvalidHashToken
+ ));
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs
new file mode 100644
index 00000000..3ac63e8e
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs
@@ -0,0 +1,13 @@
+using EasyAbp.EShop.Orders.Plugins.FlashSales;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.FlashSales;
+
+[DependsOn(
+ typeof(EShopOrdersPluginsFlashSalesApplicationModule),
+ typeof(EShopPluginsFlashSalesTestBaseModule)
+ )]
+public class EShopOrdersPluginsFlashSalesApplicationTestsModule : AbpModule
+{
+
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs
new file mode 100644
index 00000000..5abed316
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using EasyAbp.EShop.Products.Products;
+using EasyAbp.EShop.Products.Products.Dtos;
+
+namespace EasyAbp.EShop.Plugins.FlashSales;
+
+/* Inherit from this class for your application layer tests.
+ * See SampleAppService_Tests for example.
+ */
+public abstract class OrdersPluginsFlashSalesApplicationTestBase : FlashSalesTestBase
+{
+ protected virtual ProductDto CreateMockProductDto()
+ {
+ return new ProductDto
+ {
+ CreationTime = DateTime.Now,
+ IsPublished = true,
+ Id = FlashSalesTestData.Product1Id,
+ StoreId = FlashSalesTestData.Store1Id,
+ ProductGroupName = "Default",
+ ProductGroupDisplayName = "Default",
+ UniqueName = "Pencil",
+ DisplayName = "Hello pencil",
+ ProductDetailId = FlashSalesTestData.ProductDetail1Id,
+ ProductSkus = new List
+ {
+ new ProductSkuDto
+ {
+ Id = FlashSalesTestData.ProductSku1Id,
+ Name = "My SKU",
+ OrderMinQuantity = 0,
+ OrderMaxQuantity = 100,
+ AttributeOptionIds = new List(),
+ Price = 1m,
+ Currency = "USD",
+ ProductDetailId = null,
+ Inventory = 10,
+ },
+ new ProductSkuDto
+ {
+ Id = FlashSalesTestData.ProductSku2Id,
+ Name = "My SKU 2",
+ OrderMinQuantity = 0,
+ OrderMaxQuantity = 100,
+ AttributeOptionIds = new List(),
+ Price = 2m,
+ Currency = "USD",
+ ProductDetailId = FlashSalesTestData.ProductDetail2Id,
+ Inventory = 0
+ },
+ new ProductSkuDto
+ {
+ Id = FlashSalesTestData.ProductSku3Id,
+ Name = "My SKU 3",
+ OrderMinQuantity = 0,
+ OrderMaxQuantity = 100,
+ AttributeOptionIds = new List(),
+ Price = 3m,
+ Currency = "USD",
+ ProductDetailId = FlashSalesTestData.ProductDetail2Id,
+ Inventory = 1
+ }
+ },
+ InventoryStrategy = InventoryStrategy.FlashSales,
+ LastModificationTime = FlashSalesTestData.ProductLastModificationTime
+ };
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/FodyWeavers.xml b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/FodyWeavers.xsd b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Plugins.FlashSales.Application.Tests.csproj b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Plugins.FlashSales.Application.Tests.csproj
index d9dbd3e1..0719f417 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Plugins.FlashSales.Application.Tests.csproj
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp.EShop.Plugins.FlashSales.Application.Tests.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/CreateTimeRange.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/CreateTimeRange.cs
new file mode 100644
index 00000000..2dbc3aa4
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/CreateTimeRange.cs
@@ -0,0 +1,9 @@
+namespace EasyAbp.EShop.Plugins.FlashSales;
+
+public enum CreateTimeRange
+{
+ Starting,
+ NotStart,
+ Expired,
+ WillBeExpired,
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationTestModule.cs
similarity index 56%
rename from plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestModule.cs
rename to plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationTestModule.cs
index 3a60972a..cf845dff 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestModule.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationTestModule.cs
@@ -4,9 +4,9 @@ namespace EasyAbp.EShop.Plugins.FlashSales;
[DependsOn(
typeof(EShopPluginsFlashSalesApplicationModule),
- typeof(FlashSalesDomainTestModule)
+ typeof(EShopPluginsFlashSalesDomainTestModule)
)]
-public class FlashSalesApplicationTestModule : AbpModule
+public class EShopPluginsFlashSalesApplicationTestModule : AbpModule
{
}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs
new file mode 100644
index 00000000..879eb260
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs
@@ -0,0 +1,483 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EasyAbp.Eshop.Products.Products;
+using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos;
+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 Microsoft.Extensions.Options;
+using NSubstitute;
+using Shouldly;
+using Volo.Abp;
+using Volo.Abp.Caching;
+using Volo.Abp.DistributedLocking;
+using Volo.Abp.Domain.Entities;
+using Volo.Abp.EventBus.Distributed;
+using Volo.Abp.Users;
+using Xunit;
+
+namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
+public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
+{
+ protected IFlashSalePlanAppService AppService { get; }
+
+ protected IDistributedEventBus DistributedEventBus { get; }
+
+ private ProductDto Product1 { get; set; }
+
+ public FlashSalePlanAppServiceTests()
+ {
+ AppService = GetRequiredService();
+ DistributedEventBus = GetRequiredService();
+ }
+
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ Product1 = CreateMockProductDto();
+
+ var productAppService = Substitute.For();
+ productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1));
+ services.Replace(ServiceDescriptor.Singleton(productAppService));
+
+ services.Replace(ServiceDescriptor.Transient());
+
+ var distributedEventBus = Substitute.For();
+ services.Replace(ServiceDescriptor.Singleton(distributedEventBus));
+ base.AfterAddApplication(services);
+ }
+
+ [Fact]
+ public async Task GetAsync()
+ {
+ var returnFlashSalePlan = await CreateFlashSalePlanAsync();
+
+ var flashSalePlan = await AppService.GetAsync(returnFlashSalePlan.Id);
+
+ flashSalePlan.ShouldNotBeNull();
+ flashSalePlan.Id.ShouldBe(returnFlashSalePlan.Id);
+ flashSalePlan.StoreId.ShouldBe(returnFlashSalePlan.StoreId);
+ flashSalePlan.BeginTime.ShouldBe(returnFlashSalePlan.BeginTime);
+ flashSalePlan.EndTime.ShouldBe(returnFlashSalePlan.EndTime);
+ flashSalePlan.ProductId.ShouldBe(returnFlashSalePlan.ProductId);
+ flashSalePlan.ProductSkuId.ShouldBe(returnFlashSalePlan.ProductSkuId);
+ flashSalePlan.IsPublished.ShouldBe(returnFlashSalePlan.IsPublished);
+
+ returnFlashSalePlan = await CreateFlashSalePlanAsync(isPublished: false);
+
+ flashSalePlan = await AppService.GetAsync(returnFlashSalePlan.Id);
+
+ flashSalePlan.ShouldNotBeNull();
+ flashSalePlan.Id.ShouldBe(returnFlashSalePlan.Id);
+ flashSalePlan.StoreId.ShouldBe(returnFlashSalePlan.StoreId);
+ flashSalePlan.BeginTime.ShouldBe(returnFlashSalePlan.BeginTime);
+ flashSalePlan.EndTime.ShouldBe(returnFlashSalePlan.EndTime);
+ flashSalePlan.ProductId.ShouldBe(returnFlashSalePlan.ProductId);
+ flashSalePlan.ProductSkuId.ShouldBe(returnFlashSalePlan.ProductSkuId);
+ flashSalePlan.IsPublished.ShouldBe(returnFlashSalePlan.IsPublished);
+ }
+
+ [Fact]
+ public async Task GetListAsync()
+ {
+ var publishedPlan = await CreateFlashSalePlanAsync(isPublished: true);
+ var unpublishedPlan = await CreateFlashSalePlanAsync(isPublished: false);
+
+ var allListDto = await AppService.GetListAsync(new FlashSalePlanGetListInput()
+ {
+ IncludeUnpublished = true
+ });
+
+ allListDto.TotalCount.ShouldBeGreaterThan(0);
+ allListDto.Items.FirstOrDefault(x => x.Id == publishedPlan.Id)
+ .ShouldNotBeNull()
+ .Id.ShouldBe(publishedPlan.Id);
+
+ allListDto.Items.FirstOrDefault(x => x.Id == unpublishedPlan.Id)
+ .ShouldNotBeNull()
+ .Id.ShouldBe(unpublishedPlan.Id);
+
+ var publishedListDto = await AppService.GetListAsync(new FlashSalePlanGetListInput());
+
+ publishedListDto.TotalCount.ShouldBeGreaterThan(0);
+ publishedListDto.Items.FirstOrDefault(x => x.Id == publishedPlan.Id)
+ .ShouldNotBeNull()
+ .Id.ShouldBe(publishedPlan.Id);
+
+ publishedListDto.Items.FirstOrDefault(x => x.Id == unpublishedPlan.Id)
+ .ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task CreateAsync()
+ {
+ var createDto = new FlashSalePlanCreateDto()
+ {
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ };
+ var returnFlashSalePlanDto = await AppService.CreateAsync(createDto);
+
+ var flashSalePlan = await AppService.GetAsync(returnFlashSalePlanDto.Id);
+
+ flashSalePlan.Id.ShouldBe(returnFlashSalePlanDto.Id);
+ flashSalePlan.StoreId.ShouldBe(createDto.StoreId);
+ flashSalePlan.BeginTime.ShouldBe(createDto.BeginTime);
+ flashSalePlan.EndTime.ShouldBe(createDto.EndTime);
+ flashSalePlan.ProductId.ShouldBe(createDto.ProductId);
+ flashSalePlan.ProductSkuId.ShouldBe(createDto.ProductSkuId);
+ flashSalePlan.IsPublished.ShouldBe(createDto.IsPublished);
+
+ return flashSalePlan;
+ }
+
+ [Fact]
+ public async Task CreateAsync_Should_Throw_Expcetion_When_Validate_Product_Failed()
+ {
+ var createDto = new FlashSalePlanCreateDto()
+ {
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ };
+
+ Product1.StoreId = Guid.NewGuid();
+ await AppService.CreateAsync(createDto).ShouldThrowAsync();
+ Product1.StoreId = FlashSalesTestData.Store1Id;
+
+ Product1.InventoryStrategy = InventoryStrategy.ReduceAfterPlacing;
+ await AppService.CreateAsync(createDto).ShouldThrowAsync();
+
+ Product1.StoreId = FlashSalesTestData.Store1Id;
+ Product1.InventoryStrategy = InventoryStrategy.FlashSales;
+ }
+
+ [Fact]
+ public async Task UpdateAsync()
+ {
+ var createDto = new FlashSalePlanCreateDto()
+ {
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ };
+ var returnFlashSalePlanDto = await AppService.CreateAsync(createDto);
+ var updateDto = new FlashSalePlanUpdateDto()
+ {
+ BeginTime = DateTime.Now.AddMinutes(30),
+ EndTime = DateTime.Now.AddMinutes(60),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = false
+ };
+
+ var flashSalePlan = await AppService.UpdateAsync(returnFlashSalePlanDto.Id, updateDto);
+
+ flashSalePlan.Id.ShouldBe(returnFlashSalePlanDto.Id);
+ flashSalePlan.StoreId.ShouldBe(createDto.StoreId);
+ flashSalePlan.BeginTime.ShouldBe(updateDto.BeginTime);
+ flashSalePlan.EndTime.ShouldBe(updateDto.EndTime);
+ flashSalePlan.ProductId.ShouldBe(updateDto.ProductId);
+ flashSalePlan.ProductSkuId.ShouldBe(updateDto.ProductSkuId);
+ flashSalePlan.IsPublished.ShouldBe(updateDto.IsPublished);
+ }
+
+ [Fact]
+ public async Task UpdateAsync_Should_Throw_Expcetion_When_Validate_Product_Failed()
+ {
+ var createDto = new FlashSalePlanCreateDto()
+ {
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ };
+ var returnFlashSalePlanDto = await AppService.CreateAsync(createDto);
+ var updateDto = new FlashSalePlanUpdateDto()
+ {
+ BeginTime = DateTime.Now.AddMinutes(30),
+ EndTime = DateTime.Now.AddMinutes(60),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = false
+ };
+
+ Product1.StoreId = Guid.NewGuid();
+ await AppService.UpdateAsync(returnFlashSalePlanDto.Id, updateDto).ShouldThrowAsync();
+ Product1.StoreId = FlashSalesTestData.Store1Id;
+
+ Product1.InventoryStrategy = InventoryStrategy.ReduceAfterPlacing;
+ await AppService.UpdateAsync(returnFlashSalePlanDto.Id, updateDto).ShouldThrowAsync();
+
+ Product1.StoreId = FlashSalesTestData.Store1Id;
+ Product1.InventoryStrategy = InventoryStrategy.FlashSales;
+ }
+
+ [Fact]
+ public async Task UpdateAsync_Should_Throw_Expcetion_When_Has_Result_And_Change_Product()
+ {
+ var createDto = new FlashSalePlanCreateDto()
+ {
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ };
+ var returnFlashSalePlanDto = await AppService.CreateAsync(createDto);
+ var updateDto = new FlashSalePlanUpdateDto()
+ {
+ BeginTime = DateTime.Now.AddMinutes(30),
+ EndTime = DateTime.Now.AddMinutes(value: 60),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku2Id,
+ IsPublished = false
+ };
+ await CreatePendingResultAsync(returnFlashSalePlanDto.Id, returnFlashSalePlanDto.StoreId, Guid.NewGuid());
+
+ await AppService.UpdateAsync(returnFlashSalePlanDto.Id, updateDto).ShouldThrowAsync();
+ }
+
+ [Fact]
+ public async Task DeleteAsync()
+ {
+ var createDto = new FlashSalePlanCreateDto()
+ {
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ };
+ var returnFlashSalePlanDto = await AppService.CreateAsync(createDto);
+
+ await AppService.DeleteAsync(returnFlashSalePlanDto.Id);
+ await AppService.GetAsync(returnFlashSalePlanDto.Id).ShouldThrowAsync();
+ }
+
+ [Fact]
+ public async Task DeleteAsync_Should_Throw_Expcetion_When_Has_Result()
+ {
+ var createDto = new FlashSalePlanCreateDto()
+ {
+ StoreId = FlashSalesTestData.Store1Id,
+ BeginTime = DateTime.Now,
+ EndTime = DateTime.Now.AddMinutes(30),
+ ProductId = FlashSalesTestData.Product1Id,
+ ProductSkuId = FlashSalesTestData.ProductSku1Id,
+ IsPublished = true
+ };
+ var returnFlashSalePlanDto = await AppService.CreateAsync(createDto);
+ await CreatePendingResultAsync(returnFlashSalePlanDto.Id, returnFlashSalePlanDto.StoreId, Guid.NewGuid());
+
+ await AppService.DeleteAsync(returnFlashSalePlanDto.Id).ShouldThrowAsync();
+ }
+
+ [Fact]
+ public async Task PreOrderAsync()
+ {
+ var options = GetRequiredService>().Value;
+ var distributedCache = GetRequiredService>();
+ var plan = await CreateFlashSalePlanAsync();
+ var preOrderCacheKey = string.Format(FlashSalePlanAppService.PreOrderCacheKeyFormat, plan.Id, CurrentUser.Id);
+ var hashToken = await GetRequiredService()
+ .HashAsync(plan.LastModificationTime, Product1.LastModificationTime, Product1.GetSkuById(plan.ProductSkuId).LastModificationTime);
+
+ var dto = await AppService.PreOrderAsync(plan.Id);
+
+ dto.ExpiresInSeconds.ShouldBe(options.PreOrderExpires.TotalSeconds);
+ var preOrderCacheItem = await distributedCache.GetAsync(preOrderCacheKey);
+ preOrderCacheItem.ShouldNotBeNull();
+ preOrderCacheItem.PlanId.ShouldBe(plan.Id);
+ preOrderCacheItem.TenantId.ShouldBe(plan.TenantId);
+ preOrderCacheItem.HashToken.ShouldBe(hashToken);
+ preOrderCacheItem.ProductId.ShouldBe(plan.ProductId);
+ preOrderCacheItem.ProductSkuId.ShouldBe(plan.ProductSkuId);
+ preOrderCacheItem.InventoryProviderName = Product1.InventoryProviderName;
+ }
+
+ [Fact]
+ public async Task PreOrderAsync_Should_Throw_Exception_When_Validate_PreOrder_Failed()
+ {
+ var plan = await CreateFlashSalePlanAsync();
+
+ Product1.IsPublished = false;
+
+ (await AppService.PreOrderAsync(plan.Id)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.ProductIsNotPublished);
+
+ Product1.IsPublished = true;
+
+ Product1.InventoryStrategy = InventoryStrategy.ReduceAfterPlacing;
+
+ await AppService.PreOrderAsync(plan.Id)
+ .ShouldThrowAsync();
+
+ Product1.InventoryStrategy = InventoryStrategy.FlashSales;
+
+ var plan2 = await CreateFlashSalePlanAsync(isPublished: false);
+ await AppService.PreOrderAsync(plan2.Id)
+ .ShouldThrowAsync();
+
+ var plan3 = await CreateFlashSalePlanAsync(timeRange: CreateTimeRange.Expired);
+ (await AppService.PreOrderAsync(plan3.Id)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.FlashSaleIsOver);
+
+ var plan4 = await CreateFlashSalePlanAsync(useSku2: true);
+ (await AppService.PreOrderAsync(plan4.Id)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.ProductSkuInventoryExceeded);
+ }
+
+ [Fact]
+ public async Task OrderAsync()
+ {
+ var plan = await CreateFlashSalePlanAsync();
+ var hashToken = await GetRequiredService()
+ .HashAsync(plan.LastModificationTime, Product1.LastModificationTime, Product1.GetSkuById(plan.ProductSkuId).LastModificationTime);
+ var createOrderInput = new CreateOrderInput()
+ {
+ CustomerRemark = "remark1",
+ ExtraProperties = { { "key1", "value1" } }
+ };
+ await AppService.PreOrderAsync(plan.Id);
+
+ var isSucess = await AppService.OrderAsync(plan.Id, createOrderInput);
+
+ isSucess.ShouldBe(true);
+ await DistributedEventBus.Received().PublishAsync(Arg.Is(eto =>
+ eto.TenantId == plan.TenantId &&
+ eto.StoreId == plan.StoreId &&
+ eto.PlanId == plan.Id &&
+ eto.UserId == CurrentUser.GetId() &&
+ eto.HashToken == hashToken &&
+ eto.CustomerRemark == createOrderInput.CustomerRemark &&
+ eto.Plan != null &&
+ eto.Plan.TenantId == plan.TenantId &&
+ eto.Plan.StoreId == plan.StoreId &&
+ eto.Plan.BeginTime == plan.BeginTime &&
+ eto.Plan.EndTime == plan.EndTime &&
+ eto.Plan.ProductId == plan.ProductId &&
+ eto.Plan.ProductSkuId == plan.ProductSkuId &&
+ eto.Plan.IsPublished == plan.IsPublished &&
+ eto.ExtraProperties.ContainsKey("key1") &&
+ eto.ExtraProperties["key1"].ToString() == "value1"
+ ));
+ }
+
+ [Fact]
+ public async Task OrderAsync_Throw_Exception_When_Not_PreOrder()
+ {
+ var plan = await CreateFlashSalePlanAsync();
+ var createOrderInput = new CreateOrderInput();
+
+ (await AppService.OrderAsync(plan.Id, createOrderInput)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.PreOrderExpired);
+ }
+
+ [Fact]
+ public async Task OrderAsync_Throw_Exception_When_FlashSaleNotStarted()
+ {
+ var plan = await CreateFlashSalePlanAsync(timeRange: CreateTimeRange.NotStart);
+ var createOrderInput = new CreateOrderInput();
+ await AppService.PreOrderAsync(plan.Id);
+
+ (await AppService.OrderAsync(plan.Id, createOrderInput)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.FlashSaleNotStarted);
+ }
+
+ [Fact]
+ public async Task OrderAsync_Throw_Exception_When_FlashSaleIsOver()
+ {
+ var plan = await CreateFlashSalePlanAsync(timeRange: CreateTimeRange.WillBeExpired);
+ var createOrderInput = new CreateOrderInput();
+ await AppService.PreOrderAsync(plan.Id);
+
+ await Task.Delay(TimeSpan.FromSeconds(1.2));
+
+ (await AppService.OrderAsync(plan.Id, createOrderInput)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.FlashSaleIsOver);
+ }
+
+ [Fact]
+ public async Task OrderAsync_Throw_Exception_When_BusyToCreateFlashSaleOrder()
+ {
+ var plan = await CreateFlashSalePlanAsync();
+ var createOrderInput = new CreateOrderInput();
+ await AppService.PreOrderAsync(plan.Id);
+ var distributedLock = GetRequiredService();
+ var lockKey = $"create-flash-sale-order-{plan.Id}-{CurrentUser.GetId()}";
+
+ await using var handle = await distributedLock.TryAcquireAsync(lockKey);
+
+ (await AppService.OrderAsync(plan.Id, createOrderInput)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder);
+ }
+
+ [Fact]
+ public async Task OrderAsync_Throw_Exception_When_Exist_UserFlashSaleResultCache()
+ {
+ var plan = await CreateFlashSalePlanAsync();
+ var createOrderInput = new CreateOrderInput();
+ await AppService.PreOrderAsync(plan.Id);
+ var distributedCache = GetRequiredService();
+ var userId = CurrentUser.GetId();
+ var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, userId);
+ await distributedCache.SetStringAsync(userFlashSaleResultCacheKey, Guid.NewGuid().ToString());
+
+ (await AppService.OrderAsync(plan.Id, createOrderInput)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
+ }
+
+ [Fact]
+ public async Task OrderAsync_Return_False_When_TryReduceInventory_Failed()
+ {
+ var plan = await CreateFlashSalePlanAsync();
+ var createOrderInput = new CreateOrderInput();
+ await AppService.PreOrderAsync(plan.Id);
+
+ FakeFlashSaleInventoryManager.ShouldReduceSuccess = false;
+
+ (await AppService.OrderAsync(plan.Id, createOrderInput)).ShouldBe(false);
+ }
+
+ [Fact]
+ public async Task OrderAsync_Return_False_When_Exist_Not_Failed_Result()
+ {
+ var plan = await CreateFlashSalePlanAsync();
+ var createOrderInput = new CreateOrderInput();
+ await AppService.PreOrderAsync(plan.Id);
+ var userId = GetRequiredService().GetId();
+
+ await CreatePendingResultAsync(plan.Id, plan.StoreId, userId);
+
+ (await AppService.OrderAsync(plan.Id, createOrderInput)
+ .ShouldThrowAsync())
+ .Code.ShouldBe(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RollBackInventoryCreateFlashSaleOrderCompleteEventHandlerTest.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RollBackInventoryCreateFlashSaleOrderCompleteEventHandlerTest.cs
new file mode 100644
index 00000000..164faa82
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RollBackInventoryCreateFlashSaleOrderCompleteEventHandlerTest.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+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;
+using Volo.Abp.Caching;
+using Volo.Abp.EventBus.Distributed;
+using Volo.Abp.Users;
+using Xunit;
+
+namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
+
+public class RollBackInventoryCreateFlashSaleOrderCompleteEventHandlerTest : FlashSalesApplicationTestBase
+{
+ protected RollBackInventoryCreateFlashSaleOrderCompleteEventHandler EventHandler { get; }
+
+ protected IDistributedCache DistributedCache { get; }
+
+ protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; set; }
+
+ private ProductDto Product1 { get; set; }
+
+ public RollBackInventoryCreateFlashSaleOrderCompleteEventHandlerTest()
+ {
+ EventHandler = GetRequiredService();
+ DistributedCache = GetRequiredService();
+ FlashSaleInventoryManager = GetRequiredService();
+ }
+
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ Product1 = CreateMockProductDto();
+
+ var productAppService = Substitute.For();
+ productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1));
+ services.Replace(ServiceDescriptor.Singleton(productAppService));
+
+ FlashSaleInventoryManager = Substitute.For();
+ 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);
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs
index 77224ae3..e0877a3d 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs
@@ -1,9 +1,149 @@
-namespace EasyAbp.EShop.Plugins.FlashSales;
+using System.Threading.Tasks;
+using System;
+using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
+using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
+using Volo.Abp;
+using Volo.Abp.Guids;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.Timing;
+using Volo.Abp.Users;
+using EasyAbp.EShop.Products.Products.Dtos;
+using EasyAbp.EShop.Products.Products;
+using System.Collections.Generic;
+
+namespace EasyAbp.EShop.Plugins.FlashSales;
/* Inherit from this class for your application layer tests.
* See SampleAppService_Tests for example.
*/
-public abstract class FlashSalesApplicationTestBase : FlashSalesTestBase
+public abstract class FlashSalesApplicationTestBase : FlashSalesTestBase
{
+ protected IGuidGenerator GuidGenerator { get; }
+
+ protected ICurrentTenant CurrentTenant { get; }
+
+ protected IClock Clock { get; }
+
+ protected ICurrentUser CurrentUser { get; }
+
+ protected IFlashSaleResultRepository FlashSaleResultRepository { get; }
+
+ protected IFlashSalePlanRepository FlashSalePlanRepository { get; }
+
+ protected FlashSalesApplicationTestBase()
+ {
+ GuidGenerator = GetRequiredService();
+ CurrentTenant = GetRequiredService();
+ Clock = GetRequiredService();
+ CurrentUser = GetRequiredService();
+ FlashSalePlanRepository = GetRequiredService();
+ FlashSaleResultRepository = GetRequiredService();
+ }
+
+ protected virtual ProductDto CreateMockProductDto()
+ {
+ return new ProductDto
+ {
+ CreationTime = DateTime.Now,
+ IsPublished = true,
+ Id = FlashSalesTestData.Product1Id,
+ StoreId = FlashSalesTestData.Store1Id,
+ ProductGroupName = "Default",
+ ProductGroupDisplayName = "Default",
+ UniqueName = "Pencil",
+ DisplayName = "Hello pencil",
+ ProductDetailId = FlashSalesTestData.ProductDetail1Id,
+ ProductSkus = new List
+ {
+ new ProductSkuDto
+ {
+ Id = FlashSalesTestData.ProductSku1Id,
+ Name = "My SKU",
+ OrderMinQuantity = 0,
+ OrderMaxQuantity = 100,
+ AttributeOptionIds = new List(),
+ Price = 1m,
+ Currency = "USD",
+ ProductDetailId = null,
+ Inventory = 10,
+ },
+ new ProductSkuDto
+ {
+ Id = FlashSalesTestData.ProductSku2Id,
+ Name = "My SKU 2",
+ OrderMinQuantity = 0,
+ OrderMaxQuantity = 100,
+ AttributeOptionIds = new List(),
+ Price = 2m,
+ Currency = "USD",
+ ProductDetailId = FlashSalesTestData.ProductDetail2Id,
+ Inventory = 0
+ },
+ new ProductSkuDto
+ {
+ Id = FlashSalesTestData.ProductSku3Id,
+ Name = "My SKU 3",
+ OrderMinQuantity = 0,
+ OrderMaxQuantity = 100,
+ AttributeOptionIds = new List(),
+ Price = 3m,
+ Currency = "USD",
+ ProductDetailId = FlashSalesTestData.ProductDetail2Id,
+ Inventory = 1
+ }
+ },
+ InventoryStrategy = InventoryStrategy.FlashSales,
+ LastModificationTime = FlashSalesTestData.ProductLastModificationTime
+ };
+ }
+
+ protected virtual async Task CreatePendingResultAsync(Guid planId, Guid storeId, Guid userId)
+ {
+ return await WithUnitOfWorkAsync(async () =>
+ {
+ return await FlashSaleResultRepository.InsertAsync(
+ new FlashSaleResult(GuidGenerator.Create(), CurrentTenant.Id, storeId, planId, userId)
+ );
+ });
+ }
+
+ protected virtual async Task CreateFlashSalePlanAsync(bool useSku2 = false, CreateTimeRange timeRange = CreateTimeRange.Starting, bool isPublished = true)
+ {
+ DateTime beginTime;
+ DateTime endTime;
+
+ switch (timeRange)
+ {
+ case CreateTimeRange.Starting:
+ beginTime = Clock.Now;
+ endTime = beginTime.AddMinutes(30);
+ break;
+ case CreateTimeRange.NotStart:
+ beginTime = Clock.Now.AddMinutes(10);
+ endTime = beginTime.AddMinutes(30);
+ break;
+ case CreateTimeRange.Expired:
+ beginTime = Clock.Now.AddDays(-1);
+ endTime = beginTime.AddMinutes(30);
+ break;
+ case CreateTimeRange.WillBeExpired:
+ beginTime = Clock.Now.AddDays(-30);
+ endTime = Clock.Now.AddSeconds(1);
+ break;
+ default:
+ throw new AbpException();
+ }
+ var flashSalePlan = new FlashSalePlan(
+ GuidGenerator.Create(),
+ CurrentTenant.Id,
+ FlashSalesTestData.Store1Id,
+ beginTime,
+ endTime,
+ FlashSalesTestData.Product1Id,
+ useSku2 ? FlashSalesTestData.ProductSku2Id : FlashSalesTestData.ProductSku1Id,
+ isPublished
+ );
+ return await WithUnitOfWorkAsync(async () => await FlashSalePlanRepository.InsertAsync(flashSalePlan, autoSave: true));
+ }
}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs
new file mode 100644
index 00000000..ff159deb
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+using EasyAbp.Eshop.Products.Products;
+
+namespace EasyAbp.EShop.Products.Products;
+
+public class FakeFlashSaleInventoryManager : IFlashSaleInventoryManager
+{
+ public static bool ShouldReduceSuccess { get; set; }
+
+ public FakeFlashSaleInventoryManager()
+ {
+ ShouldReduceSuccess = true;
+ }
+
+ public Task TryReduceInventoryAsync(
+ Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool increaseSold)
+ {
+ return Task.FromResult(ShouldReduceSuccess);
+ }
+
+ public Task TryRollBackInventoryAsync(
+ Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool decreaseSold)
+ {
+ return Task.FromResult(true);
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDomainTestModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainTestModule.cs
similarity index 71%
rename from plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDomainTestModule.cs
rename to plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainTestModule.cs
index 3a218f7f..3a033be8 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDomainTestModule.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesDomainTestModule.cs
@@ -8,9 +8,9 @@ namespace EasyAbp.EShop.Plugins.FlashSales;
* database independent anyway.
*/
[DependsOn(
- typeof(FlashSalesEntityFrameworkCoreTestModule)
+ typeof(EShopPluginsFlashSalesEntityFrameworkCoreTestModule)
)]
-public class FlashSalesDomainTestModule : AbpModule
+public class EShopPluginsFlashSalesDomainTestModule : AbpModule
{
}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanHasherTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanHasherTests.cs
new file mode 100644
index 00000000..b0868cd8
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanHasherTests.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+
+namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
+public class FlashSalePlanHasherTests : FlashSalesDomainTestBase
+{
+ protected IFlashSalePlanHasher FlashSalePlanHasher { get; }
+
+ public FlashSalePlanHasherTests()
+ {
+ FlashSalePlanHasher = GetRequiredService();
+ }
+
+ [Fact]
+ public async Task HashAsync()
+ {
+ var time1 = DateTime.Now;
+ var time2 = DateTime.Now.AddTicks(10);
+ var time3 = DateTime.Now.AddTicks(20);
+
+ (await FlashSalePlanHasher.HashAsync(time1, time2, time3))
+ .ShouldBe(await FlashSalePlanHasher.HashAsync(time1, time2, time3));
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTests.cs
new file mode 100644
index 00000000..90b7694b
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTests.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+
+namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
+
+public class FlashSalePlanTests
+{
+ [Fact]
+ public void Should_Throw_InvalidEndTimeException()
+ {
+ Assert.Throws(() =>
+ {
+ new FlashSalePlan(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ beginTime: DateTime.Now,
+ endTime: DateTime.Now.AddSeconds(-1),
+ productId: Guid.NewGuid(),
+ productSkuId: Guid.NewGuid(),
+ isPublished: true
+ );
+ });
+ }
+
+ [Fact]
+ public void SetTimeRange()
+ {
+ var existPlan = new FlashSalePlan(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ beginTime: DateTime.Now,
+ endTime: DateTime.Now.AddSeconds(1),
+ productId: Guid.NewGuid(),
+ productSkuId: Guid.NewGuid(),
+ isPublished: true
+ );
+
+ var newBeginTime = DateTime.Now;
+ var newEndTime = newBeginTime.AddMinutes(1);
+
+ existPlan.SetTimeRange(newBeginTime, newEndTime);
+
+ existPlan.BeginTime.ShouldBe(newBeginTime);
+ existPlan.EndTime.ShouldBe(newEndTime);
+ }
+
+ [Fact]
+ public void SetTimeRange_Should_Throw_InvalidEndTimeException_When_Set_InvalidEndTime()
+ {
+ var existPlan = new FlashSalePlan(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ beginTime: DateTime.Now,
+ endTime: DateTime.Now.AddSeconds(1),
+ productId: Guid.NewGuid(),
+ productSkuId: Guid.NewGuid(),
+ isPublished: true
+ );
+
+ Assert.Throws(() => existPlan.SetTimeRange(DateTime.Now, DateTime.Now.AddMinutes(-1)));
+ }
+
+ [Fact]
+ public void SetProductSku()
+ {
+ var existPlan = new FlashSalePlan(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ beginTime: DateTime.Now,
+ endTime: DateTime.Now.AddSeconds(1),
+ productId: Guid.NewGuid(),
+ productSkuId: Guid.NewGuid(),
+ isPublished: true
+ );
+
+ var newStoreId = Guid.NewGuid();
+ var newProductId = Guid.NewGuid();
+ var newProductSkuId = Guid.NewGuid();
+
+ existPlan.SetProductSku(newStoreId, newProductId, newProductSkuId);
+
+ existPlan.StoreId.ShouldBe(newStoreId);
+ existPlan.ProductId.ShouldBe(newProductId);
+ existPlan.ProductSkuId.ShouldBe(newProductSkuId);
+ }
+
+ [Fact]
+ public void SetPublished()
+ {
+ var existPlan = new FlashSalePlan(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ beginTime: DateTime.Now,
+ endTime: DateTime.Now.AddSeconds(1),
+ productId: Guid.NewGuid(),
+ productSkuId: Guid.NewGuid(),
+ isPublished: true
+ );
+
+ existPlan.SetPublished(false);
+
+ existPlan.IsPublished.ShouldBe(false);
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs
new file mode 100644
index 00000000..5172e23f
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs
@@ -0,0 +1,85 @@
+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();
+ CreateFlashSaleOrderCompleteEventHandler = GetRequiredService();
+ GuidGenerator = GetRequiredService();
+ }
+
+ [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 CreateFlashSaleResultAsync()
+ {
+ return await WithUnitOfWorkAsync(async () =>
+ {
+ var flashSaleResult = new FlashSaleResult(
+ GuidGenerator.Create(),
+ null,
+ FlashSalesTestData.Store1Id,
+ FlashSalesTestData.Plan1Id,
+ GuidGenerator.Create());
+ await FlashSaleResultRepository.InsertAsync(flashSaleResult);
+
+ return flashSaleResult;
+ });
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs
new file mode 100644
index 00000000..7464ce4b
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+
+namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
+
+public class FlashSaleResultTests
+{
+ [Fact]
+ public void MarkAsSuccessful()
+ {
+ var flashSaleResult = new FlashSaleResult(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ planId: Guid.NewGuid(),
+ userId: Guid.NewGuid()
+ );
+
+ flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
+
+ var orderId = Guid.NewGuid();
+ flashSaleResult.MarkAsSuccessful(orderId);
+
+ flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Successful);
+ flashSaleResult.OrderId.ShouldBe(orderId);
+ }
+
+ [Fact]
+ public void MarkAsSuccessful_Should_Throw_FlashSaleResultStatusNotPendingException_When_Status_Not_Pending()
+ {
+ var flashSaleResult = new FlashSaleResult(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ planId: Guid.NewGuid(),
+ userId: Guid.NewGuid()
+ );
+
+ flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
+
+ var orderId = Guid.NewGuid();
+ flashSaleResult.MarkAsSuccessful(orderId);
+ flashSaleResult.Reason.ShouldBe(null);
+
+ Assert.Throws(() =>
+ {
+ flashSaleResult.MarkAsSuccessful(orderId);
+ });
+ }
+
+ [Fact]
+ public void MarkAsFailed()
+ {
+ var flashSaleResult = new FlashSaleResult(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ planId: Guid.NewGuid(),
+ userId: Guid.NewGuid()
+ );
+
+ flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
+
+ flashSaleResult.MarkAsFailed("reason");
+
+ flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Failed);
+ flashSaleResult.OrderId.ShouldBe(null);
+ flashSaleResult.Reason.ShouldBe("reason");
+ }
+
+ [Fact]
+ public void MarkAsFailed_Should_Throw_FlashSaleResultStatusNotPendingException_When_Status_Not_Pending()
+ {
+ var flashSaleResult = new FlashSaleResult(
+ id: Guid.NewGuid(),
+ tenantId: null,
+ storeId: Guid.NewGuid(),
+ planId: Guid.NewGuid(),
+ userId: Guid.NewGuid()
+ );
+
+ flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
+
+ flashSaleResult.MarkAsFailed("reason");
+
+ Assert.Throws(() =>
+ {
+ flashSaleResult.MarkAsFailed("reason");
+ });
+ }
+}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDomainTestBase.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDomainTestBase.cs
index 1cb6e93a..58e775a1 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDomainTestBase.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesDomainTestBase.cs
@@ -3,7 +3,7 @@
/* Inherit from this class for your domain layer tests.
* See SampleManager_Tests for example.
*/
-public abstract class FlashSalesDomainTestBase : FlashSalesTestBase
+public abstract class FlashSalesDomainTestBase : FlashSalesTestBase
{
}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesEntityFrameworkCoreTestModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/EShopPluginsFlashSalesEntityFrameworkCoreTestModule.cs
similarity index 91%
rename from plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesEntityFrameworkCoreTestModule.cs
rename to plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/EShopPluginsFlashSalesEntityFrameworkCoreTestModule.cs
index fb28fd4a..39b1283c 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesEntityFrameworkCoreTestModule.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/EShopPluginsFlashSalesEntityFrameworkCoreTestModule.cs
@@ -9,11 +9,11 @@ using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore;
[DependsOn(
- typeof(FlashSalesTestBaseModule),
+ typeof(EShopPluginsFlashSalesTestBaseModule),
typeof(EShopPluginsFlashSalesEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
-public class FlashSalesEntityFrameworkCoreTestModule : AbpModule
+public class EShopPluginsFlashSalesEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesEntityFrameworkCoreTestBase.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesEntityFrameworkCoreTestBase.cs
index 01e83ba7..0ba954e8 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesEntityFrameworkCoreTestBase.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.EntityFrameworkCore.Tests/EasyAbp/EShop/Plugins/FlashSales/EntityFrameworkCore/FlashSalesEntityFrameworkCoreTestBase.cs
@@ -3,7 +3,7 @@
/* This class can be used as a base class for EF Core integration tests,
* while SampleRepository_Tests uses a different approach.
*/
-public abstract class FlashSalesEntityFrameworkCoreTestBase : FlashSalesTestBase
+public abstract class FlashSalesEntityFrameworkCoreTestBase : FlashSalesTestBase
{
}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/ConsoleTestAppHostedService.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/ConsoleTestAppHostedService.cs
index d9e47d1d..f1d41e9b 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/ConsoleTestAppHostedService.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/ConsoleTestAppHostedService.cs
@@ -18,7 +18,7 @@ public class ConsoleTestAppHostedService : IHostedService
public async Task StartAsync(CancellationToken cancellationToken)
{
- using (var application = await AbpApplicationFactory.CreateAsync(options =>
+ using (var application = await AbpApplicationFactory.CreateAsync(options =>
{
options.Services.ReplaceConfiguration(_configuration);
options.UseAutofac();
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/FlashSalesConsoleApiClientModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesConsoleApiClientModule.cs
similarity index 80%
rename from plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/FlashSalesConsoleApiClientModule.cs
rename to plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesConsoleApiClientModule.cs
index 1d081c6c..80a95f2c 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/FlashSalesConsoleApiClientModule.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.HttpApi.Client.ConsoleTestApp/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesConsoleApiClientModule.cs
@@ -9,7 +9,7 @@ namespace EasyAbp.EShop.Plugins.FlashSales;
typeof(EShopPluginsFlashSalesHttpApiClientModule),
typeof(AbpHttpClientIdentityModelModule)
)]
-public class FlashSalesConsoleApiClientModule : AbpModule
+public class EShopPluginsFlashSalesConsoleApiClientModule : AbpModule
{
}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/FlashSalesMongoDbTestModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/EShopPluginsFlashSalesMongoDbTestModule.cs
similarity index 86%
rename from plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/FlashSalesMongoDbTestModule.cs
rename to plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/EShopPluginsFlashSalesMongoDbTestModule.cs
index 2d101fa3..3bc8bcd3 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/FlashSalesMongoDbTestModule.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/EShopPluginsFlashSalesMongoDbTestModule.cs
@@ -6,10 +6,10 @@ using Volo.Abp.Uow;
namespace EasyAbp.EShop.Plugins.FlashSales.MongoDB;
[DependsOn(
- typeof(FlashSalesTestBaseModule),
+ typeof(EShopPluginsFlashSalesTestBaseModule),
typeof(EShopPluginsFlashSalesMongoDbModule)
)]
-public class FlashSalesMongoDbTestModule : AbpModule
+public class EShopPluginsFlashSalesMongoDbTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/FlashSalesMongoDbTestBase.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/FlashSalesMongoDbTestBase.cs
index 0953ae82..585e5ef6 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/FlashSalesMongoDbTestBase.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.MongoDB.Tests/EasyAbp/EShop/Plugins/FlashSales/MongoDB/FlashSalesMongoDbTestBase.cs
@@ -3,7 +3,7 @@
/* This class can be used as a base class for MongoDB integration tests,
* while SampleRepository_Tests uses a different approach.
*/
-public abstract class FlashSalesMongoDbTestBase : FlashSalesTestBase
+public abstract class FlashSalesMongoDbTestBase : FlashSalesTestBase
{
}
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/FlashSalesTestBaseModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesTestBaseModule.cs
similarity index 94%
rename from plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/FlashSalesTestBaseModule.cs
rename to plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesTestBaseModule.cs
index 33ce5497..5bc24871 100644
--- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/FlashSalesTestBaseModule.cs
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesTestBaseModule.cs
@@ -14,7 +14,7 @@ namespace EasyAbp.EShop.Plugins.FlashSales;
typeof(AbpAuthorizationModule),
typeof(EShopPluginsFlashSalesDomainModule)
)]
-public class FlashSalesTestBaseModule : AbpModule
+public class EShopPluginsFlashSalesTestBaseModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/FlashSalesTestData.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/FlashSalesTestData.cs
new file mode 100644
index 00000000..baa98bd9
--- /dev/null
+++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.TestBase/EasyAbp/EShop/Plugins/FlashSales/FlashSalesTestData.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace EasyAbp.EShop.Plugins.FlashSales;
+
+public static class FlashSalesTestData
+{
+ public static Guid Plan1Id { get; } = Guid.NewGuid();
+
+ public static Guid Result1Id { get; } = Guid.NewGuid();
+
+ public static Guid Store1Id { get; } = Guid.NewGuid();
+
+ public static Guid Product1Id { get; } = Guid.NewGuid();
+
+ public static Guid ProductSku1Id { get; } = Guid.NewGuid();
+
+ public static Guid ProductSku2Id { get; } = Guid.NewGuid();
+
+ public static Guid ProductSku3Id { get; } = Guid.NewGuid();
+
+ public static Guid ProductDetail1Id { get; } = Guid.NewGuid();
+
+ public static Guid ProductDetail2Id { get; } = Guid.NewGuid();
+
+ public static DateTime ProductLastModificationTime { get; } = DateTime.Today;
+
+ public static DateTime ProductDetailLastModificationTime { get; } = DateTime.Today;
+}