mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Services/AuthServiceLoginRecordPropertyTests.cs
zpc 21e8ff5372 refactor: 清理遗留实体和无效代码
- 删除无数据库表的实体: UserDetail, UserAddress, PaymentOrder, Admin, AdminLoginLog, AdminOperationLog, Picture, Delivery
- 删除关联服务: AddressService, PaymentService, PaymentOrderService, PaymentRewardDispatcher, DefaultPaymentRewardHandler
- 删除关联接口: IAddressService, IPaymentService, IPaymentOrderService, IPaymentRewardHandler, IPaymentRewardDispatcher
- 删除关联控制器: AddressController
- 删除关联DTO: AddressModels, CreatePaymentOrderRequest, PaymentOrderDto, PaymentOrderQueryRequest
- 删除关联测试: PaymentOrderServicePropertyTests, PaymentRewardDispatcherPropertyTests
- 修复实体字段映射: User, UserLoginLog, UserRefreshToken, Config, OrderNotify
- 更新 NotifyController 移除 IPaymentOrderService 依赖
- 更新 ServiceModule 移除已删除服务的DI注册
- 更新 MiAssessmentDbContext 移除已删除实体的DbSet和OnModelCreating配置
2026-02-20 20:29:34 +08:00

230 lines
8.1 KiB
C#

