Files
AI-Health/backend/tests/Health.Tests/ai_client_error_tests.cs
MingNian aa44d6f0f0 refactor: 清理 AI 客户端死代码并统一错误处理
- 删除未使用的 OpenAiCompatibleClient,DTO 抽离到 ai_contracts.cs
- 新增 AiHttpResponseGuard 统一 LLM/VLM 的 HTTP 错误处理:
  错误响应带响应体(截断 2000 字符)、空响应与无效 JSON 显式抛 InvalidDataException
- 空 catch 补全精确异常类型(IOException/UnauthorizedAccessException/JsonException)
- 修正 Program.cs 数据库初始化注释
- 新增 AI 客户端错误处理单元测试
2026-06-20 22:06:31 +08:00

51 lines
1.7 KiB
C#

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,
});
}
}