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; /// /// 抽奖服务集成测试 /// 测试抽奖结果查询、中奖记录查询功能 /// Requirements: 1.1-2.3, 4.1-4.3, 5.1-5.3 /// public class LotteryServiceIntegrationTests { private HoneyBoxDbContext CreateInMemoryDbContext() { var options = new DbContextOptionsBuilder() .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>(); var mockLotteryEngine = new Mock(); return new LotteryService(dbContext, mockLotteryEngine.Object, mockLogger.Object); } #region 测试数据准备 private async Task 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 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 { 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 { 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(); 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 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(); 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) /// /// 测试一番赏抽奖结果查询 - 有效订单号返回奖品列表 /// Requirements: 1.1 /// [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); } /// /// 测试一番赏抽奖结果查询 - 无效订单号返回空列表 /// Requirements: 1.2 /// [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); } /// /// 测试一番赏抽奖结果查询 - 返回奖品详情包含所有必要字段 /// Requirements: 1.3 /// [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 } /// /// 测试一番赏抽奖结果查询 - 只返回当前用户的订单 /// Requirements: 1.1 /// [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) /// /// 测试无限赏抽奖结果查询 - 有效订单号返回奖品列表 /// Requirements: 2.1 /// [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); } /// /// 测试无限赏抽奖结果查询 - 只查询无限赏类型订单 /// Requirements: 2.2 /// [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); } /// /// 测试无限赏抽奖结果查询 - 返回格式与一番赏一致 /// Requirements: 2.3 /// [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); } /// /// 测试无限赏抽奖结果查询 - 包含用户信息 /// Requirements: 2.3 /// [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); } /// /// 测试无限赏抽奖结果查询 - 无效订单号返回空列表 /// Requirements: 2.1 /// [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) /// /// 测试无限赏中奖记录查询 - 基本查询 /// Requirements: 4.1 /// [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); } /// /// 测试无限赏中奖记录查询 - 分页功能 /// Requirements: 4.1 /// [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); } /// /// 测试无限赏中奖记录查询 - 按赏品等级过滤 /// Requirements: 4.2 /// [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)); } /// /// 测试无限赏中奖记录查询 - 商品不存在 /// Requirements: 4.1 /// [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); } /// /// 测试无限赏中奖记录查询 - 商品已下架 /// Requirements: 4.1 /// [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); } /// /// 测试无限赏中奖记录查询 - 用户昵称脱敏 /// Requirements: 4.3 /// [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); } /// /// 测试无限赏中奖记录查询 - 分类列表包含全部选项 /// Requirements: 4.1 /// [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) /// /// 测试每日抽奖记录查询 - 基本查询 /// Requirements: 5.1 /// [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); } /// /// 测试每日抽奖记录查询 - 分页功能 /// Requirements: 5.1 /// [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); } /// /// 测试每日抽奖记录查询 - 只返回当前用户的记录 /// Requirements: 5.1 /// [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(); 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)); } /// /// 测试每日抽奖记录查询 - 无记录时返回空列表 /// Requirements: 5.1 /// [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); } /// /// 测试每日抽奖记录查询 - 记录按时间倒序排列 /// Requirements: 5.3 /// [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) /// /// 测试响应格式 - 无限赏中奖记录包含所有必要字段 /// Requirements: 10.2 /// [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); } /// /// 测试响应格式 - 每日抽奖记录包含所有必要字段 /// Requirements: 10.2 /// [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 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 mockEngine) { var mockLogger = new Mock>(); return new LotteryService(dbContext, mockEngine.Object, mockLogger.Object); } /// /// 测试道具卡抽奖 - 商品不存在时返回错误 /// Requirements: 6.1 /// [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( () => service.DrawWithItemCardAsync(1, 999, new List { 1 })); Assert.Equal("盒子不存在", exception.Message); } /// /// 测试道具卡抽奖 - 商品已下架时返回错误 /// Requirements: 6.1 /// [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( () => service.DrawWithItemCardAsync(1, 1, new List { 1 })); Assert.Equal("盒子已下架", exception.Message); } /// /// 测试道具卡抽奖 - 道具卡数量不足时返回错误 /// Requirements: 6.4 /// [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( () => service.DrawWithItemCardAsync(1, 1, new List { 1 })); Assert.Equal("重抽卡数量不足", exception.Message); } /// /// 测试道具卡抽奖 - 道具卡已使用时返回错误 /// Requirements: 6.4 /// [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( () => service.DrawWithItemCardAsync(1, 1, new List { 1 })); Assert.Equal("重抽卡数量不足", exception.Message); } /// /// 测试道具卡抽奖 - 参数为空时返回错误 /// Requirements: 6.4 /// [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( () => service.DrawWithItemCardAsync(1, 1, new List())); Assert.Equal("参数错误", exception.Message); } /// /// 测试道具卡抽奖 - 订单项不存在时返回错误 /// Requirements: 6.4 /// [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( () => service.DrawWithItemCardAsync(1, 1, new List { 999 })); Assert.Equal("数据错误", exception.Message); } /// /// 测试道具卡抽奖 - 消费门槛验证 /// Requirements: 6.1 /// [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( () => service.DrawWithItemCardAsync(1, 1, new List { 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>(); var mockRewardService = new Mock(); return new LotteryEngine(dbContext, inventoryManager, mockRewardService.Object, mockLogger.Object); } private InventoryManager CreateInventoryManager(HoneyBoxDbContext dbContext) { var mockLogger = new Mock>(); return new InventoryManager(dbContext, mockLogger.Object); } private async Task CreateTestGoodsItemsWithStockAsync(HoneyBoxDbContext dbContext, int goodsId, int num = 0) { var goodsItems = new List { 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(); } /// /// 测试抽奖引擎 - 完整抽奖流程成功 /// Requirements: 7.1-7.7 /// [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); } /// /// 测试抽奖引擎 - 库存扣减正确 /// Requirements: 8.1-8.4 /// [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); } /// /// 测试抽奖引擎 - 抽奖记录创建正确 /// Requirements: 9.1-9.4 /// [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); } /// /// 测试抽奖引擎 - 无可用奖品时返回失败 /// Requirements: 7.3 /// [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); } /// /// 测试抽奖引擎 - 多次抽奖成功 /// Requirements: 7.1-7.7 /// [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); } /// /// 测试抽奖引擎 - 概率计算正确 /// Requirements: 7.1 /// [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); } /// /// 测试抽奖引擎 - 库存为0的奖品被排除 /// Requirements: 7.3 /// [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 { 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); } /// /// 测试抽奖引擎 - 验证抽奖结果 /// Requirements: 9.1-9.4 /// [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); } /// /// 测试抽奖引擎 - 验证失败的抽奖结果 /// Requirements: 9.1-9.4 /// [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 }