HaniBlindBox/server/HoneyBox/tests/HoneyBox.Tests/Integration/GoodsServiceIntegrationTests.cs
2026-01-04 01:47:02 +08:00

638 lines
23 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 商品服务集成测试
/// 测试完整的商品查询流程
/// Requirements: 1.1-1.6, 2.1-2.7
/// </summary>
public class GoodsServiceIntegrationTests
{
private HoneyBoxDbContext CreateInMemoryDbContext()
{
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new HoneyBoxDbContext(options);
}
private GoodsService CreateGoodsService(HoneyBoxDbContext dbContext, IGoodsCacheService? cacheService = null)
{
var mockLogger = new Mock<ILogger<GoodsService>>();
var mockCacheService = cacheService ?? CreateMockCacheService();
return new GoodsService(dbContext, mockCacheService, mockLogger.Object);
}
private IGoodsCacheService CreateMockCacheService()
{
var mock = new Mock<IGoodsCacheService>();
mock.Setup(x => x.GetJoinCountAsync(It.IsAny<int>())).ReturnsAsync(-1);
mock.Setup(x => x.SetJoinCountAsync(It.IsAny<int>(), It.IsAny<int>())).Returns(Task.CompletedTask);
return mock.Object;
}
#region (Requirements 1.1-1.6)
/// <summary>
/// 测试商品列表查询 - 返回分页商品
/// Requirements: 1.1
/// </summary>
[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<Good>
{
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));
}
/// <summary>
/// 测试商品列表查询 - type=-1返回默认类型商品
/// Requirements: 1.2
/// </summary>
[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<Good>
{
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 }));
}
/// <summary>
/// 测试商品列表查询 - 过滤非上架商品
/// Requirements: 1.4
/// </summary>
[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<Good>
{
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);
}
/// <summary>
/// 测试商品列表查询 - 解锁金额过滤
/// Requirements: 1.5
/// </summary>
[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<Good>
{
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);
}
/// <summary>
/// 测试商品列表查询 - 排序正确性
/// Requirements: 1.6
/// </summary>
[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<Good>
{
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
}
/// <summary>
/// 测试商品列表查询 - 分页功能
/// Requirements: 1.1
/// </summary>
[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)
/// <summary>
/// 测试商品详情查询 - 返回完整商品信息
/// Requirements: 2.1
/// </summary>
[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);
}
/// <summary>
/// 测试商品详情查询 - 自动选择箱号
/// Requirements: 2.2
/// </summary>
[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<GoodsItem>
{
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);
}
/// <summary>
/// 测试商品详情查询 - 概率计算
/// Requirements: 2.3
/// </summary>
[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<GoodsItem>
{
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);
}
/// <summary>
/// 测试商品详情查询 - 锁箱信息
/// Requirements: 2.4
/// </summary>
[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);
}
/// <summary>
/// 测试商品详情查询 - 收藏状态
/// Requirements: 2.6
/// </summary>
[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);
}
/// <summary>
/// 测试商品详情查询 - 限购信息
/// Requirements: 2.7
/// </summary>
[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);
}
/// <summary>
/// 测试商品详情查询 - 商品不存在
/// Requirements: 2.1
/// </summary>
[Fact]
public async Task GetGoodsDetail_ThrowsException_WhenGoodsNotFound()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var service = CreateGoodsService(dbContext);
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(
() => service.GetGoodsDetailAsync(999, 1, 0));
}
/// <summary>
/// 测试商品详情查询 - 商品已下架
/// Requirements: 2.1
/// </summary>
[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<InvalidOperationException>(
() => service.GetGoodsDetailAsync(1, 1, 0));
}
#endregion
}