chore: 全面规范化代码,遵循 CLAUDE.md 编码规范
- C# 文件命名改为 snake_case(28 个文件重命名) - C# 类转换为主构造函数(8 个类) - 空 catch 添加异常类型(2 处) - 新建 GlobalUsings.cs(Health.Infrastructure、Health.WebApi) - Flutter 移除 go_router,改用 Riverpod 路由栈 - Flutter 移除 flutter_secure_storage,改用 sqflite 持久化 - 修复 Flutter 构建路径(Flutter SDK 迁至 D 盘) - 后端端口改为 0.0.0.0:5000,支持局域网访问
This commit is contained in:
143
backend/tests/Health.Tests/auth_tests.cs
Normal file
143
backend/tests/Health.Tests/auth_tests.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Infrastructure.Data;
|
||||
using Health.Infrastructure.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Health.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 认证流程测试
|
||||
/// </summary>
|
||||
public class AuthTests
|
||||
{
|
||||
private AppDbContext CreateDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
return new AppDbContext(options);
|
||||
}
|
||||
|
||||
private IConfiguration CreateConfig()
|
||||
{
|
||||
var settings = new Dictionary<string, string?>
|
||||
{
|
||||
{ "JWT_SECRET", "test-secret-key-for-unit-tests-min-32-chars!!" },
|
||||
{ "JWT_ISSUER", "health-manager" },
|
||||
{ "JWT_AUDIENCE", "health-manager-app" }
|
||||
};
|
||||
return new ConfigurationBuilder().AddInMemoryCollection(settings).Build();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendSms_Should_Create_VerificationCode()
|
||||
{
|
||||
// Arrange
|
||||
using var db = CreateDbContext();
|
||||
var sms = new SmsService();
|
||||
|
||||
// Act
|
||||
var code = sms.GenerateCode();
|
||||
db.VerificationCodes.Add(new VerificationCode
|
||||
{
|
||||
Id = Guid.NewGuid(), Phone = "13800138000", Code = code,
|
||||
ExpiresAt = DateTime.UtcNow.AddMinutes(5),
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Assert
|
||||
var saved = await db.VerificationCodes.FirstOrDefaultAsync(v => v.Phone == "13800138000");
|
||||
Assert.NotNull(saved);
|
||||
Assert.Equal(code, saved!.Code);
|
||||
Assert.True(saved.ExpiresAt > DateTime.UtcNow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_Should_Create_User_And_Return_Token()
|
||||
{
|
||||
// Arrange
|
||||
using var db = CreateDbContext();
|
||||
var config = CreateConfig();
|
||||
var jwt = new JwtProvider(config);
|
||||
|
||||
var phone = "13800138000";
|
||||
|
||||
// Act
|
||||
var user = new User { Id = Guid.NewGuid(), Phone = phone, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow };
|
||||
db.Users.Add(user);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, phone);
|
||||
var refreshToken = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken { Id = Guid.NewGuid(), UserId = user.Id, Token = refreshToken, ExpiresAt = DateTime.UtcNow.AddDays(30) });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(accessToken);
|
||||
Assert.True(accessToken.Length > 50);
|
||||
Assert.NotNull(refreshToken);
|
||||
Assert.True(refreshToken.Length > 50);
|
||||
|
||||
var savedUser = await db.Users.FirstOrDefaultAsync(u => u.Phone == phone);
|
||||
Assert.NotNull(savedUser);
|
||||
|
||||
var savedToken = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == refreshToken);
|
||||
Assert.NotNull(savedToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_Should_Revoke_Old_And_Issue_New()
|
||||
{
|
||||
// Arrange
|
||||
using var db = CreateDbContext();
|
||||
var config = CreateConfig();
|
||||
var jwt = new JwtProvider(config);
|
||||
|
||||
var user = new User { Id = Guid.NewGuid(), Phone = "13800138000", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow };
|
||||
db.Users.Add(user);
|
||||
|
||||
var oldRefresh = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken { Id = Guid.NewGuid(), UserId = user.Id, Token = oldRefresh, ExpiresAt = DateTime.UtcNow.AddDays(30) });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Act — 吊销旧 token,签发新 token
|
||||
var old = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == oldRefresh && !t.IsRevoked);
|
||||
Assert.NotNull(old);
|
||||
old!.IsRevoked = true;
|
||||
|
||||
var newToken = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken { Id = Guid.NewGuid(), UserId = user.Id, Token = newToken, ExpiresAt = DateTime.UtcNow.AddDays(30) });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Assert
|
||||
var revoked = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == oldRefresh);
|
||||
Assert.True(revoked!.IsRevoked);
|
||||
|
||||
var active = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == newToken && !t.IsRevoked);
|
||||
Assert.NotNull(active);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerificationCode_Expired_Should_Fail_Login()
|
||||
{
|
||||
// Arrange
|
||||
using var db = CreateDbContext();
|
||||
var expiredCode = new VerificationCode
|
||||
{
|
||||
Id = Guid.NewGuid(), Phone = "13800138000", Code = "123456",
|
||||
ExpiresAt = DateTime.UtcNow.AddMinutes(-1), // 已过期
|
||||
};
|
||||
db.VerificationCodes.Add(expiredCode);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var valid = await db.VerificationCodes
|
||||
.Where(v => v.Phone == "13800138000" && v.Code == "123456"
|
||||
&& v.ExpiresAt > DateTime.UtcNow && !v.IsUsed)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
// Assert — 过期的验证码查不到
|
||||
Assert.Null(valid);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user