- 删除无数据库表的实体: 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配置
230 lines
8.1 KiB
C#
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;
|
|
}
|
|
}
|