Files
AI-Health/backend/tests/Health.Tests/auth_tests.cs
MingNian 25a4078688 feat: 启用百度云 SMS 真实验证码发送
- SmsService: 从开发桩改为调用 BceSmsClient.SendAsync 真实发送
- AuthService.SendCodeAsync: 加 60 秒冷却 + 发送结果检查(失败返回 40010)
- Program.cs: 注册 BceSmsClient 到 DI(AddHttpClient + 10s 超时)
- auth_tests.cs: 加 CreateSmsService helper 适配构造注入

移动号实测可收到验证码,联通号运营商环节待报备
2026-07-28 15:01:00 +08:00

192 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using Health.Domain.Entities;
using Health.Infrastructure.Auth;
using Health.Infrastructure.Data;
using Health.Infrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
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();
}
private static SmsService CreateSmsService() =>
new(new BceSmsClient(new HttpClient(), new ConfigurationBuilder().Build(), NullLogger<BceSmsClient>.Instance),
NullLogger<SmsService>.Instance);
[Fact]
public async Task SendSms_Should_Create_VerificationCode()
{
// Arrange
using var db = CreateDbContext();
var sms = CreateSmsService();
// 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 Refresh_Should_Return_Stable_User_Identity()
{
using var db = CreateDbContext();
var config = CreateConfig();
var jwt = new JwtProvider(config);
var user = new User
{
Id = Guid.NewGuid(),
Phone = "13800138000",
Role = "User",
Name = "测试用户",
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
var oldRefresh = jwt.GenerateRefreshToken();
db.Users.Add(user);
db.RefreshTokens.Add(new RefreshToken
{
Id = Guid.NewGuid(),
UserId = user.Id,
Token = oldRefresh,
ExpiresAt = DateTime.UtcNow.AddDays(30)
});
await db.SaveChangesAsync();
var service = new AuthService(
db,
jwt,
CreateSmsService(),
new AppleTokenValidator(config));
var result = await service.RefreshAsync(oldRefresh, CancellationToken.None);
Assert.Equal(0, result.Code);
var data = JsonSerializer.SerializeToElement(result.Data);
var refreshedUser = data.GetProperty("user");
Assert.Equal(user.Id, refreshedUser.GetProperty("Id").GetGuid());
Assert.Equal(user.Phone, refreshedUser.GetProperty("Phone").GetString());
Assert.Equal(user.Role, refreshedUser.GetProperty("Role").GetString());
}
[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);
}
}