190 lines
6.9 KiB
C#
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;
|
|
}
|
|
}
|