using HoneyBox.Core.Interfaces; using HoneyBox.Core.Services; using HoneyBox.Model.Data; using HoneyBox.Model.Entities; using HoneyBox.Model.Models.Goods; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace HoneyBox.Tests.Integration; /// /// 商品服务集成测试 /// 测试完整的商品查询流程 /// Requirements: 1.1-1.6, 2.1-2.7 /// public class GoodsServiceIntegrationTests { private HoneyBoxDbContext CreateInMemoryDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new HoneyBoxDbContext(options); } private GoodsService CreateGoodsService(HoneyBoxDbContext dbContext, IGoodsCacheService? cacheService = null) { var mockLogger = new Mock>(); var mockCacheService = cacheService ?? CreateMockCacheService(); return new GoodsService(dbContext, mockCacheService, mockLogger.Object); } private IGoodsCacheService CreateMockCacheService() { var mock = new Mock(); mock.Setup(x => x.GetJoinCountAsync(It.IsAny())).ReturnsAsync(-1); mock.Setup(x => x.SetJoinCountAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); return mock.Object; } #region 商品列表测试 (Requirements 1.1-1.6) /// /// 测试商品列表查询 - 返回分页商品 /// Requirements: 1.1 /// [Fact] public async Task GetGoodsList_WithTypeFilter_ReturnsMatchingGoods() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); // 添加商品类型 await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 6, Name = "全局赏", FlName = "全局赏", CornerText = "全局赏" }); await dbContext.SaveChangesAsync(); // 添加测试商品 var goods = new List { new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, new() { Id = 2, Title = "商品2", Type = 2, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" }, new() { Id = 3, Title = "商品3", Type = 6, Status = 1, ShowIs = 0, Price = 30, Stock = 30, Sort = 3, ImgUrl = "img3.jpg", ImgUrlDetail = "detail3.jpg" } }; await dbContext.Goods.AddRangeAsync(goods); await dbContext.SaveChangesAsync(); // Act - 查询类型2的商品 var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; var result = await service.GetGoodsListAsync(request, 0); // Assert Assert.NotNull(result); Assert.Equal(2, result.Total); Assert.All(result.Data, g => Assert.Equal(2, g.Type)); } /// /// 测试商品列表查询 - type=-1返回默认类型商品 /// Requirements: 1.2 /// [Fact] public async Task GetGoodsList_TypeMinusOne_ReturnsDefaultTypeGoods() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); // 添加商品类型 await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 6, Name = "全局赏", FlName = "全局赏", CornerText = "全局赏" }); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 8, Name = "领主赏", FlName = "领主赏", CornerText = "领主赏" }); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 16, Name = "其他", FlName = "其他", CornerText = "其他" }); await dbContext.SaveChangesAsync(); // 添加测试商品 - 默认类型: 2, 6, 8, 16 var goods = new List { new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, new() { Id = 2, Title = "商品2", Type = 6, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" }, new() { Id = 3, Title = "商品3", Type = 1, Status = 1, ShowIs = 0, Price = 30, Stock = 30, Sort = 3, ImgUrl = "img3.jpg", ImgUrlDetail = "detail3.jpg" } // 非默认类型 }; await dbContext.Goods.AddRangeAsync(goods); await dbContext.SaveChangesAsync(); // Act - type=-1 返回默认类型 var request = new GoodsListRequest { Type = -1, Page = 1, PageSize = 10 }; var result = await service.GetGoodsListAsync(request, 0); // Assert Assert.NotNull(result); Assert.Equal(2, result.Total); // 只有类型2和6的商品 Assert.All(result.Data, g => Assert.Contains(g.Type, new[] { 2, 6, 8, 16 })); } /// /// 测试商品列表查询 - 过滤非上架商品 /// Requirements: 1.4 /// [Fact] public async Task GetGoodsList_FiltersInactiveGoods() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new List { new() { Id = 1, Title = "上架商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, new() { Id = 2, Title = "下架商品", Type = 2, Status = 0, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" } }; await dbContext.Goods.AddRangeAsync(goods); await dbContext.SaveChangesAsync(); // Act var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; var result = await service.GetGoodsListAsync(request, 0); // Assert Assert.Single(result.Data); Assert.Equal("上架商品", result.Data[0].Title); } /// /// 测试商品列表查询 - 解锁金额过滤 /// Requirements: 1.5 /// [Fact] public async Task GetGoodsList_FiltersUnlockAmountForAnonymousUser() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new List { new() { Id = 1, Title = "无门槛商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, UnlockAmount = 0, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, new() { Id = 2, Title = "有门槛商品", Type = 2, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, UnlockAmount = 100, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" } }; await dbContext.Goods.AddRangeAsync(goods); await dbContext.SaveChangesAsync(); // Act - 未登录用户 (userId=0) var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; var result = await service.GetGoodsListAsync(request, 0); // Assert - 只能看到无门槛商品 Assert.Single(result.Data); Assert.Equal("无门槛商品", result.Data[0].Title); } /// /// 测试商品列表查询 - 排序正确性 /// Requirements: 1.6 /// [Fact] public async Task GetGoodsList_SortsBySortDescThenIdDesc() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new List { new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, new() { Id = 2, Title = "商品2", Type = 2, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" }, new() { Id = 3, Title = "商品3", Type = 2, Status = 1, ShowIs = 0, Price = 30, Stock = 30, Sort = 2, ImgUrl = "img3.jpg", ImgUrlDetail = "detail3.jpg" } }; await dbContext.Goods.AddRangeAsync(goods); await dbContext.SaveChangesAsync(); // Act var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; var result = await service.GetGoodsListAsync(request, 0); // Assert - 按sort DESC, id DESC排序 Assert.Equal(3, result.Data.Count); Assert.Equal(3, result.Data[0].Id); // sort=2, id=3 Assert.Equal(2, result.Data[1].Id); // sort=2, id=2 Assert.Equal(1, result.Data[2].Id); // sort=1, id=1 } /// /// 测试商品列表查询 - 分页功能 /// Requirements: 1.1 /// [Fact] public async Task GetGoodsList_PaginationWorksCorrectly() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); // 添加15条商品 var goods = Enumerable.Range(1, 15).Select(i => new Good { Id = i, Title = $"商品{i}", Type = 2, Status = 1, ShowIs = 0, Price = i * 10, Stock = 10, Sort = i, ImgUrl = $"img{i}.jpg", ImgUrlDetail = $"detail{i}.jpg" }).ToList(); await dbContext.Goods.AddRangeAsync(goods); await dbContext.SaveChangesAsync(); // Act - 第一页 var page1Request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; var page1 = await service.GetGoodsListAsync(page1Request, 0); // Act - 第二页 var page2Request = new GoodsListRequest { Type = 2, Page = 2, PageSize = 10 }; var page2 = await service.GetGoodsListAsync(page2Request, 0); // Assert Assert.Equal(15, page1.Total); Assert.Equal(2, page1.LastPage); Assert.Equal(10, page1.Data.Count); Assert.Equal(5, page2.Data.Count); } #endregion #region 商品详情测试 (Requirements 2.1-2.7) /// /// 测试商品详情查询 - 返回完整商品信息 /// Requirements: 2.1 /// [Fact] public async Task GetGoodsDetail_ReturnsCompleteGoodsInfo() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); // 添加商品类型 await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); // 添加商品 var goods = new Good { Id = 1, Title = "测试商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 5, SaleStock = 2, LockIs = 0, ImgUrl = "img.jpg", ImgUrlDetail = "detail.jpg", CouponIs = 1, CouponPro = 10, IntegralIs = 1, RageIs = 0, LingzhuIs = 0, DailyXiangou = 0, QuanjuXiangou = 0 }; await dbContext.Goods.AddAsync(goods); // 添加奖品等级 await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 10, Title = "A赏", Color = "#FF0000" }); await dbContext.SaveChangesAsync(); // 添加奖品 var goodsItem = new GoodsItem { Id = 1, GoodsId = 1, Num = 1, Title = "奖品1", Stock = 10, SurplusStock = 8, Price = 100, ScMoney = 50, ShangId = 10, GoodsListId = 0, ImgUrl = "prize.jpg", Sort = 1 }; await dbContext.GoodsItems.AddAsync(goodsItem); await dbContext.SaveChangesAsync(); // Act var result = await service.GetGoodsDetailAsync(1, 1, 0); // Assert Assert.NotNull(result); Assert.NotNull(result.Goods); Assert.Equal(1, result.Goods.Id); Assert.Equal("测试商品", result.Goods.Title); Assert.Equal(2, result.Goods.Type); Assert.NotNull(result.LockInfo); Assert.NotNull(result.GoodsList); Assert.NotNull(result.LimitInfo); } /// /// 测试商品详情查询 - 自动选择箱号 /// Requirements: 2.2 /// [Fact] public async Task GetGoodsDetail_AutoSelectsBoxNumber_WhenGoodsNumIsZero() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new Good { Id = 1, Title = "测试商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 3, ImgUrl = "img.jpg", ImgUrlDetail = "detail.jpg" }; await dbContext.Goods.AddAsync(goods); // 添加奖品 - 箱号1无库存,箱号2有库存 var goodsItems = new List { new() { Id = 1, GoodsId = 1, Num = 1, Title = "奖品1", Stock = 10, SurplusStock = 0, ShangId = 10, GoodsListId = 0, ImgUrl = "p1.jpg" }, new() { Id = 2, GoodsId = 1, Num = 2, Title = "奖品2", Stock = 10, SurplusStock = 5, ShangId = 10, GoodsListId = 0, ImgUrl = "p2.jpg" } }; await dbContext.GoodsItems.AddRangeAsync(goodsItems); await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 10, Title = "A赏" }); await dbContext.SaveChangesAsync(); // Act - goodsNum=0 应自动选择有库存的箱号 var result = await service.GetGoodsDetailAsync(1, 0, 0); // Assert - 应选择箱号2(有库存) Assert.Equal(2, result.Goods.Num); } /// /// 测试商品详情查询 - 概率计算 /// Requirements: 2.3 /// [Fact] public async Task GetGoodsDetail_CalculatesProbabilityCorrectly() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new Good { Id = 1, Title = "测试商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 1, ImgUrl = "img.jpg", ImgUrlDetail = "detail.jpg" }; await dbContext.Goods.AddAsync(goods); // 添加奖品等级 await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 10, Title = "A赏" }); await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 11, Title = "B赏" }); await dbContext.SaveChangesAsync(); // 添加奖品 - 总剩余库存=10, A赏剩余2, B赏剩余8 var goodsItems = new List { new() { Id = 1, GoodsId = 1, Num = 1, Title = "A赏奖品", Stock = 5, SurplusStock = 2, ShangId = 10, GoodsListId = 0, ImgUrl = "a.jpg", Sort = 2 }, new() { Id = 2, GoodsId = 1, Num = 1, Title = "B赏奖品", Stock = 10, SurplusStock = 8, ShangId = 11, GoodsListId = 0, ImgUrl = "b.jpg", Sort = 1 } }; await dbContext.GoodsItems.AddRangeAsync(goodsItems); await dbContext.SaveChangesAsync(); // Act var result = await service.GetGoodsDetailAsync(1, 1, 0); // Assert - 概率计算: A赏=2/10*100=20%, B赏=8/10*100=80% Assert.Equal(2, result.GoodsList.Count); var aItem = result.GoodsList.First(x => x.ShangId == 10); var bItem = result.GoodsList.First(x => x.ShangId == 11); Assert.Contains("20", aItem.Pro); Assert.Contains("80", bItem.Pro); } /// /// 测试商品详情查询 - 锁箱信息 /// Requirements: 2.4 /// [Fact] public async Task GetGoodsDetail_ReturnsLockInfo() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new Good { Id = 1, Title = "测试商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 1, LockIs = 1, // 支持锁箱 ImgUrl = "img.jpg", ImgUrlDetail = "detail.jpg" }; await dbContext.Goods.AddAsync(goods); // 添加锁箱记录 var futureTime = DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds(); var goodsLock = new GoodsLock { Id = 1, GoodsIdNum = "1_1", UserId = 100, EndTime = futureTime }; await dbContext.GoodsLocks.AddAsync(goodsLock); // 添加锁箱用户 var user = new User { Id = 100, OpenId = "test_openid_100", Uid = "test_uid_100", Nickname = "锁箱用户", HeadImg = "avatar.jpg" }; await dbContext.Users.AddAsync(user); await dbContext.SaveChangesAsync(); // Act var result = await service.GetGoodsDetailAsync(1, 1, 0); // Assert Assert.NotNull(result.LockInfo); Assert.Equal(1, result.LockInfo.LockIs); Assert.Equal("锁箱用户", result.LockInfo.GoodsLockUserNickname); Assert.True(result.LockInfo.GoodsLockSurplusTime > 0); } /// /// 测试商品详情查询 - 收藏状态 /// Requirements: 2.6 /// [Fact] public async Task GetGoodsDetail_ReturnsCollectionStatus() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new Good { Id = 1, Title = "测试商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 1, ImgUrl = "img.jpg", ImgUrlDetail = "detail.jpg" }; await dbContext.Goods.AddAsync(goods); // 添加收藏记录 var collection = new GoodsCollection { Id = 1, UserId = 100, GoodsId = 1, Num = 1, Type = 2 }; await dbContext.GoodsCollections.AddAsync(collection); await dbContext.SaveChangesAsync(); // Act - 已收藏用户 var result = await service.GetGoodsDetailAsync(1, 1, 100); // Assert Assert.Equal(1, result.Goods.CollectionIs); } /// /// 测试商品详情查询 - 限购信息 /// Requirements: 2.7 /// [Fact] public async Task GetGoodsDetail_ReturnsLimitInfo() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); await dbContext.SaveChangesAsync(); var goods = new Good { Id = 1, Title = "测试商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 1, DailyXiangou = 5, QuanjuXiangou = 10, ImgUrl = "img.jpg", ImgUrlDetail = "detail.jpg" }; await dbContext.Goods.AddAsync(goods); await dbContext.SaveChangesAsync(); // Act var result = await service.GetGoodsDetailAsync(1, 1, 0); // Assert Assert.NotNull(result.LimitInfo); Assert.Equal(5, result.LimitInfo.DailyXiangou); Assert.Equal(10, result.LimitInfo.QuanjuXiangou); } /// /// 测试商品详情查询 - 商品不存在 /// Requirements: 2.1 /// [Fact] public async Task GetGoodsDetail_ThrowsException_WhenGoodsNotFound() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); // Act & Assert await Assert.ThrowsAsync( () => service.GetGoodsDetailAsync(999, 1, 0)); } /// /// 测试商品详情查询 - 商品已下架 /// Requirements: 2.1 /// [Fact] public async Task GetGoodsDetail_ThrowsException_WhenGoodsOffline() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateGoodsService(dbContext); var goods = new Good { Id = 1, Title = "下架商品", Type = 2, Status = 0, // 下架 ShowIs = 0, Price = 10, Stock = 1, ImgUrl = "img.jpg", ImgUrlDetail = "detail.jpg" }; await dbContext.Goods.AddAsync(goods); await dbContext.SaveChangesAsync(); // Act & Assert await Assert.ThrowsAsync( () => service.GetGoodsDetailAsync(1, 1, 0)); } #endregion }