using MiAssessment.Admin.Business.Models; using MiAssessment.Admin.Business.Models.User; using MiAssessment.Admin.Business.Services; using MiAssessment.Model.Data; using MiAssessment.Model.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace MiAssessment.Tests.Services; /// /// UserBusinessService 单元测试 /// public class UserBusinessServiceTests : IDisposable { private readonly MiAssessmentDbContext _dbContext; private readonly Mock> _mockLogger; private readonly UserBusinessService _userService; public UserBusinessServiceTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning)) .Options; _dbContext = new MiAssessmentDbContext(options); _mockLogger = new Mock>(); _userService = new UserBusinessService(_dbContext, _mockLogger.Object); } public void Dispose() { _dbContext.Dispose(); } #region Helper Methods private async Task CreateTestUserAsync( string nickname = "TestUser", decimal money = 100, decimal integral = 50, decimal score = 30, byte status = 1, int isTest = 0, int pid = 0) { var user = new User { OpenId = Guid.NewGuid().ToString("N"), Uid = $"UID{DateTime.Now.Ticks}", Nickname = nickname, HeadImg = "https://example.com/avatar.png", Mobile = "13800138000", Money = money, Integral = integral, Score = score, Status = status, IsTest = isTest, Pid = pid, Vip = 1, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; _dbContext.Users.Add(user); await _dbContext.SaveChangesAsync(); return user; } private async Task CreateTestCouponAsync() { var coupon = new Coupon { Title = "测试优惠券", Price = 10, ManPrice = 100, EffectiveDay = 30, Status = 1, CreatedAt = DateTime.Now }; _dbContext.Coupons.Add(coupon); await _dbContext.SaveChangesAsync(); return coupon; } private async Task<(Good goods, GoodsItem item)> CreateTestGoodsWithItemAsync() { var goods = new Good { Title = "测试盒子", Price = 10, ImgUrl = "https://example.com/goods.png", ImgUrlDetail = "https://example.com/goods_detail.png", Type = 1, Status = 1, Stock = 100, SaleStock = 0, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; _dbContext.Goods.Add(goods); await _dbContext.SaveChangesAsync(); var item = new GoodsItem { GoodsId = goods.Id, Title = "测试奖品", ImgUrl = "https://example.com/item.png", Price = 50, Money = 30, Stock = 10, SurplusStock = 10, Type = 1, ShangId = 1, UpdatedAt = DateTime.Now }; _dbContext.GoodsItems.Add(item); await _dbContext.SaveChangesAsync(); return (goods, item); } private async Task CreateTestVipLevelAsync() { var vipLevel = new VipLevel { Level = 1, Title = "VIP1", Number = 100, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; _dbContext.VipLevels.Add(vipLevel); await _dbContext.SaveChangesAsync(); return vipLevel; } #endregion #region GetUserListAsync Tests [Fact] public async Task GetUserListAsync_ReturnsPagedResult() { // Arrange await CreateTestUserAsync("User1"); await CreateTestUserAsync("User2"); await CreateTestUserAsync("User3"); var request = new UserListRequest { Page = 1, PageSize = 2 }; // Act var result = await _userService.GetUserListAsync(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 GetUserListAsync_WithNicknameFilter_ReturnsFilteredResult() { // Arrange await CreateTestUserAsync("Alice"); await CreateTestUserAsync("Bob"); await CreateTestUserAsync("AliceSmith"); var request = new UserListRequest { Nickname = "Alice" }; // Act var result = await _userService.GetUserListAsync(request); // Assert Assert.Equal(2, result.Total); Assert.All(result.List, u => Assert.Contains("Alice", u.Nickname!)); } [Fact] public async Task GetUserListAsync_WithParentIdFilter_ReturnsSubordinates() { // Arrange var parent = await CreateTestUserAsync("Parent"); await CreateTestUserAsync("Child1", pid: parent.Id); await CreateTestUserAsync("Child2", pid: parent.Id); await CreateTestUserAsync("Other"); var request = new UserListRequest { ParentId = parent.Id }; // Act var result = await _userService.GetUserListAsync(request); // Assert Assert.Equal(2, result.Total); Assert.All(result.List, u => Assert.Equal(parent.Id, u.ParentId)); } [Fact] public async Task GetUserListAsync_WithDateRangeFilter_ReturnsFilteredResult() { // Arrange var user1 = await CreateTestUserAsync("User1"); user1.CreatedAt = DateTime.Now.AddDays(-5); var user2 = await CreateTestUserAsync("User2"); user2.CreatedAt = DateTime.Now.AddDays(-1); await _dbContext.SaveChangesAsync(); var request = new UserListRequest { StartDate = DateTime.Now.AddDays(-3), EndDate = DateTime.Now }; // Act var result = await _userService.GetUserListAsync(request); // Assert Assert.Equal(1, result.Total); } #endregion #region GetUserDetailAsync Tests [Fact] public async Task GetUserDetailAsync_WithExistingUser_ReturnsDetail() { // Arrange var user = await CreateTestUserAsync("DetailUser", money: 200, integral: 100, score: 50); // Act var result = await _userService.GetUserDetailAsync(user.Id); // Assert Assert.NotNull(result); Assert.Equal(user.Id, result.Id); Assert.Equal("DetailUser", result.Nickname); Assert.Equal(200, result.Balance); Assert.Equal(100, result.Integral); Assert.Equal(50, result.Diamond); } [Fact] public async Task GetUserDetailAsync_WithNonExistingUser_ReturnsNull() { // Act var result = await _userService.GetUserDetailAsync(99999); // Assert Assert.Null(result); } #endregion #region ChangeUserMoneyAsync Tests [Fact] public async Task ChangeUserMoneyAsync_AddBalance_IncreasesBalance() { // Arrange var user = await CreateTestUserAsync(money: 100); var request = new UserMoneyChangeRequest { Type = MoneyChangeType.Balance, Amount = 50, Operation = OperationType.Add, Remark = "测试增加" }; // Act var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(150, updatedUser!.Money); // 验证记录了变动日志 var profitLog = await _dbContext.ProfitMoneys.FirstOrDefaultAsync(p => p.UserId == user.Id); Assert.NotNull(profitLog); Assert.Equal(50, profitLog.ChangeMoney); } [Fact] public async Task ChangeUserMoneyAsync_SubtractBalance_DecreasesBalance() { // Arrange var user = await CreateTestUserAsync(money: 100); var request = new UserMoneyChangeRequest { Type = MoneyChangeType.Balance, Amount = 30, Operation = OperationType.Subtract }; // Act var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(70, updatedUser!.Money); } [Fact] public async Task ChangeUserMoneyAsync_SubtractMoreThanBalance_ThrowsException() { // Arrange var user = await CreateTestUserAsync(money: 50); var request = new UserMoneyChangeRequest { Type = MoneyChangeType.Balance, Amount = 100, Operation = OperationType.Subtract }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.ChangeUserMoneyAsync(user.Id, request, 1)); Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code); Assert.Contains("不足", ex.Message); } [Fact] public async Task ChangeUserMoneyAsync_AddIntegral_IncreasesIntegral() { // Arrange var user = await CreateTestUserAsync(integral: 50); var request = new UserMoneyChangeRequest { Type = MoneyChangeType.Integral, Amount = 25, Operation = OperationType.Add }; // Act var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(75, updatedUser!.Integral); } [Fact] public async Task ChangeUserMoneyAsync_AddDiamond_IncreasesDiamond() { // Arrange var user = await CreateTestUserAsync(score: 30); var request = new UserMoneyChangeRequest { Type = MoneyChangeType.Diamond, Amount = 20, Operation = OperationType.Add }; // Act var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(50, updatedUser!.Score); } [Fact] public async Task ChangeUserMoneyAsync_NonExistingUser_ThrowsException() { // Arrange var request = new UserMoneyChangeRequest { Type = MoneyChangeType.Balance, Amount = 50, Operation = OperationType.Add }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.ChangeUserMoneyAsync(99999, request, 1)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region SetUserStatusAsync Tests [Fact] public async Task SetUserStatusAsync_BanUser_SetsStatusToDisabled() { // Arrange var user = await CreateTestUserAsync(status: 1); // Act var result = await _userService.SetUserStatusAsync(user.Id, 0); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(0, updatedUser!.Status); } [Fact] public async Task SetUserStatusAsync_UnbanUser_SetsStatusToEnabled() { // Arrange var user = await CreateTestUserAsync(status: 0); // Act var result = await _userService.SetUserStatusAsync(user.Id, 1); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(1, updatedUser!.Status); } [Fact] public async Task SetUserStatusAsync_NonExistingUser_ThrowsException() { // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.SetUserStatusAsync(99999, 0)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region SetTestAccountAsync Tests [Fact] public async Task SetTestAccountAsync_SetAsTest_UpdatesIsTest() { // Arrange var user = await CreateTestUserAsync(isTest: 0); // Act var result = await _userService.SetTestAccountAsync(user.Id, 1); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(1, updatedUser!.IsTest); } [Fact] public async Task SetTestAccountAsync_RemoveTest_UpdatesIsTest() { // Arrange var user = await CreateTestUserAsync(isTest: 1); // Act var result = await _userService.SetTestAccountAsync(user.Id, 0); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Equal(0, updatedUser!.IsTest); } #endregion #region ClearMobileAsync Tests [Fact] public async Task ClearMobileAsync_ClearsMobileAndSavesOld() { // Arrange var user = await CreateTestUserAsync(); var originalMobile = user.Mobile; // Act var result = await _userService.ClearMobileAsync(user.Id); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.Null(updatedUser!.Mobile); Assert.Equal(originalMobile, updatedUser.OldMobile); } #endregion #region ClearWeChatAsync Tests [Fact] public async Task ClearWeChatAsync_GeneratesNewOpenId() { // Arrange var user = await CreateTestUserAsync(); var originalOpenId = user.OpenId; // Act var result = await _userService.ClearWeChatAsync(user.Id); // Assert Assert.True(result); var updatedUser = await _dbContext.Users.FindAsync(user.Id); Assert.NotEqual(originalOpenId, updatedUser!.OpenId); Assert.StartsWith("cleared_", updatedUser.OpenId); Assert.Null(updatedUser.UnionId); Assert.Null(updatedUser.GzhOpenId); } #endregion #region GiftCouponAsync Tests [Fact] public async Task GiftCouponAsync_CreatesCouponReceiveRecords() { // Arrange var user = await CreateTestUserAsync(); var coupon = await CreateTestCouponAsync(); var request = new GiftCouponRequest { CouponId = coupon.Id, Quantity = 3 }; // Act var result = await _userService.GiftCouponAsync(user.Id, request); // Assert Assert.True(result); var receives = await _dbContext.CouponReceives .Where(r => r.UserId == user.Id && r.CouponId == coupon.Id) .ToListAsync(); Assert.Equal(3, receives.Count); Assert.All(receives, r => { Assert.Equal(coupon.Title, r.Title); Assert.Equal(coupon.Price, r.Price); Assert.Equal((byte)1, r.Status); Assert.Equal((byte?)0, r.State); }); } [Fact] public async Task GiftCouponAsync_NonExistingUser_ThrowsException() { // Arrange var coupon = await CreateTestCouponAsync(); var request = new GiftCouponRequest { CouponId = coupon.Id, Quantity = 1 }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.GiftCouponAsync(99999, request)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } [Fact] public async Task GiftCouponAsync_NonExistingCoupon_ThrowsException() { // Arrange var user = await CreateTestUserAsync(); var request = new GiftCouponRequest { CouponId = 99999, Quantity = 1 }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.GiftCouponAsync(user.Id, request)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region GiftCardAsync Tests [Fact] public async Task GiftCardAsync_CreatesOrderAndOrderItems() { // Arrange var user = await CreateTestUserAsync(); var (goods, item) = await CreateTestGoodsWithItemAsync(); var request = new GiftCardRequest { GoodsId = goods.Id, GoodsListId = item.Id, Quantity = 2 }; // Act var result = await _userService.GiftCardAsync(user.Id, request); // Assert Assert.True(result); // 验证订单 var order = await _dbContext.Orders .FirstOrDefaultAsync(o => o.UserId == user.Id && o.OrderType == 9); Assert.NotNull(order); Assert.Equal(0, order.Price); Assert.Equal(2, order.Num); // 验证订单详情 var orderItems = await _dbContext.OrderItems .Where(oi => oi.OrderId == order.Id) .ToListAsync(); Assert.Equal(2, orderItems.Count); Assert.All(orderItems, oi => { Assert.Equal(item.Title, oi.GoodslistTitle); Assert.Equal(9, oi.OrderType); Assert.NotNull(oi.PrizeCode); }); } [Fact] public async Task GiftCardAsync_NonExistingGoods_ThrowsException() { // Arrange var user = await CreateTestUserAsync(); var request = new GiftCardRequest { GoodsId = 99999, GoodsListId = 1, Quantity = 1 }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.GiftCardAsync(user.Id, request)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region VIP Management Tests [Fact] public async Task GetVipLevelsAsync_ReturnsAllLevels() { // Arrange await CreateTestVipLevelAsync(); var vip2 = new VipLevel { Level = 2, Title = "VIP2", Number = 500, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; _dbContext.VipLevels.Add(vip2); await _dbContext.SaveChangesAsync(); // Act var result = await _userService.GetVipLevelsAsync(); // Assert Assert.Equal(2, result.Count); Assert.Equal("VIP1", result[0].Title); Assert.Equal("VIP2", result[1].Title); } [Fact] public async Task UpdateVipLevelAsync_UpdatesLevel() { // Arrange var vipLevel = await CreateTestVipLevelAsync(); var request = new VipLevelUpdateRequest { Title = "Updated VIP", Number = 200 }; // Act var result = await _userService.UpdateVipLevelAsync(vipLevel.Id, request); // Assert Assert.True(result); var updated = await _dbContext.VipLevels.FindAsync(vipLevel.Id); Assert.Equal("Updated VIP", updated!.Title); Assert.Equal(200, updated.Number); } [Fact] public async Task UpdateVipLevelAsync_NonExistingLevel_ThrowsException() { // Arrange var request = new VipLevelUpdateRequest { Title = "Test", Number = 100 }; // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.UpdateVipLevelAsync(99999, request)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region GetSubordinateUsersAsync Tests [Fact] public async Task GetSubordinateUsersAsync_ReturnsSubordinates() { // Arrange var parent = await CreateTestUserAsync("Parent"); await CreateTestUserAsync("Child1", pid: parent.Id); await CreateTestUserAsync("Child2", pid: parent.Id); // Act var result = await _userService.GetSubordinateUsersAsync(parent.Id, 1, 10); // Assert Assert.Equal(2, result.Total); Assert.Equal(2, result.List.Count); Assert.All(result.List, u => Assert.Equal(parent.Id, u.ParentId)); } [Fact] public async Task GetSubordinateUsersAsync_NonExistingUser_ThrowsException() { // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.GetSubordinateUsersAsync(99999, 1, 10)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion #region GetUserProfitLossAsync Tests [Fact] public async Task GetUserProfitLossAsync_ReturnsStatistics() { // Arrange var user = await CreateTestUserAsync(money: 100, integral: 50, score: 30); // Act var result = await _userService.GetUserProfitLossAsync(user.Id, null); // Assert Assert.Equal(user.Id, result.UserId); Assert.Equal(100, result.CurrentBalance); Assert.Equal(50, result.CurrentIntegral); Assert.Equal(30, result.CurrentDiamond); } [Fact] public async Task GetUserProfitLossAsync_NonExistingUser_ThrowsException() { // Act & Assert var ex = await Assert.ThrowsAsync( () => _userService.GetUserProfitLossAsync(99999, null)); Assert.Equal(BusinessErrorCodes.NotFound, ex.Code); } #endregion }