using HoneyBox.Admin.Business.Models; using HoneyBox.Admin.Business.Models.Reward; using HoneyBox.Admin.Business.Models.SignConfig; using HoneyBox.Admin.Business.Services; using HoneyBox.Model.Data; using HoneyBox.Model.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace HoneyBox.Tests.Services; /// /// SignConfigService 单元测试 /// public class SignConfigServiceTests : IDisposable { private readonly HoneyBoxDbContext _dbContext; private readonly Mock> _mockLogger; private readonly SignConfigService _signConfigService; public SignConfigServiceTests() { // 使用 InMemory 数据库 var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _dbContext = new HoneyBoxDbContext(options); _mockLogger = new Mock>(); _signConfigService = new SignConfigService(_dbContext, _mockLogger.Object); } public void Dispose() { _dbContext.Dispose(); } #region GetSignConfigsAsync Tests [Fact] public async Task GetSignConfigsAsync_WithDailyType_ReturnsDailyConfigs() { // Arrange await SeedTestSignConfigs(); var request = new SignConfigListRequest { Page = 1, PageSize = 10, Type = 1 }; // Act var result = await _signConfigService.GetSignConfigsAsync(request); // Assert Assert.NotNull(result); Assert.Equal(2, result.Total); Assert.All(result.List, s => Assert.Equal(1, s.Type)); } [Fact] public async Task GetSignConfigsAsync_WithCumulativeType_ReturnsCumulativeConfigs() { // Arrange await SeedTestSignConfigs(); var request = new SignConfigListRequest { Page = 1, PageSize = 10, Type = 2 }; // Act var result = await _signConfigService.GetSignConfigsAsync(request); // Assert Assert.NotNull(result); Assert.Equal(1, result.Total); Assert.All(result.List, s => Assert.Equal(2, s.Type)); } [Fact] public async Task GetSignConfigsAsync_WithKeywordFilter_ReturnsFilteredConfigs() { // Arrange await SeedTestSignConfigs(); var request = new SignConfigListRequest { Page = 1, PageSize = 10, Type = 1, Keyword = "第1天" }; // Act var result = await _signConfigService.GetSignConfigsAsync(request); // Assert Assert.NotNull(result); Assert.Equal(1, result.Total); Assert.Contains("第1天", result.List[0].Title); } [Fact] public async Task GetSignConfigsAsync_WithPagination_ReturnsCorrectPage() { // Arrange await SeedTestSignConfigs(); var request = new SignConfigListRequest { Page = 1, PageSize = 1, Type = 1 }; // Act var result = await _signConfigService.GetSignConfigsAsync(request); // Assert Assert.NotNull(result); Assert.Equal(2, result.Total); Assert.Single(result.List); Assert.Equal(1, result.Page); Assert.Equal(1, result.PageSize); } [Fact] public async Task GetSignConfigsAsync_WithRewards_ReturnsConfigsWithRewards() { // Arrange await SeedTestSignConfigsWithRewards(); var request = new SignConfigListRequest { Page = 1, PageSize = 10, Type = 1 }; // Act var result = await _signConfigService.GetSignConfigsAsync(request); // Assert Assert.NotNull(result); Assert.True(result.List.Count > 0); Assert.True(result.List[0].Rewards.Count > 0); } #endregion #region GetSignConfigByIdAsync Tests [Fact] public async Task GetSignConfigByIdAsync_WithExistingId_ReturnsConfig() { // Arrange await SeedTestSignConfigs(); // Act var result = await _signConfigService.GetSignConfigByIdAsync(1); // Assert Assert.NotNull(result); Assert.Equal(1, result.Id); } [Fact] public async Task GetSignConfigByIdAsync_WithNonExistingId_ReturnsNull() { // Arrange await SeedTestSignConfigs(); // Act var result = await _signConfigService.GetSignConfigByIdAsync(999); // Assert Assert.Null(result); } #endregion #region CreateSignConfigAsync Tests [Fact] public async Task CreateSignConfigAsync_WithValidRequest_CreatesConfig() { // Arrange var request = new SignConfigCreateRequest { Type = 1, Day = 1, Title = "测试签到配置", Icon = "https://example.com/icon.png", Sort = 1, Description = "测试描述", Rewards = new List { new() { RewardType = 1, RewardValue = 100, Description = "钻石奖励" } } }; // Act var id = await _signConfigService.CreateSignConfigAsync(request); // Assert Assert.True(id > 0); var config = await _dbContext.SignConfigs.FindAsync(id); Assert.NotNull(config); Assert.Equal((byte)1, config.Type); Assert.Equal((byte)1, config.Day); Assert.Equal("测试签到配置", config.Title); } [Fact] public async Task CreateSignConfigAsync_WithInvalidType_ThrowsException() { // Arrange var request = new SignConfigCreateRequest { Type = 3, Day = 1, Title = "测试签到配置" }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.CreateSignConfigAsync(request)); Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code); } [Fact] public async Task CreateSignConfigAsync_WithEmptyTitle_ThrowsException() { // Arrange var request = new SignConfigCreateRequest { Type = 1, Day = 1, Title = "" }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.CreateSignConfigAsync(request)); Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code); } [Fact] public async Task CreateSignConfigAsync_WithZeroDay_ThrowsException() { // Arrange var request = new SignConfigCreateRequest { Type = 1, Day = 0, Title = "测试签到配置" }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.CreateSignConfigAsync(request)); Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code); } #endregion #region UpdateSignConfigAsync Tests [Fact] public async Task UpdateSignConfigAsync_WithExistingConfig_UpdatesConfig() { // Arrange await SeedTestSignConfigs(); var request = new SignConfigUpdateRequest { Type = 1, Day = 2, Title = "更新后的标题", Icon = "https://example.com/new-icon.png", Sort = 10, Description = "更新后的描述" }; // Act var result = await _signConfigService.UpdateSignConfigAsync(1, request); // Assert Assert.True(result); var config = await _dbContext.SignConfigs.FindAsync(1); Assert.NotNull(config); Assert.Equal((byte)2, config.Day); Assert.Equal("更新后的标题", config.Title); } [Fact] public async Task UpdateSignConfigAsync_WithNonExistingConfig_ThrowsException() { // Arrange var request = new SignConfigUpdateRequest { Type = 1, Day = 1, Title = "测试" }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.UpdateSignConfigAsync(999, request)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region DeleteSignConfigAsync Tests [Fact] public async Task DeleteSignConfigAsync_WithExistingConfig_DeletesConfig() { // Arrange await SeedTestSignConfigs(); // Act var result = await _signConfigService.DeleteSignConfigAsync(1); // Assert Assert.True(result); var config = await _dbContext.SignConfigs.FindAsync(1); Assert.Null(config); } [Fact] public async Task DeleteSignConfigAsync_WithNonExistingConfig_ThrowsException() { // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.DeleteSignConfigAsync(999)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } [Fact] public async Task DeleteSignConfigAsync_WithRewards_DeletesConfigAndRewards() { // Arrange await SeedTestSignConfigsWithRewards(); var configBefore = await _dbContext.SignConfigs.FindAsync(1); var rewardsBefore = await _dbContext.Rewards.Where(r => r.RewardId == configBefore!.RewardId).ToListAsync(); Assert.NotEmpty(rewardsBefore); // Act var result = await _signConfigService.DeleteSignConfigAsync(1); // Assert Assert.True(result); var config = await _dbContext.SignConfigs.FindAsync(1); Assert.Null(config); var rewardsAfter = await _dbContext.Rewards.Where(r => r.RewardId == configBefore!.RewardId).ToListAsync(); Assert.Empty(rewardsAfter); } #endregion #region UpdateSignConfigStatusAsync Tests [Fact] public async Task UpdateSignConfigStatusAsync_WithValidStatus_UpdatesStatus() { // Arrange await SeedTestSignConfigs(); // Act var result = await _signConfigService.UpdateSignConfigStatusAsync(1, 0); // Assert Assert.True(result); var config = await _dbContext.SignConfigs.FindAsync(1); Assert.NotNull(config); Assert.Equal(0, config.Status); } [Fact] public async Task UpdateSignConfigStatusAsync_WithInvalidStatus_ThrowsException() { // Arrange await SeedTestSignConfigs(); // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.UpdateSignConfigStatusAsync(1, 2)); Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code); } [Fact] public async Task UpdateSignConfigStatusAsync_WithNonExistingConfig_ThrowsException() { // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.UpdateSignConfigStatusAsync(999, 1)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region UpdateSignConfigSortAsync Tests [Fact] public async Task UpdateSignConfigSortAsync_WithValidSort_UpdatesSort() { // Arrange await SeedTestSignConfigs(); // Act var result = await _signConfigService.UpdateSignConfigSortAsync(1, 100); // Assert Assert.True(result); var config = await _dbContext.SignConfigs.FindAsync(1); Assert.NotNull(config); Assert.Equal(100, config.Sort); } [Fact] public async Task UpdateSignConfigSortAsync_WithNonExistingConfig_ThrowsException() { // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.UpdateSignConfigSortAsync(999, 1)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region UpdateSignConfigRewardAsync Tests [Fact] public async Task UpdateSignConfigRewardAsync_WithValidRequest_UpdatesRewards() { // Arrange await SeedTestSignConfigsWithRewards(); var request = new SignConfigRewardRequest { Rewards = new List { new() { RewardType = 1, RewardValue = 200, Description = "新钻石奖励" }, new() { RewardType = 2, RewardValue = 100, Description = "新UU币奖励" } } }; // Act var result = await _signConfigService.UpdateSignConfigRewardAsync(1, request); // Assert Assert.True(result); var config = await _dbContext.SignConfigs.FindAsync(1); var rewards = await _dbContext.Rewards.Where(r => r.RewardId == config!.RewardId).ToListAsync(); Assert.Equal(2, rewards.Count); } [Fact] public async Task UpdateSignConfigRewardAsync_WithEmptyRewards_DeletesExistingRewards() { // Arrange await SeedTestSignConfigsWithRewards(); var request = new SignConfigRewardRequest { Rewards = new List() }; // Act var result = await _signConfigService.UpdateSignConfigRewardAsync(1, request); // Assert Assert.True(result); var config = await _dbContext.SignConfigs.FindAsync(1); var rewards = await _dbContext.Rewards.Where(r => r.RewardId == config!.RewardId).ToListAsync(); Assert.Empty(rewards); } [Fact] public async Task UpdateSignConfigRewardAsync_WithNonExistingConfig_ThrowsException() { // Arrange var request = new SignConfigRewardRequest { Rewards = new List() }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _signConfigService.UpdateSignConfigRewardAsync(999, request)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region Helper Methods private async Task SeedTestSignConfigs() { var configs = new List { new() { Id = 1, Type = 1, Day = 1, Title = "第1天签到", Icon = "https://example.com/icon1.png", Status = 1, Sort = 1, RewardId = "sign_001", Description = "第1天签到奖励", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }, new() { Id = 2, Type = 1, Day = 2, Title = "第2天签到", Icon = "https://example.com/icon2.png", Status = 1, Sort = 2, RewardId = "sign_002", Description = "第2天签到奖励", CreatedAt = DateTime.Now.AddMinutes(-1), UpdatedAt = DateTime.Now.AddMinutes(-1) }, new() { Id = 3, Type = 2, Day = 7, Title = "累计7天签到", Icon = "https://example.com/icon3.png", Status = 1, Sort = 1, RewardId = "sign_003", Description = "累计7天签到奖励", CreatedAt = DateTime.Now.AddMinutes(-2), UpdatedAt = DateTime.Now.AddMinutes(-2) } }; _dbContext.SignConfigs.AddRange(configs); await _dbContext.SaveChangesAsync(); } private async Task SeedTestSignConfigsWithRewards() { var configs = new List { new() { Id = 1, Type = 1, Day = 1, Title = "第1天签到", Icon = "https://example.com/icon1.png", Status = 1, Sort = 1, RewardId = "sign_reward_001", Description = "第1天签到奖励", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now } }; var rewards = new List { new() { Id = 100, RewardId = "sign_reward_001", RewardType = 1, RewardValue = 100, Description = "钻石奖励", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }, new() { Id = 101, RewardId = "sign_reward_001", RewardType = 2, RewardValue = 50, Description = "UU币奖励", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now } }; _dbContext.SignConfigs.AddRange(configs); _dbContext.Rewards.AddRange(rewards); await _dbContext.SaveChangesAsync(); } #endregion }