2025-01-09 23:17:41 -06:00
|
|
|
using AnthropicClient.Tests.Files;
|
|
|
|
|
|
2024-07-01 23:59:24 -05:00
|
|
|
namespace AnthropicClient.Tests.Integration;
|
|
|
|
|
|
|
|
|
|
public class AnthropicApiClientTests : IntegrationTest
|
|
|
|
|
{
|
|
|
|
|
[Theory]
|
|
|
|
|
[ClassData(typeof(ErrorTestData))]
|
2024-07-03 16:57:27 -05:00
|
|
|
public async Task CreateMessageAsync_WhenCalledAndErrorReturned_ItShouldHandleError(
|
2024-07-01 23:59:24 -05:00
|
|
|
HttpStatusCode statusCode,
|
|
|
|
|
string content,
|
|
|
|
|
Type errorType
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
statusCode,
|
|
|
|
|
"application/json",
|
|
|
|
|
content
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var request = new MessageRequest(
|
2024-07-01 23:59:24 -05:00
|
|
|
model: AnthropicModels.Claude3Haiku,
|
|
|
|
|
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var result = await Client.CreateMessageAsync(request);
|
2024-07-01 23:59:24 -05:00
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
2024-07-07 16:21:23 -05:00
|
|
|
|
2024-07-01 23:59:24 -05:00
|
|
|
var actualErrorType = result.Error.Error!.GetType();
|
|
|
|
|
actualErrorType.Should().Be(errorType);
|
|
|
|
|
}
|
2024-07-03 00:11:48 -05:00
|
|
|
|
2025-01-12 13:26:31 -06:00
|
|
|
[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());
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-03 00:11:48 -05:00
|
|
|
[Fact]
|
2024-07-03 16:57:27 -05:00
|
|
|
public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithTextContent_ItShouldReturnMessage()
|
2024-07-03 00:11:48 -05:00
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""content"": [
|
|
|
|
|
{
|
|
|
|
|
""text"": ""Hi! My name is Claude."",
|
|
|
|
|
""type"": ""text""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""id"": ""msg_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""model"": ""claude-3-5-sonnet-20240620"",
|
|
|
|
|
""role"": ""assistant"",
|
|
|
|
|
""stop_reason"": ""end_turn"",
|
|
|
|
|
""stop_sequence"": null,
|
|
|
|
|
""type"": ""message"",
|
|
|
|
|
""usage"": {
|
|
|
|
|
""input_tokens"": 10,
|
|
|
|
|
""output_tokens"": 25
|
|
|
|
|
}
|
2025-01-05 21:20:27 -06:00
|
|
|
}"
|
2024-07-03 00:11:48 -05:00
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var request = new MessageRequest(
|
2024-07-03 00:11:48 -05:00
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var result = await Client.CreateMessageAsync(request);
|
2024-07-03 00:11:48 -05:00
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
2024-07-03 16:57:27 -05:00
|
|
|
result.Value.Should().BeOfType<MessageResponse>();
|
2024-07-03 00:11:48 -05:00
|
|
|
|
|
|
|
|
var message = result.Value;
|
|
|
|
|
message.Id.Should().Be("msg_013Zva2CMHLNnXjNJJKqJ2EF");
|
|
|
|
|
message.Model.Should().Be("claude-3-5-sonnet-20240620");
|
|
|
|
|
message.Role.Should().Be("assistant");
|
|
|
|
|
message.StopReason.Should().Be("end_turn");
|
|
|
|
|
message.StopSequence.Should().BeNull();
|
|
|
|
|
message.Type.Should().Be("message");
|
|
|
|
|
message.Usage.InputTokens.Should().Be(10);
|
|
|
|
|
message.Usage.OutputTokens.Should().Be(25);
|
|
|
|
|
message.Content.Should().HaveCount(1);
|
|
|
|
|
message.ToolCall.Should().BeNull();
|
2024-07-07 16:21:23 -05:00
|
|
|
|
2024-07-03 00:11:48 -05:00
|
|
|
var textContent = message.Content[0];
|
|
|
|
|
textContent.Should().BeOfType<TextContent>();
|
|
|
|
|
textContent.As<TextContent>().Text.Should().Be("Hi! My name is Claude.");
|
|
|
|
|
textContent.As<TextContent>().Type.Should().Be("text");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2024-07-03 16:57:27 -05:00
|
|
|
public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithToolUseContentAndToolIsProvided_ItShouldReturnMessageWithToolCall()
|
2024-07-03 00:11:48 -05:00
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""content"": [
|
|
|
|
|
{
|
|
|
|
|
""id"": ""toolu_01D7FLrfh4GYq7yT1ULFeyMV"",
|
|
|
|
|
""name"": ""get_stock_price"",
|
|
|
|
|
""input"": { ""ticker"": ""^GSPC"" },
|
|
|
|
|
""type"": ""tool_use""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""id"": ""msg_01D7FLrfh4GYq7yT1ULFeyMV"",
|
|
|
|
|
""model"": ""claude-3-5-sonnet-20240620"",
|
|
|
|
|
""role"": ""assistant"",
|
|
|
|
|
""stop_reason"": ""end_turn"",
|
|
|
|
|
""stop_sequence"": null,
|
|
|
|
|
""type"": ""message"",
|
|
|
|
|
""usage"": {
|
|
|
|
|
""input_tokens"": 10,
|
|
|
|
|
""output_tokens"": 25
|
|
|
|
|
}
|
2025-01-05 21:20:27 -06:00
|
|
|
}"
|
2024-07-03 00:11:48 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var func = (string ticker) => ticker;
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var request = new MessageRequest(
|
2024-07-03 00:11:48 -05:00
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
|
|
|
|
],
|
|
|
|
|
tools: [
|
|
|
|
|
Tool.CreateFromFunction("get_stock_price", "Gets the stock price for a given ticker", func)
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var result = await Client.CreateMessageAsync(request);
|
2024-07-03 00:11:48 -05:00
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
2024-07-03 16:57:27 -05:00
|
|
|
result.Value.Should().BeOfType<MessageResponse>();
|
2024-07-03 00:11:48 -05:00
|
|
|
|
|
|
|
|
var message = result.Value;
|
|
|
|
|
message.Id.Should().Be("msg_01D7FLrfh4GYq7yT1ULFeyMV");
|
|
|
|
|
message.Model.Should().Be("claude-3-5-sonnet-20240620");
|
|
|
|
|
message.Role.Should().Be("assistant");
|
|
|
|
|
message.StopReason.Should().Be("end_turn");
|
|
|
|
|
message.StopSequence.Should().BeNull();
|
|
|
|
|
message.Type.Should().Be("message");
|
|
|
|
|
message.Usage.InputTokens.Should().Be(10);
|
|
|
|
|
message.Usage.OutputTokens.Should().Be(25);
|
|
|
|
|
message.Content.Should().HaveCount(1);
|
|
|
|
|
message.ToolCall.Should().NotBeNull();
|
|
|
|
|
|
|
|
|
|
var toolUseContent = message.Content[0];
|
|
|
|
|
toolUseContent.Should().BeOfType<ToolUseContent>();
|
|
|
|
|
|
|
|
|
|
toolUseContent.As<ToolUseContent>().Id.Should().Be("toolu_01D7FLrfh4GYq7yT1ULFeyMV");
|
|
|
|
|
toolUseContent.As<ToolUseContent>().Name.Should().Be("get_stock_price");
|
|
|
|
|
toolUseContent.As<ToolUseContent>().Type.Should().Be("tool_use");
|
|
|
|
|
|
|
|
|
|
var input = toolUseContent.As<ToolUseContent>().Input;
|
|
|
|
|
var ticker = input.GetValueOrDefault("ticker");
|
|
|
|
|
ticker!.ToString().Should().Be("^GSPC");
|
|
|
|
|
|
|
|
|
|
var toolCallResult = await message.ToolCall!.InvokeAsync();
|
|
|
|
|
toolCallResult.IsSuccess.Should().BeTrue();
|
|
|
|
|
toolCallResult.Value!.ToString().Should().Be("^GSPC");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2024-07-03 16:57:27 -05:00
|
|
|
public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithToolUseButNoToolProvided_ItShouldReturnMessageWithoutToolCall()
|
2024-07-03 00:11:48 -05:00
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""content"": [
|
|
|
|
|
{
|
|
|
|
|
""id"": ""toolu_01D7FLrfh4GYq7yT1ULFeyMV"",
|
|
|
|
|
""name"": ""get_stock_price"",
|
|
|
|
|
""input"": { ""ticker"": ""^GSPC"" },
|
|
|
|
|
""type"": ""tool_use""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""id"": ""msg_01D7FLrfh4GYq7yT1ULFeyMV"",
|
|
|
|
|
""model"": ""claude-3-5-sonnet-20240620"",
|
|
|
|
|
""role"": ""assistant"",
|
|
|
|
|
""stop_reason"": ""end_turn"",
|
|
|
|
|
""stop_sequence"": null,
|
|
|
|
|
""type"": ""message"",
|
|
|
|
|
""usage"": {
|
|
|
|
|
""input_tokens"": 10,
|
|
|
|
|
""output_tokens"": 25
|
|
|
|
|
}
|
2025-01-05 21:20:27 -06:00
|
|
|
}"
|
2024-07-03 00:11:48 -05:00
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var request = new MessageRequest(
|
2024-07-03 00:11:48 -05:00
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var result = await Client.CreateMessageAsync(request);
|
2024-07-03 00:11:48 -05:00
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
2024-07-03 16:57:27 -05:00
|
|
|
result.Value.Should().BeOfType<MessageResponse>();
|
2024-07-03 00:11:48 -05:00
|
|
|
|
|
|
|
|
var message = result.Value;
|
|
|
|
|
message.Id.Should().Be("msg_01D7FLrfh4GYq7yT1ULFeyMV");
|
|
|
|
|
message.Model.Should().Be("claude-3-5-sonnet-20240620");
|
|
|
|
|
message.Role.Should().Be("assistant");
|
|
|
|
|
message.StopReason.Should().Be("end_turn");
|
|
|
|
|
message.StopSequence.Should().BeNull();
|
|
|
|
|
message.Type.Should().Be("message");
|
|
|
|
|
message.Usage.InputTokens.Should().Be(10);
|
|
|
|
|
message.Usage.OutputTokens.Should().Be(25);
|
|
|
|
|
message.Content.Should().HaveCount(1);
|
|
|
|
|
message.ToolCall.Should().BeNull();
|
|
|
|
|
|
|
|
|
|
var toolUseContent = message.Content[0];
|
|
|
|
|
toolUseContent.Should().BeOfType<ToolUseContent>();
|
|
|
|
|
|
|
|
|
|
toolUseContent.As<ToolUseContent>().Id.Should().Be("toolu_01D7FLrfh4GYq7yT1ULFeyMV");
|
|
|
|
|
toolUseContent.As<ToolUseContent>().Name.Should().Be("get_stock_price");
|
|
|
|
|
toolUseContent.As<ToolUseContent>().Type.Should().Be("tool_use");
|
|
|
|
|
|
|
|
|
|
var input = toolUseContent.As<ToolUseContent>().Input;
|
|
|
|
|
var ticker = input.GetValueOrDefault("ticker");
|
|
|
|
|
ticker!.ToString().Should().Be("^GSPC");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
|
|
|
|
[ClassData(typeof(EventTestData))]
|
2024-07-03 16:57:27 -05:00
|
|
|
public async Task CreateMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldHandleAllEventTypes(string eventString, AnthropicEvent anthropicEvent)
|
2024-07-03 00:11:48 -05:00
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateStreamMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"text/event-stream",
|
|
|
|
|
new MemoryStream(Encoding.UTF8.GetBytes(eventString))
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var request = new StreamMessageRequest(
|
2024-07-03 00:11:48 -05:00
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var result = Client.CreateMessageAsync(request);
|
2024-07-03 00:11:48 -05:00
|
|
|
var e = await result.FirstOrDefaultAsync();
|
|
|
|
|
|
|
|
|
|
e.Should().BeEquivalentTo(anthropicEvent);
|
|
|
|
|
}
|
2024-07-03 13:58:58 -05:00
|
|
|
|
|
|
|
|
[Fact]
|
2024-07-03 16:57:27 -05:00
|
|
|
public async Task CreateMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldReturnAllExpectedEvents()
|
2024-07-03 13:58:58 -05:00
|
|
|
{
|
|
|
|
|
var eventStream = EventTestData.GetEventStream();
|
|
|
|
|
var events = EventTestData.GetAllEvents();
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateStreamMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"text/event-stream",
|
|
|
|
|
eventStream
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var request = new StreamMessageRequest(
|
2024-07-03 13:58:58 -05:00
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var result = Client.CreateMessageAsync(request);
|
2024-07-03 13:58:58 -05:00
|
|
|
|
|
|
|
|
var actualEvents = await result.ToListAsync();
|
|
|
|
|
|
|
|
|
|
actualEvents.Should().BeEquivalentTo(events);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2024-07-03 16:57:27 -05:00
|
|
|
public async Task CreateMessageAsync_WhenCalledMessageIsStreamedAndToolProvide_ItShouldReturnToolCall()
|
2024-07-03 13:58:58 -05:00
|
|
|
{
|
|
|
|
|
var eventStream = EventTestData.GetEventStream();
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateStreamMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"text/event-stream",
|
|
|
|
|
eventStream
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var getWeather = (string location, string unit) => $"The weather in {location} is 72°{unit}";
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var request = new StreamMessageRequest(
|
2024-07-03 13:58:58 -05:00
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
|
|
|
|
],
|
|
|
|
|
tools: [
|
|
|
|
|
Tool.CreateFromFunction("get_weather", "Gets the weather for a location", getWeather)
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-03 16:57:27 -05:00
|
|
|
var result = Client.CreateMessageAsync(request);
|
2024-07-03 13:58:58 -05:00
|
|
|
var msgCompleteEvent = await result
|
|
|
|
|
.Where(e => e.Type is EventType.MessageComplete)
|
|
|
|
|
.FirstAsync();
|
2024-07-07 16:21:23 -05:00
|
|
|
|
2024-07-03 13:58:58 -05:00
|
|
|
msgCompleteEvent.Data.Should().BeOfType<MessageCompleteEventData>();
|
2024-07-07 16:21:23 -05:00
|
|
|
|
2024-07-03 13:58:58 -05:00
|
|
|
var toolCall = msgCompleteEvent.Data.As<MessageCompleteEventData>().Message.ToolCall;
|
|
|
|
|
var toolCallResult = await toolCall!.InvokeAsync<string>();
|
|
|
|
|
|
|
|
|
|
toolCallResult.IsSuccess.Should().BeTrue();
|
2024-07-07 16:21:23 -05:00
|
|
|
toolCallResult.Value.Should().Be(getWeather("San Francisco, CA", "fahrenheit"));
|
2024-07-03 13:58:58 -05:00
|
|
|
}
|
2024-07-19 21:15:59 -05:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreateMessageAsync_WhenCalledMessageIsStreamAndRequestFails_ItShouldReturnErrorEvent()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateStreamMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
2025-01-05 21:20:27 -06:00
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
|
|
|
|
}
|
|
|
|
|
}"
|
2024-07-19 21:15:59 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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 InvalidRequestError(
|
|
|
|
|
"messages: roles must alternate between user and assistant, but found multiple user roles in a row"
|
|
|
|
|
)
|
|
|
|
|
));
|
|
|
|
|
}
|
2024-11-21 13:55:56 -06:00
|
|
|
|
2025-01-12 13:26:31 -06:00
|
|
|
[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()));
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-21 13:59:45 -06:00
|
|
|
[Fact]
|
2024-11-21 13:55:56 -06:00
|
|
|
public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithDocumentContent_ItShouldReturnMessage()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""content"": [
|
|
|
|
|
{
|
|
|
|
|
""text"": ""It is a PDF"",
|
|
|
|
|
""type"": ""text""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""id"": ""msg_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""model"": ""claude-3-5-sonnet-20240620"",
|
|
|
|
|
""role"": ""assistant"",
|
|
|
|
|
""stop_reason"": ""end_turn"",
|
|
|
|
|
""stop_sequence"": null,
|
|
|
|
|
""type"": ""message"",
|
|
|
|
|
""usage"": {
|
|
|
|
|
""input_tokens"": 10,
|
|
|
|
|
""output_tokens"": 25
|
|
|
|
|
}
|
2025-01-05 21:20:27 -06:00
|
|
|
}"
|
2024-11-21 13:55:56 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var request = new MessageRequest(
|
|
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("What is this?")]),
|
|
|
|
|
new(MessageRole.User, [new DocumentContent("application/pdf", "data")])
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CreateMessageAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<MessageResponse>();
|
|
|
|
|
|
|
|
|
|
var message = result.Value;
|
|
|
|
|
message.Id.Should().Be("msg_013Zva2CMHLNnXjNJJKqJ2EF");
|
|
|
|
|
message.Model.Should().Be("claude-3-5-sonnet-20240620");
|
|
|
|
|
message.Role.Should().Be("assistant");
|
|
|
|
|
message.StopReason.Should().Be("end_turn");
|
|
|
|
|
message.StopSequence.Should().BeNull();
|
|
|
|
|
message.Type.Should().Be("message");
|
|
|
|
|
message.Usage.InputTokens.Should().Be(10);
|
|
|
|
|
message.Usage.OutputTokens.Should().Be(25);
|
|
|
|
|
message.Content.Should().HaveCount(1);
|
|
|
|
|
message.ToolCall.Should().BeNull();
|
|
|
|
|
|
|
|
|
|
var textContent = message.Content[0];
|
|
|
|
|
textContent.Should().BeOfType<TextContent>();
|
|
|
|
|
textContent.As<TextContent>().Text.Should().Be("It is a PDF");
|
|
|
|
|
textContent.As<TextContent>().Type.Should().Be("text");
|
|
|
|
|
}
|
2025-01-02 20:41:12 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CountMessageTokensAsync_WhenCalled_ItShouldReturnCountTokensResponse()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCountMessageTokensRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
2025-01-05 21:20:27 -06:00
|
|
|
""input_tokens"": 10
|
|
|
|
|
}"
|
2025-01-02 20:41:12 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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(10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CountMessageTokensAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCountMessageTokensRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
2025-01-05 21:20:27 -06:00
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
|
|
|
|
}
|
|
|
|
|
}"
|
2025-01-02 20:41:12 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var request = new CountMessageTokensRequest(
|
|
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")])
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CountMessageTokensAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2025-01-05 21:36:34 -06:00
|
|
|
public async Task CountMessageTokensAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
|
2025-01-02 20:41:12 -06:00
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCountMessageTokensRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-02 20:41:12 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var request = new CountMessageTokensRequest(
|
|
|
|
|
model: AnthropicModels.Claude35Sonnet,
|
|
|
|
|
messages: [
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
|
|
|
|
new(MessageRole.User, [new TextContent("Hello!")])
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CountMessageTokensAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
2025-01-05 21:20:27 -06:00
|
|
|
|
2025-01-12 13:26:31 -06:00
|
|
|
[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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-05 21:20:27 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task ListModelsAsync_WhenCalledWithPagingRequestUsingDefaultValues_ItShouldReturnListOfModels()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""type"": ""model"",
|
|
|
|
|
""id"": ""claude-3-5-sonnet-20241022"",
|
|
|
|
|
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
|
|
|
|
""created_at"": ""2024-10-22T00:00:00Z""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""first_id"",
|
|
|
|
|
""last_id"": ""last_id""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListModelsAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<AnthropicModel>>();
|
|
|
|
|
result.Value.HasMore.Should().BeTrue();
|
|
|
|
|
result.Value.FirstId.Should().Be("first_id");
|
|
|
|
|
result.Value.LastId.Should().Be("last_id");
|
|
|
|
|
result.Value.Data.Should().BeEquivalentTo(new AnthropicModel[]
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Type = "model",
|
|
|
|
|
Id = "claude-3-5-sonnet-20241022",
|
|
|
|
|
DisplayName = "Claude 3.5 Sonnet (New)",
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-10-22T00:00:00Z")
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListModelsAsync_WhenCalledWithPagingRequestUsingCustomValues_ItShouldReturnListOfModels()
|
|
|
|
|
{
|
2025-01-05 21:55:53 -06:00
|
|
|
var pagingRequest = new PagingRequest(afterId: "next_id", limit: 10);
|
2025-01-05 21:20:27 -06:00
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.WithQueryString(new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
{ "after_id", pagingRequest.AfterId },
|
|
|
|
|
{ "limit", pagingRequest.Limit.ToString() },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""type"": ""model"",
|
|
|
|
|
""id"": ""claude-3-5-sonnet-20241022"",
|
|
|
|
|
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
|
|
|
|
""created_at"": ""2024-10-22T00:00:00Z""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""first_id"",
|
|
|
|
|
""last_id"": ""last_id""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListModelsAsync(pagingRequest);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<AnthropicModel>>();
|
|
|
|
|
result.Value.HasMore.Should().BeTrue();
|
|
|
|
|
result.Value.FirstId.Should().Be("first_id");
|
|
|
|
|
result.Value.LastId.Should().Be("last_id");
|
|
|
|
|
result.Value.Data.Should().BeEquivalentTo(new AnthropicModel[]
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Type = "model",
|
|
|
|
|
Id = "claude-3-5-sonnet-20241022",
|
|
|
|
|
DisplayName = "Claude 3.5 Sonnet (New)",
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-10-22T00:00:00Z")
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListModelsAsync_WhenCalledAndNoModelsReturned_ItShouldReturnEmptyList()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [],
|
|
|
|
|
""has_more"": false,
|
|
|
|
|
""first_id"": null,
|
|
|
|
|
""last_id"": 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().BeNull();
|
|
|
|
|
result.Value.LastId.Should().BeNull();
|
|
|
|
|
result.Value.Data.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListModelsAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.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.ListModelsAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2025-01-05 21:36:34 -06:00
|
|
|
public async Task ListModelsAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
|
2025-01-05 21:20:27 -06:00
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-05 21:20:27 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListModelsAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-12 13:26:31 -06:00
|
|
|
[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();
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-05 21:20:27 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task ListAllModelsAsync_WhenCalled_ItShouldReturnAllModels()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""type"": ""model"",
|
|
|
|
|
""id"": ""claude-3-5-sonnet-20241022"",
|
|
|
|
|
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
|
|
|
|
""created_at"": ""2024-10-22T00:00:00Z""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "after_id", "1" },
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""type"": ""model"",
|
|
|
|
|
""id"": ""claude-3-5-sonnet-20241023"",
|
|
|
|
|
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
|
|
|
|
""created_at"": ""2024-10-23T00:00:00Z""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": false,
|
|
|
|
|
""first_id"": ""2"",
|
|
|
|
|
""last_id"": ""2""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var pageResponses = Client.ListAllModelsAsync();
|
|
|
|
|
var collectedPages = new List<Page<AnthropicModel>>();
|
|
|
|
|
|
|
|
|
|
await foreach (var response in pageResponses)
|
|
|
|
|
{
|
|
|
|
|
response.IsSuccess.Should().BeTrue();
|
|
|
|
|
response.Value.Should().BeOfType<Page<AnthropicModel>>();
|
|
|
|
|
collectedPages.Add(response.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
collectedPages.Should().HaveCount(2);
|
|
|
|
|
collectedPages.Should().BeEquivalentTo(new Page<AnthropicModel>[]
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
HasMore = true,
|
|
|
|
|
FirstId = "1",
|
|
|
|
|
LastId = "1",
|
|
|
|
|
Data = [
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Type = "model",
|
|
|
|
|
Id = "claude-3-5-sonnet-20241022",
|
|
|
|
|
DisplayName = "Claude 3.5 Sonnet (New)",
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-10-22T00:00:00Z")
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
HasMore = false,
|
|
|
|
|
FirstId = "2",
|
|
|
|
|
LastId = "2",
|
|
|
|
|
Data = [
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Type = "model",
|
|
|
|
|
Id = "claude-3-5-sonnet-20241023",
|
|
|
|
|
DisplayName = "Claude 3.5 Sonnet (New)",
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-10-23T00:00:00Z")
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListAllModelsAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.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 responses = Client.ListAllModelsAsync();
|
|
|
|
|
var count = 0;
|
|
|
|
|
|
|
|
|
|
await foreach (var page in responses)
|
|
|
|
|
{
|
|
|
|
|
count++;
|
|
|
|
|
page.IsSuccess.Should().BeFalse();
|
|
|
|
|
page.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
page.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count.Should().Be(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2025-01-05 21:36:34 -06:00
|
|
|
public async Task ListAllModelsAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
|
2025-01-05 21:20:27 -06:00
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-05 21:20:27 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var responses = Client.ListAllModelsAsync();
|
|
|
|
|
var count = 0;
|
|
|
|
|
|
|
|
|
|
await foreach (var page in responses)
|
|
|
|
|
{
|
|
|
|
|
count++;
|
|
|
|
|
page.IsSuccess.Should().BeFalse();
|
|
|
|
|
page.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
page.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count.Should().Be(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListAllModelsAsync_WhenFirstPageSucceedsAndSecondPageFails_ItShouldReturnFirstPageAndError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""type"": ""model"",
|
|
|
|
|
""id"": ""claude-3-5-sonnet-20241022"",
|
|
|
|
|
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
|
|
|
|
""created_at"": ""2024-10-22T00:00:00Z""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListModelsRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
{ "after_id", "1" },
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.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 responses = Client.ListAllModelsAsync();
|
|
|
|
|
var count = 0;
|
|
|
|
|
|
|
|
|
|
await foreach (var page in responses)
|
|
|
|
|
{
|
|
|
|
|
count++;
|
|
|
|
|
|
|
|
|
|
if (count == 1)
|
|
|
|
|
{
|
|
|
|
|
page.IsSuccess.Should().BeTrue();
|
|
|
|
|
page.Value.Should().BeOfType<Page<AnthropicModel>>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
page.IsSuccess.Should().BeFalse();
|
|
|
|
|
page.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
page.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count.Should().Be(2);
|
|
|
|
|
}
|
2025-01-05 21:36:34 -06:00
|
|
|
|
2025-01-12 13:26:31 -06:00
|
|
|
[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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-05 21:36:34 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetModelAsync_WhenCalled_ItShouldReturnModel()
|
|
|
|
|
{
|
|
|
|
|
var modelId = "claude-3-5-sonnet-20241022";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetModelRequest(modelId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""type"": ""model"",
|
|
|
|
|
""id"": ""claude-3-5-sonnet-20241022"",
|
|
|
|
|
""display_name"": ""Claude 3.5 Sonnet (New)"",
|
|
|
|
|
""created_at"": ""2024-10-22T00:00:00Z""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetModelAsync(modelId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<AnthropicModel>();
|
|
|
|
|
result.Value.Type.Should().Be("model");
|
|
|
|
|
result.Value.Id.Should().Be("claude-3-5-sonnet-20241022");
|
|
|
|
|
result.Value.DisplayName.Should().Be("Claude 3.5 Sonnet (New)");
|
|
|
|
|
result.Value.CreatedAt.Should().Be(DateTimeOffset.Parse("2024-10-22T00:00:00Z"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetModelAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
|
|
|
|
{
|
|
|
|
|
var modelId = "claude-3-5-sonnet-20241022";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetModelRequest(modelId)
|
|
|
|
|
.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.GetModelAsync(modelId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetModelAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
|
|
|
|
|
{
|
|
|
|
|
var modelId = "claude-3-5-sonnet-20241022";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetModelRequest(modelId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-05 21:36:34 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetModelAsync(modelId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetModelAsync_WhenCalledAndCanNotDeserializeModel_ItShouldReturnEmptyModel()
|
|
|
|
|
{
|
|
|
|
|
var modelId = "claude-3-5-sonnet-20241022";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetModelRequest(modelId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-05 21:36:34 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetModelAsync(modelId);
|
|
|
|
|
|
2025-01-05 21:37:50 -06:00
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<AnthropicModel>();
|
2025-01-05 21:36:34 -06:00
|
|
|
}
|
2025-01-08 20:37:58 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreateMessageBatchAsync_WhenCalled_ItShouldReturnBatch()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageBatchRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch"",
|
|
|
|
|
""processing_status"": ""in_progress"",
|
|
|
|
|
""request_counts"": {
|
|
|
|
|
""processing"": 100,
|
|
|
|
|
""succeeded"": 50,
|
|
|
|
|
""errored"": 30,
|
|
|
|
|
""canceled"": 10,
|
|
|
|
|
""expired"": 10
|
|
|
|
|
},
|
|
|
|
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var request = new MessageBatchRequest([new("custom_id", new())]);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CreateMessageBatchAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<MessageBatchResponse>();
|
|
|
|
|
result.Value.Id.Should().Be("msgbatch_013Zva2CMHLNnXjNJJKqJ2EF");
|
|
|
|
|
result.Value.Type.Should().Be("message_batch");
|
|
|
|
|
result.Value.ProcessingStatus.Should().Be("in_progress");
|
|
|
|
|
result.Value.RequestCounts.Should().BeEquivalentTo(new MessageBatchRequestCounts
|
|
|
|
|
{
|
|
|
|
|
Processing = 100,
|
|
|
|
|
Succeeded = 50,
|
|
|
|
|
Errored = 30,
|
|
|
|
|
Canceled = 10,
|
|
|
|
|
Expired = 10
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
result.Value.EndedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
|
|
|
|
|
result.Value.CreatedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
|
|
|
|
|
result.Value.ExpiresAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
|
|
|
|
|
result.Value.ArchivedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
|
|
|
|
|
result.Value.CancelInitiatedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
|
|
|
|
|
result.Value.ResultsUrl.Should().Be("https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results");
|
|
|
|
|
}
|
2025-01-09 23:17:41 -06:00
|
|
|
|
2025-01-08 22:33:09 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task CreateMessageBatchAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageBatchRequest()
|
|
|
|
|
.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 request = new MessageBatchRequest([new("custom_id", new())]);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CreateMessageBatchAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
2025-01-09 23:17:41 -06:00
|
|
|
|
2025-01-08 22:33:09 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task CreateMessageBatchAsync_WhenCalledRequestFailsAndErrorCanNotBeDeserialized_ItShouldReturnUnknownError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageBatchRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-08 22:33:09 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var request = new MessageBatchRequest([new("custom_id", new())]);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CreateMessageBatchAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
2025-01-09 23:17:41 -06:00
|
|
|
|
2025-01-08 22:33:09 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task CreateMessageBatchAsync_WhenCalledRequestSucceedsAndCanNotDeserializeResponse_ItShouldReturnEmptyResponse()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateMessageBatchRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-08 22:33:09 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var request = new MessageBatchRequest([new("custom_id", new())]);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CreateMessageBatchAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<MessageBatchResponse>();
|
|
|
|
|
result.Value.Should().BeEquivalentTo(new MessageBatchResponse());
|
|
|
|
|
}
|
2025-01-08 23:15:24 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetMessageBatchAsync_WhenCalled_ItShouldReturnBatch()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetMessageBatchRequest(batchId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch"",
|
|
|
|
|
""processing_status"": ""in_progress"",
|
|
|
|
|
""request_counts"": {
|
|
|
|
|
""processing"": 100,
|
|
|
|
|
""succeeded"": 50,
|
|
|
|
|
""errored"": 30,
|
|
|
|
|
""canceled"": 10,
|
|
|
|
|
""expired"": 10
|
|
|
|
|
},
|
|
|
|
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetMessageBatchAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
2025-01-13 13:11:47 -06:00
|
|
|
result.Value.Should().BeEquivalentTo(new MessageBatchResponse()
|
2025-01-08 23:15:24 -06:00
|
|
|
{
|
2025-01-13 13:11:47 -06:00
|
|
|
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
Type = "message_batch",
|
|
|
|
|
ProcessingStatus = "in_progress",
|
|
|
|
|
RequestCounts = new MessageBatchRequestCounts
|
|
|
|
|
{
|
|
|
|
|
Processing = 100,
|
|
|
|
|
Succeeded = 50,
|
|
|
|
|
Errored = 30,
|
|
|
|
|
Canceled = 10,
|
|
|
|
|
Expired = 10
|
|
|
|
|
},
|
|
|
|
|
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
2025-01-08 23:15:24 -06:00
|
|
|
});
|
|
|
|
|
}
|
2025-01-09 23:17:41 -06:00
|
|
|
|
2025-01-08 23:15:24 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetMessageBatchAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetMessageBatchRequest(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.GetMessageBatchAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
2025-01-09 23:17:41 -06:00
|
|
|
|
2025-01-08 23:15:24 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetMessageBatchAsync_WhenCalledRequestFailsAndErrorCanNotBeDeserialized_ItShouldReturnUnknownError()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetMessageBatchRequest(batchId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-08 23:15:24 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetMessageBatchAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
2025-01-09 23:17:41 -06:00
|
|
|
|
2025-01-08 23:15:24 -06:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetMessageBatchAsync_WhenCalledAndCanNotDeserializeResponse_ItShouldReturnEmptyResponse()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetMessageBatchRequest(batchId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
2025-01-12 13:26:31 -06:00
|
|
|
@"null"
|
2025-01-08 23:15:24 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetMessageBatchAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<MessageBatchResponse>();
|
|
|
|
|
}
|
2025-01-09 23:17:41 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetMessageBatchResultsAsync_WhenCalledAndSuccessful_ItShouldReturnAllBatchResults()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
var batchResultsPath = TestFileHelper.GetTestFilePath("batch_results.jsonl");
|
|
|
|
|
var batchResultsText = await File.ReadAllTextAsync(batchResultsPath);
|
|
|
|
|
var batchResults = batchResultsText.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
var expectedResults = batchResults.Select(Deserialize<MessageBatchResultItem>);
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetMessageBatchResultsRequest(batchId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/x-jsonl",
|
|
|
|
|
batchResultsText
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetMessageBatchResultsAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
|
|
|
|
|
var actualResults = await result.Value.ToListAsync();
|
|
|
|
|
|
|
|
|
|
actualResults.Should().BeEquivalentTo(expectedResults);
|
|
|
|
|
}
|
2025-01-12 13:26:31 -06:00
|
|
|
|
|
|
|
|
[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>();
|
|
|
|
|
}
|
2025-01-12 13:46:05 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListMessageBatchesAsync_WhenCalledAndSuccessful_ItShouldReturnPageOfBatches()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch"",
|
|
|
|
|
""processing_status"": ""in_progress"",
|
|
|
|
|
""request_counts"": {
|
|
|
|
|
""processing"": 100,
|
|
|
|
|
""succeeded"": 50,
|
|
|
|
|
""errored"": 30,
|
|
|
|
|
""canceled"": 10,
|
|
|
|
|
""expired"": 10
|
|
|
|
|
},
|
|
|
|
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListMessageBatchesAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<MessageBatchResponse>>();
|
|
|
|
|
result.Value.HasMore.Should().BeTrue();
|
|
|
|
|
result.Value.FirstId.Should().Be("1");
|
|
|
|
|
result.Value.LastId.Should().Be("1");
|
|
|
|
|
result.Value.Data.Should().BeEquivalentTo(new MessageBatchResponse[]
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
Type = "message_batch",
|
|
|
|
|
ProcessingStatus = "in_progress",
|
|
|
|
|
RequestCounts = new MessageBatchRequestCounts
|
|
|
|
|
{
|
|
|
|
|
Processing = 100,
|
|
|
|
|
Succeeded = 50,
|
|
|
|
|
Errored = 30,
|
|
|
|
|
Canceled = 10,
|
|
|
|
|
Expired = 10
|
|
|
|
|
},
|
|
|
|
|
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListMessageBatchesAsync_WhenCalledWithPagingRequestAndSuccessful_ItShouldReturnPageOfBatches()
|
|
|
|
|
{
|
|
|
|
|
var pagingRequest = new PagingRequest(afterId: "next_id", limit: 10);
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.WithQueryString(new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
{ "after_id", pagingRequest.AfterId },
|
|
|
|
|
{ "limit", pagingRequest.Limit.ToString() },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch"",
|
|
|
|
|
""processing_status"": ""in_progress"",
|
|
|
|
|
""request_counts"": {
|
|
|
|
|
""processing"": 100,
|
|
|
|
|
""succeeded"": 50,
|
|
|
|
|
""errored"": 30,
|
|
|
|
|
""canceled"": 10,
|
|
|
|
|
""expired"": 10
|
|
|
|
|
},
|
|
|
|
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListMessageBatchesAsync(pagingRequest);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<MessageBatchResponse>>();
|
|
|
|
|
result.Value.HasMore.Should().BeTrue();
|
|
|
|
|
result.Value.FirstId.Should().Be("1");
|
|
|
|
|
result.Value.LastId.Should().Be("1");
|
|
|
|
|
result.Value.Data.Should().BeEquivalentTo(new MessageBatchResponse[]
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
Type = "message_batch",
|
|
|
|
|
ProcessingStatus = "in_progress",
|
|
|
|
|
RequestCounts = new MessageBatchRequestCounts
|
|
|
|
|
{
|
|
|
|
|
Processing = 100,
|
|
|
|
|
Succeeded = 50,
|
|
|
|
|
Errored = 30,
|
|
|
|
|
Canceled = 10,
|
|
|
|
|
Expired = 10
|
|
|
|
|
},
|
|
|
|
|
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListMessageBatchesAsync_WhenCalledAndNoBatchesReturned_ItShouldReturnEmptyList()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [],
|
|
|
|
|
""has_more"": false,
|
|
|
|
|
""first_id"": null,
|
|
|
|
|
""last_id"": null
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListMessageBatchesAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<MessageBatchResponse>>();
|
|
|
|
|
result.Value.HasMore.Should().BeFalse();
|
|
|
|
|
result.Value.FirstId.Should().BeNull();
|
|
|
|
|
result.Value.LastId.Should().BeNull();
|
|
|
|
|
result.Value.Data.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListMessageBatchesAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.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.ListMessageBatchesAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListMessageBatchesAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"null"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListMessageBatchesAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListMessageBatchesAsync_WhenCalledRequestSucceedsAndCanNotDeserializeResponse_ItShouldReturnEmptyPage()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"null"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListMessageBatchesAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<MessageBatchResponse>>();
|
|
|
|
|
result.Value.HasMore.Should().BeFalse();
|
|
|
|
|
result.Value.FirstId.Should().BeEmpty();
|
|
|
|
|
result.Value.LastId.Should().BeEmpty();
|
|
|
|
|
result.Value.Data.Should().BeEmpty();
|
|
|
|
|
}
|
2025-01-13 12:42:23 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListAllMessageBatchesAsync_WhenCalled_ItShouldReturnAllBatches()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch"",
|
|
|
|
|
""processing_status"": ""in_progress"",
|
|
|
|
|
""request_counts"": {
|
|
|
|
|
""processing"": 100,
|
|
|
|
|
""succeeded"": 50,
|
|
|
|
|
""errored"": 30,
|
|
|
|
|
""canceled"": 10,
|
|
|
|
|
""expired"": 10
|
|
|
|
|
},
|
|
|
|
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListMessageBatchesRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "after_id", "1" },
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch"",
|
|
|
|
|
""processing_status"": ""in_progress"",
|
|
|
|
|
""request_counts"": {
|
|
|
|
|
""processing"": 100,
|
|
|
|
|
""succeeded"": 50,
|
|
|
|
|
""errored"": 30,
|
|
|
|
|
""canceled"": 10,
|
|
|
|
|
""expired"": 10
|
|
|
|
|
},
|
|
|
|
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": false,
|
|
|
|
|
""first_id"": ""2"",
|
|
|
|
|
""last_id"": ""2""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var pageResponses = Client.ListAllMessageBatchesAsync();
|
|
|
|
|
var collectedPages = new List<Page<MessageBatchResponse>>();
|
|
|
|
|
|
|
|
|
|
await foreach (var response in pageResponses)
|
|
|
|
|
{
|
|
|
|
|
response.IsSuccess.Should().BeTrue();
|
|
|
|
|
response.Value.Should().BeOfType<Page<MessageBatchResponse>>();
|
|
|
|
|
collectedPages.Add(response.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var expectedMessageBatchResponse = new MessageBatchResponse()
|
|
|
|
|
{
|
|
|
|
|
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
Type = "message_batch",
|
|
|
|
|
ProcessingStatus = "in_progress",
|
|
|
|
|
RequestCounts = new MessageBatchRequestCounts
|
|
|
|
|
{
|
|
|
|
|
Processing = 100,
|
|
|
|
|
Succeeded = 50,
|
|
|
|
|
Errored = 30,
|
|
|
|
|
Canceled = 10,
|
|
|
|
|
Expired = 10
|
|
|
|
|
},
|
|
|
|
|
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
collectedPages.Should().HaveCount(2);
|
|
|
|
|
collectedPages.Should().BeEquivalentTo(new List<Page<MessageBatchResponse>>()
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Data = [expectedMessageBatchResponse],
|
|
|
|
|
FirstId = "1",
|
|
|
|
|
LastId = "1",
|
|
|
|
|
HasMore = true
|
|
|
|
|
},
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Data = [expectedMessageBatchResponse],
|
|
|
|
|
FirstId = "2",
|
|
|
|
|
LastId = "2",
|
|
|
|
|
HasMore = false
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-01-13 13:11:47 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CancelMessageBatchAsync_WhenCalled_ItShouldReturnBatch()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCancelMessageBatchRequest(batchId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch"",
|
|
|
|
|
""processing_status"": ""in_progress"",
|
|
|
|
|
""request_counts"": {
|
|
|
|
|
""processing"": 100,
|
|
|
|
|
""succeeded"": 50,
|
|
|
|
|
""errored"": 30,
|
|
|
|
|
""canceled"": 10,
|
|
|
|
|
""expired"": 10
|
|
|
|
|
},
|
|
|
|
|
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
|
|
|
|
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CancelMessageBatchAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeEquivalentTo(new MessageBatchResponse()
|
|
|
|
|
{
|
|
|
|
|
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
Type = "message_batch",
|
|
|
|
|
ProcessingStatus = "in_progress",
|
|
|
|
|
RequestCounts = new MessageBatchRequestCounts
|
|
|
|
|
{
|
|
|
|
|
Processing = 100,
|
|
|
|
|
Succeeded = 50,
|
|
|
|
|
Errored = 30,
|
|
|
|
|
Canceled = 10,
|
|
|
|
|
Expired = 10
|
|
|
|
|
},
|
|
|
|
|
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
|
|
|
|
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CancelMessageBatchAsync_WhenCalledAndFails_ItShouldReturnError()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCancelMessageBatchRequest(batchId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""batch: batch not found""
|
|
|
|
|
}
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.CancelMessageBatchAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
2025-01-14 13:41:32 -06:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task DeleteMessageBatchAsync_WhenCalled_ItShouldReturnDeletionResponse()
|
|
|
|
|
{
|
|
|
|
|
var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenDeleteMessageBatchRequest(batchId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""message_batch_deleted""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.DeleteMessageBatchAsync(batchId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<MessageBatchDeleteResponse>();
|
|
|
|
|
result.Value.Id.Should().Be(batchId);
|
|
|
|
|
result.Value.Type.Should().Be("message_batch_deleted");
|
|
|
|
|
}
|
2025-07-12 21:49:49 -05:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreateFileAsync_WhenCalled_ItShouldReturnFile()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateFileRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""created_at"": ""2023-11-07T05:31:56Z"",
|
|
|
|
|
""downloadable"": false,
|
|
|
|
|
""filename"": ""example.txt"",
|
|
|
|
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""mime_type"": ""text/plain"",
|
|
|
|
|
""size_bytes"": 1234,
|
|
|
|
|
""type"": ""file""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var fileContent = new MemoryStream(Encoding.UTF8.GetBytes("Example file content"));
|
|
|
|
|
var request = new CreateFileRequest(fileContent, "example.txt", "text/plain");
|
|
|
|
|
|
|
|
|
|
var result = await Client.CreateFileAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeEquivalentTo(new AnthropicFile()
|
|
|
|
|
{
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
|
|
|
|
|
Downloadable = false,
|
|
|
|
|
Name = "example.txt",
|
|
|
|
|
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
MimeType = "text/plain",
|
|
|
|
|
Size = 1234,
|
|
|
|
|
Type = "file"
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-13 21:12:37 -05:00
|
|
|
|
2025-07-14 19:04:46 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task CreateFileAsync_WhenCalledAndRequestFails_ItShouldReturnError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenCreateFileRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""file: file size exceeds limit""
|
|
|
|
|
}
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var fileContent = new MemoryStream(Encoding.UTF8.GetBytes("Example file content"));
|
|
|
|
|
var request = new CreateFileRequest(fileContent, "example.txt", "text/plain");
|
|
|
|
|
|
|
|
|
|
var result = await Client.CreateFileAsync(request);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-13 21:12:37 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task ListFilesAsync_WhenCalled_ItShouldReturnPageOfFiles()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListFilesRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""created_at"": ""2023-11-07T05:31:56Z"",
|
|
|
|
|
""downloadable"": false,
|
|
|
|
|
""filename"": ""example.txt"",
|
|
|
|
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""mime_type"": ""text/plain"",
|
|
|
|
|
""size_bytes"": 1234,
|
|
|
|
|
""type"": ""file""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListFilesAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<AnthropicFile>>();
|
|
|
|
|
result.Value.HasMore.Should().BeTrue();
|
|
|
|
|
result.Value.FirstId.Should().Be("1");
|
|
|
|
|
result.Value.LastId.Should().Be("1");
|
|
|
|
|
result.Value.Data.Should().BeEquivalentTo(new AnthropicFile[]
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
|
|
|
|
|
Downloadable = false,
|
|
|
|
|
Name = "example.txt",
|
|
|
|
|
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
MimeType = "text/plain",
|
|
|
|
|
Size = 1234,
|
|
|
|
|
Type = "file"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 19:04:46 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task ListFilesAsync_WhenCalledAndRequestFails_ItShouldReturnError()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListFilesRequest()
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""files: file not found""
|
|
|
|
|
}
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListFilesAsync();
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-13 21:12:37 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task ListFilesAsync_WhenCalledWithPagingRequest_ItShouldReturnPageOfFiles()
|
|
|
|
|
{
|
|
|
|
|
var pagingRequest = new PagingRequest(afterId: "next_id", limit: 10);
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListFilesRequest()
|
|
|
|
|
.WithQueryString(new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
{ "after_id", pagingRequest.AfterId },
|
|
|
|
|
{ "limit", pagingRequest.Limit.ToString() },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""created_at"": ""2023-11-07T05:31:56Z"",
|
|
|
|
|
""downloadable"": false,
|
|
|
|
|
""filename"": ""example.txt"",
|
|
|
|
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""mime_type"": ""text/plain"",
|
|
|
|
|
""size_bytes"": 1234,
|
|
|
|
|
""type"": ""file""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.ListFilesAsync(pagingRequest);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<Page<AnthropicFile>>();
|
|
|
|
|
result.Value.HasMore.Should().BeTrue();
|
|
|
|
|
result.Value.FirstId.Should().Be("1");
|
|
|
|
|
result.Value.LastId.Should().Be("1");
|
|
|
|
|
result.Value.Data.Should().BeEquivalentTo(new AnthropicFile[]
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
|
|
|
|
|
Downloadable = false,
|
|
|
|
|
Name = "example.txt",
|
|
|
|
|
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
MimeType = "text/plain",
|
|
|
|
|
Size = 1234,
|
|
|
|
|
Type = "file"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-13 22:33:11 -05:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListAllFilesAsync_WhenCalled_ItShouldReturnAllFiles()
|
|
|
|
|
{
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListFilesRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""created_at"": ""2023-11-07T05:31:56Z"",
|
|
|
|
|
""downloadable"": false,
|
|
|
|
|
""filename"": ""example.txt"",
|
|
|
|
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""mime_type"": ""text/plain"",
|
|
|
|
|
""size_bytes"": 1234,
|
|
|
|
|
""type"": ""file""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": true,
|
|
|
|
|
""first_id"": ""1"",
|
|
|
|
|
""last_id"": ""1""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenListFilesRequest()
|
|
|
|
|
.WithExactQueryString(new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "after_id", "1" },
|
|
|
|
|
{ "limit", "20" },
|
|
|
|
|
})
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""data"": [
|
|
|
|
|
{
|
|
|
|
|
""created_at"": ""2023-11-07T05:31:56Z"",
|
|
|
|
|
""downloadable"": false,
|
|
|
|
|
""filename"": ""example.txt"",
|
|
|
|
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""mime_type"": ""text/plain"",
|
|
|
|
|
""size_bytes"": 1234,
|
|
|
|
|
""type"": ""file""
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
""has_more"": false,
|
|
|
|
|
""first_id"": ""2"",
|
|
|
|
|
""last_id"": ""2""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var pageResponses = Client.ListAllFilesAsync();
|
|
|
|
|
var collectedPages = new List<Page<AnthropicFile>>();
|
|
|
|
|
|
|
|
|
|
await foreach (var response in pageResponses)
|
|
|
|
|
{
|
|
|
|
|
response.IsSuccess.Should().BeTrue();
|
|
|
|
|
response.Value.Should().BeOfType<Page<AnthropicFile>>();
|
|
|
|
|
collectedPages.Add(response.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var expectedFile = new AnthropicFile()
|
|
|
|
|
{
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
|
|
|
|
|
Downloadable = false,
|
|
|
|
|
Name = "example.txt",
|
|
|
|
|
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
MimeType = "text/plain",
|
|
|
|
|
Size = 1234,
|
|
|
|
|
Type = "file"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
collectedPages.Should().HaveCount(2);
|
|
|
|
|
collectedPages.Should().BeEquivalentTo(new List<Page<AnthropicFile>>()
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Data = [expectedFile],
|
|
|
|
|
FirstId = "1",
|
|
|
|
|
LastId = "1",
|
|
|
|
|
HasMore = true
|
|
|
|
|
},
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Data = [expectedFile],
|
|
|
|
|
FirstId = "2",
|
|
|
|
|
LastId = "2",
|
|
|
|
|
HasMore = false
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-14 18:46:32 -05:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetFileInfoAsync_WhenCalled_ItShouldReturnFile()
|
|
|
|
|
{
|
|
|
|
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetFileRequest(fileId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""created_at"": ""2023-11-07T05:31:56Z"",
|
|
|
|
|
""downloadable"": false,
|
|
|
|
|
""filename"": ""example.txt"",
|
|
|
|
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""mime_type"": ""text/plain"",
|
|
|
|
|
""size_bytes"": 1234,
|
|
|
|
|
""type"": ""file""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetFileInfoAsync(fileId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeEquivalentTo(new AnthropicFile()
|
|
|
|
|
{
|
|
|
|
|
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
|
|
|
|
|
Downloadable = false,
|
|
|
|
|
Name = "example.txt",
|
|
|
|
|
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
|
|
|
|
|
MimeType = "text/plain",
|
|
|
|
|
Size = 1234,
|
|
|
|
|
Type = "file"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 19:04:46 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetFileInfoAsync_WhenCalledAndRequestFails_ItShouldReturnError()
|
|
|
|
|
{
|
|
|
|
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetFileRequest(fileId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""file: file not found""
|
|
|
|
|
}
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetFileInfoAsync(fileId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 18:46:32 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetFileAsync_WhenCalled_ItShouldReturnFileContent()
|
|
|
|
|
{
|
|
|
|
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
var fileContent = new MemoryStream(Encoding.UTF8.GetBytes("Example file content"));
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetFileContentRequest(fileId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/octet-stream",
|
|
|
|
|
fileContent
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetFileAsync(fileId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeAssignableTo<Stream>();
|
|
|
|
|
|
|
|
|
|
using var streamReader = new StreamReader(result.Value);
|
|
|
|
|
var content = await streamReader.ReadToEndAsync();
|
|
|
|
|
content.Should().Be("Example file content");
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 19:04:46 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetFileAsync_WhenCalledAndRequestFails_ItShouldReturnError()
|
|
|
|
|
{
|
|
|
|
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetFileContentRequest(fileId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""file: file not found""
|
|
|
|
|
}
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetFileAsync(fileId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 19:17:35 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task GetFileAsync_WhenCalledAndCanNotDeserializeResponse_ItShouldReturnError()
|
|
|
|
|
{
|
|
|
|
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenGetFileContentRequest(fileId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"null"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.GetFileAsync(fileId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<ApiError>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 18:46:32 -05:00
|
|
|
[Fact]
|
|
|
|
|
public async Task DeleteFileAsync_WhenCalled_ItShouldReturnDeletionResponse()
|
|
|
|
|
{
|
|
|
|
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenDeleteFileRequest(fileId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.OK,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
|
|
|
|
""type"": ""file_deleted""
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.DeleteFileAsync(fileId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
|
|
|
result.Value.Should().BeOfType<AnthropicFileDeleteResponse>();
|
|
|
|
|
result.Value.Id.Should().Be(fileId);
|
|
|
|
|
result.Value.Type.Should().Be("file_deleted");
|
|
|
|
|
}
|
2025-07-14 19:04:46 -05:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task DeleteFileAsync_WhenCalledAndRequestFails_ItShouldReturnError()
|
|
|
|
|
{
|
|
|
|
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
|
|
|
|
|
|
|
|
|
_mockHttpMessageHandler
|
|
|
|
|
.WhenDeleteFileRequest(fileId)
|
|
|
|
|
.Respond(
|
|
|
|
|
HttpStatusCode.BadRequest,
|
|
|
|
|
"application/json",
|
|
|
|
|
@"{
|
|
|
|
|
""type"": ""error"",
|
|
|
|
|
""error"": {
|
|
|
|
|
""type"": ""invalid_request_error"",
|
|
|
|
|
""message"": ""file: file not found""
|
|
|
|
|
}
|
|
|
|
|
}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var result = await Client.DeleteFileAsync(fileId);
|
|
|
|
|
|
|
|
|
|
result.IsSuccess.Should().BeFalse();
|
|
|
|
|
result.Error.Should().BeOfType<AnthropicError>();
|
|
|
|
|
result.Error.Error.Should().BeOfType<InvalidRequestError>();
|
|
|
|
|
}
|
2024-07-01 23:59:24 -05:00
|
|
|
}
|