diff --git a/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj b/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj index 491e4db..f21321f 100644 --- a/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj +++ b/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs b/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs new file mode 100644 index 0000000..d073564 --- /dev/null +++ b/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs @@ -0,0 +1,34 @@ +namespace AnthropicClient.Tests.Integration; + +public class AnthropicApiClientTests : IntegrationTest +{ + [Theory] + [ClassData(typeof(ErrorTestData))] + public async Task CreateChatMessageAsync_WhenCalledAndErrorReturned_ItShouldHandleError( + HttpStatusCode statusCode, + string content, + Type errorType + ) + { + _mockHttpMessageHandler + .WhenCreateMessageRequest() + .Respond( + statusCode, + "application/json", + content + ); + + var request = new ChatMessageRequest( + model: AnthropicModels.Claude3Haiku, + messages: [new(MessageRole.User, [new TextContent("Hello!")])] + ); + + var result = await Client.CreateChatMessageAsync(request); + + result.IsSuccess.Should().BeFalse(); + result.Error.Should().BeOfType(); + + var actualErrorType = result.Error.Error!.GetType(); + actualErrorType.Should().Be(errorType); + } +} \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Integration/ErrorTestData.cs b/tests/AnthropicClient.Tests/Integration/ErrorTestData.cs new file mode 100644 index 0000000..ed20c58 --- /dev/null +++ b/tests/AnthropicClient.Tests/Integration/ErrorTestData.cs @@ -0,0 +1,100 @@ +namespace AnthropicClient.Tests.Integration; + +public class ErrorTestData : IEnumerable +{ + public IEnumerator GetEnumerator() + { + yield return new object[] + { + HttpStatusCode.BadRequest, + @"{ + ""type"": ""error"", + ""error"": { + ""type"": ""invalid_request_error"", + ""message"": ""Invalid request."" + } + }", + typeof(InvalidRequestError) + }; + + yield return new object[] + { + HttpStatusCode.Unauthorized, + @"{ + ""type"": ""error"", + ""error"": { + ""type"": ""authentication_error"", + ""message"": ""Authentication error."" + } + }", + typeof(AuthenticationError) + }; + + yield return new object[] + { + HttpStatusCode.Forbidden, + @"{ + ""type"": ""error"", + ""error"": { + ""type"": ""permission_error"", + ""message"": ""Permission error."" + } + }", + typeof(PermissionError) + }; + + yield return new object[] + { + HttpStatusCode.NotFound, + @"{ + ""type"": ""error"", + ""error"": { + ""type"": ""not_found_error"", + ""message"": ""Not found error."" + } + }", + typeof(NotFoundError) + }; + + yield return new object[] + { + HttpStatusCode.TooManyRequests, + @"{ + ""type"": ""error"", + ""error"": { + ""type"": ""rate_limit_error"", + ""message"": ""Rate limit error."" + } + }", + typeof(RateLimitError) + }; + + yield return new object[] + { + HttpStatusCode.InternalServerError, + @"{ + ""type"": ""error"", + ""error"": { + ""type"": ""api_error"", + ""message"": ""Internal server error."" + } + }", + typeof(ApiError) + }; + + yield return new object[] + { + (HttpStatusCode)529, + @"{ + ""type"": ""error"", + ""error"": { + ""type"": ""overloaded_error"", + ""message"": ""Overloaded error."" + } + }", + typeof(OverloadedError) + }; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs b/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs new file mode 100644 index 0000000..857fb0f --- /dev/null +++ b/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs @@ -0,0 +1,40 @@ +namespace AnthropicClient.Tests.Integration; + +public class IntegrationTest +{ + protected readonly MockHttpMessageHandler _mockHttpMessageHandler = new(); + protected AnthropicApiClient Client => CreateClient(); + + private AnthropicApiClient CreateClient() + { + return new AnthropicApiClient("test-key", _mockHttpMessageHandler.ToHttpClient()); + } +} + +public static class MockHttpMessageHandlerExtensions +{ + private static MockedRequest SetupBaseRequest(this MockHttpMessageHandler mockHttpMessageHandler) + { + return mockHttpMessageHandler + .When(HttpMethod.Post, "https://api.anthropic.com/v1/messages") + .WithHeaders(new Dictionary + { + { "anthropic-version", "2023-06-01" }, + { "x-api-key", "test-key" } + }); + } + + public static MockedRequest WhenCreateMessageRequest(this MockHttpMessageHandler mockHttpMessageHandler) + { + return mockHttpMessageHandler + .SetupBaseRequest() + .WithJsonContent(r => r.Stream == false); + } + + public static MockedRequest WhenCreateStreamMessageRequest(this MockHttpMessageHandler mockHttpMessageHandler) + { + return mockHttpMessageHandler + .SetupBaseRequest() + .WithJsonContent(r => r.Stream == true); + } +} \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs b/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs index 906d68c..b7abfce 100644 --- a/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs +++ b/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.Text.Json.Nodes; namespace AnthropicClient.Tests.Unit.Utils; diff --git a/tests/AnthropicClient.Tests/Usings.cs b/tests/AnthropicClient.Tests/Usings.cs index 4b78fc5..27ef28a 100644 --- a/tests/AnthropicClient.Tests/Usings.cs +++ b/tests/AnthropicClient.Tests/Usings.cs @@ -6,4 +6,8 @@ global using AnthropicClient.Utils; global using FluentAssertions; -global using Microsoft.Extensions.Configuration; \ No newline at end of file +global using Microsoft.Extensions.Configuration; + +global using RichardSzalay.MockHttp; +global using System.Net; +global using System.Collections; \ No newline at end of file