- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
77 lines
2.6 KiB
C#
77 lines
2.6 KiB
C#
using Health.Infrastructure.AI;
|
|
using Health.Infrastructure.Data;
|
|
using Health.Infrastructure.Data.Records;
|
|
using Health.Infrastructure.Reports;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Health.Tests;
|
|
|
|
public sealed class PersistencePipelineTests
|
|
{
|
|
[Fact]
|
|
public async Task AiConfirmation_CanOnlyBeTakenOnceByOwner()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var store = new EfAiWriteConfirmationStore(db);
|
|
var ownerId = Guid.NewGuid();
|
|
var command = await store.CreateAsync(ownerId, "record_health_data", "{}", TimeSpan.FromMinutes(10), CancellationToken.None);
|
|
|
|
var wrongUser = await store.TakeAsync(command.Id, Guid.NewGuid(), CancellationToken.None);
|
|
var firstTake = await store.TakeAsync(command.Id, ownerId, CancellationToken.None);
|
|
var secondTake = await store.TakeAsync(command.Id, ownerId, CancellationToken.None);
|
|
|
|
Assert.Null(wrongUser);
|
|
Assert.NotNull(firstTake);
|
|
Assert.Null(secondTake);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReportTask_RetryUsesBackoffBeforeMaxAttempts()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var task = NewReportTask(attempts: 1);
|
|
db.ReportAnalysisTasks.Add(task);
|
|
await db.SaveChangesAsync();
|
|
var queue = new EfReportAnalysisQueue(db);
|
|
var before = DateTime.UtcNow;
|
|
|
|
await queue.RetryAsync(task.Id, "temporary failure", CancellationToken.None);
|
|
|
|
Assert.Equal("Pending", task.Status);
|
|
Assert.True(task.AvailableAt > before);
|
|
Assert.Equal("temporary failure", task.LastError);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReportTask_RetryMarksFailedAtMaxAttempts()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var task = NewReportTask(attempts: 3);
|
|
db.ReportAnalysisTasks.Add(task);
|
|
await db.SaveChangesAsync();
|
|
var queue = new EfReportAnalysisQueue(db);
|
|
|
|
await queue.RetryAsync(task.Id, new string('x', 2500), CancellationToken.None);
|
|
|
|
Assert.Equal("Failed", task.Status);
|
|
Assert.Equal(2000, task.LastError!.Length);
|
|
}
|
|
|
|
private static ReportAnalysisTaskRecord NewReportTask(int attempts) => new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ReportId = Guid.NewGuid(),
|
|
FilePath = "report.jpg",
|
|
Status = "Processing",
|
|
Attempts = attempts,
|
|
AvailableAt = DateTime.UtcNow,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
};
|
|
|
|
private static AppDbContext CreateDbContext() => new(
|
|
new DbContextOptionsBuilder<AppDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options);
|
|
}
|