using HoneyBox.Core.Services; using HoneyBox.Model.Data; using HoneyBox.Model.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace HoneyBox.Tests.Integration; /// /// 优惠券服务集成测试 /// Requirements: 3.1-7.5 /// public class CouponServiceIntegrationTests { private HoneyBoxDbContext CreateInMemoryDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning)) .Options; return new HoneyBoxDbContext(options); } private CouponService CreateCouponService(HoneyBoxDbContext dbContext) { var mockLogger = new Mock>(); return new CouponService(dbContext, mockLogger.Object); } private User CreateTestUser(int id, decimal integral = 1000) { return new User { Id = id, OpenId = $"openid_{Guid.NewGuid():N}", Uid = $"uid_{Guid.NewGuid():N}", Nickname = "测试用户", HeadImg = "", Integral = integral, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; } [Fact] public async Task GetCouponList_UnusedStatus_ReturnsUnusedCoupons() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; await dbContext.UserCoupons.AddRangeAsync(new List { new() { UserId = userId, Level = 3, Num = 100, Status = 1, Type = 1, Title = "高级赏券", FromId = "0", CreatedAt = DateTime.Now }, new() { UserId = userId, Level = 4, Num = 50, Status = 1, Type = 1, Title = "普通赏券", FromId = "0", CreatedAt = DateTime.Now }, new() { UserId = userId, Level = 3, Num = 80, Status = 2, Type = 1, Title = "已分享券", FromId = "0", ShareTime = DateTime.Now, CreatedAt = DateTime.Now } }); await dbContext.Users.AddAsync(CreateTestUser(userId)); await dbContext.SaveChangesAsync(); var result = await service.GetCouponListAsync(userId, 1, 1, 15); Assert.Equal(2, result.List.Count); } [Fact] public async Task GetCouponList_SharedStatus_ReturnsSharedCoupons() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; await dbContext.UserCoupons.AddRangeAsync(new List { new() { UserId = userId, Level = 3, Num = 100, Status = 1, Type = 1, Title = "未分享券", FromId = "0", CreatedAt = DateTime.Now }, new() { UserId = userId, Level = 3, Num = 80, Status = 2, Type = 1, Title = "已分享券1", FromId = "0", ShareTime = DateTime.Now.AddHours(-1), CreatedAt = DateTime.Now }, new() { UserId = userId, Level = 4, Num = 60, Status = 2, Type = 1, Title = "已分享券2", FromId = "0", ShareTime = DateTime.Now, CreatedAt = DateTime.Now } }); await dbContext.Users.AddAsync(CreateTestUser(userId)); await dbContext.SaveChangesAsync(); var result = await service.GetCouponListAsync(userId, 2, 1, 15); Assert.Equal(2, result.List.Count); } [Theory] [InlineData(1, "特级赏券")] [InlineData(2, "终极赏券")] [InlineData(3, "高级赏券")] [InlineData(4, "普通赏券")] public async Task GetCouponList_LevelMapping_ReturnsCorrectLevelText(int level, string expectedText) { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; await dbContext.UserCoupons.AddAsync(new UserCoupon { UserId = userId, Level = level, Num = 100, Status = 1, Type = 1, Title = "测试券", FromId = "0", CreatedAt = DateTime.Now }); await dbContext.Users.AddAsync(CreateTestUser(userId)); await dbContext.SaveChangesAsync(); var result = await service.GetCouponListAsync(userId, 1, 1, 15); Assert.Single(result.List); Assert.Equal(expectedText, result.List[0].LevelText); } [Fact] public async Task GetCouponList_ReturnsStatistics() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; await dbContext.Users.AddAsync(CreateTestUser(userId, 500)); await dbContext.SaveChangesAsync(); var result = await service.GetCouponListAsync(userId, 1, 1, 15); Assert.Equal(50, result.ZCount); Assert.Equal(20, result.KeHcCount); Assert.Equal("10%", result.SunHao); Assert.Equal(500, result.UserIntegral); } [Fact] public async Task ShareCoupon_Success_UpdatesStatusAndShareTime() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; var coupon = new UserCoupon { UserId = userId, Level = 3, Num = 100, Status = 1, Type = 1, Title = "测试券", FromId = "0", CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(coupon); await dbContext.SaveChangesAsync(); var result = await service.ShareCouponAsync(userId, coupon.Id); Assert.True(result); var updatedCoupon = await dbContext.UserCoupons.FindAsync(coupon.Id); Assert.Equal(2, updatedCoupon!.Status); Assert.NotNull(updatedCoupon.ShareTime); } [Fact] public async Task ShareCoupon_NotOwner_ThrowsException() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var coupon = new UserCoupon { UserId = 1, Level = 3, Num = 100, Status = 1, Type = 1, Title = "测试券", FromId = "0", CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(coupon); await dbContext.SaveChangesAsync(); await Assert.ThrowsAsync(() => service.ShareCouponAsync(2, coupon.Id)); } [Fact] public async Task ClaimCoupon_OwnCoupon_ThrowsException() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; var coupon = new UserCoupon { UserId = userId, Level = 3, Num = 100, Status = 2, Type = 1, Title = "测试券", FromId = "0", Other = 20, KlNum = 5, CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(coupon); await dbContext.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => service.ClaimCouponAsync(userId, coupon.Id)); Assert.Equal("请勿开启自己的劵", ex.Message); } [Fact] public async Task ClaimCoupon_AlreadyClaimed_ThrowsException() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var coupon = new UserCoupon { UserId = 1, Level = 3, Num = 100, Status = 2, Type = 1, Title = "测试券", FromId = "0", Other = 20, KlNum = 5, CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(coupon); await dbContext.SaveChangesAsync(); var claimRecord = new UserCoupon { UserId = 2, Level = 3, Num = 0, LNum = 5, Status = 3, Type = 2, Title = "测试券", FromId = coupon.Id.ToString(), CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(claimRecord); await dbContext.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => service.ClaimCouponAsync(2, coupon.Id)); Assert.Equal("你已经领取过了", ex.Message); } [Fact] public async Task ClaimCoupon_NoRemainingAmount_ThrowsException() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var coupon = new UserCoupon { UserId = 1, Level = 3, Num = 100, Status = 2, Type = 1, Title = "测试券", FromId = "0", Other = 0, KlNum = 0, CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(coupon); await dbContext.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => service.ClaimCouponAsync(2, coupon.Id)); Assert.Equal("来晚了, 已经被人领完了", ex.Message); } [Fact] public async Task CalculateSynthesis_AppliesLossRate() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; var coupons = new List { new() { UserId = userId, Level = 4, Num = 100, Status = 1, Type = 1, Title = "普通赏券1", FromId = "0", CreatedAt = DateTime.Now }, new() { UserId = userId, Level = 4, Num = 100, Status = 1, Type = 1, Title = "普通赏券2", FromId = "0", CreatedAt = DateTime.Now } }; await dbContext.UserCoupons.AddRangeAsync(coupons); await dbContext.SaveChangesAsync(); var result = await service.CalculateSynthesisAsync(userId, string.Join(",", coupons.Select(c => c.Id))); Assert.Equal(200, result.SumNum); Assert.Equal(180, result.ShNum); } [Theory] [InlineData(1)] [InlineData(2)] public async Task CalculateSynthesis_HighLevelCoupon_ThrowsException(int level) { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; var coupon = new UserCoupon { UserId = userId, Level = level, Num = 5000, Status = 1, Type = 1, Title = "高级券", FromId = "0", CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(coupon); await dbContext.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => service.CalculateSynthesisAsync(userId, coupon.Id.ToString())); Assert.Equal("特级,终极赏券不能合成", ex.Message); } [Fact] public async Task CalculateSynthesis_ExceedsMaxCount_ThrowsException() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; var coupons = Enumerable.Range(1, 21).Select(i => new UserCoupon { UserId = userId, Level = 4, Num = 10, Status = 1, Type = 1, Title = $"普通赏券{i}", FromId = "0", CreatedAt = DateTime.Now }).ToList(); await dbContext.UserCoupons.AddRangeAsync(coupons); await dbContext.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => service.CalculateSynthesisAsync(userId, string.Join(",", coupons.Select(c => c.Id)))); Assert.Equal("最多只能20个合成", ex.Message); } [Fact] public async Task SynthesisCoupons_Success_CreatesNewCoupon() { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; var coupons = new List { new() { UserId = userId, Level = 4, Num = 300, Status = 1, Type = 1, Title = "普通赏券1", FromId = "0", CreatedAt = DateTime.Now }, new() { UserId = userId, Level = 4, Num = 300, Status = 1, Type = 1, Title = "普通赏券2", FromId = "0", CreatedAt = DateTime.Now } }; await dbContext.UserCoupons.AddRangeAsync(coupons); await dbContext.SaveChangesAsync(); var result = await service.SynthesisCouponsAsync(userId, string.Join(",", coupons.Select(c => c.Id))); Assert.True(result); var originalCoupons = await dbContext.UserCoupons.Where(c => coupons.Select(x => x.Id).Contains(c.Id)).ToListAsync(); Assert.All(originalCoupons, c => Assert.Equal(4, c.Status)); var newCoupon = await dbContext.UserCoupons.FirstOrDefaultAsync(c => c.UserId == userId && c.Type == 3); Assert.NotNull(newCoupon); Assert.Equal(1, newCoupon.Status); Assert.Equal(3, newCoupon.Level); } [Theory] [InlineData(5000, 1)] [InlineData(2000, 2)] [InlineData(500, 3)] [InlineData(100, 4)] public async Task CalculateSynthesis_CalculatesCorrectLevel(int totalNum, int expectedLevel) { var dbContext = CreateInMemoryDbContext(); var service = CreateCouponService(dbContext); var userId = 1; var coupon = new UserCoupon { UserId = userId, Level = 4, Num = totalNum, Status = 1, Type = 1, Title = "普通赏券", FromId = "0", CreatedAt = DateTime.Now }; await dbContext.UserCoupons.AddAsync(coupon); await dbContext.SaveChangesAsync(); var result = await service.CalculateSynthesisAsync(userId, coupon.Id.ToString()); Assert.Equal(expectedLevel, result.Coupon.Level); } }