581 lines
22 KiB
C#
581 lines
22 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: 2.3, 2.4, 2.5, 3.3, 3.4, 3.5
|
||
/// </summary>
|
||
public class DesignatedPrizeLotteryIntegrationTests : IDisposable
|
||
{
|
||
private readonly HoneyBoxDbContext _dbContext;
|
||
private readonly Mock<IInventoryManager> _mockInventoryManager;
|
||
private readonly Mock<IRewardService> _mockRewardService;
|
||
private readonly Mock<ILogger<LotteryEngine>> _mockLogger;
|
||
private readonly LotteryEngine _lotteryEngine;
|
||
|
||
public DesignatedPrizeLotteryIntegrationTests()
|
||
{
|
||
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
|
||
.Options;
|
||
|
||
_dbContext = new HoneyBoxDbContext(options);
|
||
_mockInventoryManager = new Mock<IInventoryManager>();
|
||
_mockRewardService = new Mock<IRewardService>();
|
||
_mockLogger = new Mock<ILogger<LotteryEngine>>();
|
||
_lotteryEngine = new LotteryEngine(_dbContext, _mockInventoryManager.Object, _mockRewardService.Object, _mockLogger.Object);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
_dbContext.Dispose();
|
||
}
|
||
|
||
#region Test Data Setup
|
||
|
||
private async Task<User> CreateTestUserAsync(int id, string nickname = "测试用户")
|
||
{
|
||
var user = new User
|
||
{
|
||
Id = id,
|
||
OpenId = $"test_openid_{id}",
|
||
Uid = $"test_uid_{id}",
|
||
Nickname = nickname,
|
||
HeadImg = "avatar.jpg",
|
||
Mobile = $"1380013800{id}",
|
||
Money = 1000,
|
||
Integral = 10000,
|
||
Money2 = 5000,
|
||
IsTest = 0,
|
||
Status = 1,
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
_dbContext.Users.Add(user);
|
||
await _dbContext.SaveChangesAsync();
|
||
return user;
|
||
}
|
||
|
||
private async Task<Good> CreateTestGoodsAsync(int id, byte type = 1, int status = 1)
|
||
{
|
||
var goods = new Good
|
||
{
|
||
Id = id,
|
||
Title = $"测试商品{id}",
|
||
Type = type,
|
||
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",
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
_dbContext.Goods.Add(goods);
|
||
await _dbContext.SaveChangesAsync();
|
||
return goods;
|
||
}
|
||
|
||
private async Task<List<GoodsItem>> CreateTestGoodsItemsAsync(int goodsId, int count = 5)
|
||
{
|
||
var items = new List<GoodsItem>();
|
||
for (int i = 1; i <= count; i++)
|
||
{
|
||
var item = new GoodsItem
|
||
{
|
||
Id = goodsId * 100 + i,
|
||
GoodsId = goodsId,
|
||
Num = 1,
|
||
Title = $"奖品{i}",
|
||
Stock = 10,
|
||
SurplusStock = 10,
|
||
Price = 100 * i,
|
||
Money = 50 * i,
|
||
ScMoney = 40 * i,
|
||
ShangId = i,
|
||
GoodsListId = 0,
|
||
ImgUrl = $"prize{i}.jpg",
|
||
Sort = i,
|
||
RealPro = 20, // 每个奖品20%概率
|
||
GoodsType = 1,
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
items.Add(item);
|
||
}
|
||
_dbContext.GoodsItems.AddRange(items);
|
||
await _dbContext.SaveChangesAsync();
|
||
return items;
|
||
}
|
||
|
||
private async Task<GoodsDesignatedPrize> CreateDesignatedPrizeConfigAsync(int goodsId, int goodsItemId, int userId, bool isActive = true)
|
||
{
|
||
var config = new GoodsDesignatedPrize
|
||
{
|
||
GoodsId = goodsId,
|
||
GoodsItemId = goodsItemId,
|
||
UserId = userId,
|
||
IsActive = isActive,
|
||
Remark = "测试指定中奖配置",
|
||
CreatedAt = DateTime.Now
|
||
};
|
||
_dbContext.GoodsDesignatedPrizes.Add(config);
|
||
await _dbContext.SaveChangesAsync();
|
||
return config;
|
||
}
|
||
|
||
private List<PrizeProbability> CreatePrizeProbabilities(List<GoodsItem> items)
|
||
{
|
||
var totalStock = items.Sum(i => i.SurplusStock);
|
||
return items.Select(i => new PrizeProbability
|
||
{
|
||
GoodsItemId = i.Id,
|
||
Title = i.Title,
|
||
ImgUrl = i.ImgUrl,
|
||
ShangId = i.ShangId ?? 0,
|
||
SurplusStock = i.SurplusStock,
|
||
Probability = (decimal)i.SurplusStock / totalStock * 100,
|
||
Weight = i.SurplusStock,
|
||
Price = i.Price,
|
||
ScMoney = i.ScMoney,
|
||
GoodsType = i.GoodsType,
|
||
Doubling = i.Doubling,
|
||
IsLingzhu = i.IsLingzhu,
|
||
ParentGoodsListId = i.GoodsListId
|
||
}).ToList();
|
||
}
|
||
|
||
private List<PrizeProbability> CreatePrizeProbabilitiesByRealPro(List<GoodsItem> items)
|
||
{
|
||
var totalPro = items.Sum(i => i.RealPro);
|
||
return items.Select(i => new PrizeProbability
|
||
{
|
||
GoodsItemId = i.Id,
|
||
Title = i.Title,
|
||
ImgUrl = i.ImgUrl,
|
||
ShangId = i.ShangId ?? 0,
|
||
SurplusStock = i.SurplusStock,
|
||
Probability = i.RealPro,
|
||
Weight = (double)i.RealPro,
|
||
Price = i.Price,
|
||
ScMoney = i.ScMoney,
|
||
GoodsType = i.GoodsType,
|
||
Doubling = i.Doubling,
|
||
IsLingzhu = i.IsLingzhu,
|
||
ParentGoodsListId = i.GoodsListId
|
||
}).ToList();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 有限赏完整流程测试 (Requirements 2.3, 2.4, 2.5)
|
||
|
||
/// <summary>
|
||
/// 有限赏完整流程:配置指定中奖 → 指定用户抽奖 → 验证指定用户可以抽到指定奖品
|
||
/// Requirements: 2.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task FiniteLottery_DesignatedUser_CanDrawDesignatedPrize()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 1); // 有限赏
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
var designatedItem = items[0]; // 第一个奖品指定给用户1
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, designatedItem.Id, designatedUser.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilities(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, designatedUser.Id, designatedPrizes);
|
||
|
||
// Assert
|
||
Assert.Contains(filteredPool, p => p.GoodsItemId == designatedItem.Id);
|
||
Assert.Equal(5, filteredPool.Count); // 指定用户可以看到所有奖品(普通+自己的指定)
|
||
}
|
||
|
||
/// <summary>
|
||
/// 有限赏完整流程:配置指定中奖 → 普通用户抽奖 → 验证普通用户无法抽到指定奖品
|
||
/// Requirements: 2.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task FiniteLottery_NonDesignatedUser_CannotDrawDesignatedPrize()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var normalUser = await CreateTestUserAsync(2, "普通用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 1);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
var designatedItem = items[0];
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, designatedItem.Id, designatedUser.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilities(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, normalUser.Id, designatedPrizes);
|
||
|
||
// Assert
|
||
Assert.DoesNotContain(filteredPool, p => p.GoodsItemId == designatedItem.Id);
|
||
Assert.Equal(4, filteredPool.Count); // 普通用户只能看到4个普通奖品
|
||
}
|
||
|
||
/// <summary>
|
||
/// 有限赏兜底机制测试:只剩指定奖品时普通用户可以抽奖
|
||
/// Requirements: 2.5
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task FiniteLottery_FallbackMechanism_WhenOnlyDesignatedPrizesRemain()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var normalUser = await CreateTestUserAsync(2, "普通用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 1);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 3);
|
||
|
||
// 所有奖品都指定给用户1
|
||
foreach (var item in items)
|
||
{
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, item.Id, designatedUser.Id);
|
||
}
|
||
|
||
var prizePool = CreatePrizeProbabilities(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, normalUser.Id, designatedPrizes);
|
||
|
||
// Assert - 兜底机制触发,普通用户可以抽到所有奖品
|
||
Assert.Equal(3, filteredPool.Count);
|
||
Assert.Equal(prizePool.Count, filteredPool.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 有限赏:多个指定奖品配置测试
|
||
/// Requirements: 2.3, 2.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task FiniteLottery_MultipleDesignatedPrizes_FilteredCorrectly()
|
||
{
|
||
// Arrange
|
||
var user1 = await CreateTestUserAsync(1, "用户1");
|
||
var user2 = await CreateTestUserAsync(2, "用户2");
|
||
var user3 = await CreateTestUserAsync(3, "用户3");
|
||
var goods = await CreateTestGoodsAsync(1, type: 1);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
|
||
// 奖品1指定给用户1,奖品2指定给用户2
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, items[0].Id, user1.Id);
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, items[1].Id, user2.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilities(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act - 用户1的奖品池
|
||
var user1Pool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, user1.Id, designatedPrizes);
|
||
// Act - 用户2的奖品池
|
||
var user2Pool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, user2.Id, designatedPrizes);
|
||
// Act - 用户3的奖品池(普通用户)
|
||
var user3Pool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, user3.Id, designatedPrizes);
|
||
|
||
// Assert
|
||
Assert.Equal(4, user1Pool.Count); // 3个普通 + 1个自己的指定
|
||
Assert.Contains(user1Pool, p => p.GoodsItemId == items[0].Id);
|
||
Assert.DoesNotContain(user1Pool, p => p.GoodsItemId == items[1].Id);
|
||
|
||
Assert.Equal(4, user2Pool.Count); // 3个普通 + 1个自己的指定
|
||
Assert.Contains(user2Pool, p => p.GoodsItemId == items[1].Id);
|
||
Assert.DoesNotContain(user2Pool, p => p.GoodsItemId == items[0].Id);
|
||
|
||
Assert.Equal(3, user3Pool.Count); // 只有3个普通奖品
|
||
Assert.DoesNotContain(user3Pool, p => p.GoodsItemId == items[0].Id);
|
||
Assert.DoesNotContain(user3Pool, p => p.GoodsItemId == items[1].Id);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 无限赏完整流程测试 (Requirements 3.3, 3.4, 3.5)
|
||
|
||
/// <summary>
|
||
/// 无限赏完整流程:配置指定中奖 → 指定用户抽奖 → 验证指定用户可以抽到指定奖品
|
||
/// Requirements: 3.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task InfiniteLottery_DesignatedUser_CanDrawDesignatedPrize()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 2); // 无限赏
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
var designatedItem = items[0];
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, designatedItem.Id, designatedUser.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilitiesByRealPro(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolStrict(prizePool, designatedUser.Id, designatedPrizes);
|
||
|
||
// Assert
|
||
Assert.Contains(filteredPool, p => p.GoodsItemId == designatedItem.Id);
|
||
Assert.Equal(5, filteredPool.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 无限赏完整流程:配置指定中奖 → 普通用户抽奖 → 验证普通用户永远无法抽到指定奖品
|
||
/// Requirements: 3.3, 3.5
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task InfiniteLottery_NonDesignatedUser_CanNeverDrawDesignatedPrize()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var normalUser = await CreateTestUserAsync(2, "普通用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 2);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
var designatedItem = items[0];
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, designatedItem.Id, designatedUser.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilitiesByRealPro(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolStrict(prizePool, normalUser.Id, designatedPrizes);
|
||
|
||
// Assert - 严格模式,普通用户永远看不到指定奖品
|
||
Assert.DoesNotContain(filteredPool, p => p.GoodsItemId == designatedItem.Id);
|
||
Assert.Equal(4, filteredPool.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 无限赏严格模式:所有奖品都指定给其他用户时,普通用户奖品池为空(无兜底)
|
||
/// Requirements: 3.5
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task InfiniteLottery_StrictMode_NoFallback_WhenAllPrizesDesignated()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var normalUser = await CreateTestUserAsync(2, "普通用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 2);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 3);
|
||
|
||
// 所有奖品都指定给用户1
|
||
foreach (var item in items)
|
||
{
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, item.Id, designatedUser.Id);
|
||
}
|
||
|
||
var prizePool = CreatePrizeProbabilitiesByRealPro(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolStrict(prizePool, normalUser.Id, designatedPrizes);
|
||
|
||
// Assert - 严格模式无兜底,奖品池为空
|
||
Assert.Empty(filteredPool);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 无限赏:多个指定奖品配置测试
|
||
/// Requirements: 3.3, 3.4
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task InfiniteLottery_MultipleDesignatedPrizes_StrictFiltering()
|
||
{
|
||
// Arrange
|
||
var user1 = await CreateTestUserAsync(1, "用户1");
|
||
var user2 = await CreateTestUserAsync(2, "用户2");
|
||
var user3 = await CreateTestUserAsync(3, "用户3");
|
||
var goods = await CreateTestGoodsAsync(1, type: 2);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, items[0].Id, user1.Id);
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, items[1].Id, user2.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilitiesByRealPro(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var user1Pool = _lotteryEngine.FilterPrizePoolStrict(prizePool, user1.Id, designatedPrizes);
|
||
var user2Pool = _lotteryEngine.FilterPrizePoolStrict(prizePool, user2.Id, designatedPrizes);
|
||
var user3Pool = _lotteryEngine.FilterPrizePoolStrict(prizePool, user3.Id, designatedPrizes);
|
||
|
||
// Assert
|
||
Assert.Equal(4, user1Pool.Count);
|
||
Assert.Contains(user1Pool, p => p.GoodsItemId == items[0].Id);
|
||
Assert.DoesNotContain(user1Pool, p => p.GoodsItemId == items[1].Id);
|
||
|
||
Assert.Equal(4, user2Pool.Count);
|
||
Assert.Contains(user2Pool, p => p.GoodsItemId == items[1].Id);
|
||
Assert.DoesNotContain(user2Pool, p => p.GoodsItemId == items[0].Id);
|
||
|
||
Assert.Equal(3, user3Pool.Count);
|
||
Assert.DoesNotContain(user3Pool, p => p.GoodsItemId == items[0].Id);
|
||
Assert.DoesNotContain(user3Pool, p => p.GoodsItemId == items[1].Id);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 配置状态测试
|
||
|
||
/// <summary>
|
||
/// 测试禁用的指定中奖配置不生效
|
||
/// Requirements: 1.3
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task DisabledDesignatedPrizeConfig_ShouldNotAffectFiltering()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var normalUser = await CreateTestUserAsync(2, "普通用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 1);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
|
||
// 创建禁用的配置
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, items[0].Id, designatedUser.Id, isActive: false);
|
||
|
||
var prizePool = CreatePrizeProbabilities(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var normalUserPool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, normalUser.Id, designatedPrizes);
|
||
|
||
// Assert - 禁用的配置不生效,普通用户可以看到所有奖品
|
||
Assert.Equal(5, normalUserPool.Count);
|
||
Assert.Contains(normalUserPool, p => p.GoodsItemId == items[0].Id);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试无指定中奖配置时返回原始奖品池
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task NoDesignatedPrizeConfig_ReturnsOriginalPool()
|
||
{
|
||
// Arrange
|
||
var user = await CreateTestUserAsync(1, "用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 1);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 5);
|
||
|
||
var prizePool = CreatePrizeProbabilities(items);
|
||
var designatedPrizes = new Dictionary<int, int>(); // 空配置
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, user.Id, designatedPrizes);
|
||
|
||
// Assert
|
||
Assert.Equal(prizePool.Count, filteredPool.Count);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 边界情况测试
|
||
|
||
/// <summary>
|
||
/// 测试空奖品池
|
||
/// </summary>
|
||
[Fact]
|
||
public void EmptyPrizePool_ReturnsEmptyPool()
|
||
{
|
||
// Arrange
|
||
var prizePool = new List<PrizeProbability>();
|
||
var designatedPrizes = new Dictionary<int, int> { { 1, 1 } };
|
||
|
||
// Act
|
||
var filteredPool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, 1, designatedPrizes);
|
||
|
||
// Assert
|
||
Assert.Empty(filteredPool);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试单个奖品且为指定奖品的情况(有限赏兜底)
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task SingleDesignatedPrize_FiniteLottery_FallbackTriggered()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var normalUser = await CreateTestUserAsync(2, "普通用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 1);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 1);
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, items[0].Id, designatedUser.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilities(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var normalUserPool = _lotteryEngine.FilterPrizePoolWithFallback(prizePool, normalUser.Id, designatedPrizes);
|
||
|
||
// Assert - 兜底机制触发
|
||
Assert.Single(normalUserPool);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试单个奖品且为指定奖品的情况(无限赏严格模式)
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task SingleDesignatedPrize_InfiniteLottery_EmptyPool()
|
||
{
|
||
// Arrange
|
||
var designatedUser = await CreateTestUserAsync(1, "指定用户");
|
||
var normalUser = await CreateTestUserAsync(2, "普通用户");
|
||
var goods = await CreateTestGoodsAsync(1, type: 2);
|
||
var items = await CreateTestGoodsItemsAsync(goods.Id, 1);
|
||
await CreateDesignatedPrizeConfigAsync(goods.Id, items[0].Id, designatedUser.Id);
|
||
|
||
var prizePool = CreatePrizeProbabilitiesByRealPro(items);
|
||
var designatedPrizes = await _dbContext.GoodsDesignatedPrizes
|
||
.Where(dp => dp.GoodsId == goods.Id && dp.IsActive)
|
||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||
|
||
// Act
|
||
var normalUserPool = _lotteryEngine.FilterPrizePoolStrict(prizePool, normalUser.Id, designatedPrizes);
|
||
|
||
// Assert - 严格模式无兜底
|
||
Assert.Empty(normalUserPool);
|
||
}
|
||
|
||
#endregion
|
||
}
|