mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Services/UserServicePropertyTests.cs
2026-02-23 21:12:09 +08:00

190 lines
6.9 KiB
C#

using FsCheck;
using FsCheck.Xunit;
using MiAssessment.Core.Services;
using MiAssessment.Model.Data;
using MiAssessment.Model.Entities;
using MiAssessment.Model.Models.Auth;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace MiAssessment.Tests.Services;
public class UserServicePropertyTests
{
private MiAssessmentDbContext CreateInMemoryDbContext()
{
var options = new DbContextOptionsBuilder<MiAssessmentDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new MiAssessmentDbContext(options);
}
private UserService CreateUserService(MiAssessmentDbContext dbContext)
{
var mockLogger = new Mock<ILogger<UserService>>();
return new UserService(dbContext, mockLogger.Object);
}
/// <summary>
/// Property 2: 新用户创建完整性
/// For any valid CreateUserDto, the created user should have all required fields populated:
/// - Valid uid (non-empty)
/// - Non-empty nickname
/// - Valid headimg URL
/// - Correct pid if provided
/// Validates: Requirements 1.3, 2.3
/// </summary>
[Property(MaxTest = 100)]
public async Task<bool> CreatedUserHasAllRequiredFields()
{
var dbContext = CreateInMemoryDbContext();
var userService = CreateUserService(dbContext);
var createDto = new CreateUserDto
{
OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
Nickname = "TestUser_" + Random.Shared.Next(1000, 9999),
Headimg = "https://example.com/avatar.jpg",
Pid = 0,
ClickId = null
};
var createdUser = await userService.CreateUserAsync(createDto);
// Verify all required fields are populated
var hasValidUid = !string.IsNullOrWhiteSpace(createdUser.Uid);
var hasNickname = !string.IsNullOrWhiteSpace(createdUser.Nickname);
var hasHeadImg = !string.IsNullOrWhiteSpace(createdUser.HeadImg);
var hasCorrectPid = createdUser.Pid == 0;
return hasValidUid && hasNickname && hasHeadImg && hasCorrectPid;
}
/// <summary>
/// Property 9: 用户信息完整性
/// For any user, GetUserInfoAsync should return complete information including:
/// - All required fields (id, uid, nickname, headimg, mobile, mobileIs, vip, day)
/// - Masked mobile number in format XXX****XXXX
/// - Correct MobileIs flag (0 if no mobile, 1 if mobile exists)
/// Validates: Requirements 4.1, 4.4
/// </summary>
[Property(MaxTest = 100)]
public async Task<bool> UserInfoIsComplete(NonEmptyString openId)
{
var dbContext = CreateInMemoryDbContext();
var userService = CreateUserService(dbContext);
var createDto = new CreateUserDto
{
OpenId = openId.Item,
Nickname = "TestUser",
Headimg = "https://example.com/avatar.jpg",
Mobile = "13812345678",
Pid = 0
};
var createdUser = await userService.CreateUserAsync(createDto);
var userInfo = await userService.GetUserInfoAsync(createdUser.Id);
// Verify all required fields are present
var hasId = userInfo?.Id > 0;
var hasUid = !string.IsNullOrWhiteSpace(userInfo?.Uid);
var hasNickname = !string.IsNullOrWhiteSpace(userInfo?.Nickname);
var hasHeadimg = !string.IsNullOrWhiteSpace(userInfo?.Headimg);
var hasMobileInfo = userInfo?.MobileIs == 1;
var hasMaskedMobile = userInfo?.Mobile != null && userInfo.Mobile.Contains("****");
return hasId && hasUid && hasNickname && hasHeadimg && hasMobileInfo && hasMaskedMobile;
}
/// <summary>
/// Property 10: 昵称更新验证
/// For any user, updating with a valid (non-empty) nickname should succeed,
/// and updating with empty/whitespace nickname should not change the nickname.
/// Validates: Requirements 4.2
/// </summary>
[Property(MaxTest = 100)]
public async Task<bool> NicknameUpdateValidation()
{
var dbContext = CreateInMemoryDbContext();
var userService = CreateUserService(dbContext);
var createDto = new CreateUserDto
{
OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
Nickname = "OriginalNickname",
Headimg = "https://example.com/avatar.jpg",
Pid = 0
};
var createdUser = await userService.CreateUserAsync(createDto);
var originalNickname = createdUser.Nickname;
// Update with valid nickname
var newNickname = "UpdatedNickname_" + Random.Shared.Next(1000, 9999);
var updateDto = new UpdateUserDto
{
Nickname = newNickname
};
await userService.UpdateUserAsync(createdUser.Id, updateDto);
var updatedUser = await userService.GetUserByIdAsync(createdUser.Id);
// Verify nickname was updated
return updatedUser?.Nickname == newNickname && updatedUser.Nickname != originalNickname;
}
/// <summary>
/// Property 11: VIP等级计算
/// For any user with a given spending amount, CalculateVipLevelAsync should return
/// a VIP level that matches the spending threshold configured in VipLevel table.
/// Validates: Requirements 4.5
/// </summary>
[Property(MaxTest = 100)]
public async Task<bool> VipLevelCalculationIsCorrect(PositiveInt spendingAmount)
{
var dbContext = CreateInMemoryDbContext();
var userService = CreateUserService(dbContext);
// Setup VIP levels
var vipLevels = new[]
{
new VipLevel { Level = 1, Title = "Bronze", Number = 100, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow },
new VipLevel { Level = 2, Title = "Silver", Number = 500, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow },
new VipLevel { Level = 3, Title = "Gold", Number = 1000, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }
};
await dbContext.VipLevels.AddRangeAsync(vipLevels);
await dbContext.SaveChangesAsync();
// Create user with spending
var createDto = new CreateUserDto
{
OpenId = "openid123",
Nickname = "TestUser",
Headimg = "https://example.com/avatar.jpg",
Pid = 0
};
var createdUser = await userService.CreateUserAsync(createDto);
createdUser.Money = spendingAmount.Item;
dbContext.Users.Update(createdUser);
await dbContext.SaveChangesAsync();
// Calculate VIP level
var calculatedVip = await userService.CalculateVipLevelAsync(createdUser.Id, 0);
// Verify VIP level matches spending
int expectedVip = 0;
if (spendingAmount.Item >= 1000) expectedVip = 3;
else if (spendingAmount.Item >= 500) expectedVip = 2;
else if (spendingAmount.Item >= 100) expectedVip = 1;
return calculatedVip == expectedVip;
}
}