1439 lines
49 KiB
C#
1439 lines
49 KiB
C#
using HoneyBox.Core.Interfaces;
|
||
using HoneyBox.Core.Services;
|
||
using HoneyBox.Model.Data;
|
||
using HoneyBox.Model.Entities;
|
||
using HoneyBox.Model.Models.Lottery;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||
using Microsoft.Extensions.Logging;
|
||
using Moq;
|
||
using Xunit;
|
||
|
||
namespace HoneyBox.Tests.Integration;
|
||
|
||
/// <summary>
|
||
/// 抽奖服务集成测试
|
||
/// 测试抽奖结果查询、中奖记录查询功能
|
||
/// Requirements: 1.1-2.3, 4.1-4.3, 5.1-5.3
|
||
/// </summary>
|
||
public class LotteryServiceIntegrationTests
|
||
{
|
||
private HoneyBoxDbContext CreateInMemoryDbContext()
|
||
{
|
||
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
|
||
.Options;
|
||
|
||
return new HoneyBoxDbContext(options);
|
||
}
|
||
|
||
private LotteryService CreateLotteryService(HoneyBoxDbContext dbContext)
|
||
{
|
||
var mockLogger = new Mock<ILogger<LotteryService>>();
|
||
var mockLotteryEngine = new Mock<ILotteryEngine>();
|
||
return new LotteryService(dbContext, mockLotteryEngine.Object, mockLogger.Object);
|
||
}
|
||
|
||
#region 测试数据准备
|
||
|
||
private async Task<User> CreateTestUserAsync(HoneyBoxDbContext dbContext, int id = 1, string nickname = "测试用户")
|
||
{
|
||
var user = new User
|
||
{
|
||
Id = id,
|
||
OpenId = $"test_openid_{id}",
|
||
Uid = $"test_uid_{id}",
|
||
Nickname = nickname,
|
||
HeadImg = "avatar.jpg",
|
||
Mobile = "13800138000",
|
||
Money = 100,
|
||
Integral = 1000,
|
||
Money2 = 500,
|
||
IsTest = 0,
|
||
Status = 1,
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
await dbContext.Users.AddAsync(user);
|
||
await dbContext.SaveChangesAsync();
|
||
return user;
|
||
}
|
||
|
||
private async Task<Good> CreateTestGoodsAsync(HoneyBoxDbContext dbContext, int id = 1, byte type = 2, int status = 1)
|
||
{
|
||
var goods = new Good
|
||
{
|
||
Id = id,
|
||
Title = "测试无限赏商品",
|
||
Type = type, // 2 = 无限赏
|
||
Status = (byte)status,
|
||
ShowIs = 0,
|
||
Price = 10,
|
||
Stock = 100,
|
||
SaleStock = 0,
|
||
LockIs = 0,
|
||
IsShouZhe = 0,
|
||
QuanjuXiangou = 0,
|
||
DailyXiangou = 0,
|
||
ChoujiangXianzhi = 0,
|
||
ImgUrl = "img.jpg",
|
||
ImgUrlDetail = "detail.jpg"
|
||
};
|
||
await dbContext.Goods.AddAsync(goods);
|
||
await dbContext.SaveChangesAsync();
|
||
return goods;
|
||
}
|
||
|
||
private async Task CreateTestPrizeLevelsAsync(HoneyBoxDbContext dbContext)
|
||
{
|
||
var prizeLevels = new List<PrizeLevel>
|
||
{
|
||
new() { Id = 10, Title = "A赏", Color = "#FF0000", Sort = 1 },
|
||
new() { Id = 11, Title = "B赏", Color = "#00FF00", Sort = 2 },
|
||
new() { Id = 12, Title = "C赏", Color = "#0000FF", Sort = 3 }
|
||
};
|
||
await dbContext.PrizeLevels.AddRangeAsync(prizeLevels);
|
||
await dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
private async Task CreateTestGoodsItemsAsync(HoneyBoxDbContext dbContext, int goodsId)
|
||
{
|
||
var goodsItems = new List<GoodsItem>
|
||
{
|
||
new() { Id = 1, GoodsId = goodsId, Num = 0, Title = "A赏奖品", Stock = 5, SurplusStock = 5, Price = 100, ScMoney = 50, ShangId = 10, GoodsListId = 0, ImgUrl = "a.jpg", Sort = 1 },
|
||
new() { Id = 2, GoodsId = goodsId, Num = 0, Title = "B赏奖品", Stock = 10, SurplusStock = 10, Price = 50, ScMoney = 25, ShangId = 11, GoodsListId = 0, ImgUrl = "b.jpg", Sort = 2 },
|
||
new() { Id = 3, GoodsId = goodsId, Num = 0, Title = "C赏奖品", Stock = 20, SurplusStock = 20, Price = 30, ScMoney = 15, ShangId = 12, GoodsListId = 0, ImgUrl = "c.jpg", Sort = 3 }
|
||
};
|
||
await dbContext.GoodsItems.AddRangeAsync(goodsItems);
|
||
await dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
private async Task CreateTestOrderItemsAsync(HoneyBoxDbContext dbContext, int goodsId, int userId, int count = 5)
|
||
{
|
||
var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||
var orderItems = new List<OrderItem>();
|
||
|
||
for (int i = 1; i <= count; i++)
|
||
{
|
||
orderItems.Add(new OrderItem
|
||
{
|
||
Id = i,
|
||
OrderId = 1,
|
||
UserId = userId,
|
||
GoodsId = goodsId,
|
||
GoodslistId = i % 3 + 1,
|
||
GoodslistTitle = $"奖品{i}",
|
||
GoodslistImgurl = $"prize{i}.jpg",
|
||
GoodslistPrice = 50 + i * 10,
|
||
GoodslistMoney = 25 + i * 5,
|
||
ShangId = 10 + (i % 3),
|
||
Num = 0,
|
||
OrderType = LotteryOrderType.WuXianShang, // 无限赏
|
||
Source = PrizeSource.Lottery, // 抽奖获得
|
||
Status = 0,
|
||
LuckNo = i,
|
||
Addtime = now - i * 60 // 每条记录间隔1分钟
|
||
});
|
||
}
|
||
|
||
await dbContext.OrderItems.AddRangeAsync(orderItems);
|
||
await dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
private async Task<Order> CreateTestOrderAsync(HoneyBoxDbContext dbContext, int userId, int goodsId, string orderNum, int orderType = 1)
|
||
{
|
||
var order = new Order
|
||
{
|
||
Id = 1,
|
||
UserId = userId,
|
||
OrderNum = orderNum,
|
||
GoodsId = goodsId,
|
||
GoodsTitle = "测试商品",
|
||
GoodsImgurl = "test.jpg",
|
||
GoodsPrice = 10,
|
||
OrderTotal = 50,
|
||
OrderZheTotal = 50,
|
||
Price = 50,
|
||
PrizeNum = 5,
|
||
Status = 1, // 已支付
|
||
OrderType = (byte)orderType,
|
||
Addtime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||
PayTime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
await dbContext.Orders.AddAsync(order);
|
||
await dbContext.SaveChangesAsync();
|
||
return order;
|
||
}
|
||
|
||
private async Task CreateTestOrderItemsWithOrderAsync(HoneyBoxDbContext dbContext, int orderId, int goodsId, int userId, int orderType, int count = 5)
|
||
{
|
||
var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||
var orderItems = new List<OrderItem>();
|
||
|
||
for (int i = 1; i <= count; i++)
|
||
{
|
||
orderItems.Add(new OrderItem
|
||
{
|
||
Id = i,
|
||
OrderId = orderId,
|
||
UserId = userId,
|
||
GoodsId = goodsId,
|
||
GoodslistId = i % 3 + 1,
|
||
GoodslistTitle = $"奖品{i}",
|
||
GoodslistImgurl = $"prize{i}.jpg",
|
||
GoodslistPrice = 50 + i * 10,
|
||
GoodslistMoney = 25 + i * 5,
|
||
ShangId = 10 + (i % 3),
|
||
Num = 0,
|
||
OrderType = (byte)orderType,
|
||
Source = PrizeSource.Lottery,
|
||
Status = 0,
|
||
LuckNo = i,
|
||
PrizeCode = $"P{DateTime.Now:yyyyMMddHHmmss}{1000 + i}",
|
||
Addtime = now - i * 60
|
||
});
|
||
}
|
||
|
||
await dbContext.OrderItems.AddRangeAsync(orderItems);
|
||
await dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 一番赏抽奖结果查询测试 (Requirements 1.1-1.4)
|
||
|
||
/// <summary>
|
||
/// 测试一番赏抽奖结果查询 - 有效订单号返回奖品列表
|
||
/// Requirements: 1.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetPrizeOrderLogAsync_ValidOrderNum_ReturnsPrizeList()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1); // 一番赏
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
var order = await CreateTestOrderAsync(dbContext, 1, 1, "TEST202601010001", orderType: 1);
|
||
await CreateTestOrderItemsWithOrderAsync(dbContext, order.Id, 1, 1, LotteryOrderType.YiFanShang, 5);
|
||
|
||
// Act
|
||
var result = await service.GetPrizeOrderLogAsync(1, "TEST202601010001");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(5, result.PrizeNum);
|
||
Assert.Equal(5, result.Data.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试一番赏抽奖结果查询 - 无效订单号返回空列表
|
||
/// Requirements: 1.2
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetPrizeOrderLogAsync_InvalidOrderNum_ReturnsEmptyList()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
|
||
// Act
|
||
var result = await service.GetPrizeOrderLogAsync(1, "INVALID_ORDER_NUM");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(0, result.PrizeNum);
|
||
Assert.Empty(result.Data);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试一番赏抽奖结果查询 - 返回奖品详情包含所有必要字段
|
||
/// Requirements: 1.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetPrizeOrderLogAsync_ReturnsPrizeDetails_ContainsAllRequiredFields()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
var order = await CreateTestOrderAsync(dbContext, 1, 1, "TEST202601010002", orderType: 1);
|
||
await CreateTestOrderItemsWithOrderAsync(dbContext, order.Id, 1, 1, LotteryOrderType.YiFanShang, 1);
|
||
|
||
// Act
|
||
var result = await service.GetPrizeOrderLogAsync(1, "TEST202601010002");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Single(result.Data);
|
||
|
||
var prize = result.Data.First();
|
||
Assert.NotEmpty(prize.GoodsListTitle); // title
|
||
Assert.NotEmpty(prize.GoodsListImgUrl); // imgurl
|
||
Assert.NotEmpty(prize.GoodsListPrice); // price
|
||
Assert.NotEmpty(prize.GoodsListMoney); // recovery value (money)
|
||
Assert.True(prize.LuckNo > 0); // luck_no
|
||
Assert.True(prize.ShangId > 0); // shang_id
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试一番赏抽奖结果查询 - 只返回当前用户的订单
|
||
/// Requirements: 1.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetPrizeOrderLogAsync_OnlyReturnsCurrentUserOrder()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext, 1, "用户1");
|
||
await CreateTestUserAsync(dbContext, 2, "用户2");
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
|
||
// 创建用户2的订单
|
||
var order = await CreateTestOrderAsync(dbContext, 2, 1, "TEST202601010003", orderType: 1);
|
||
await CreateTestOrderItemsWithOrderAsync(dbContext, order.Id, 1, 2, LotteryOrderType.YiFanShang, 3);
|
||
|
||
// Act - 用户1查询用户2的订单
|
||
var result = await service.GetPrizeOrderLogAsync(1, "TEST202601010003");
|
||
|
||
// Assert - 应该返回空,因为订单不属于用户1
|
||
Assert.NotNull(result);
|
||
Assert.Equal(0, result.PrizeNum);
|
||
Assert.Empty(result.Data);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 无限赏抽奖结果查询测试 (Requirements 2.1-2.3)
|
||
|
||
/// <summary>
|
||
/// 测试无限赏抽奖结果查询 - 有效订单号返回奖品列表
|
||
/// Requirements: 2.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeOrderLogAsync_ValidOrderNum_ReturnsPrizeList()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 2); // 无限赏
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
var order = await CreateTestOrderAsync(dbContext, 1, 1, "INF202601010001", orderType: 2);
|
||
await CreateTestOrderItemsWithOrderAsync(dbContext, order.Id, 1, 1, LotteryOrderType.WuXianShang, 5);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeOrderLogAsync(1, "INF202601010001");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(5, result.PrizeNum);
|
||
Assert.Equal(5, result.Data.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏抽奖结果查询 - 只查询无限赏类型订单
|
||
/// Requirements: 2.2
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeOrderLogAsync_OnlyReturnsInfiniteOrderType()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 2);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
var order = await CreateTestOrderAsync(dbContext, 1, 1, "INF202601010002", orderType: 2);
|
||
await CreateTestOrderItemsWithOrderAsync(dbContext, order.Id, 1, 1, LotteryOrderType.WuXianShang, 3);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeOrderLogAsync(1, "INF202601010002");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(3, result.Data.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏抽奖结果查询 - 返回格式与一番赏一致
|
||
/// Requirements: 2.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeOrderLogAsync_ResponseFormatConsistentWithYiFanShang()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 2);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
var order = await CreateTestOrderAsync(dbContext, 1, 1, "INF202601010003", orderType: 2);
|
||
await CreateTestOrderItemsWithOrderAsync(dbContext, order.Id, 1, 1, LotteryOrderType.WuXianShang, 1);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeOrderLogAsync(1, "INF202601010003");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Single(result.Data);
|
||
|
||
var prize = result.Data.First();
|
||
// 验证与一番赏相同的字段
|
||
Assert.NotEmpty(prize.GoodsListTitle);
|
||
Assert.NotEmpty(prize.GoodsListImgUrl);
|
||
Assert.NotEmpty(prize.GoodsListPrice);
|
||
Assert.NotEmpty(prize.GoodsListMoney);
|
||
Assert.True(prize.LuckNo > 0);
|
||
Assert.True(prize.ShangId > 0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏抽奖结果查询 - 包含用户信息
|
||
/// Requirements: 2.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeOrderLogAsync_ContainsUserInfo()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext, nickname: "测试昵称");
|
||
await CreateTestGoodsAsync(dbContext, type: 2);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
var order = await CreateTestOrderAsync(dbContext, 1, 1, "INF202601010004", orderType: 2);
|
||
await CreateTestOrderItemsWithOrderAsync(dbContext, order.Id, 1, 1, LotteryOrderType.WuXianShang, 1);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeOrderLogAsync(1, "INF202601010004");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.NotNull(result.UserInfo);
|
||
Assert.NotEmpty(result.UserInfo.Nickname);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏抽奖结果查询 - 无效订单号返回空列表
|
||
/// Requirements: 2.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeOrderLogAsync_InvalidOrderNum_ReturnsEmptyList()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeOrderLogAsync(1, "INVALID_ORDER_NUM");
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(0, result.PrizeNum);
|
||
Assert.Empty(result.Data);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 无限赏中奖记录查询测试 (Requirements 4.1-4.3)
|
||
|
||
/// <summary>
|
||
/// 测试无限赏中奖记录查询 - 基本查询
|
||
/// Requirements: 4.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_BasicQuery_ReturnsRecords()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 5);
|
||
|
||
// Act
|
||
var result = await service.GetInfiniteShangLogAsync(1, 0, 0, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(5, result.Total);
|
||
Assert.Equal(5, result.Data.Count);
|
||
Assert.NotNull(result.Category);
|
||
Assert.True(result.Category.Count > 0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏中奖记录查询 - 分页功能
|
||
/// Requirements: 4.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_Pagination_ReturnsCorrectPage()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 15);
|
||
|
||
// Act - 第一页
|
||
var page1 = await service.GetInfiniteShangLogAsync(1, 0, 0, 1, 10);
|
||
|
||
// Act - 第二页
|
||
var page2 = await service.GetInfiniteShangLogAsync(1, 0, 0, 2, 10);
|
||
|
||
// Assert
|
||
Assert.Equal(15, page1.Total);
|
||
Assert.Equal(2, page1.LastPage);
|
||
Assert.Equal(10, page1.Data.Count);
|
||
Assert.Equal(5, page2.Data.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏中奖记录查询 - 按赏品等级过滤
|
||
/// Requirements: 4.2
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_FilterByShangId_ReturnsFilteredRecords()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 9); // 创建9条记录,每个等级3条
|
||
|
||
// Act - 过滤A赏 (shangId = 10)
|
||
var result = await service.GetInfiniteShangLogAsync(1, 10, 0, 1, 100);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.True(result.Data.All(d => d.ShangId == 10));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏中奖记录查询 - 商品不存在
|
||
/// Requirements: 4.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_GoodsNotFound_ReturnsEmptyResult()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
// Act
|
||
var result = await service.GetInfiniteShangLogAsync(999, 0, 0, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(0, result.Total);
|
||
Assert.Empty(result.Data);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏中奖记录查询 - 商品已下架
|
||
/// Requirements: 4.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_GoodsOffline_ReturnsEmptyResult()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestGoodsAsync(dbContext, status: 0); // 下架状态
|
||
|
||
// Act
|
||
var result = await service.GetInfiniteShangLogAsync(1, 0, 0, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(0, result.Total);
|
||
Assert.Empty(result.Data);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏中奖记录查询 - 用户昵称脱敏
|
||
/// Requirements: 4.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_UserNicknameMasked_ReturnsMaskedNickname()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext, nickname: "测试用户名");
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 1);
|
||
|
||
// Act
|
||
var result = await service.GetInfiniteShangLogAsync(1, 0, 0, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Single(result.Data);
|
||
var record = result.Data.First();
|
||
Assert.NotNull(record.UserInfo);
|
||
// 验证昵称已脱敏(包含***)
|
||
Assert.Contains("***", record.UserInfo.Nickname);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无限赏中奖记录查询 - 分类列表包含全部选项
|
||
/// Requirements: 4.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_CategoryList_ContainsAllOption()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
|
||
// Act
|
||
var result = await service.GetInfiniteShangLogAsync(1, 0, 0, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result.Category);
|
||
Assert.True(result.Category.Count > 0);
|
||
Assert.Equal(0, result.Category.First().ShangId);
|
||
Assert.Equal("全部", result.Category.First().ShangTitle);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 每日抽奖记录查询测试 (Requirements 5.1-5.3)
|
||
|
||
/// <summary>
|
||
/// 测试每日抽奖记录查询 - 基本查询
|
||
/// Requirements: 5.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeRecordsAsync_BasicQuery_ReturnsRecords()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 5);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeRecordsAsync(1, 1, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(5, result.Total);
|
||
Assert.Equal(5, result.Data.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试每日抽奖记录查询 - 分页功能
|
||
/// Requirements: 5.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeRecordsAsync_Pagination_ReturnsCorrectPage()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 15);
|
||
|
||
// Act - 第一页
|
||
var page1 = await service.GetInfinitePrizeRecordsAsync(1, 1, 1, 10);
|
||
|
||
// Act - 第二页
|
||
var page2 = await service.GetInfinitePrizeRecordsAsync(1, 1, 2, 10);
|
||
|
||
// Assert
|
||
Assert.Equal(15, page1.Total);
|
||
Assert.Equal(2, page1.LastPage);
|
||
Assert.Equal(10, page1.Data.Count);
|
||
Assert.Equal(5, page2.Data.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试每日抽奖记录查询 - 只返回当前用户的记录
|
||
/// Requirements: 5.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeRecordsAsync_OnlyCurrentUserRecords_ReturnsFilteredRecords()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext, 1, "用户1");
|
||
await CreateTestUserAsync(dbContext, 2, "用户2");
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
|
||
// 为用户1创建5条记录
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 5);
|
||
|
||
// 为用户2创建3条记录
|
||
var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||
var user2Items = new List<OrderItem>();
|
||
for (int i = 6; i <= 8; i++)
|
||
{
|
||
user2Items.Add(new OrderItem
|
||
{
|
||
Id = i,
|
||
OrderId = 2,
|
||
UserId = 2,
|
||
GoodsId = 1,
|
||
GoodslistId = 1,
|
||
GoodslistTitle = $"奖品{i}",
|
||
GoodslistImgurl = $"prize{i}.jpg",
|
||
GoodslistPrice = 50,
|
||
GoodslistMoney = 25,
|
||
ShangId = 10,
|
||
Num = 0,
|
||
OrderType = LotteryOrderType.WuXianShang,
|
||
Source = PrizeSource.Lottery,
|
||
Status = 0,
|
||
LuckNo = i,
|
||
Addtime = now - i * 60
|
||
});
|
||
}
|
||
await dbContext.OrderItems.AddRangeAsync(user2Items);
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
// Act - 查询用户1的记录
|
||
var result = await service.GetInfinitePrizeRecordsAsync(1, 1, 1, 100);
|
||
|
||
// Assert
|
||
Assert.Equal(5, result.Total);
|
||
Assert.All(result.Data, d => Assert.Equal(1, d.UserId));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试每日抽奖记录查询 - 无记录时返回空列表
|
||
/// Requirements: 5.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeRecordsAsync_NoRecords_ReturnsEmptyList()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeRecordsAsync(1, 1, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(0, result.Total);
|
||
Assert.Empty(result.Data);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试每日抽奖记录查询 - 记录按时间倒序排列
|
||
/// Requirements: 5.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeRecordsAsync_OrderByTimeDescending_ReturnsOrderedRecords()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 5);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeRecordsAsync(1, 1, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(5, result.Data.Count);
|
||
|
||
// 验证记录按ID倒序(ID越大时间越新)
|
||
var ids = result.Data.Select(d => d.UserId).ToList();
|
||
// 由于我们按id倒序,第一条应该是最新的
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 响应格式测试 (Requirements 10.1-10.4)
|
||
|
||
/// <summary>
|
||
/// 测试响应格式 - 无限赏中奖记录包含所有必要字段
|
||
/// Requirements: 10.2
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfiniteShangLogAsync_ResponseFormat_ContainsAllRequiredFields()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 1);
|
||
|
||
// Act
|
||
var result = await service.GetInfiniteShangLogAsync(1, 0, 0, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Single(result.Data);
|
||
|
||
var record = result.Data.First();
|
||
Assert.True(record.Id > 0);
|
||
Assert.True(record.UserId > 0);
|
||
Assert.NotEmpty(record.GoodslistTitle);
|
||
Assert.NotEmpty(record.GoodslistImgurl);
|
||
Assert.NotEmpty(record.GoodslistPrice);
|
||
Assert.NotEmpty(record.GoodslistMoney);
|
||
Assert.True(record.ShangId > 0);
|
||
Assert.NotEmpty(record.Addtime);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试响应格式 - 每日抽奖记录包含所有必要字段
|
||
/// Requirements: 10.2
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task GetInfinitePrizeRecordsAsync_ResponseFormat_ContainsAllRequiredFields()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsAsync(dbContext, 1);
|
||
await CreateTestOrderItemsAsync(dbContext, 1, 1, 1);
|
||
|
||
// Act
|
||
var result = await service.GetInfinitePrizeRecordsAsync(1, 1, 1, 10);
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Single(result.Data);
|
||
|
||
var record = result.Data.First();
|
||
Assert.True(record.UserId > 0);
|
||
Assert.NotEmpty(record.GoodslistTitle);
|
||
Assert.NotEmpty(record.GoodslistImgurl);
|
||
Assert.NotEmpty(record.Addtime);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 道具卡抽奖测试 (Requirements 6.1-6.4)
|
||
|
||
private async Task<UserItemCard> CreateTestItemCardAsync(HoneyBoxDbContext dbContext, int userId, int status = 1)
|
||
{
|
||
var itemCard = new UserItemCard
|
||
{
|
||
Id = 1,
|
||
UserId = userId,
|
||
Type = 1,
|
||
ItemCardId = 1,
|
||
Title = "重抽卡",
|
||
Status = (byte)status, // 1=未使用,2=已使用
|
||
Addtime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||
Updatetime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||
};
|
||
await dbContext.UserItemCards.AddAsync(itemCard);
|
||
await dbContext.SaveChangesAsync();
|
||
return itemCard;
|
||
}
|
||
|
||
private LotteryService CreateLotteryServiceWithMockedEngine(HoneyBoxDbContext dbContext, Mock<ILotteryEngine> mockEngine)
|
||
{
|
||
var mockLogger = new Mock<ILogger<LotteryService>>();
|
||
return new LotteryService(dbContext, mockEngine.Object, mockLogger.Object);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试道具卡抽奖 - 商品不存在时返回错误
|
||
/// Requirements: 6.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DrawWithItemCardAsync_GoodsNotFound_ThrowsException()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestItemCardAsync(dbContext, 1);
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||
() => service.DrawWithItemCardAsync(1, 999, new List<int> { 1 }));
|
||
|
||
Assert.Equal("盒子不存在", exception.Message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试道具卡抽奖 - 商品已下架时返回错误
|
||
/// Requirements: 6.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DrawWithItemCardAsync_GoodsOffline_ThrowsException()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, status: 0); // 下架状态
|
||
await CreateTestItemCardAsync(dbContext, 1);
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||
() => service.DrawWithItemCardAsync(1, 1, new List<int> { 1 }));
|
||
|
||
Assert.Equal("盒子已下架", exception.Message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试道具卡抽奖 - 道具卡数量不足时返回错误
|
||
/// Requirements: 6.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DrawWithItemCardAsync_NoItemCards_ThrowsException()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
// 不创建道具卡
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||
() => service.DrawWithItemCardAsync(1, 1, new List<int> { 1 }));
|
||
|
||
Assert.Equal("重抽卡数量不足", exception.Message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试道具卡抽奖 - 道具卡已使用时返回错误
|
||
/// Requirements: 6.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DrawWithItemCardAsync_ItemCardAlreadyUsed_ThrowsException()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestItemCardAsync(dbContext, 1, status: 2); // 已使用状态
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||
() => service.DrawWithItemCardAsync(1, 1, new List<int> { 1 }));
|
||
|
||
Assert.Equal("重抽卡数量不足", exception.Message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试道具卡抽奖 - 参数为空时返回错误
|
||
/// Requirements: 6.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DrawWithItemCardAsync_EmptyOrderListIds_ThrowsException()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestItemCardAsync(dbContext, 1);
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||
() => service.DrawWithItemCardAsync(1, 1, new List<int>()));
|
||
|
||
Assert.Equal("参数错误", exception.Message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试道具卡抽奖 - 订单项不存在时返回错误
|
||
/// Requirements: 6.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DrawWithItemCardAsync_OrderItemNotFound_ThrowsException()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext);
|
||
await CreateTestItemCardAsync(dbContext, 1);
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||
() => service.DrawWithItemCardAsync(1, 1, new List<int> { 999 }));
|
||
|
||
Assert.Equal("数据错误", exception.Message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试道具卡抽奖 - 消费门槛验证
|
||
/// Requirements: 6.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DrawWithItemCardAsync_SpendingThresholdNotMet_ThrowsException()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var service = CreateLotteryService(dbContext);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
// 创建有消费门槛的商品
|
||
var goods = new Good
|
||
{
|
||
Id = 1,
|
||
Title = "测试商品",
|
||
Type = 2,
|
||
Status = 1,
|
||
Price = 10,
|
||
Stock = 100,
|
||
ChoujiangXianzhi = 1000, // 消费门槛1000元
|
||
ImgUrl = "img.jpg",
|
||
ImgUrlDetail = "detail.jpg",
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
await dbContext.Goods.AddAsync(goods);
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
await CreateTestItemCardAsync(dbContext, 1);
|
||
|
||
// Act & Assert
|
||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||
() => service.DrawWithItemCardAsync(1, 1, new List<int> { 1 }));
|
||
|
||
Assert.Contains("消费满", exception.Message);
|
||
}
|
||
|
||
// Note: The test for valid item card draw (DrawWithItemCardAsync_ValidItemCard_ExecutesDrawAndMarksCardAsUsed)
|
||
// is skipped because InMemory database does not support ExecuteUpdateAsync.
|
||
// This functionality should be tested with a real database in end-to-end tests.
|
||
|
||
#endregion
|
||
|
||
#region 抽奖引擎集成测试 (Requirements 7.1-9.4)
|
||
|
||
private LotteryEngine CreateLotteryEngine(HoneyBoxDbContext dbContext, IInventoryManager inventoryManager)
|
||
{
|
||
var mockLogger = new Mock<ILogger<LotteryEngine>>();
|
||
return new LotteryEngine(dbContext, inventoryManager, mockLogger.Object);
|
||
}
|
||
|
||
private InventoryManager CreateInventoryManager(HoneyBoxDbContext dbContext)
|
||
{
|
||
var mockLogger = new Mock<ILogger<InventoryManager>>();
|
||
return new InventoryManager(dbContext, mockLogger.Object);
|
||
}
|
||
|
||
private async Task CreateTestGoodsItemsWithStockAsync(HoneyBoxDbContext dbContext, int goodsId, int num = 0)
|
||
{
|
||
var goodsItems = new List<GoodsItem>
|
||
{
|
||
new() { Id = 101, GoodsId = goodsId, Num = num, Title = "A赏奖品", Stock = 5, SurplusStock = 5, Price = 100, ScMoney = 50, ShangId = 10, GoodsListId = 0, ImgUrl = "a.jpg", Sort = 1 },
|
||
new() { Id = 102, GoodsId = goodsId, Num = num, Title = "B赏奖品", Stock = 10, SurplusStock = 10, Price = 50, ScMoney = 25, ShangId = 11, GoodsListId = 0, ImgUrl = "b.jpg", Sort = 2 },
|
||
new() { Id = 103, GoodsId = goodsId, Num = num, Title = "C赏奖品", Stock = 20, SurplusStock = 20, Price = 30, ScMoney = 15, ShangId = 12, GoodsListId = 0, ImgUrl = "c.jpg", Sort = 3 }
|
||
};
|
||
await dbContext.GoodsItems.AddRangeAsync(goodsItems);
|
||
await dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 完整抽奖流程成功
|
||
/// Requirements: 7.1-7.7
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_DrawAsync_CompletesSuccessfully()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsWithStockAsync(dbContext, 1);
|
||
|
||
var request = new LotteryDrawRequest
|
||
{
|
||
UserId = 1,
|
||
GoodsId = 1,
|
||
Num = 0,
|
||
OrderId = 1,
|
||
OrderType = LotteryOrderType.YiFanShang,
|
||
Source = PrizeSource.Lottery
|
||
};
|
||
|
||
// Act
|
||
var result = await engine.DrawAsync(request);
|
||
|
||
// Assert
|
||
Assert.True(result.Success);
|
||
Assert.True(result.GoodsItemId > 0);
|
||
Assert.NotEmpty(result.Title);
|
||
Assert.NotEmpty(result.PrizeCode);
|
||
Assert.True(result.LuckNo > 0);
|
||
Assert.True(result.OrderItemId > 0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 库存扣减正确
|
||
/// Requirements: 8.1-8.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_DrawAsync_DeductsStockCorrectly()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsWithStockAsync(dbContext, 1);
|
||
|
||
// 获取初始总库存
|
||
var initialTotalStock = await dbContext.GoodsItems
|
||
.Where(gi => gi.GoodsId == 1 && gi.Num == 0)
|
||
.SumAsync(gi => gi.SurplusStock);
|
||
|
||
var request = new LotteryDrawRequest
|
||
{
|
||
UserId = 1,
|
||
GoodsId = 1,
|
||
Num = 0,
|
||
OrderId = 1,
|
||
OrderType = LotteryOrderType.YiFanShang,
|
||
Source = PrizeSource.Lottery
|
||
};
|
||
|
||
// Act
|
||
var result = await engine.DrawAsync(request);
|
||
|
||
// Assert
|
||
Assert.True(result.Success);
|
||
|
||
// 验证库存已扣减
|
||
var finalTotalStock = await dbContext.GoodsItems
|
||
.Where(gi => gi.GoodsId == 1 && gi.Num == 0)
|
||
.SumAsync(gi => gi.SurplusStock);
|
||
|
||
Assert.Equal(initialTotalStock - 1, finalTotalStock);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 抽奖记录创建正确
|
||
/// Requirements: 9.1-9.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_DrawAsync_CreatesRecordCorrectly()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsWithStockAsync(dbContext, 1);
|
||
|
||
var request = new LotteryDrawRequest
|
||
{
|
||
UserId = 1,
|
||
GoodsId = 1,
|
||
Num = 0,
|
||
OrderId = 1,
|
||
OrderType = LotteryOrderType.YiFanShang,
|
||
Source = PrizeSource.Lottery
|
||
};
|
||
|
||
// Act
|
||
var result = await engine.DrawAsync(request);
|
||
|
||
// Assert
|
||
Assert.True(result.Success);
|
||
|
||
// 验证记录已创建
|
||
var orderItem = await dbContext.OrderItems.FindAsync(result.OrderItemId);
|
||
Assert.NotNull(orderItem);
|
||
Assert.Equal(1, orderItem.UserId);
|
||
Assert.Equal(1, orderItem.GoodsId);
|
||
Assert.Equal(result.GoodsItemId, orderItem.GoodslistId);
|
||
Assert.Equal(result.ShangId, orderItem.ShangId);
|
||
Assert.Equal(result.PrizeCode, orderItem.PrizeCode);
|
||
Assert.Equal(result.LuckNo, orderItem.LuckNo);
|
||
Assert.Equal((byte)PrizeSource.Lottery, orderItem.Source);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 无可用奖品时返回失败
|
||
/// Requirements: 7.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_DrawAsync_NoPrizes_ReturnsFailed()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
// 不创建奖品
|
||
|
||
var request = new LotteryDrawRequest
|
||
{
|
||
UserId = 1,
|
||
GoodsId = 1,
|
||
Num = 0,
|
||
OrderId = 1,
|
||
OrderType = LotteryOrderType.YiFanShang,
|
||
Source = PrizeSource.Lottery
|
||
};
|
||
|
||
// Act
|
||
var result = await engine.DrawAsync(request);
|
||
|
||
// Assert
|
||
Assert.False(result.Success);
|
||
Assert.Equal("暂无可抽奖品", result.ErrorMessage);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 多次抽奖成功
|
||
/// Requirements: 7.1-7.7
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_DrawMultipleAsync_CompletesSuccessfully()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsWithStockAsync(dbContext, 1);
|
||
|
||
var request = new LotteryDrawRequest
|
||
{
|
||
UserId = 1,
|
||
GoodsId = 1,
|
||
Num = 0,
|
||
OrderId = 1,
|
||
OrderType = LotteryOrderType.YiFanShang,
|
||
Source = PrizeSource.Lottery
|
||
};
|
||
|
||
// Act
|
||
var results = await engine.DrawMultipleAsync(request, 3);
|
||
|
||
// Assert
|
||
Assert.Equal(3, results.Count);
|
||
Assert.All(results, r => Assert.True(r.Success));
|
||
|
||
// 验证每次抽奖都创建了记录
|
||
var orderItemCount = await dbContext.OrderItems.CountAsync();
|
||
Assert.Equal(3, orderItemCount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 概率计算正确
|
||
/// Requirements: 7.1
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_CalculateProbabilitiesAsync_ReturnsCorrectProbabilities()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestGoodsItemsWithStockAsync(dbContext, 1);
|
||
|
||
// Act
|
||
var probabilities = await engine.CalculateProbabilitiesAsync(1, 0);
|
||
|
||
// Assert
|
||
Assert.Equal(3, probabilities.Count);
|
||
|
||
// 验证概率总和为100%
|
||
var totalProbability = probabilities.Sum(p => p.Probability);
|
||
Assert.Equal(100m, totalProbability, 2);
|
||
|
||
// 验证概率与库存成正比
|
||
// A赏: 5/(5+10+20) = 14.29%
|
||
// B赏: 10/(5+10+20) = 28.57%
|
||
// C赏: 20/(5+10+20) = 57.14%
|
||
var aPrize = probabilities.First(p => p.ShangId == 10);
|
||
var bPrize = probabilities.First(p => p.ShangId == 11);
|
||
var cPrize = probabilities.First(p => p.ShangId == 12);
|
||
|
||
Assert.True(cPrize.Probability > bPrize.Probability);
|
||
Assert.True(bPrize.Probability > aPrize.Probability);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 库存为0的奖品被排除
|
||
/// Requirements: 7.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_CalculateProbabilitiesAsync_ExcludesZeroStockPrizes()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
|
||
// 创建包含库存为0的奖品
|
||
var goodsItems = new List<GoodsItem>
|
||
{
|
||
new() { Id = 201, GoodsId = 1, Num = 0, Title = "A赏奖品", Stock = 5, SurplusStock = 0, Price = 100, ScMoney = 50, ShangId = 10, GoodsListId = 0, ImgUrl = "a.jpg", Sort = 1 },
|
||
new() { Id = 202, GoodsId = 1, Num = 0, Title = "B赏奖品", Stock = 10, SurplusStock = 10, Price = 50, ScMoney = 25, ShangId = 11, GoodsListId = 0, ImgUrl = "b.jpg", Sort = 2 }
|
||
};
|
||
await dbContext.GoodsItems.AddRangeAsync(goodsItems);
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
// Act
|
||
var probabilities = await engine.CalculateProbabilitiesAsync(1, 0);
|
||
|
||
// Assert
|
||
Assert.Single(probabilities);
|
||
Assert.Equal(11, probabilities.First().ShangId);
|
||
Assert.Equal(100m, probabilities.First().Probability);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 验证抽奖结果
|
||
/// Requirements: 9.1-9.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_ValidateDrawResultAsync_ValidResult_ReturnsTrue()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
await CreateTestUserAsync(dbContext);
|
||
await CreateTestGoodsAsync(dbContext, type: 1);
|
||
await CreateTestPrizeLevelsAsync(dbContext);
|
||
await CreateTestGoodsItemsWithStockAsync(dbContext, 1);
|
||
|
||
var request = new LotteryDrawRequest
|
||
{
|
||
UserId = 1,
|
||
GoodsId = 1,
|
||
Num = 0,
|
||
OrderId = 1,
|
||
OrderType = LotteryOrderType.YiFanShang,
|
||
Source = PrizeSource.Lottery
|
||
};
|
||
|
||
var drawResult = await engine.DrawAsync(request);
|
||
|
||
// Act
|
||
var isValid = await engine.ValidateDrawResultAsync(drawResult);
|
||
|
||
// Assert
|
||
Assert.True(isValid);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试抽奖引擎 - 验证失败的抽奖结果
|
||
/// Requirements: 9.1-9.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task LotteryEngine_ValidateDrawResultAsync_FailedResult_ReturnsFalse()
|
||
{
|
||
// Arrange
|
||
var dbContext = CreateInMemoryDbContext();
|
||
var inventoryManager = CreateInventoryManager(dbContext);
|
||
var engine = CreateLotteryEngine(dbContext, inventoryManager);
|
||
|
||
var failedResult = new LotteryDrawResult { Success = false };
|
||
|
||
// Act
|
||
var isValid = await engine.ValidateDrawResultAsync(failedResult);
|
||
|
||
// Assert
|
||
Assert.False(isValid);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|