HaniBlindBox/server/HoneyBox/tests/HoneyBox.Tests/Services/QyLevelServiceTests.cs
zpc 2550f6d4c2 refactor: 统一权益系统命名 VIP -> Equity
后端更改:
- 重命名数据库表: vip_levels -> equity_levels, vip_level_rewards -> equity_level_rewards, user_vip_rewards -> user_equity_rewards
- 重命名实体类: VipLevel -> EquityLevel, VipLevelReward -> EquityLevelReward, UserVipReward -> UserEquityReward
- 重命名 DTO: VipInfoResponse -> EquityInfoResponse, VipUserInfoDto -> EquityUserInfoDto, VipLevelDto -> EquityLevelDto
- 重命名服务: VipService -> EquityService, IVipService -> IEquityService
- 更新 API 端点: /vip_list -> /equity_list
- 移动命名空间: Models/Vip -> Models/Equity
- 删除重复的 VipController (与 QyLevelController 功能重复)
- 删除未使用的 equity_levels 和 equity_level_prizes 旧表实体

前端更改:
- 更新 API 调用: getVipList -> getEquityList
- 更新 vip.vue 页面使用新的 API 函数

保持兼容:
- JSON 响应字段名保持不变 (vip, last_vip, jin_du 等)
- 用户表 vip 字段保持不变
2026-02-05 18:21:07 +08:00

661 lines
18 KiB
C#

