refactor: 清理 AI 客户端死代码并统一错误处理

- 删除未使用的 OpenAiCompatibleClient,DTO 抽离到 ai_contracts.cs
- 新增 AiHttpResponseGuard 统一 LLM/VLM 的 HTTP 错误处理:
  错误响应带响应体(截断 2000 字符)、空响应与无效 JSON 显式抛 InvalidDataException
- 空 catch 补全精确异常类型(IOException/UnauthorizedAccessException/JsonException)
- 修正 Program.cs 数据库初始化注释
- 新增 AI 客户端错误处理单元测试
This commit is contained in:
MingNian
2026-06-20 22:06:31 +08:00
parent 4d213b5a44
commit aa44d6f0f0
9 changed files with 182 additions and 256 deletions

View File

@@ -0,0 +1,50 @@
using System.Net;
using Health.Infrastructure.AI;
using Microsoft.Extensions.Configuration;
namespace Health.Tests;
public sealed class AiClientErrorTests
{
[Fact]
public async Task DeepSeek_ErrorResponse_IncludesStatusAndBody()
{
var client = CreateClient(HttpStatusCode.TooManyRequests, "{\"error\":\"rate limit\"}");
var error = await Assert.ThrowsAsync<HttpRequestException>(() => client.ChatAsync([]));
Assert.Contains("429", error.Message);
Assert.Contains("rate limit", error.Message);
}
[Fact]
public async Task DeepSeek_NullJsonResponse_ThrowsClearDataError()
{
var client = CreateClient(HttpStatusCode.OK, "null");
var error = await Assert.ThrowsAsync<InvalidDataException>(() => client.ChatAsync([]));
Assert.Contains("响应内容为空", error.Message);
}
private static DeepSeekClient CreateClient(HttpStatusCode statusCode, string body)
{
var http = new HttpClient(new StubHandler(statusCode, body))
{
BaseAddress = new Uri("https://example.test/"),
};
var config = new ConfigurationBuilder().AddInMemoryCollection(
new Dictionary<string, string?> { ["DEEPSEEK_MODEL"] = "test-model" }).Build();
return new DeepSeekClient(http, config);
}
private sealed class StubHandler(HttpStatusCode statusCode, string body) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
Task.FromResult(new HttpResponseMessage(statusCode)
{
Content = new StringContent(body),
RequestMessage = request,
});
}
}