Files
AI-Health/backend/tests/Health.Tests/auth_tests.cs
MingNian 9cea41705e feat: 文件存储安全加固 + 认证增强 + 媒体URL保护 + provider 重构
## 后端安全加固
- 新增 UserUploadPathResolver: 用户上传文件路径安全解析, 防目录穿越
- LocalReportFileStorage: 文件存储路径安全加固
- local_account_file_cleanup: 账号删除时文件清理逻辑增强
- AuthService: 认证逻辑增强
- file_endpoints / report_endpoints: 文件访问接口安全加固
- ai_chat_endpoints / doctor_endpoints: 接口安全调整
- Program.cs: 服务注册调整

## 前端认证与媒体
- 新增 authenticated_network_image.dart: 带认证的图片加载组件
- auth_provider: 认证状态管理大幅增强(+173)
- api_client: 网络客户端增强(+124)
- chat_provider: 聊天 provider 重构(+76)
- omron_device_provider: 蓝牙设备 provider 增强(+53)
- sse_handler: SSE 处理增强(+35)
- consultation_provider / data_providers / conversation_history_provider: 调整

## 页面调整
- remaining_pages: 健康档案/饮食记录等页面增强(+115)
- home_page / chat_messages_view: 主页微调
- doctor 端多页微调(consultations/dashboard/followups/patient_detail/profile/report_detail/reports)
- report_pages / settings_pages / notification_prefs_page: 微调
- device_scan_page / diet_capture_page / admin_home_page: 微调

## 测试
- 新增 file_path_security_tests: 文件路径安全测试
- 新增 protected_media_url_test: 媒体URL保护测试
- 新增 user_session_identity_test: 用户会话身份测试
- account_deletion_tests / application_service_tests / auth_tests: 更新
2026-07-20 10:19:01 +08:00

187 lines
6.4 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;
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 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,
new SmsService(),
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);
}
}