using HoneyBox.Admin.Business.Models;
using HoneyBox.Admin.Business.Models.QyLevel;
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;
/// <summary>
/// QyLevelService 单元测试
/// </summary>
public class QyLevelServiceTests : IDisposable
{
private readonly HoneyBoxDbContext _dbContext;
private readonly Mock<ILogger<QyLevelService>> _mockLogger;
private readonly QyLevelService _qyLevelService;
public QyLevelServiceTests()
{
// 使用 InMemory 数据库
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_dbContext = new HoneyBoxDbContext(options);
_mockLogger = new Mock<ILogger<QyLevelService>>();
_qyLevelService = new QyLevelService(_dbContext, _mockLogger.Object);
}
public void Dispose()
{
_dbContext.Dispose();
}
#region GetQyLevelsAsync Tests
[Fact]
public async Task GetQyLevelsAsync_WithNoFilters_ReturnsAllLevels()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelListRequest { Page = 1, PageSize = 10 };
// Act
var result = await _qyLevelService.GetQyLevelsAsync(request);
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Total);
Assert.Equal(3, result.List.Count);
}
[Fact]
public async Task GetQyLevelsAsync_WithKeywordFilter_ReturnsFilteredLevels()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelListRequest { Page = 1, PageSize = 10, Keyword = "青铜" };
// Act
var result = await _qyLevelService.GetQyLevelsAsync(request);
// Assert
Assert.NotNull(result);
Assert.Equal(1, result.Total);
Assert.Contains("青铜", result.List[0].Title);
}
[Fact]
public async Task GetQyLevelsAsync_WithPagination_ReturnsCorrectPage()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelListRequest { Page = 1, PageSize = 2 };
// Act
var result = await _qyLevelService.GetQyLevelsAsync(request);
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Total);
Assert.Equal(2, result.List.Count);
Assert.Equal(1, result.Page);
Assert.Equal(2, result.PageSize);
}
[Fact]
public async Task GetQyLevelsAsync_ExcludesDeletedLevels()
{
// Arrange
await SeedTestQyLevels();
// Mark one level as deleted
var level = await _dbContext.EquityLevels.FindAsync(1);
level!.DeletedAt = DateTime.Now;
await _dbContext.SaveChangesAsync();
var request = new QyLevelListRequest { Page = 1, PageSize = 10 };
// Act
var result = await _qyLevelService.GetQyLevelsAsync(request);
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Total);
}
#endregion
#region GetQyLevelByIdAsync Tests
[Fact]
public async Task GetQyLevelByIdAsync_WithExistingId_ReturnsLevel()
{
// Arrange
await SeedTestQyLevels();
// Act
var result = await _qyLevelService.GetQyLevelByIdAsync(1);
// Assert
Assert.NotNull(result);
Assert.Equal(1, result.Id);
Assert.Equal("青铜会员", result.Title);
}
[Fact]
public async Task GetQyLevelByIdAsync_WithNonExistingId_ReturnsNull()
{
// Arrange
await SeedTestQyLevels();
// Act
var result = await _qyLevelService.GetQyLevelByIdAsync(999);
// Assert
Assert.Null(result);
}
[Fact]
public async Task GetQyLevelByIdAsync_WithDeletedLevel_ReturnsNull()
{
// Arrange
await SeedTestQyLevels();
var level = await _dbContext.EquityLevels.FindAsync(1);
level!.DeletedAt = DateTime.Now;
await _dbContext.SaveChangesAsync();
// Act
var result = await _qyLevelService.GetQyLevelByIdAsync(1);
// Assert
Assert.Null(result);
}
#endregion
#region UpdateQyLevelAsync Tests
[Fact]
public async Task UpdateQyLevelAsync_WithValidRequest_UpdatesLevel()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelUpdateRequest
{
Level = 1,
Title = "更新后的青铜会员",
RequiredPoints = 200
};
// Act
var result = await _qyLevelService.UpdateQyLevelAsync(1, request);
// Assert
Assert.True(result);
var level = await _dbContext.EquityLevels.FindAsync(1);
Assert.NotNull(level);
Assert.Equal("更新后的青铜会员", level.Title);
Assert.Equal(200, level.Number);
}
[Fact]
public async Task UpdateQyLevelAsync_WithNonExistingLevel_ThrowsException()
{
// Arrange
var request = new QyLevelUpdateRequest
{
Level = 1,
Title = "测试",
RequiredPoints = 100
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.UpdateQyLevelAsync(999, request));
Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
}
[Fact]
public async Task UpdateQyLevelAsync_WithInvalidLevel_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelUpdateRequest
{
Level = 0,
Title = "测试",
RequiredPoints = 100
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.UpdateQyLevelAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
[Fact]
public async Task UpdateQyLevelAsync_WithEmptyTitle_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelUpdateRequest
{
Level = 1,
Title = "",
RequiredPoints = 100
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.UpdateQyLevelAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
[Fact]
public async Task UpdateQyLevelAsync_WithNegativeRequiredPoints_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelUpdateRequest
{
Level = 1,
Title = "测试",
RequiredPoints = -1
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.UpdateQyLevelAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
#endregion
#region GetQyLevelPrizesAsync Tests
[Fact]
public async Task GetQyLevelPrizesAsync_WithNoFilters_ReturnsAllPrizes()
{
// Arrange
await SeedTestQyLevelsWithPrizes();
var request = new QyLevelPrizeListRequest { Page = 1, PageSize = 10 };
// Act
var result = await _qyLevelService.GetQyLevelPrizesAsync(1, request);
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Total);
Assert.Equal(2, result.List.Count);
}
[Fact]
public async Task GetQyLevelPrizesAsync_WithTypeFilter_ReturnsFilteredPrizes()
{
// Arrange
await SeedTestQyLevelsWithPrizes();
var request = new QyLevelPrizeListRequest { Page = 1, PageSize = 10, Type = 2 };
// Act
var result = await _qyLevelService.GetQyLevelPrizesAsync(1, request);
// Assert
Assert.NotNull(result);
Assert.Equal(1, result.Total);
Assert.All(result.List, p => Assert.Equal(2, p.Type));
}
[Fact]
public async Task GetQyLevelPrizesAsync_WithNonExistingLevel_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeListRequest { Page = 1, PageSize = 10 };
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.GetQyLevelPrizesAsync(999, request));
Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
}
[Fact]
public async Task GetQyLevelPrizesAsync_ExcludesDeletedPrizes()
{
// Arrange
await SeedTestQyLevelsWithPrizes();
// Mark one prize as deleted
var prize = await _dbContext.EquityLevelRewards.FindAsync(1);
prize!.DeletedAt = DateTime.Now;
await _dbContext.SaveChangesAsync();
var request = new QyLevelPrizeListRequest { Page = 1, PageSize = 10 };
// Act
var result = await _qyLevelService.GetQyLevelPrizesAsync(1, request);
// Assert
Assert.NotNull(result);
Assert.Equal(1, result.Total);
}
#endregion
#region CreateQyLevelPrizeAsync Tests
[Fact]
public async Task CreateQyLevelPrizeAsync_WithValidPhysicalPrize_CreatesPrize()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeCreateRequest
{
Type = 2,
Title = "测试实物奖品",
Value = 100,
ExchangePrice = 50,
ReferencePrice = 120,
Probability = 10.5m,
Image = "http://example.com/image.jpg"
};
// Act
var id = await _qyLevelService.CreateQyLevelPrizeAsync(1, request);
// Assert
Assert.True(id > 0);
var prize = await _dbContext.EquityLevelRewards.FindAsync(id);
Assert.NotNull(prize);
Assert.Equal(2, prize.Type);
Assert.Equal("测试实物奖品", prize.Title);
}
[Fact]
public async Task CreateQyLevelPrizeAsync_WithValidCouponPrize_CreatesPrize()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeCreateRequest
{
Type = 1,
CouponId = 1,
Quantity = 5
};
// Act
var id = await _qyLevelService.CreateQyLevelPrizeAsync(1, request);
// Assert
Assert.True(id > 0);
var prize = await _dbContext.EquityLevelRewards.FindAsync(id);
Assert.NotNull(prize);
Assert.Equal(1, prize.Type);
Assert.Equal(1, prize.CouponId);
Assert.Equal(5, prize.ZNum);
}
[Fact]
public async Task CreateQyLevelPrizeAsync_WithNonExistingLevel_ThrowsException()
{
// Arrange
var request = new QyLevelPrizeCreateRequest
{
Type = 2,
Title = "测试",
Value = 100,
ExchangePrice = 50,
ReferencePrice = 120,
Image = "http://example.com/image.jpg"
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.CreateQyLevelPrizeAsync(999, request));
Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
}
[Fact]
public async Task CreateQyLevelPrizeAsync_WithInvalidType_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeCreateRequest
{
Type = 3,
Title = "测试"
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.CreateQyLevelPrizeAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
[Fact]
public async Task CreateQyLevelPrizeAsync_WithCouponTypeButNoCouponId_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeCreateRequest
{
Type = 1,
Quantity = 5
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.CreateQyLevelPrizeAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
[Fact]
public async Task CreateQyLevelPrizeAsync_WithPhysicalTypeButNoTitle_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeCreateRequest
{
Type = 2,
Value = 100,
ExchangePrice = 50,
ReferencePrice = 120,
Image = "http://example.com/image.jpg"
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.CreateQyLevelPrizeAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
[Fact]
public async Task CreateQyLevelPrizeAsync_WithInvalidProbability_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeCreateRequest
{
Type = 2,
Title = "测试",
Value = 100,
ExchangePrice = 50,
ReferencePrice = 120,
Probability = 150, // Invalid: > 100
Image = "http://example.com/image.jpg"
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.CreateQyLevelPrizeAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
[Fact]
public async Task CreateQyLevelPrizeAsync_WithTooManyDecimalPlaces_ThrowsException()
{
// Arrange
await SeedTestQyLevels();
var request = new QyLevelPrizeCreateRequest
{
Type = 2,
Title = "测试",
Value = 100,
ExchangePrice = 50,
ReferencePrice = 120,
Probability = 10.123m, // Invalid: > 2 decimal places
Image = "http://example.com/image.jpg"
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.CreateQyLevelPrizeAsync(1, request));
Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
}
#endregion
#region UpdateQyLevelPrizeAsync Tests
[Fact]
public async Task UpdateQyLevelPrizeAsync_WithValidRequest_UpdatesPrize()
{
// Arrange
await SeedTestQyLevelsWithPrizes();
var request = new QyLevelPrizeUpdateRequest
{
Type = 2,
Title = "更新后的奖品",
Value = 200,
ExchangePrice = 100,
ReferencePrice = 250,
Image = "http://example.com/new-image.jpg"
};
// Act
var result = await _qyLevelService.UpdateQyLevelPrizeAsync(2, request);
// Assert
Assert.True(result);
var prize = await _dbContext.EquityLevelRewards.FindAsync(2);
Assert.NotNull(prize);
Assert.Equal("更新后的奖品", prize.Title);
Assert.Equal(200, prize.JiangPrice);
}
[Fact]
public async Task UpdateQyLevelPrizeAsync_WithNonExistingPrize_ThrowsException()
{
// Arrange
var request = new QyLevelPrizeUpdateRequest
{
Type = 2,
Title = "测试",
Value = 100,
ExchangePrice = 50,
ReferencePrice = 120,
Image = "http://example.com/image.jpg"
};
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.UpdateQyLevelPrizeAsync(999, request));
Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
}
#endregion
#region DeleteQyLevelPrizeAsync Tests
[Fact]
public async Task DeleteQyLevelPrizeAsync_WithExistingPrize_SoftDeletesPrize()
{
// Arrange
await SeedTestQyLevelsWithPrizes();
// Act
var result = await _qyLevelService.DeleteQyLevelPrizeAsync(1);
// Assert
Assert.True(result);
var prize = await _dbContext.EquityLevelRewards.FindAsync(1);
Assert.NotNull(prize);
Assert.NotNull(prize.DeletedAt);
}
[Fact]
public async Task DeleteQyLevelPrizeAsync_WithNonExistingPrize_ThrowsException()
{
// Act & Assert
var ex = await Assert.ThrowsAsync<BusinessException>(
() => _qyLevelService.DeleteQyLevelPrizeAsync(999));
Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
}
#endregion
#region Helper Methods
private async Task SeedTestQyLevels()
{
var levels = new List<EquityLevel>
{
new()
{
Id = 1,
Level = 1,
Title = "青铜会员",
Number = 100,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
},
new()
{
Id = 2,
Level = 2,
Title = "白银会员",
Number = 500,
CreatedAt = DateTime.Now.AddMinutes(-1),
UpdatedAt = DateTime.Now.AddMinutes(-1)
},
new()
{
Id = 3,
Level = 3,
Title = "黄金会员",
Number = 1000,
CreatedAt = DateTime.Now.AddMinutes(-2),
UpdatedAt = DateTime.Now.AddMinutes(-2)
}
};
_dbContext.EquityLevels.AddRange(levels);
await _dbContext.SaveChangesAsync();
}
private async Task SeedTestQyLevelsWithPrizes()
{
await SeedTestQyLevels();
var prizes = new List<EquityLevelReward>
{
new()
{
Id = 1,
EquityLevelId = 1,
Type = 1,
Title = "优惠券奖品",
CouponId = 1,
ZNum = 5,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
},
new()
{
Id = 2,
EquityLevelId = 1,
Type = 2,
Title = "实物奖品",
JiangPrice = 100,
Money = 50,
ScMoney = 120,
Probability = 10.5m,
ImgUrl = "http://example.com/image.jpg",
CreatedAt = DateTime.Now.AddMinutes(-1),
UpdatedAt = DateTime.Now.AddMinutes(-1)
}
};
_dbContext.EquityLevelRewards.AddRange(prizes);
await _dbContext.SaveChangesAsync();
}
#endregion
}