using FsCheck;
using FsCheck.Xunit;
using MiAssessment.Core.Interfaces;
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;
/// <summary>
/// AuthService登录记录和账号注销属性测试
/// 测试登录记录和账号注销相关的核心属性
/// </summary>
public class AuthServiceLoginRecordPropertyTests
{
private MiAssessmentDbContext CreateInMemoryDbContext()
{
var options = new DbContextOptionsBuilder<MiAssessmentDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.InMemoryEventId.TransactionIgnoredWarning))
.Options;
return new MiAssessmentDbContext(options);
}
private (AuthService authService, Mock<IIpLocationService> ipLocationMock) CreateAuthService(MiAssessmentDbContext dbContext)
{
var mockUserLogger = new Mock<ILogger<UserService>>();
var userService = new UserService(dbContext, mockUserLogger.Object);
var jwtSettings = new JwtSettings
{
Secret = "your-secret-key-must-be-at-least-32-characters-long-for-hs256",
Issuer = "MiAssessment",
Audience = "MiAssessmentUsers",
ExpirationMinutes = 1440,
RefreshTokenExpirationDays = 7
};
var mockJwtLogger = new Mock<ILogger<JwtService>>();
var jwtService = new JwtService(jwtSettings, mockJwtLogger.Object);
var mockWechatService = new Mock<IWechatService>();
var mockIpLocationService = new Mock<IIpLocationService>();
var mockRedisService = new Mock<IRedisService>();
var mockAuthLogger = new Mock<ILogger<AuthService>>();
var authService = new AuthService(
dbContext,
userService,
jwtService,
mockWechatService.Object,
mockIpLocationService.Object,
mockRedisService.Object,
jwtSettings,
mockAuthLogger.Object);
return (authService, mockIpLocationService);
}
/// <summary>
/// Property 15: 登录日志记录
/// For any successful login, the system should:
/// - Create a record in UserLoginLog table
/// - Update UserAccount table with last login time and IP info
/// Validates: Requirements 6.1, 6.3
/// Feature: user-auth-migration, Property 15: 登录日志记录
/// </summary>
[Property(MaxTest = 100)]
public async Task<bool> LoginLogRecording()
{
var dbContext = CreateInMemoryDbContext();
var (authService, ipLocationMock) = CreateAuthService(dbContext);
// Setup IP location mock
ipLocationMock.Setup(x => x.GetLocationAsync(It.IsAny<string>()))
.ReturnsAsync(new IpLocationResult
{
Success = true,
Province = "北京",
City = "北京",
Adcode = "110000"
});
// 创建用户
var user = new User
{
OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
Uid = "uid123",
Nickname = "TestUser",
Avatar = "https://example.com/avatar.jpg",
Status = 1,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
await dbContext.Users.AddAsync(user);
await dbContext.SaveChangesAsync();
var device = "iOS";
var clientIp = "192.168.1.1";
// Act
await authService.RecordLoginAsync(user.Id, device, clientIp);
// Assert
// 1. UserLoginLog 应该被创建
var loginLog = await dbContext.UserLoginLogs.FirstOrDefaultAsync(l => l.UserId == user.Id);
var logCreated = loginLog != null && loginLog.UserAgent == device;
// 2. 用户最后登录时间应该被更新
var updatedUser = await dbContext.Users.FirstOrDefaultAsync(u => u.Id == user.Id);
var userUpdated = updatedUser != null && updatedUser.LastLoginTime != null;
return logCreated && userUpdated;
}
/// <summary>
/// Property 16: recordLogin接口返回值
/// For any recordLogin call, the system should return:
/// - User's uid
/// - User's nickname
/// - User's headimg
/// Validates: Requirements 6.4
/// Feature: user-auth-migration, Property 16: recordLogin接口返回值
/// </summary>
[Property(MaxTest = 100)]
public async Task<bool> RecordLoginReturnValue()
{
var dbContext = CreateInMemoryDbContext();
var (authService, ipLocationMock) = CreateAuthService(dbContext);
ipLocationMock.Setup(x => x.GetLocationAsync(It.IsAny<string>()))
.ReturnsAsync(new IpLocationResult { Success = true });
var expectedUid = "uid_" + Random.Shared.Next(1000, 9999);
var expectedNickname = "TestUser_" + Random.Shared.Next(1000, 9999);
var expectedHeadimg = "https://example.com/avatar_" + Random.Shared.Next(1000, 9999) + ".jpg";
// 创建用户
var user = new User
{
OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
Uid = expectedUid,
Nickname = expectedNickname,
Avatar = expectedHeadimg,
Status = 1,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
await dbContext.Users.AddAsync(user);
await dbContext.SaveChangesAsync();
// Act
var result = await authService.RecordLoginAsync(user.Id, "Android", "192.168.1.1");
// Assert
return result.Uid == expectedUid &&
result.Nickname == expectedNickname &&
result.Headimg == expectedHeadimg;
}
/// <summary>
/// Property 17: 账号注销类型处理
/// For any log off request:
/// - type=0 should deactivate the account (status=0)
/// - type=1 should reactivate the account (status=1)
/// Validates: Requirements 7.1, 7.2, 7.3
/// Feature: user-auth-migration, Property 17: 账号注销类型处理
/// </summary>
[Property(MaxTest = 100)]
public async Task<bool> LogOffTypeHandling()
{
// Test type=0 (deactivate)
var dbContext1 = CreateInMemoryDbContext();
var (authService1, _) = CreateAuthService(dbContext1);
var user1 = new User
{
OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
Uid = "uid123",
Nickname = "TestUser",
Avatar = "https://example.com/avatar.jpg",
Status = 1, // Active
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
await dbContext1.Users.AddAsync(user1);
await dbContext1.SaveChangesAsync();
// Act: Deactivate account
await authService1.LogOffAsync(user1.Id, 0);
// Assert: Status should be 0
var deactivatedUser = await dbContext1.Users.FirstOrDefaultAsync(u => u.Id == user1.Id);
var deactivateSuccess = deactivatedUser?.Status == 0;
// Test type=1 (reactivate)
var dbContext2 = CreateInMemoryDbContext();
var (authService2, _) = CreateAuthService(dbContext2);
var user2 = new User
{
OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
Uid = "uid456",
Nickname = "TestUser2",
Avatar = "https://example.com/avatar.jpg",
Status = 0, // Inactive
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
await dbContext2.Users.AddAsync(user2);
await dbContext2.SaveChangesAsync();
// Act: Reactivate account
await authService2.LogOffAsync(user2.Id, 1);
// Assert: Status should be 1
var reactivatedUser = await dbContext2.Users.FirstOrDefaultAsync(u => u.Id == user2.Id);
var reactivateSuccess = reactivatedUser?.Status == 1;
return deactivateSuccess && reactivateSuccess;
}
}