using MiAssessment.Admin.Business.Models; using MiAssessment.Admin.Business.Models.QyLevel; using MiAssessment.Admin.Business.Services; using MiAssessment.Model.Data; using MiAssessment.Model.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace MiAssessment.Tests.Services; /// /// QyLevelService 单元测试 /// public class QyLevelServiceTests : IDisposable { private readonly MiAssessmentDbContext _dbContext; private readonly Mock> _mockLogger; private readonly QyLevelService _qyLevelService; public QyLevelServiceTests() { // 使用 InMemory 数据库 var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _dbContext = new MiAssessmentDbContext(options); _mockLogger = new Mock>(); _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.RequiredPoints); } [Fact] public async Task UpdateQyLevelAsync_WithNonExistingLevel_ThrowsException() { // Arrange var request = new QyLevelUpdateRequest { Level = 1, Title = "测试", RequiredPoints = 100 }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _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( () => _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( () => _qyLevelService.UpdateQyLevelAsync(1, request)); Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code); } [Fact] public async Task UpdateQyLevelAsync_WithInvalidRequiredPoints_ThrowsException() { // Arrange await SeedTestQyLevels(); var request = new QyLevelUpdateRequest { Level = 1, Title = "测试", RequiredPoints = 0 }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _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( () => _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.EquityLevelPrizes.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.EquityLevelPrizes.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.EquityLevelPrizes.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( () => _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( () => _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( () => _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( () => _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( () => _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( () => _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.EquityLevelPrizes.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( () => _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.EquityLevelPrizes.FindAsync(1); Assert.NotNull(prize); Assert.NotNull(prize.DeletedAt); } [Fact] public async Task DeleteQyLevelPrizeAsync_WithNonExistingPrize_ThrowsException() { // Act & Assert var ex = await Assert.ThrowsAsync( () => _qyLevelService.DeleteQyLevelPrizeAsync(999)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region Helper Methods private async Task SeedTestQyLevels() { var levels = new List { new() { Id = 1, Level = 1, Title = "青铜会员", RequiredPoints = 100, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }, new() { Id = 2, Level = 2, Title = "白银会员", RequiredPoints = 500, CreatedAt = DateTime.Now.AddMinutes(-1), UpdatedAt = DateTime.Now.AddMinutes(-1) }, new() { Id = 3, Level = 3, Title = "黄金会员", RequiredPoints = 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 { new() { Id = 1, QyLevelId = 1, QyLevel = 1, Type = 1, Title = "优惠券奖品", CouponId = 1, ZNum = 5, Sort = 1, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }, new() { Id = 2, QyLevelId = 1, QyLevel = 1, Type = 2, Title = "实物奖品", JiangPrice = 100, Money = 50, ScMoney = 120, Probability = 10.5m, ImgUrl = "http://example.com/image.jpg", Sort = 2, CreatedAt = DateTime.Now.AddMinutes(-1), UpdatedAt = DateTime.Now.AddMinutes(-1) } }; _dbContext.EquityLevelPrizes.AddRange(prizes); await _dbContext.SaveChangesAsync(); } #endregion }