feat: create chat message

This commit is contained in:
Stevan Freeborn
2024-06-27 23:26:53 -05:00
parent 68b359163d
commit 4c2cee643a
77 changed files with 3860 additions and 0 deletions
@@ -0,0 +1,21 @@
namespace AnthropicClient.Tests.EndToEnd;
public class ClientTests(
HttpClientFixture httpClientFixture,
ConfigurationFixture configFixture
) : EndToEndTest(httpClientFixture, configFixture)
{
[Fact]
public async Task CreateChatMessage_WhenCalled_ShouldReturnChatResponse()
{
var request = new ChatMessageRequest(
model: AnthropicModels.Claude3Haiku,
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
);
var result = await _client.CreateChatMessageAsync(request);
result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<ChatResponse>();
}
}
@@ -0,0 +1,23 @@
namespace AnthropicClient.Tests.EndToEnd;
public class ConfigurationFixture
{
public string AnthropicApiKey { get; }
public ConfigurationFixture()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.Test.json")
.AddEnvironmentVariables()
.Build();
var anthropicApiKey = configuration["AnthropicApiKey"];
if (string.IsNullOrEmpty(anthropicApiKey))
{
throw new InvalidOperationException("AnthropicApiKey is required");
}
AnthropicApiKey = anthropicApiKey;
}
}
@@ -0,0 +1,9 @@
namespace AnthropicClient.Tests.EndToEnd;
public class EndToEndTest(
HttpClientFixture httpClientFixture,
ConfigurationFixture configFixture
) : IClassFixture<HttpClientFixture>, IClassFixture<ConfigurationFixture>
{
protected readonly Client _client = new(configFixture.AnthropicApiKey, httpClientFixture.HttpClient);
}
@@ -0,0 +1,11 @@
namespace AnthropicClient.Tests.EndToEnd;
public class HttpClientFixture
{
public HttpClient HttpClient { get; }
public HttpClientFixture()
{
HttpClient = new HttpClient();
}
}
@@ -0,0 +1,168 @@
namespace AnthropicClient.Tests.Unit.Models;
public class AnthropicErrorTests : SerializationTest
{
private readonly string _testApiErrorJson = @"{
""type"": ""error"",
""error"": {
""type"": ""api_error"",
""message"": ""message""
}
}";
private readonly string _testAuthenticationErrorJson = @"{
""type"": ""error"",
""error"": {
""type"": ""authentication_error"",
""message"": ""message""
}
}";
private readonly string _testRateLimitErrorJson = @"{
""type"": ""error"",
""error"": {
""type"": ""rate_limit_error"",
""message"": ""message""
}
}";
private readonly string _testPermissionErrorJson = @"{
""type"": ""error"",
""error"": {
""type"": ""permission_error"",
""message"": ""message""
}
}";
private readonly string _testNotFoundErrorJson = @"{
""type"": ""error"",
""error"": {
""type"": ""not_found_error"",
""message"": ""message""
}
}";
private readonly string _testOverloadedErrorJson = @"{
""type"": ""error"",
""error"": {
""type"": ""overloaded_error"",
""message"": ""message""
}
}";
private readonly string _testInvalidRequestErrorJson = @"{
""type"": ""error"",
""error"": {
""type"": ""invalid_request_error"",
""message"": ""message""
}
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var apiError = new ApiError("message");
var error = new AnthropicError(apiError);
error.Type.Should().Be("error");
error.Error.Should().BeSameAs(apiError);
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldBeExpectedShape()
{
var apiError = new ApiError("message");
var error = new AnthropicError(apiError);
var json = Serialize(error);
JsonAssert.Equal(_testApiErrorJson, json);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModel()
{
var error = Deserialize<AnthropicError>(_testApiErrorJson);
error!.Type.Should().Be("error");
error.Error.Should().BeOfType<ApiError>();
error.Error!.Message.Should().Be("message");
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForAuthenticationError()
{
var error = Deserialize<AnthropicError>(_testAuthenticationErrorJson);
error!.Type.Should().Be("error");
error.Error.Should().BeOfType<AuthenticationError>();
error.Error!.Message.Should().Be("message");
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForRateLimitError()
{
var error = Deserialize<AnthropicError>(_testRateLimitErrorJson);
error!.Type.Should().Be("error");
error.Error.Should().BeOfType<RateLimitError>();
error.Error!.Message.Should().Be("message");
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForPermissionError()
{
var error = Deserialize<AnthropicError>(_testPermissionErrorJson);
error!.Type.Should().Be("error");
error.Error.Should().BeOfType<PermissionError>();
error.Error!.Message.Should().Be("message");
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForNotFoundError()
{
var error = Deserialize<AnthropicError>(_testNotFoundErrorJson);
error!.Type.Should().Be("error");
error.Error.Should().BeOfType<NotFoundError>();
error.Error!.Message.Should().Be("message");
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForOverloadedError()
{
var error = Deserialize<AnthropicError>(_testOverloadedErrorJson);
error!.Type.Should().Be("error");
error.Error.Should().BeOfType<OverloadedError>();
error.Error!.Message.Should().Be("message");
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForInvalidRequestError()
{
var error = Deserialize<AnthropicError>(_testInvalidRequestErrorJson);
error!.Type.Should().Be("error");
error.Error.Should().BeOfType<InvalidRequestError>();
error.Error!.Message.Should().Be("message");
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForUnknownError()
{
var json = @"{
""type"": ""error"",
""error"": {
""type"": ""unknown_error"",
""message"": ""message""
}
}";
var action = () => Deserialize<AnthropicError>(json);
action.Should().Throw<JsonException>();
}
}
@@ -0,0 +1,67 @@
namespace AnthropicClient.Tests.Unit.Models;
public class AnthropicModelsTests
{
[Fact]
public void Claude3Opus_WhenCalled_ItShouldReturnExpectedValue()
{
var expected = "claude-3-opus-20240229";
var actual = AnthropicModels.Claude3Opus;
actual.Should().Be(expected);
}
[Fact]
public void Claude3Sonnet_WhenCalled_ItShouldReturnExpectedValue()
{
var expected = "claude-3-sonnet-20240229";
var actual = AnthropicModels.Claude3Sonnet;
actual.Should().Be(expected);
}
[Fact]
public void Claude35Sonnet_WhenCalled_ItShouldReturnExpectedValue()
{
var expected = "claude-3-5-sonnet-20240620";
var actual = AnthropicModels.Claude35Sonnet;
actual.Should().Be(expected);
}
[Fact]
public void Claude3Haiku_WhenCalled_ItShouldReturnExpectedValue()
{
var expected = "claude-3-haiku-20240307";
var actual = AnthropicModels.Claude3Haiku;
actual.Should().Be(expected);
}
[Fact]
public void Claude35Sonnet_WhenCalled_ItShouldExpectedValue()
{
var expected = "claude-3-5-sonnet-20240620";
var actual = AnthropicModels.Claude35Sonnet;
actual.Should().Be(expected);
}
[Theory]
[InlineData("claude-3-opus-20240229", true)]
[InlineData("claude-3-sonnet-20240229", true)]
[InlineData("claude-3-5-sonnet-20240620", true)]
[InlineData("claude-3-haiku-20240307", true)]
[InlineData("invalid", false)]
public void IsValidModel_WhenCalled_ItShouldReturnExpectedValue(string modelId, bool expected)
{
var actual = AnthropicModels.IsValidModel(modelId);
actual.Should().Be(expected);
}
}
@@ -0,0 +1,30 @@
namespace AnthropicClient.Tests.Unit.Models;
public class AnthropicResultTests
{
[Fact]
public void Success_WhenCalled_ItShouldReturnSuccessResult()
{
var value = "success";
var requestId = Guid.NewGuid().ToString();
var actual = AnthropicResult<string>.Success(value, requestId);
actual.IsSuccess.Should().BeTrue();
actual.Value.Should().Be(value);
actual.RequestId.Should().Be(requestId);
}
[Fact]
public void Failure_WhenCalled_ItShouldReturnFailureResult()
{
var error = new AnthropicError(new AuthenticationError("message"));
var requestId = Guid.NewGuid().ToString();
var actual = AnthropicResult<string>.Failure(error, requestId);
actual.IsSuccess.Should().BeFalse();
actual.Error.Should().Be(error);
actual.RequestId.Should().Be(requestId);
}
}
@@ -0,0 +1,38 @@
namespace AnthropicClient.Tests.Unit.Models;
public class AnyToolChoiceTests : SerializationTest
{
private readonly string _testJson = @"{
""type"": ""any""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldSetTypeToAny()
{
var expected = "any";
var actual = new AnyToolChoice();
actual.Type.Should().Be(expected);
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var choice = new AnyToolChoice();
var actual = Serialize(choice);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new AnyToolChoice();
var actual = Deserialize<AnyToolChoice>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,40 @@
namespace AnthropicClient.Tests.Unit.Models;
public class AuthenticationErrorTests : SerializationTest
{
private readonly string _testJson = @"{
""message"": ""message"",
""type"": ""authentication_error""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var expectedMessage = "message";
var actual = new AuthenticationError(expectedMessage);
actual.Message.Should().Be(expectedMessage);
actual.Type.Should().Be("authentication_error");
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var error = new AuthenticationError("message");
var actual = Serialize(error);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new AuthenticationError("message");
var actual = Deserialize<AuthenticationError>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,38 @@
namespace AnthropicClient.Tests.Unit.Models;
public class AutoToolChoiceTests : SerializationTest
{
private readonly string _testJson = @"{
""type"": ""auto""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldSetTypeToAuto()
{
var expected = "auto";
var actual = new AutoToolChoice();
actual.Type.Should().Be(expected);
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var choice = new AutoToolChoice();
var actual = Serialize(choice);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new AutoToolChoice();
var actual = Deserialize<AutoToolChoice>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,653 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ChatMessageRequestTests : SerializationTest
{
private readonly string _testJson = @"{
""model"": ""claude-3-sonnet-20240229"",
""system"": ""test-system"",
""messages"": [
{ ""role"": ""user"", ""content"": [{ ""text"": ""Hello!"", ""type"": ""text"" }] }
],
""max_tokens"": 512,
""metadata"": { ""test"":""test"" },
""stop_sequences"": [],
""temperature"": 0.5,
""topK"": 10,
""topP"": 0.5,
""tool_choice"": { ""type"":""auto"" },
""tools"": [
{
""name"": ""test-tool"",
""description"": ""test-description"",
""input_schema"": {
""type"": ""object"",
""properties"": {
""test-property"": {
""type"": ""string"",
""description"": ""test-description""
}
},
""required"": [""test-property""]
}
}
],
""stream"": false
}";
private readonly string _testJsonWithAnyToolChoice = @"{
""model"": ""claude-3-sonnet-20240229"",
""system"": ""test-system"",
""messages"":[
{ ""role"": ""user"", ""content"": [{ ""text"": ""Hello!"", ""type"":""text"" }] }
],
""max_tokens"": 512,
""metadata"": {""test"":""test""},
""stop_sequences"": [],
""temperature"": 0.5,
""topK"": 10,
""topP"": 0.5,
""tool_choice"": {""type"":""any""},
""tools"": [
{
""name"": ""test-tool"",
""description"": ""test-description"",
""input_schema"": {
""type"": ""object"",
""properties"": {
""test-property"": {
""type"": ""string"",
""description"": ""test-description""
}
},
""required"": [""test-property""]
}
}
],
""stream"":false
}";
private readonly string _testJsonWithSpecificToolChoice = @"{
""model"": ""claude-3-sonnet-20240229"",
""system"": ""test-system"",
""messages"": [
{ ""role"": ""user"", ""content"": [{ ""text"": ""Hello!"", ""type"": ""text"" }] }
],
""max_tokens"": 512,
""metadata"": { ""test"": ""test"" },
""stop_sequences"": [],
""temperature"": 0.5,
""topK"": 10,
""topP"": 0.5,
""tool_choice"": { ""type"": ""tool"", ""name"": ""test-tool"" },
""tools"": [
{
""name"": ""test-tool"",
""description"": ""test-description"",
""input_schema"": {
""type"": ""object"",
""properties"": {
""test-property"": {
""type"": ""string"",
""description"": ""test-description""
}
},
""required"": [""test-property""]
}
}
],
""stream"": false
}";
private readonly string _testJsonWithImageContent = @"{
""model"": ""claude-3-sonnet-20240229"",
""system"": ""test-system"",
""messages"":[
{
""role"": ""user"",
""content"": [
{
""type"": ""image"",
""source"": { ""media_type"": ""image/jpeg"", ""data"": ""data"" }
}
]
}
],
""max_tokens"": 512,
""metadata"": { ""test"": ""test"" },
""stop_sequences"": [],
""temperature"": 0.5,
""topK"": 10,
""topP"": 0.5,
""tool_choice"": { ""type"": ""auto"" },
""tools"":[
{
""name"": ""test-tool"",
""description"": ""test-description"",
""input_schema"": {
""type"": ""object"",
""properties"": {
""test-property"": {
""type"": ""string"",
""description"": ""test-description""
}
},
""required"": [""test-property""]
}
}
],
""stream"": false
}";
private readonly string _testJsonWithUnknownContent = @"{
""model"": ""claude-3-sonnet-20240229"",
""system"": ""test-system"",
""messages"": [{ ""role"": ""user"", ""content"": [{ ""type"": ""unknown"", ""text"": ""text"" }] }],
""max_tokens"": 512,
""metadata"": { ""test"": ""test"" },
""stop_sequences"": [],
""temperature"": 0.5,
""topK"": 10,
""topP"": 0.5,
""tool_choice"": { ""type"":""auto"" },
""tools"": [
{
""name"": ""test-tool"",
""description"": ""test-description"",
""input_schema"": {
""type"": ""object"",
""properties"": {
""test-property"": {
""type"": ""string"",
""description"": ""test-description""
}
},
""required"": [""test-property""]
}
}
],
""stream"": false
}";
private readonly string _testJsonWithToolUseContent = @"{
""model"": ""claude-3-sonnet-20240229"",
""system"": ""test-system"",
""messages"": [
{
""role"": ""assistant"",
""content"": [
{
""type"": ""tool_use"",
""name"": ""test-tool"",
""id"": ""test-tool-id"",
""input"": {
""test-property"": ""test-value""
}
}
]
}
],
""max_tokens"": 512,
""metadata"": { ""test"": ""test"" },
""stop_sequences"": [],
""temperature"": 0.5,
""topK"": 10,
""topP"": 0.5,
""tool_choice"": { ""type"": ""auto"" },
""tools"": [
{
""name"": ""test-tool"",
""description"": ""test-description"",
""input_schema"": {
""type"": ""object"",
""properties"": {
""test-property"": {
""type"": ""string"",
""description"": ""test-description""
}
},
""required"": [""test-property""]
}
}
],
""stream"": false
}";
private readonly string _testJsonWithToolResultContent = @"{
""model"": ""claude-3-sonnet-20240229"",
""system"": ""test-system"",
""messages"": [
{
""role"": ""assistant"",
""content"": [
{
""type"": ""tool_result"",
""tool_use_id"": ""test-tool"",
""content"": ""test-value""
}
]
}
],
""max_tokens"": 512,
""metadata"": { ""test"": ""test"" },
""stop_sequences"": [],
""temperature"": 0.5,
""topK"": 10,
""topP"": 0.5,
""tool_choice"": { ""type"": ""auto"" },
""tools"": [
{
""name"": ""test-tool"",
""description"": ""test-description"",
""input_schema"": {
""type"": ""object"",
""properties"": {
""test-property"": {
""type"": ""string"",
""description"": ""test-description""
}
},
""required"": [""test-property""]
}
}
],
""stream"":false
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var model = AnthropicModels.Claude3Sonnet;
var messages = new List<ChatMessage> { new() };
var maxTokens = 512;
var system = "test-system";
var metadata = new Dictionary<string, object> { ["test"] = "test" };
var temperature = 0.5m;
var topK = 10;
var topP = 0.5m;
var toolChoice = new AutoToolChoice();
var tools = new List<Tool> { new() };
var chatMessageRequest = new ChatMessageRequest(
model: model,
messages: messages,
maxTokens: maxTokens,
system: system,
metadata: metadata,
temperature: temperature,
topK: topK,
topP: topP,
toolChoice: toolChoice,
tools: tools
);
chatMessageRequest.Model.Should().Be(model);
chatMessageRequest.Messages.Should().BeSameAs(messages);
chatMessageRequest.MaxTokens.Should().Be(maxTokens);
chatMessageRequest.System.Should().Be(system);
chatMessageRequest.Metadata.Should().BeSameAs(metadata);
chatMessageRequest.Temperature.Should().Be(temperature);
chatMessageRequest.TopK.Should().Be(topK);
chatMessageRequest.TopP.Should().Be(topP);
chatMessageRequest.ToolChoice.Should().Be(toolChoice);
chatMessageRequest.Tools.Should().BeSameAs(tools);
chatMessageRequest.Stream.Should().BeFalse();
}
[Fact]
public void Constructor_WhenCalledAndModelIsNull_ItShouldThrowArgumentNullException()
{
var action = () => new ChatMessageRequest(
model: null!,
messages: [new()]
);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndMessagesIsNull_ItShouldThrowArgumentNullException()
{
var action = () => new ChatMessageRequest(
model: AnthropicModels.Claude3Sonnet,
messages: null!
);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndModelIsInvalid_ItShouldThrowArgumentException()
{
var action = () => new ChatMessageRequest(
model: "invalid-model",
messages: [new()]
);
action.Should().Throw<ArgumentException>();
}
[Fact]
public void Constructor_WhenCalledAndMessagesIsEmpty_ItShouldThrowArgumentException()
{
var action = () => new ChatMessageRequest(
model: AnthropicModels.Claude3Sonnet,
messages: []
);
action.Should().Throw<ArgumentException>();
}
[Fact]
public void Constructor_WhenCalledAndMaxTokensIsInvalid_ItShouldThrowArgumentException()
{
var action = () => new ChatMessageRequest(
model: AnthropicModels.Claude3Sonnet,
messages: [new()],
maxTokens: 0
);
action.Should().Throw<ArgumentException>();
}
[Theory]
[InlineData(-1)]
[InlineData(2)]
public void Constructor_WhenCalledAndTemperatureIsInvalid_ItShouldThrowArgumentException(decimal temperature)
{
var action = () => new ChatMessageRequest(
model: AnthropicModels.Claude3Sonnet,
messages: [new()],
temperature: temperature
);
action.Should().Throw<ArgumentException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var messages = new List<ChatMessage>()
{
new()
{
Role = MessageRole.User,
Content = [new TextContent("Hello!")]
}
};
var model = AnthropicModels.Claude3Sonnet;
var maxTokens = 512;
var system = "test-system";
var metadata = new Dictionary<string, object>
{
["test"] = "test"
};
var temperature = 0.5m;
var topK = 10;
var topP = 0.5m;
var toolChoice = new AutoToolChoice();
var tools = new List<Tool>
{
new()
{
Name = "test-tool",
Description = "test-description",
InputSchema = new InputSchema(
properties: new Dictionary<string, InputProperty>
{
["test-property"] = new InputProperty(
type: "string",
description: "test-description"
)
},
required: ["test-property"]
),
}
};
var chatMessageRequest = new ChatMessageRequest(
model: model,
messages: messages,
maxTokens: maxTokens,
system: system,
metadata: metadata,
temperature: temperature,
topK: topK,
topP: topP,
toolChoice: toolChoice,
tools: tools
);
var actual = Serialize(chatMessageRequest);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJson);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(1);
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
chatMessageRequest.Stream.Should().BeFalse();
}
[Fact]
public void JsonDeserialization_WhenDeserializedWithAnyToolChoice_ItShouldHaveExpectedShape()
{
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithAnyToolChoice);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AnyToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("any");
chatMessageRequest.Tools.Should().HaveCount(1);
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
}
[Fact]
public void JsonDeserialization_WhenDeserializedWithSpecificToolChoice_ItShouldHaveExpectedShape()
{
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithSpecificToolChoice);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<SpecificToolChoice>();
var specificToolChoice = chatMessageRequest.ToolChoice as SpecificToolChoice;
specificToolChoice!.Type.Should().Be("tool");
specificToolChoice.Name.Should().Be("test-tool");
chatMessageRequest.Tools.Should().HaveCount(1);
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
}
[Fact]
public void JsonDeserialization_WhenDeserializedWithUnknownToolChoice_ItShouldThrowJsonException()
{
var json = @"{""model"":""claude-3-sonnet-20240229"",""system"":""test-system"",""messages"":[{""role"":""user"",""content"":[{""text"":""Hello!"",""type"":""text""}]}],""max_tokens"":512,""metadata"":{""test"":""test""},""stop_sequences"":[],""temperature"":0.5,""topK"":10,""topP"":0.5,""tool_choice"":{""type"":""unknown""},""tools"":[{""name"":""test-tool"",""description"":""test-description"",""input_schema"":{""type"":""object"",""properties"":{""test-property"":{""type"":""string"",""description"":""test-description""}},""required"":[""test-property""]}}],""stream"":false}";
var action = () => Deserialize<ChatMessageRequest>(json);
action.Should().Throw<JsonException>();
}
[Fact]
public void JsonDeserialization_WhenDeserializedWithImageContent_ItShouldHaveExpectedShape()
{
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithImageContent);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(1);
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
chatMessageRequest.Messages[0].Content.Should().HaveCount(1);
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ImageContent>();
var imageContent = chatMessageRequest.Messages[0].Content[0] as ImageContent;
imageContent!.Type.Should().Be("image");
imageContent.Source.MediaType.Should().Be("image/jpeg");
imageContent.Source.Data.Should().Be("data");
}
[Fact]
public void JsonDeserialization_WhenDeserializedWithToolUseContent_ItShouldHaveExpectedShape()
{
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithToolUseContent);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(1);
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
chatMessageRequest.Messages[0].Content.Should().HaveCount(1);
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ToolUseContent>();
var toolUseContent = chatMessageRequest.Messages[0].Content[0] as ToolUseContent;
toolUseContent!.Type.Should().Be("tool_use");
toolUseContent.Name.Should().Be("test-tool");
toolUseContent.Id.Should().Be("test-tool-id");
toolUseContent.Input.Should().HaveCount(1);
toolUseContent.Input["test-property"]!.ToString().Should().Be("test-value");
}
[Fact]
public void JsonDeserialization_WhenDeserializedWithToolResultContent_ItShouldHaveExpectedShape()
{
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithToolResultContent);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(1);
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
chatMessageRequest.Messages[0].Content.Should().HaveCount(1);
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ToolResultContent>();
var toolResultContent = chatMessageRequest.Messages[0].Content[0] as ToolResultContent;
toolResultContent!.Type.Should().Be("tool_result");
toolResultContent.ToolUseId.Should().Be("test-tool");
toolResultContent.Content.Should().Be("test-value");
}
[Fact]
public void JsonDeserialization_WhenDeserializedWithUnknownContent_ItShouldThrowJsonException()
{
var action = () => Deserialize<ChatMessageRequest>(_testJsonWithUnknownContent);
action.Should().Throw<JsonException>();
}
}
@@ -0,0 +1,78 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ChatMessageTests : SerializationTest
{
private readonly string _testJson = @"{
""role"": ""assistant"",
""content"": [
{ ""text"": ""text"", ""type"": ""text"" }
]
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var role = "assistant";
var content = new List<Content> { new TextContent("text") };
var chatMessage = new ChatMessage(role, content);
chatMessage.Role.Should().Be(role);
chatMessage.Content.Should().BeSameAs(content);
}
[Fact]
public void Constructor_WhenCalledAndRoleIsNull_ItShouldThrowArgumentNullException()
{
var content = new List<Content> { new TextContent("text") };
var action = () => new ChatMessage(null!, content);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndContentIsNull_ItShouldThrowArgumentNullException()
{
var role = "assistant";
var action = () => new ChatMessage(role, null!);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndRoleIsInvalid_ItShouldThrowArgumentException()
{
var role = "invalid";
var content = new List<Content> { new TextContent("text") };
var action = () => new ChatMessage(role, content);
action.Should().Throw<ArgumentException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldReturnJsonString()
{
var role = "assistant";
var content = new List<Content> { new TextContent("text") };
var chatMessage = new ChatMessage(role, content);
var actual = Serialize(chatMessage);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldReturnChatMessage()
{
var chatMessage = Deserialize<ChatMessage>(_testJson);
chatMessage.Should().NotBeNull();
chatMessage!.Role.Should().Be("assistant");
chatMessage.Content.Should().HaveCount(1);
chatMessage.Content[0].Should().BeOfType<TextContent>();
chatMessage.Content[0].As<TextContent>().Text.Should().Be("text");
}
}
@@ -0,0 +1,120 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ChatResponseTests : SerializationTest
{
[Fact]
public void Constructor_WhenCalled_ShouldInitializeProperties()
{
var expectedId = "id";
var expectedModel = "model";
var expectedRole = "role";
var expectedStopReason = "stop reason";
var expectedStopSequence = "stop sequence";
var expectedType = "type";
var expectedUsage = new ChatUsage
{
InputTokens = 1,
OutputTokens = 2
};
var expectedContent = new List<Content>
{
new TextContent("text content"),
};
var chatResponse = new ChatResponse
{
Id = expectedId,
Model = expectedModel,
Role = expectedRole,
StopReason = expectedStopReason,
StopSequence = expectedStopSequence,
Type = expectedType,
Usage = expectedUsage,
Content = expectedContent
};
chatResponse.Id.Should().Be(expectedId);
chatResponse.Model.Should().Be(expectedModel);
chatResponse.Role.Should().Be(expectedRole);
chatResponse.StopReason.Should().Be(expectedStopReason);
chatResponse.StopSequence.Should().Be(expectedStopSequence);
chatResponse.Type.Should().Be(expectedType);
chatResponse.Usage.Should().BeEquivalentTo(expectedUsage);
chatResponse.Content.Should().BeEquivalentTo(expectedContent);
}
[Fact]
public void JsonSerialization_WhenCalled_ItShouldSerializeCorrectly()
{
var expectedJson = @"{
""id"": ""id"",
""model"": ""model"",
""role"": ""role"",
""stop_reason"": ""stop reason"",
""stop_sequence"": ""stop sequence"",
""type"": ""type"",
""usage"": { ""input_tokens"": 1, ""output_tokens"": 2 },
""content"": [
{ ""text"": ""text content"", ""type"": ""text"" }
]
}";
var chatResponse = new ChatResponse
{
Id = "id",
Model = "model",
Role = "role",
StopReason = "stop reason",
StopSequence = "stop sequence",
Type = "type",
Usage = new ChatUsage
{
InputTokens = 1,
OutputTokens = 2
},
Content =
[
new TextContent("text content"),
]
};
var actual = Serialize(chatResponse);
JsonAssert.Equal(expectedJson, actual);
}
[Fact]
public void JsonDeserialization_WhenCalled_ItShouldDeserializeCorrectly()
{
var json = @"{
""id"": ""id"",
""model"": ""model"",
""role"": ""role"",
""stop_reason"": ""stop reason"",
""stop_sequence"": ""stop sequence"",
""type"": ""type"",
""usage"": { ""input_tokens"": 1, ""output_tokens"": 2 },
""content"": [
{ ""text"": ""text content"", ""type"": ""text"" }
]
}";
var chatResponse = Deserialize<ChatResponse>(json);
chatResponse!.Id.Should().Be("id");
chatResponse.Model.Should().Be("model");
chatResponse.Role.Should().Be("role");
chatResponse.StopReason.Should().Be("stop reason");
chatResponse.StopSequence.Should().Be("stop sequence");
chatResponse.Type.Should().Be("type");
chatResponse.Usage.Should().BeEquivalentTo(new ChatUsage
{
InputTokens = 1,
OutputTokens = 2
});
chatResponse.Content.Should().BeEquivalentTo(new List<Content>
{
new TextContent("text content"),
});
}
}
@@ -0,0 +1,47 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ChatUsageTests : SerializationTest
{
[Fact]
public void Constructor_WhenCalled_ShouldInitializeProperties()
{
var expectedInputTokens = 1;
var expectedOutputTokens = 2;
var chatUsage = new ChatUsage
{
InputTokens = expectedInputTokens,
OutputTokens = expectedOutputTokens
};
chatUsage.InputTokens.Should().Be(expectedInputTokens);
chatUsage.OutputTokens.Should().Be(expectedOutputTokens);
}
[Fact]
public void JsonSerialization_WhenCalled_ItShouldSerializeCorrectly()
{
var expectedJson = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }";
var chatUsage = new ChatUsage
{
InputTokens = 1,
OutputTokens = 2
};
var actual = Serialize(chatUsage);
JsonAssert.Equal(expectedJson, actual);
}
[Fact]
public void JsonDeserialization_WhenCalled_ItShouldDeserializeCorrectly()
{
var json = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }";
var chatUsage = Deserialize<ChatUsage>(json);
chatUsage!.InputTokens.Should().Be(1);
chatUsage.OutputTokens.Should().Be(2);
}
}
@@ -0,0 +1,44 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ContentTypeTests
{
[Fact]
public void Text_WhenCalled_ItShouldReturnText()
{
var expected = "text";
var actual = ContentType.Text;
actual.Should().Be(expected);
}
[Fact]
public void Image_WhenCalled_ItShouldReturnImage()
{
var expected = "image";
var actual = ContentType.Image;
actual.Should().Be(expected);
}
[Fact]
public void ToolUse_WhenCalled_ItShouldReturnToolUse()
{
var expected = "tool_use";
var actual = ContentType.ToolUse;
actual.Should().Be(expected);
}
[Fact]
public void ToolResult_WhenCalled_ItShouldReturnToolResult()
{
var expected = "tool_result";
var actual = ContentType.ToolResult;
actual.Should().Be(expected);
}
}
@@ -0,0 +1,74 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ImageContentTests : SerializationTest
{
private readonly string _testJson = @"{
""source"": {
""media_type"": ""image/png"",
""data"": ""data""
},
""type"": ""image""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeSource()
{
var expectedMediaType = "image/png";
var expectedData = "data";
var result = new ImageContent(expectedMediaType, expectedData);
result.Source.Should().BeEquivalentTo(new ImageSource(expectedMediaType, expectedData));
}
[Fact]
public void Constructor_WhenCalledAndMediaTypeIsNull_ItShouldThrowArgumentNullException()
{
var expectedData = "data";
var action = () => new ImageContent(null!, expectedData);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndDataIsNull_ItShouldThrowArgumentNullException()
{
var expectedMediaType = "image/png";
var action = () => new ImageContent(expectedMediaType, null!);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledWithInvalidMediaType_ItShouldThrowArgumentException()
{
var expectedMediaType = "invalid";
var expectedData = "data";
var action = () => new ImageContent(expectedMediaType, expectedData);
action.Should().Throw<ArgumentException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var content = new ImageContent("image/png", "data");
var actual = Serialize(content);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new ImageContent("image/png", "data");
var actual = Deserialize<ImageContent>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,57 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ImageTypeTests
{
[Fact]
public void Jpg_WhenCalled_ItShouldReturnJpg()
{
var expected = "image/jpeg";
var actual = ImageType.Jpg;
actual.Should().Be(expected);
}
[Fact]
public void Png_WhenCalled_ItShouldReturnPng()
{
var expected = "image/png";
var actual = ImageType.Png;
actual.Should().Be(expected);
}
[Fact]
public void Gif_WhenCalled_ItShouldReturnGif()
{
var expected = "image/gif";
var actual = ImageType.Gif;
actual.Should().Be(expected);
}
[Fact]
public void Webp_WhenCalled_ItShouldReturnWebp()
{
var expected = "image/webp";
var actual = ImageType.Webp;
actual.Should().Be(expected);
}
[Theory]
[InlineData("image/jpeg", true)]
[InlineData("image/png", true)]
[InlineData("image/gif", true)]
[InlineData("image/webp", true)]
[InlineData("invalid", false)]
public void IsValidImageType_WhenCalled_ItShouldReturnExpectedValue(string imageType, bool expected)
{
var actual = ImageType.IsValidImageType(imageType);
actual.Should().Be(expected);
}
}
@@ -0,0 +1,60 @@
namespace AnthropicClient.Tests.Unit.Models;
public class InputPropertyTests : SerializationTest
{
private readonly string _testJson = @"{
""type"": ""type"",
""description"": ""description""
}";
[Fact]
public void Constructor_WhenCalled_ShouldInitializeProperties()
{
var type = "type";
var description = "description";
var inputProperty = new InputProperty(type, description);
inputProperty.Type.Should().Be(type);
inputProperty.Description.Should().Be(description);
}
[Fact]
public void Constructor_WhenTypeIsNull_ShouldThrowArgumentNullException()
{
var description = "description";
var action = () => new InputProperty(null!, description);
action.Should().Throw<ArgumentNullException>().WithMessage("Value cannot be null. (Parameter 'type')");
}
[Fact]
public void Constructor_WhenDescriptionIsNull_ShouldThrowArgumentNullException()
{
var type = "type";
var action = () => new InputProperty(type, null!);
action.Should().Throw<ArgumentNullException>().WithMessage("Value cannot be null. (Parameter 'description')");
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var inputProperty = new InputProperty("type", "description");
var json = Serialize(inputProperty);
JsonAssert.Equal(_testJson, json);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var inputProperty = Deserialize<InputProperty>(_testJson);
inputProperty!.Type.Should().Be("type");
inputProperty.Description.Should().Be("description");
}
}
@@ -0,0 +1,116 @@
namespace AnthropicClient.Tests.Unit.Models;
public class InputSchemaTests : SerializationTest
{
private readonly string _testJson = @$"{{
""type"": ""object"",
""properties"": {{
""property1"": {{ ""type"": ""type1"", ""description"": ""description1"" }},
""property2"": {{ ""type"": ""type2"", ""description"": ""description2"" }}
}},
""required"": [""property1""]
}}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var inputProperties = new Dictionary<string, InputProperty>
{
{ "property1", new InputProperty("type1", "description1") },
{ "property2", new InputProperty("type2", "description2") }
};
var required = new List<string> { "property1" };
var inputSchema = new InputSchema(
properties: inputProperties,
required: required
);
inputSchema.Type.Should().Be("object");
inputSchema.Properties.Should().BeSameAs(inputProperties);
inputSchema.Required.Should().BeSameAs(required);
}
[Fact]
public void Constructor_WhenPropertiesIsNull_ItShouldThrowArgumentNullException()
{
var required = new List<string> { "property1" };
var action = () => new InputSchema(null!, required);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenRequiredIsNull_ItShouldThrowArgumentNullException()
{
var inputProperties = new Dictionary<string, InputProperty>
{
{ "property1", new InputProperty("type1", "description1") },
{ "property2", new InputProperty("type2", "description2") }
};
var action = () => new InputSchema(inputProperties, null!);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenRequiredPropertyIsNotInProperties_ItShouldThrowArgumentException()
{
var inputProperties = new Dictionary<string, InputProperty>
{
{ "property1", new InputProperty("type1", "description1") },
{ "property2", new InputProperty("type2", "description2") }
};
var required = new List<string> { "property3" };
var action = () => new InputSchema(inputProperties, required);
action.Should().Throw<ArgumentException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var inputProperties = new Dictionary<string, InputProperty>
{
{ "property1", new InputProperty("type1", "description1") },
{ "property2", new InputProperty("type2", "description2") }
};
var required = new List<string> { "property1" };
var inputSchema = new InputSchema(
properties: inputProperties,
required: required
);
var actual = Serialize(inputSchema);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var inputProperties = new Dictionary<string, InputProperty>
{
{ "property1", new InputProperty("type1", "description1") },
{ "property2", new InputProperty("type2", "description2") }
};
var required = new List<string> { "property1" };
var expected = new InputSchema(
properties: inputProperties,
required: required
);
var actual = Deserialize<InputSchema>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,47 @@
namespace AnthropicClient.Tests.Unit.Models;
public class InvalidRequestErrorTests : SerializationTest
{
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var message = "message";
var error = new InvalidRequestError(message);
error.Type.Should().Be("invalid_request_error");
error.Message.Should().Be(message);
}
[Fact]
public void Serialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var message = "message";
var error = new InvalidRequestError(message);
var serialized = Serialize(error);
JsonAssert.Equal(
@"{
""type"": ""invalid_request_error"",
""message"": ""message""
}",
serialized
);
}
[Fact]
public void Deserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
{
var json = @"{
""type"": ""invalid_request_error"",
""message"": ""message""
}";
var error = Deserialize<InvalidRequestError>(json);
error!.Type.Should().Be("invalid_request_error");
error.Message.Should().Be("message");
}
}
@@ -0,0 +1,35 @@
namespace AnthropicClient.Tests.Unit.Models;
public class MessageRoleTests
{
[Fact]
public void User_WhenCalled_ItShouldReturnUser()
{
var expected = "user";
var actual = MessageRole.User;
actual.Should().Be(expected);
}
[Fact]
public void Assistant_WhenCalled_ItShouldReturnAssistant()
{
var expected = "assistant";
var actual = MessageRole.Assistant;
actual.Should().Be(expected);
}
[Theory]
[InlineData("user", true)]
[InlineData("assistant", true)]
[InlineData("invalid", false)]
public void IsValidRole_WhenCalled_ItShouldReturnExpectedValue(string role, bool expected)
{
var actual = MessageRole.IsValidRole(role);
actual.Should().Be(expected);
}
}
@@ -0,0 +1,40 @@
namespace AnthropicClient.Tests.Unit.Models;
public class NotFoundErrorTests : SerializationTest
{
private readonly string _testJson = @"{
""message"": ""message"",
""type"": ""not_found_error""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var expectedMessage = "message";
var actual = new NotFoundError(expectedMessage);
actual.Message.Should().Be(expectedMessage);
actual.Type.Should().Be("not_found_error");
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var error = new NotFoundError("message");
var actual = Serialize(error);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new NotFoundError("message");
var actual = Deserialize<NotFoundError>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,40 @@
namespace AnthropicClient.Tests.Unit.Models;
public class OverloadedErrorTests : SerializationTest
{
private readonly string _testJson = @"{
""message"": ""message"",
""type"": ""overloaded_error""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var expectedMessage = "message";
var actual = new OverloadedError(expectedMessage);
actual.Message.Should().Be(expectedMessage);
actual.Type.Should().Be("overloaded_error");
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var error = new OverloadedError("message");
var actual = Serialize(error);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new OverloadedError("message");
var actual = Deserialize<OverloadedError>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,40 @@
namespace AnthropicClient.Tests.Unit.Models;
public class PermissionErrorTests : SerializationTest
{
private readonly string _testJson = @"{
""message"": ""message"",
""type"": ""permission_error""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var expectedMessage = "message";
var actual = new PermissionError(expectedMessage);
actual.Message.Should().Be(expectedMessage);
actual.Type.Should().Be("permission_error");
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var error = new PermissionError("message");
var actual = Serialize(error);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new PermissionError("message");
var actual = Deserialize<PermissionError>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,40 @@
namespace AnthropicClient.Tests.Unit.Models;
public class RateLimitErrorTests : SerializationTest
{
private readonly string _testJson = @"{
""message"": ""message"",
""type"": ""rate_limit_error""
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var expectedMessage = "message";
var actual = new RateLimitError(expectedMessage);
actual.Message.Should().Be(expectedMessage);
actual.Type.Should().Be("rate_limit_error");
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var error = new RateLimitError("message");
var actual = Serialize(error);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new RateLimitError("message");
var actual = Deserialize<RateLimitError>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,51 @@
namespace AnthropicClient.Tests.Unit.Models;
public class SpecificToolChoiceTests : SerializationTest
{
private readonly string _testToolName = "tool";
private string GetTestJson(string name) => @$"{{
""name"": ""{name}"",
""type"": ""tool""
}}";
[Fact]
public void Constructor_WhenCalled_ItShouldSetTypeToTool()
{
var expectedType = "tool";
var actual = new SpecificToolChoice(_testToolName);
actual.Name.Should().Be(_testToolName);
actual.Type.Should().Be(expectedType);
}
[Fact]
public void Constructor_WhenCalledWithNull_ItShouldThrowArgumentNullException()
{
var action = () => new SpecificToolChoice(null!);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var expected = GetTestJson(_testToolName);
var choice = new SpecificToolChoice(_testToolName);
var actual = Serialize(choice);
JsonAssert.Equal(expected, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new SpecificToolChoice(_testToolName);
var actual = Deserialize<SpecificToolChoice>(GetTestJson(_testToolName));
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,44 @@
namespace AnthropicClient.Tests.Unit.Models;
public class StopReasonTypeTests
{
[Fact]
public void EndTurn_WhenCalled_ItShouldReturnEndTurn()
{
var expected = "end_turn";
var actual = StopReasonType.EndTurn;
actual.Should().Be(expected);
}
[Fact]
public void MaxTokens_WhenCalled_ItShouldReturnMaxTokens()
{
var expected = "max_tokens";
var actual = StopReasonType.MaxTokens;
actual.Should().Be(expected);
}
[Fact]
public void StopSequence_WhenCalled_ItShouldReturnStopSequence()
{
var expected = "stop_sequence";
var actual = StopReasonType.StopSequence;
actual.Should().Be(expected);
}
[Fact]
public void ToolUse_WhenCalled_ItShouldReturnToolUse()
{
var expected = "tool_use";
var actual = StopReasonType.ToolUse;
actual.Should().Be(expected);
}
}
@@ -0,0 +1,44 @@
namespace AnthropicClient.Tests.Unit.Models;
public class TextContentTests : SerializationTest
{
private readonly string _testJson = @"{ ""text"": ""text"", ""type"": ""text"" }";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeText()
{
var expectedText = "text";
var result = new TextContent(expectedText);
result.Text.Should().Be(expectedText);
}
[Fact]
public void Constructor_WhenCalledAndTextIsNull_ItShouldThrowArgumentNullException()
{
var action = () => new TextContent(null!);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var content = new TextContent("text");
var actual = Serialize(content);
JsonAssert.Equal(_testJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var expected = new TextContent("text");
var actual = Deserialize<TextContent>(_testJson);
actual.Should().BeEquivalentTo(expected);
}
}
@@ -0,0 +1,46 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ToolChoiceTypeTests
{
[Fact]
public void Auto_WhenCalled_ItShouldReturnAuto()
{
var expected = "auto";
var actual = ToolChoiceType.Auto;
actual.Should().Be(expected);
}
[Fact]
public void Any_WhenCalled_ItShouldReturnAny()
{
var expected = "any";
var actual = ToolChoiceType.Any;
Assert.Equal(expected, actual);
}
[Fact]
public void Tool_WhenCalled_ItShouldReturnTool()
{
var expected = "tool";
var actual = ToolChoiceType.Tool;
Assert.Equal(expected, actual);
}
[Theory]
[InlineData("auto", true)]
[InlineData("any", true)]
[InlineData("tool", true)]
[InlineData("invalid", false)]
public void IsValidType_WhenCalledWithType_ItShouldReturnExpectedResult(string type, bool expected)
{
var actual = ToolChoiceType.IsValidType(type);
Assert.Equal(expected, actual);
}
}
@@ -0,0 +1,79 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ToolResultContentTests : SerializationTest
{
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var toolUseId = Guid.NewGuid().ToString();
var content = "content";
var actual = new ToolResultContent(toolUseId, content);
actual.ToolUseId.Should().Be(toolUseId);
actual.Content.Should().Be(content);
actual.Type.Should().Be("tool_result");
}
[Fact]
public void Constructor_WhenCalledAndToolUseIdIsNull_ItShouldThrowArgumentNullException()
{
var content = "content";
var action = () => new ToolResultContent(null!, content);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndContentIsNull_ItShouldThrowArgumentNullException()
{
var toolUseId = Guid.NewGuid().ToString();
var action = () => new ToolResultContent(toolUseId, null!);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldReturnJsonString()
{
var toolUseId = Guid.NewGuid().ToString();
var content = "content";
var expectedJson = @$"{{
""tool_use_id"": ""{toolUseId}"",
""content"": ""{content}"",
""type"": ""tool_result""
}}";
var toolResultContent = new ToolResultContent
{
ToolUseId = toolUseId,
Content = content
};
var actual = Serialize(toolResultContent);
JsonAssert.Equal(expectedJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldReturnToolResultContent()
{
var toolUseId = Guid.NewGuid().ToString();
var content = "content";
var json = @$"{{
""tool_use_id"": ""{toolUseId}"",
""content"": ""{content}"",
""type"": ""tool_result""
}}";
var actual = Deserialize<ToolResultContent>(json);
actual!.ToolUseId.Should().Be(toolUseId);
actual.Content.Should().Be(content);
actual.Type.Should().Be("tool_result");
}
}
@@ -0,0 +1,88 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ToolTests : SerializationTest
{
private readonly string _testJson = @"{
""name"": ""test-name"",
""description"": ""test-description"",
""input_schema"": { ""type"": ""object"", ""properties"": {}, ""required"": [] }
}";
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var name = "test-name";
var description = "test-description";
var inputSchema = new InputSchema();
var tool = new Tool(
name: name,
description: description,
inputSchema: inputSchema
);
tool.Name.Should().Be(name);
tool.Description.Should().Be(description);
tool.InputSchema.Should().Be(inputSchema);
}
[Fact]
public void Constructor_WhenCalledAndNameIsNull_ItShouldThrowArgumentNullException()
{
var action = () => new Tool(
name: null!,
description: "test-description",
inputSchema: new()
);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndDescriptionIsNull_ItShouldThrowArgumentNullException()
{
var action = () => new Tool(
name: "test-name",
description: null!,
inputSchema: new()
);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void Constructor_WhenCalledAndInputSchemaIsNull_ItShouldThrowArgumentNullException()
{
var action = () => new Tool(
name: "test-name",
description: "test-description",
inputSchema: null!
);
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{
var tool = new Tool(
name: "test-name",
description: "test-description",
inputSchema: new()
);
var json = Serialize(tool);
JsonAssert.Equal(_testJson, json);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{
var tool = Deserialize<Tool>(_testJson);
tool!.Name.Should().Be("test-name");
tool.Description.Should().Be("test-description");
tool.InputSchema.Should().BeEquivalentTo(new InputSchema());
}
}
@@ -0,0 +1,83 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ToolUseContentTests : SerializationTest
{
[Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties()
{
var id = Guid.NewGuid().ToString();
var name = "name";
var input = new Dictionary<string, object?> { { "name", "input" } };
var actual = new ToolUseContent()
{
Id = id,
Name = name,
Input = input
};
actual.Id.Should().Be(id);
actual.Name.Should().Be(name);
actual.Input.Should().BeEquivalentTo(input);
actual.Type.Should().Be("tool_use");
}
[Fact]
public void JsonSerialization_WhenSerialized_ItShouldReturnJsonString()
{
var id = Guid.NewGuid().ToString();
var name = "name";
var input = new Dictionary<string, object?> { { "name", "input" } };
var expectedJson = @$"{{
""id"": ""{id}"",
""name"": ""{name}"",
""input"": {{ ""name"": ""input"" }},
""type"": ""tool_use""
}}";
var toolUseContent = new ToolUseContent()
{
Id = id,
Name = name,
Input = input
};
var actual = Serialize(toolUseContent);
JsonAssert.Equal(expectedJson, actual);
}
[Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldReturnToolUseContent()
{
var id = Guid.NewGuid().ToString();
var name = "name";
var input = new Dictionary<string, object?> { { "name", "input" } };
var json = @$"{{
""id"": ""{id}"",
""name"": ""{name}"",
""input"": {{ ""name"": ""input"" }},
""type"": ""tool_use""
}}";
var expected = new ToolUseContent()
{
Id = id,
Name = name,
Input = input
};
var actual = Deserialize<ToolUseContent>(json);
actual!.Id.Should().Be(expected.Id);
actual.Name.Should().Be(expected.Name);
var actualInput = actual.Input.GetValueOrDefault("name")!.ToString();
var expectedInput = expected.Input.GetValueOrDefault("name")!.ToString();
actualInput.Should().Be(expectedInput);
actual.Type.Should().Be(expected.Type);
}
}
@@ -0,0 +1,12 @@
using AnthropicClient.Json;
namespace AnthropicClient.Tests.Unit;
public class SerializationTest
{
private readonly JsonSerializerOptions _jsonSerializerOptions = JsonSerializationOptions.DefaultOptions;
protected string Serialize<T>(T obj) => JsonSerializer.Serialize(obj, _jsonSerializerOptions);
protected T? Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, _jsonSerializerOptions);
}
@@ -0,0 +1,24 @@
namespace AnthropicClient.Tests.Unit.Utils;
public class ArgumentValidatorTests
{
[Fact]
public void ThrowIfNull_WhenValueIsNull_ItShouldThrowArgumentNullException()
{
object? value = null;
var action = () => ArgumentValidator.ThrowIfNull(value, "value");
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void ThrowIfNull_WhenValueIsNotNull_ItShouldNotThrow()
{
var value = new object();
var action = () => ArgumentValidator.ThrowIfNull(value, "value");
action.Should().NotThrow();
}
}
+9
View File
@@ -0,0 +1,9 @@
global using FluentAssertions;
global using AnthropicClient.Models;
global using AnthropicClient.Utils;
global using System.Text.Json;
global using System.Text.Json.JsonDiffPatch.Xunit;
global using Microsoft.Extensions.Configuration;