mirror of https://github.com/EasyAbp/EShop.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
753 lines
29 KiB
753 lines
29 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using EasyAbp.EShop.Orders.Orders.Dtos;
|
|
using EasyAbp.EShop.Orders.Settings;
|
|
using EasyAbp.EShop.Products.ProductDetails;
|
|
using EasyAbp.EShop.Products.ProductDetails.Dtos;
|
|
using EasyAbp.EShop.Products.Products;
|
|
using EasyAbp.EShop.Products.Products.Dtos;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using NSubstitute;
|
|
using Shouldly;
|
|
using Volo.Abp;
|
|
using Volo.Abp.Domain.Repositories;
|
|
using Volo.Abp.Timing;
|
|
using Xunit;
|
|
|
|
namespace EasyAbp.EShop.Orders.Orders
|
|
{
|
|
public class OrderAppServiceTests : OrdersApplicationTestBase
|
|
{
|
|
private readonly IClock _clock;
|
|
private readonly IOrderAppService _orderAppService;
|
|
private readonly IMoneyDistributor _moneyDistributor;
|
|
|
|
private ProductDto Product1 { get; set; }
|
|
|
|
public OrderAppServiceTests()
|
|
{
|
|
_clock = GetRequiredService<IClock>();
|
|
_orderAppService = GetRequiredService<IOrderAppService>();
|
|
_moneyDistributor = GetRequiredService<IMoneyDistributor>();
|
|
}
|
|
|
|
protected override void AfterAddApplication(IServiceCollection services)
|
|
{
|
|
Product1 = new ProductDto
|
|
{
|
|
CreationTime = DateTime.Now,
|
|
IsPublished = true,
|
|
Id = OrderTestData.Product1Id,
|
|
StoreId = OrderTestData.Store1Id,
|
|
ProductGroupName = "Default",
|
|
ProductGroupDisplayName = "Default",
|
|
UniqueName = "Pencil",
|
|
DisplayName = "Hello pencil",
|
|
ProductDetailId = OrderTestData.ProductDetail1Id,
|
|
ProductSkus = new List<ProductSkuDto>
|
|
{
|
|
new ProductSkuDto
|
|
{
|
|
Id = OrderTestData.ProductSku1Id,
|
|
Name = "My SKU",
|
|
OrderMinQuantity = 0,
|
|
OrderMaxQuantity = 100,
|
|
AttributeOptionIds = new List<Guid>(),
|
|
Price = 1m,
|
|
Currency = "USD",
|
|
ProductDetailId = null
|
|
},
|
|
new ProductSkuDto
|
|
{
|
|
Id = OrderTestData.ProductSku2Id,
|
|
Name = "My SKU 2",
|
|
OrderMinQuantity = 0,
|
|
OrderMaxQuantity = 100,
|
|
AttributeOptionIds = new List<Guid>(),
|
|
Price = 2m,
|
|
Currency = "USD",
|
|
ProductDetailId = OrderTestData.ProductDetail2Id
|
|
},
|
|
new ProductSkuDto
|
|
{
|
|
Id = OrderTestData.ProductSku3Id,
|
|
Name = "My SKU 3",
|
|
OrderMinQuantity = 0,
|
|
OrderMaxQuantity = 100,
|
|
AttributeOptionIds = new List<Guid>(),
|
|
Price = 3m,
|
|
Currency = "USD",
|
|
ProductDetailId = OrderTestData.ProductDetail2Id
|
|
}
|
|
},
|
|
InventoryStrategy = InventoryStrategy.NoNeed,
|
|
LastModificationTime = OrderTestData.ProductLastModificationTime
|
|
};
|
|
|
|
var productAppService = Substitute.For<IProductAppService>();
|
|
productAppService.GetAsync(OrderTestData.Product1Id).Returns(Task.FromResult(Product1));
|
|
|
|
services.AddTransient(_ => productAppService);
|
|
|
|
var productDetailAppService = Substitute.For<IProductDetailAppService>();
|
|
|
|
productDetailAppService.GetAsync(OrderTestData.ProductDetail1Id).Returns(Task.FromResult(
|
|
new ProductDetailDto
|
|
{
|
|
Id = OrderTestData.ProductDetail1Id,
|
|
CreationTime = OrderTestData.ProductDetailLastModificationTime,
|
|
LastModificationTime = OrderTestData.ProductDetailLastModificationTime,
|
|
StoreId = OrderTestData.Store1Id,
|
|
Description = "My Details 1"
|
|
}));
|
|
|
|
productDetailAppService.GetAsync(OrderTestData.ProductDetail2Id).Returns(Task.FromResult(
|
|
new ProductDetailDto
|
|
{
|
|
Id = OrderTestData.ProductDetail2Id,
|
|
StoreId = OrderTestData.Store1Id,
|
|
Description = "My Details 2"
|
|
}));
|
|
|
|
services.AddTransient(_ => productDetailAppService);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Check_Create_Order_Should_Succeed()
|
|
{
|
|
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
|
|
var orderCount = 0;
|
|
await WithUnitOfWorkAsync(async () => { orderCount = await orderRepository.CountAsync(); });
|
|
|
|
// Arrange
|
|
var checkCreateOrderInput = new CheckCreateOrderInput
|
|
{
|
|
CustomerRemark = "customer remark",
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 10
|
|
}
|
|
}
|
|
};
|
|
|
|
CheckCreateOrderResultDto resultDto = null;
|
|
// Act
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
resultDto = await _orderAppService.CheckCreateAsync(checkCreateOrderInput);
|
|
});
|
|
|
|
// Assert
|
|
resultDto.CanCreate.ShouldBeTrue();
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
orderCount.ShouldBeEquivalentTo(await orderRepository.CountAsync());
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Check_Create_Order_Should_Fail()
|
|
{
|
|
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
|
|
var orderCount = 0;
|
|
await WithUnitOfWorkAsync(async () => { orderCount = await orderRepository.CountAsync(); });
|
|
|
|
// Arrange
|
|
var checkCreateOrderInput = new CheckCreateOrderInput
|
|
{
|
|
CustomerRemark = "customer remark",
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 101 // limited range: 1-100
|
|
}
|
|
}
|
|
};
|
|
|
|
CheckCreateOrderResultDto resultDto = null;
|
|
// Act
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
resultDto = await _orderAppService.CheckCreateAsync(checkCreateOrderInput);
|
|
});
|
|
|
|
// Assert
|
|
resultDto.CanCreate.ShouldBeFalse();
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
orderCount.ShouldBeEquivalentTo(await orderRepository.CountAsync());
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Order_Should_Be_Created()
|
|
{
|
|
// Arrange
|
|
var createOrderDto = new CreateOrderDto
|
|
{
|
|
CustomerRemark = "customer remark",
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 10
|
|
},
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku2Id,
|
|
Quantity = 1
|
|
}
|
|
}
|
|
};
|
|
|
|
OrderDto createResponse = null;
|
|
// Act
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
createResponse = await _orderAppService.CreateAsync(createOrderDto);
|
|
});
|
|
|
|
var response = await _orderAppService.GetAsync(createResponse.Id);
|
|
|
|
// Assert
|
|
UsingDbContext(context =>
|
|
{
|
|
context.Orders.Count().ShouldBe(1);
|
|
var order = context.Orders.Include(x => x.OrderLines).First();
|
|
order.ShouldNotBeNull();
|
|
order.Currency.ShouldBe("USD");
|
|
order.CanceledTime.ShouldBeNull();
|
|
order.CancellationReason.ShouldBeNullOrEmpty();
|
|
order.CompletionTime.ShouldBeNull();
|
|
order.CustomerRemark.ShouldBe("customer remark");
|
|
order.OrderNumber.ShouldNotBeNull();
|
|
order.OrderStatus.ShouldBe(OrderStatus.Pending);
|
|
order.PaidTime.ShouldBeNull();
|
|
order.PaymentId.ShouldBeNull();
|
|
order.RefundAmount.ShouldBe(0m);
|
|
order.StaffRemark.ShouldBeNullOrEmpty();
|
|
order.StoreId.ShouldBe(OrderTestData.Store1Id);
|
|
order.TotalDiscount.ShouldBe(0m);
|
|
order.TotalPrice.ShouldBe(order.ActualTotalPrice);
|
|
order.ActualTotalPrice.ShouldBe(order.ActualTotalPrice);
|
|
order.CustomerUserId.ShouldBe(Guid.Parse("2e701e62-0953-4dd3-910b-dc6cc93ccb0d"));
|
|
order.ProductTotalPrice.ShouldBe(order.ActualTotalPrice);
|
|
order.ReducedInventoryAfterPaymentTime.ShouldBeNull();
|
|
order.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
|
|
order.OrderLines.Count.ShouldBe(2);
|
|
|
|
var orderLine1 = response.OrderLines.Single(x => x.ProductSkuId == OrderTestData.ProductSku1Id);
|
|
orderLine1.ProductId.ShouldBe(OrderTestData.Product1Id);
|
|
orderLine1.ProductSkuId.ShouldBe(OrderTestData.ProductSku1Id);
|
|
orderLine1.ProductDetailId.ShouldBe(OrderTestData.ProductDetail1Id);
|
|
orderLine1.ProductDisplayName.ShouldBe("Hello pencil");
|
|
orderLine1.ProductUniqueName.ShouldBe("Pencil");
|
|
orderLine1.ProductGroupName.ShouldBe("Default");
|
|
orderLine1.ProductGroupDisplayName.ShouldBe("Default");
|
|
orderLine1.SkuName.ShouldBe("My SKU");
|
|
orderLine1.UnitPrice.ShouldBe(1m);
|
|
orderLine1.TotalPrice.ShouldBe(10m);
|
|
orderLine1.TotalDiscount.ShouldBe(0m);
|
|
orderLine1.ActualTotalPrice.ShouldBe(10m);
|
|
orderLine1.Currency.ShouldBe("USD");
|
|
orderLine1.Quantity.ShouldBe(10);
|
|
orderLine1.ProductModificationTime.ShouldBe(OrderTestData.ProductLastModificationTime);
|
|
orderLine1.ProductDetailModificationTime.ShouldBe(OrderTestData.ProductDetailLastModificationTime);
|
|
orderLine1.RefundAmount.ShouldBe(0m);
|
|
orderLine1.RefundedQuantity.ShouldBe(0);
|
|
|
|
var orderLine2 = response.OrderLines.Single(x => x.ProductSkuId == OrderTestData.ProductSku2Id);
|
|
orderLine2.ProductDetailId.ShouldBe(OrderTestData.ProductDetail2Id);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Order_Should_Be_Completed()
|
|
{
|
|
// Arrange
|
|
await Order_Should_Be_Created();
|
|
Guid orderId = Guid.Empty;
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.First();
|
|
order.OrderStatus.ShouldNotBe(OrderStatus.Completed);
|
|
order.CompletionTime.ShouldBeNull();
|
|
orderId = order.Id;
|
|
order.SetPaid(DateTime.Now);
|
|
order.SetReducedInventoryAfterPaymentTime(DateTime.Now);
|
|
db.SaveChanges();
|
|
});
|
|
|
|
// Act
|
|
var response = await _orderAppService.CompleteAsync(orderId);
|
|
|
|
// Assert
|
|
response.ShouldNotBeNull();
|
|
response.Id.ShouldBe(orderId);
|
|
response.OrderStatus.ShouldBe(OrderStatus.Completed);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Order_Should_Be_Canceled()
|
|
{
|
|
// Arrange
|
|
await Order_Should_Be_Created();
|
|
Guid orderId = Guid.Empty;
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.First();
|
|
order.OrderStatus.ShouldNotBe(OrderStatus.Canceled);
|
|
order.CanceledTime.ShouldBeNull();
|
|
orderId = order.Id;
|
|
});
|
|
|
|
var cancelRequestDto = new CancelOrderInput
|
|
{
|
|
CancellationReason = "Repeat orders."
|
|
};
|
|
|
|
// Act
|
|
var response = await _orderAppService.CancelAsync(orderId, cancelRequestDto);
|
|
|
|
// Assert
|
|
response.ShouldNotBeNull();
|
|
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
response.CanceledTime.ShouldNotBeNull();
|
|
response.CancellationReason.ShouldBe("Repeat orders.");
|
|
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
|
|
order.ShouldNotBeNull();
|
|
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
order.CancellationReason.ShouldBe("Repeat orders.");
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unpaid_Order_Should_Be_Auto_Canceled_When_Payment_Is_Canceled()
|
|
{
|
|
// Arrange
|
|
await Order_Should_Be_Created();
|
|
Guid orderId = Guid.Empty;
|
|
UsingDbContext(async db =>
|
|
{
|
|
var order = db.Orders.First();
|
|
orderId = order.Id;
|
|
await order.StartPaymentAsync(Guid.NewGuid(), order.ActualTotalPrice, _moneyDistributor);
|
|
await db.SaveChangesAsync();
|
|
});
|
|
|
|
// Act
|
|
var now = _clock.Now;
|
|
|
|
UsingDbContext(async db =>
|
|
{
|
|
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
|
|
var order = await orderRepository.GetAsync(orderId);
|
|
typeof(Order).GetProperty(nameof(Order.PaymentExpiration))!.SetValue(order, now);
|
|
order.CancelPayment();
|
|
await orderRepository.UpdateAsync(order, true);
|
|
});
|
|
|
|
var response = await _orderAppService.GetAsync(orderId);
|
|
|
|
// Assert
|
|
response.ShouldNotBeNull();
|
|
response.PaymentId.ShouldBeNull();
|
|
response.PaymentExpiration.ShouldBe(now);
|
|
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
response.CanceledTime.ShouldNotBeNull();
|
|
response.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
|
|
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
|
|
order.ShouldNotBeNull();
|
|
order.PaymentId.ShouldBeNull();
|
|
order.PaymentExpiration.ShouldBe(now);
|
|
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
order.CanceledTime.ShouldNotBeNull();
|
|
order.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Paid_Order_Should_Not_Be_Auto_Canceled_When_Payment_Overtime()
|
|
{
|
|
// Arrange
|
|
await Order_Should_Be_Created();
|
|
Guid orderId = Guid.Empty;
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
|
|
orderId = await (await orderRepository.GetQueryableAsync()).Select(x => x.Id).FirstAsync();
|
|
var order = await orderRepository.GetAsync(orderId);
|
|
await order.StartPaymentAsync(Guid.NewGuid(), order.ActualTotalPrice, _moneyDistributor);
|
|
order.SetPaid(_clock.Now);
|
|
await orderRepository.UpdateAsync(order, true);
|
|
});
|
|
|
|
// Act
|
|
var now = _clock.Now;
|
|
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
|
|
var order = await orderRepository.GetAsync(orderId);
|
|
typeof(Order).GetProperty(nameof(Order.PaymentExpiration))!.SetValue(order, now);
|
|
await orderRepository.UpdateAsync(order, true);
|
|
});
|
|
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
var backgroundJob = ServiceProvider.GetRequiredService<UnpaidOrderAutoCancelJob>();
|
|
await backgroundJob.ExecuteAsync(new UnpaidOrderAutoCancelArgs
|
|
{
|
|
TenantId = null,
|
|
OrderId = orderId
|
|
});
|
|
});
|
|
|
|
var response = await _orderAppService.GetAsync(orderId);
|
|
|
|
// Assert
|
|
response.ShouldNotBeNull();
|
|
response.PaymentId.ShouldNotBeNull();
|
|
response.PaymentExpiration.ShouldBe(now);
|
|
response.OrderStatus.ShouldBe(OrderStatus.Processing);
|
|
response.CanceledTime.ShouldBeNull();
|
|
response.CancellationReason.ShouldBeNull();
|
|
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
|
|
order.ShouldNotBeNull();
|
|
order.PaymentId.ShouldNotBeNull();
|
|
order.PaymentExpiration.ShouldBe(now);
|
|
order.OrderStatus.ShouldBe(OrderStatus.Processing);
|
|
order.CanceledTime.ShouldBeNull();
|
|
order.CancellationReason.ShouldBeNull();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unpaid_Order_Should_Be_Auto_Canceled_When_Payment_Overtime()
|
|
{
|
|
// Arrange
|
|
await Order_Should_Be_Created();
|
|
Guid orderId = Guid.Empty;
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.First();
|
|
orderId = order.Id;
|
|
});
|
|
|
|
// Act
|
|
var now = _clock.Now;
|
|
|
|
UsingDbContext(async db =>
|
|
{
|
|
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
|
|
var order = await orderRepository.GetAsync(orderId);
|
|
typeof(Order).GetProperty(nameof(Order.PaymentExpiration))!.SetValue(order, now);
|
|
await orderRepository.UpdateAsync(order, true);
|
|
});
|
|
|
|
UsingDbContext(async db =>
|
|
{
|
|
var backgroundJob = ServiceProvider.GetRequiredService<UnpaidOrderAutoCancelJob>();
|
|
await backgroundJob.ExecuteAsync(new UnpaidOrderAutoCancelArgs
|
|
{
|
|
TenantId = null,
|
|
OrderId = orderId
|
|
});
|
|
});
|
|
|
|
var response = await _orderAppService.GetAsync(orderId);
|
|
|
|
// Assert
|
|
response.ShouldNotBeNull();
|
|
response.PaymentId.ShouldBeNull();
|
|
response.PaymentExpiration.ShouldBe(now);
|
|
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
response.CanceledTime.ShouldNotBeNull();
|
|
response.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
|
|
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
|
|
order.ShouldNotBeNull();
|
|
order.PaymentId.ShouldBeNull();
|
|
order.PaymentExpiration.ShouldBe(now);
|
|
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
order.CanceledTime.ShouldNotBeNull();
|
|
order.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Payment_Pending_Order_Should_Be_Auto_Canceled_When_Payment_Overtime()
|
|
{
|
|
// Arrange
|
|
await Order_Should_Be_Created();
|
|
Guid orderId = Guid.Empty;
|
|
UsingDbContext(async db =>
|
|
{
|
|
var order = db.Orders.First();
|
|
orderId = order.Id;
|
|
await order.StartPaymentAsync(Guid.NewGuid(), order.ActualTotalPrice, _moneyDistributor);
|
|
await db.SaveChangesAsync();
|
|
});
|
|
|
|
// Act
|
|
var now = _clock.Now;
|
|
|
|
UsingDbContext(async db =>
|
|
{
|
|
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
|
|
var order = await orderRepository.GetAsync(orderId);
|
|
typeof(Order).GetProperty(nameof(Order.PaymentExpiration))!.SetValue(order, now);
|
|
await orderRepository.UpdateAsync(order, true);
|
|
});
|
|
|
|
UsingDbContext(async db =>
|
|
{
|
|
var backgroundJob = ServiceProvider.GetRequiredService<UnpaidOrderAutoCancelJob>();
|
|
await backgroundJob.ExecuteAsync(new UnpaidOrderAutoCancelArgs
|
|
{
|
|
TenantId = null,
|
|
OrderId = orderId
|
|
});
|
|
});
|
|
|
|
var response = await _orderAppService.GetAsync(orderId);
|
|
|
|
// Assert
|
|
response.ShouldNotBeNull();
|
|
response.PaymentId.ShouldBeNull();
|
|
response.PaymentExpiration.ShouldBe(now);
|
|
response.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
response.CanceledTime.ShouldNotBeNull();
|
|
response.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
|
|
|
|
UsingDbContext(db =>
|
|
{
|
|
var order = db.Orders.FirstOrDefault(o => o.Id == orderId);
|
|
order.ShouldNotBeNull();
|
|
order.PaymentId.ShouldBeNull();
|
|
order.PaymentExpiration.ShouldBe(now);
|
|
order.OrderStatus.ShouldBe(OrderStatus.Canceled);
|
|
order.CanceledTime.ShouldNotBeNull();
|
|
order.CancellationReason.ShouldBe(OrdersConsts.UnpaidAutoCancellationReason);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_Override_Unit_Price()
|
|
{
|
|
var createOrderDto = new CreateOrderDto
|
|
{
|
|
CustomerRemark = "customer remark",
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 10
|
|
},
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku3Id,
|
|
Quantity = 2
|
|
}
|
|
}
|
|
};
|
|
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
var order = await _orderAppService.CreateAsync(createOrderDto);
|
|
var orderLine = order.OrderLines.Find(x => x.ProductSkuId == OrderTestData.ProductSku3Id);
|
|
|
|
order.ProductTotalPrice.ShouldBe(10 * 1m + 2 * TestOrderLinePriceOverrider.Sku3UnitPrice);
|
|
orderLine.ShouldNotBeNull();
|
|
orderLine.UnitPrice.ShouldBe(TestOrderLinePriceOverrider.Sku3UnitPrice);
|
|
orderLine.TotalPrice.ShouldBe(orderLine.Quantity * orderLine.UnitPrice);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_Not_Create_Order_With_Flash_Sales_Products()
|
|
{
|
|
var createOrderDto = new CreateOrderDto
|
|
{
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 1
|
|
}
|
|
}
|
|
};
|
|
|
|
try
|
|
{
|
|
Product1.InventoryStrategy = InventoryStrategy.FlashSales;
|
|
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
var exception =
|
|
await Should.ThrowAsync<BusinessException>(() => _orderAppService.CreateAsync(createOrderDto));
|
|
|
|
exception.Code.ShouldBe(OrdersErrorCodes.ExistFlashSalesProduct);
|
|
});
|
|
|
|
Product1.InventoryStrategy = InventoryStrategy.NoNeed;
|
|
}
|
|
catch
|
|
{
|
|
Product1.InventoryStrategy = InventoryStrategy.NoNeed;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_Throw_If_Product_Sku_Uses_Unexpected_Currency()
|
|
{
|
|
var createOrderDto = new CreateOrderDto
|
|
{
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 1
|
|
}
|
|
}
|
|
};
|
|
|
|
try
|
|
{
|
|
OrdersSettingDefinitionProvider.DefaultCurrency = "CNY"; // The effective value is "USD"
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
var exception =
|
|
await Should.ThrowAsync<BusinessException>(() => _orderAppService.CreateAsync(createOrderDto));
|
|
|
|
exception.Code.ShouldBe(OrdersErrorCodes.UnexpectedCurrency);
|
|
});
|
|
|
|
OrdersSettingDefinitionProvider.DefaultCurrency = "USD";
|
|
}
|
|
catch
|
|
{
|
|
OrdersSettingDefinitionProvider.DefaultCurrency = "USD";
|
|
throw;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_Set_ReducedInventoryAfterPlacingTime_If_No_ReduceAfterPlacing_OrderLine()
|
|
{
|
|
var createOrderDto = new CreateOrderDto
|
|
{
|
|
CustomerRemark = "customer remark",
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 10
|
|
},
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku2Id,
|
|
Quantity = 1
|
|
}
|
|
}
|
|
};
|
|
|
|
OrderDto createResponse = null;
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
createResponse = await _orderAppService.CreateAsync(createOrderDto);
|
|
});
|
|
|
|
var order = await _orderAppService.GetAsync(createResponse.Id);
|
|
|
|
// The fake inventory reducer (OrderCreatedEventHandler) should skip handling.
|
|
OrderCreatedEventHandler.LastOrderId.ShouldBe(order.Id);
|
|
OrderCreatedEventHandler.SkippedHandling.ShouldBeTrue();
|
|
|
|
order.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_Not_Set_ReducedInventoryAfterPlacingTime_If_Contains_ReduceAfterPlacing_OrderLine()
|
|
{
|
|
Product1.InventoryStrategy = InventoryStrategy.ReduceAfterPlacing;
|
|
|
|
var createOrderDto = new CreateOrderDto
|
|
{
|
|
CustomerRemark = "customer remark",
|
|
StoreId = OrderTestData.Store1Id,
|
|
OrderLines = new List<CreateOrderLineDto>
|
|
{
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku1Id,
|
|
Quantity = 10
|
|
},
|
|
new()
|
|
{
|
|
ProductId = OrderTestData.Product1Id,
|
|
ProductSkuId = OrderTestData.ProductSku2Id,
|
|
Quantity = 1
|
|
}
|
|
}
|
|
};
|
|
|
|
OrderDto createResponse = null;
|
|
await WithUnitOfWorkAsync(async () =>
|
|
{
|
|
createResponse = await _orderAppService.CreateAsync(createOrderDto);
|
|
});
|
|
|
|
var order = await _orderAppService.GetAsync(createResponse.Id);
|
|
|
|
// The fake inventory reducer (OrderCreatedEventHandler) should handle it.
|
|
OrderCreatedEventHandler.LastOrderId.ShouldBe(order.Id);
|
|
OrderCreatedEventHandler.SkippedHandling.ShouldBeFalse();
|
|
|
|
order.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
|
|
}
|
|
}
|
|
}
|