using Health.Domain.Entities; using Health.Infrastructure.Data; using Health.Infrastructure.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; namespace Health.Tests; /// /// 认证流程测试 /// public class AuthTests { private AppDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; return new AppDbContext(options); } private IConfiguration CreateConfig() { var settings = new Dictionary { { "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); } }