tests: cover null responses

This commit is contained in:
Stevan Freeborn
2025-01-12 13:26:31 -06:00
parent 299af3b759
commit 876c6f571c
10 changed files with 370 additions and 17 deletions
@@ -1,7 +1,6 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using AnthropicClient.Json;
using AnthropicClient.Models;
@@ -1,4 +1,3 @@
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -1,4 +1,3 @@
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -1,5 +1,4 @@
using AnthropicClient.Tests.Files;
using AnthropicClient.Tests.Unit;
namespace AnthropicClient.Tests.Integration;
@@ -35,6 +34,51 @@ public class AnthropicApiClientTests : IntegrationTest
actualErrorType.Should().Be(errorType);
}
[Fact]
public async Task CreateMessageAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
{
_mockHttpMessageHandler
.WhenCreateMessageRequest()
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"null"
);
var request = new MessageRequest(
model: AnthropicModels.Claude3Haiku,
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
);
var result = await Client.CreateMessageAsync(request);
result.IsSuccess.Should().BeFalse();
result.Error.Should().BeOfType<AnthropicError>();
result.Error.Error.Should().BeOfType<ApiError>();
}
[Fact]
public async Task CreateMessageAsync_WhenCalledRequestSucceedsAndCanNotDeserializeResponse_ItShouldReturnEmptyResponse()
{
_mockHttpMessageHandler
.WhenCreateMessageRequest()
.Respond(
HttpStatusCode.OK,
"application/json",
@"null"
);
var request = new MessageRequest(
model: AnthropicModels.Claude3Haiku,
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
);
var result = await Client.CreateMessageAsync(request);
result.IsSuccess.Should().BeTrue();
result.Value.Should().BeEquivalentTo(new MessageResponse());
}
[Fact]
public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithTextContent_ItShouldReturnMessage()
{
@@ -363,6 +407,34 @@ public class AnthropicApiClientTests : IntegrationTest
));
}
[Fact]
public async Task CreateMessageAsync_WhenCalledMessageIsStreamedRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownErrorEvent()
{
_mockHttpMessageHandler
.WhenCreateStreamMessageRequest()
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"null"
);
var request = new StreamMessageRequest(
model: AnthropicModels.Claude35Sonnet,
messages: [
new(MessageRole.User, [new TextContent("Hello!")]),
new(MessageRole.User, [new TextContent("Hello!")])
]
);
var result = Client.CreateMessageAsync(request);
var events = await result.ToListAsync();
events.Should().HaveCount(1);
events[0].Type.Should().Be(EventType.Error);
events[0].Data.Should().BeOfType<ErrorEventData>();
events[0].Data.Should().BeEquivalentTo(new ErrorEventData(new ApiError()));
}
[Fact]
public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithDocumentContent_ItShouldReturnMessage()
{
@@ -489,7 +561,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"{}"
@"null"
);
var request = new CountMessageTokensRequest(
@@ -507,6 +579,31 @@ public class AnthropicApiClientTests : IntegrationTest
result.Error.Error.Should().BeOfType<ApiError>();
}
[Fact]
public async Task CountMessageTokensAsync_WhenCalledAndResponseCanNotBeDeserialized_ItShouldReturnEmptyResponse()
{
_mockHttpMessageHandler
.WhenCountMessageTokensRequest()
.Respond(
HttpStatusCode.OK,
"application/json",
@"null"
);
var request = new CountMessageTokensRequest(
model: AnthropicModels.Claude35Sonnet,
messages: [
new(MessageRole.User, [new TextContent("Hello!")]),
]
);
var result = await Client.CountMessageTokensAsync(request);
result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<TokenCountResponse>();
result.Value.InputTokens.Should().Be(0);
}
[Fact]
public async Task ListModelsAsync_WhenCalledWithPagingRequestUsingDefaultValues_ItShouldReturnListOfModels()
{
@@ -656,7 +753,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"{}"
@"null"
);
var result = await Client.ListModelsAsync();
@@ -666,6 +763,27 @@ public class AnthropicApiClientTests : IntegrationTest
result.Error.Error.Should().BeOfType<ApiError>();
}
[Fact]
public async Task ListModelAsync_WhenCalledRequestSucceedsAndCanNotDeserializeResponse_ItShouldReturnEmptyPage()
{
_mockHttpMessageHandler
.WhenListModelsRequest()
.Respond(
HttpStatusCode.OK,
"application/json",
@"null"
);
var result = await Client.ListModelsAsync();
result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<Page<AnthropicModel>>();
result.Value.HasMore.Should().BeFalse();
result.Value.FirstId.Should().BeEmpty();
result.Value.LastId.Should().BeEmpty();
result.Value.Data.Should().BeEmpty();
}
[Fact]
public async Task ListAllModelsAsync_WhenCalled_ItShouldReturnAllModels()
{
@@ -803,7 +921,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"{}"
@"null"
);
var responses = Client.ListAllModelsAsync();
@@ -889,6 +1007,35 @@ public class AnthropicApiClientTests : IntegrationTest
count.Should().Be(2);
}
[Fact]
public async Task ListAllModelsAsync_WhenFirstPageSucceedsButResponseCanNotBeDeserialized_ItShouldReturnEmptyPage()
{
_mockHttpMessageHandler
.WhenListModelsRequest()
.WithExactQueryString(new Dictionary<string, string>
{
{ "limit", "20" },
})
.Respond(
HttpStatusCode.OK,
"application/json",
@"null"
);
var responses = Client.ListAllModelsAsync();
var count = 0;
await foreach (var page in responses)
{
count++;
page.IsSuccess.Should().BeTrue();
page.Value.Should().BeOfType<Page<AnthropicModel>>();
page.Value.Data.Should().BeEmpty();
}
count.Should().Be(1);
}
[Fact]
public async Task GetModelAsync_WhenCalled_ItShouldReturnModel()
{
@@ -953,7 +1100,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"{}"
@"null"
);
var result = await Client.GetModelAsync(modelId);
@@ -973,7 +1120,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.OK,
"application/json",
@"{}"
@"null"
);
var result = await Client.GetModelAsync(modelId);
@@ -1070,7 +1217,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"{}"
@"null"
);
var request = new MessageBatchRequest([new("custom_id", new())]);
@@ -1090,7 +1237,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.OK,
"application/json",
@"{}"
@"null"
);
var request = new MessageBatchRequest([new("custom_id", new())]);
@@ -1193,7 +1340,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"{}"
@"null"
);
var result = await Client.GetMessageBatchAsync(batchId);
@@ -1213,7 +1360,7 @@ public class AnthropicApiClientTests : IntegrationTest
.Respond(
HttpStatusCode.OK,
"application/json",
@"{}"
@"null"
);
var result = await Client.GetMessageBatchAsync(batchId);
@@ -1247,4 +1394,76 @@ public class AnthropicApiClientTests : IntegrationTest
actualResults.Should().BeEquivalentTo(expectedResults);
}
[Fact]
public async Task GetMessageBatchResultsAsync_WhenCalledSuccessfulAndResultCanNotBeDeserialized_ItShouldReturnEmptyResults()
{
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
var expectedResults = new List<MessageBatchResultItem>()
{
new(),
};
_mockHttpMessageHandler
.WhenGetMessageBatchResultsRequest(batchId)
.Respond(
HttpStatusCode.OK,
"application/x-jsonl",
"null"
);
var result = await Client.GetMessageBatchResultsAsync(batchId);
result.IsSuccess.Should().BeTrue();
var actualResults = await result.Value.ToListAsync();
actualResults.Should().BeEquivalentTo(expectedResults);
}
[Fact]
public async Task GetMessageBatchResultsAsync_WhenCalledAndRequestFails_ItShouldReturnError()
{
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
_mockHttpMessageHandler
.WhenGetMessageBatchResultsRequest(batchId)
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"{
""type"": ""error"",
""error"": {
""type"": ""invalid_request_error"",
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
}
}"
);
var result = await Client.GetMessageBatchResultsAsync(batchId);
result.IsSuccess.Should().BeFalse();
result.Error.Should().BeOfType<AnthropicError>();
result.Error.Error.Should().BeOfType<InvalidRequestError>();
}
[Fact]
public async Task GetMessageBatchResultsAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
{
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
_mockHttpMessageHandler
.WhenGetMessageBatchResultsRequest(batchId)
.Respond(
HttpStatusCode.BadRequest,
"application/json",
@"null"
);
var result = await Client.GetMessageBatchResultsAsync(batchId);
result.IsSuccess.Should().BeFalse();
result.Error.Should().BeOfType<AnthropicError>();
result.Error.Error.Should().BeOfType<ApiError>();
}
}
@@ -0,0 +1,34 @@
namespace AnthropicClient.Tests.Unit.Models;
public class CanceledMessageBatchResultTests : SerializationTest
{
private const string SampleJson = @"{
""type"": ""canceled""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
{
var result = new CanceledMessageBatchResult();
result.Type.Should().Be(MessageBatchResultType.Canceled);
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var result = new CanceledMessageBatchResult();
var json = Serialize(result);
JsonAssert.Equal(SampleJson, json);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
{
var result = Deserialize<CanceledMessageBatchResult>(SampleJson);
result!.Type.Should().Be(MessageBatchResultType.Canceled);
}
}
@@ -0,0 +1,48 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ErroredMessageBatchResultTests : SerializationTest
{
private const string SampleJson = @"{
""type"": ""errored"",
""error"": {
""type"": ""error"",
""error"": {
""type"": ""api_error"",
""message"": ""An error occurred.""
}
}
}";
[Fact]
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
{
var result = new ErroredMessageBatchResult();
result.Type.Should().Be(MessageBatchResultType.Errored);
result.Error.Error.Should().BeOfType<ApiError>();
result.Error.Error.Message.Should().BeEmpty();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var result = new ErroredMessageBatchResult
{
Error = new(new ApiError("An error occurred."))
};
var json = Serialize(result);
JsonAssert.Equal(SampleJson, json);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
{
var result = Deserialize<ErroredMessageBatchResult>(SampleJson);
result!.Type.Should().Be(MessageBatchResultType.Errored);
result.Error.Error.Should().BeOfType<ApiError>();
result.Error.Error.Message.Should().Be("An error occurred.");
}
}
@@ -0,0 +1,34 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ExpiredMessageBatchResultTests : SerializationTest
{
private const string SampleJson = @"{
""type"": ""expired""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
{
var result = new ExpiredMessageBatchResult();
result.Type.Should().Be(MessageBatchResultType.Expired);
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var result = new ExpiredMessageBatchResult();
var json = Serialize(result);
JsonAssert.Equal(SampleJson, json);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
{
var result = Deserialize<ExpiredMessageBatchResult>(SampleJson);
result!.Type.Should().Be(MessageBatchResultType.Expired);
}
}
@@ -0,0 +1,25 @@
namespace AnthropicClient.Tests.Unit.Models;
public class MessageBatchResultTests : SerializationTest
{
[Fact]
public void JsonDeserialization_WhenHasUnknownType_ItShouldThrowException()
{
var json = @"{""type"":""unknown""}";
var action = () => Deserialize<MessageBatchResult>(json);
action.Should().Throw<JsonException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var expectedJson = @"{""type"":""expired""}";
var messageBatchResult = new ExpiredMessageBatchResult();
var json = Serialize<MessageBatchResult>(messageBatchResult);
JsonAssert.Equal(expectedJson, json);
}
}
@@ -1,5 +1,3 @@
using System.Text.Json.Nodes;
namespace AnthropicClient.Tests.Unit.Models;
public class ToolCallTests : SerializationTest
@@ -1,5 +1,3 @@
using AnthropicClient.Json;
namespace AnthropicClient.Tests.Unit;
public class SerializationTest