Skip to content

Commit

Permalink
refactor: improve error information with tailored format
Browse files Browse the repository at this point in the history
  • Loading branch information
Tr00d committed Oct 15, 2024
1 parent c875b62 commit f11e417
Show file tree
Hide file tree
Showing 33 changed files with 389 additions and 229 deletions.
66 changes: 66 additions & 0 deletions Vonage.Test/Common/Client/ApiErrorsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#region
using System.Net;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Vonage.Common;
using Vonage.Common.Client;
using Vonage.Common.Failures;
using Vonage.Common.Monads;
using Vonage.Serialization;
using Vonage.Test.Common.Extensions;
using Vonage.Test.Common.TestHelpers;
using Xunit;
#endregion

namespace Vonage.Test.Common.Client;

[Trait("Category", "Unit")]
public class ApiErrorsTest
{
private readonly SerializationTestHelper helper = new SerializationTestHelper(typeof(ApiErrorsTest).Namespace,
JsonSerializerBuilder.BuildWithSnakeCase());

private readonly JsonSerializer serializer = new JsonSerializer();

[Fact]
public async Task ShouldReturnErrorContent_GivenVideoApiError()
{
var expectedJson = this.helper.GetResponseJson("ApiErrorVideo");
var sut = this.BuildClient<VideoApiError>(expectedJson);
var result = await sut.SendAsync(BuildFakeRequest());
result.Should().BeFailure(HttpFailure.From(HttpStatusCode.BadRequest,
"You do not pass in a session ID or you pass in an invalid session ID.", expectedJson));
}

[Fact]
public async Task ShouldReturnErrorContent_GivenNetworkApiError()
{
var expectedJson = this.helper.GetResponseJson("ApiErrorNetwork");
var sut = this.BuildClient<NetworkApiError>(expectedJson);
var result = await sut.SendAsync(BuildFakeRequest());
result.Should().BeFailure(HttpFailure.From(HttpStatusCode.BadRequest,
"Client specified an invalid argument, request body or query param", expectedJson));
}

[Fact]
public async Task ShouldReturnErrorContent_GivenStandardApiError()
{
var expectedJson = this.helper.GetResponseJson("ApiErrorStandard");
var sut = this.BuildClient<StandardApiError>(expectedJson);
var result = await sut.SendAsync(BuildFakeRequest());
result.Should().BeFailure(HttpFailure.From(HttpStatusCode.BadRequest,
"You did not provide correct credentials.", expectedJson));
}

private static Result<VonageHttpClientTest.FakeRequest> BuildFakeRequest() =>
Result<VonageHttpClientTest.FakeRequest>.FromSuccess(new VonageHttpClientTest.FakeRequest());

private VonageHttpClient<T> BuildClient<T>(string responseContent) where T : IApiError
{
var messageHandler = FakeHttpRequestHandler.Build(HttpStatusCode.BadRequest)
.WithResponseContent(responseContent);
var configuration = new VonageHttpClientConfiguration(messageHandler.ToHttpClient(),
new AuthenticationHeaderValue("Anonymous"), "userAgent");
return new VonageHttpClient<T>(configuration, this.serializer);
}
}
5 changes: 5 additions & 0 deletions Vonage.Test/Common/Client/Data/ApiErrorNetwork-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"status": 400,
"code": "INVALID_ARGUMENT",
"message": "Client specified an invalid argument, request body or query param"
}
6 changes: 6 additions & 0 deletions Vonage.Test/Common/Client/Data/ApiErrorStandard-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "https://developer.vonage.com/api-errors/#unathorized",
"title": "You did not provide correct credentials.",
"detail": "Check that you're using the correct credentials, and that your account has this feature enabled",
"instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf"
}
4 changes: 4 additions & 0 deletions Vonage.Test/Common/Client/Data/ApiErrorVideo-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"code": 400,
"message": "You do not pass in a session ID or you pass in an invalid session ID."
}
69 changes: 42 additions & 27 deletions Vonage.Test/Common/Client/VonageHttpClientTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#region
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
Expand All @@ -13,6 +14,7 @@
using Vonage.Test.Common.Extensions;
using Vonage.Test.Common.TestHelpers;
using Xunit;
#endregion

namespace Vonage.Test.Common.Client;

Expand All @@ -39,27 +41,30 @@ public async Task SendAsync_ShouldThrowException_GivenOperationExceedsTimeout()
[Property]
public Property SendAsync_VerifyReturnsFailureGivenApiResponseIsError() =>
this.VerifyReturnsFailureGivenApiResponseIsError(BuildExpectedRequest(),
configuration => new VonageHttpClient(configuration, this.serializer).SendAsync(this.request));
configuration =>
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendAsync(this.request));

[Property]
public Property SendAsync_VerifyReturnsFailureGivenErrorCannotBeParsed() =>
this.VerifyReturnsFailureGivenErrorCannotBeParsed(BuildExpectedRequest(),
configuration => new VonageHttpClient(configuration, this.serializer).SendAsync(this.request));
configuration =>
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendAsync(this.request));

[Fact]
public async Task SendAsync_VerifyReturnsFailureGivenRequestIsFailure() =>
await this.VerifyReturnsFailureGivenRequestIsFailure((configuration, failureRequest) =>
new VonageHttpClient(configuration, this.serializer).SendAsync(failureRequest));
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendAsync(failureRequest));

[Fact]
public async Task SendAsync_VerifyReturnsFailureGivenTokenGenerationFails() =>
await this.VerifyReturnsFailureGivenTokenGenerationFails(configuration =>
new VonageHttpClient(configuration, this.serializer).SendAsync(this.request));
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendAsync(this.request));

[Fact]
public async Task SendAsync_VerifyReturnsUnitGivenApiResponseIsSuccess() =>
await this.VerifyReturnsExpectedValueGivenApiResponseIsSuccess(BuildExpectedRequest(),
configuration => new VonageHttpClient(configuration, this.serializer).SendAsync(this.request));
configuration =>
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendAsync(this.request));

[Fact]
public async Task SendWithoutHeaderAsync_ShouldThrowException_GivenOperationExceedsTimeout() =>
Expand All @@ -69,24 +74,28 @@ await this.VerifyReturnsFailureGivenOperationExceedsTimeout(client =>
[Property]
public Property SendWithoutHeaderAsync_VerifyReturnsFailureGivenApiResponseIsError() =>
this.VerifyReturnsFailureGivenApiResponseIsError(BuildExpectedRequest(),
configuration => new VonageHttpClient(configuration, this.serializer).SendAsync(this.request));
configuration =>
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendAsync(this.request));

[Property]
public Property SendWithoutHeaderAsync_VerifyReturnsFailureGivenErrorCannotBeParsed() =>
this.VerifyReturnsFailureGivenErrorCannotBeParsed(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer).SendWithoutHeadersAsync(this.request));
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendWithoutHeadersAsync(
this.request));

[Fact]
public async Task SendWithoutHeaderAsync_VerifyReturnsFailureGivenRequestIsFailure() =>
await this.VerifyReturnsFailureGivenRequestIsFailure((configuration, failureRequest) =>
new VonageHttpClient(configuration, this.serializer).SendWithoutHeadersAsync(failureRequest));
new VonageHttpClient<VideoApiError>(configuration, this.serializer)
.SendWithoutHeadersAsync(failureRequest));

[Fact]
public async Task SendWithoutHeaderAsync_VerifyReturnsUnitGivenApiResponseIsSuccess() =>
await this.VerifyReturnsExpectedValueGivenApiResponseIsSuccess(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer).SendWithoutHeadersAsync(this.request));
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendWithoutHeadersAsync(
this.request));

[Fact]
public async Task SendWithRawResponseAsync_ShouldThrowException_GivenOperationExceedsTimeout() =>
Expand All @@ -97,29 +106,33 @@ await this.VerifyReturnsFailureGivenOperationExceedsTimeout(client =>
public async Task SendWithRawResponseAsync_VerifyReturnsExpectedValueGivenApiResponseIsSuccess() =>
await this.VerifyReturnsRawContentGivenApiResponseIsSuccess(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer).SendWithRawResponseAsync(this.request));
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendWithRawResponseAsync(
this.request));

[Property]
public Property SendWithRawResponseAsync_VerifyReturnsFailureGivenApiResponseIsError() =>
this.VerifyReturnsFailureGivenApiResponseIsError(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer).SendWithRawResponseAsync(this.request));
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendWithRawResponseAsync(
this.request));

[Property]
public Property SendWithRawResponseAsync_VerifyReturnsFailureGivenErrorCannotBeParsed() =>
this.VerifyReturnsFailureGivenErrorCannotBeParsed(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer).SendWithRawResponseAsync(this.request));
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendWithRawResponseAsync(
this.request));

[Fact]
public async Task SendWithRawResponseAsync_VerifyReturnsFailureGivenRequestIsFailure() =>
await this.VerifyReturnsFailureGivenRequestIsFailure((configuration, failureRequest) =>
new VonageHttpClient(configuration, this.serializer).SendWithRawResponseAsync(failureRequest));
new VonageHttpClient<VideoApiError>(configuration, this.serializer)
.SendWithRawResponseAsync(failureRequest));

[Fact]
public async Task SendWithRawResponseAsync_VerifyReturnsFailureGivenTokenGenerationFails() =>
await this.VerifyReturnsFailureGivenTokenGenerationFails(configuration =>
new VonageHttpClient(configuration, this.serializer).SendWithRawResponseAsync(
new VonageHttpClient<VideoApiError>(configuration, this.serializer).SendWithRawResponseAsync(
this.request));

[Fact]
Expand All @@ -131,45 +144,47 @@ await this.VerifyReturnsFailureGivenOperationExceedsTimeout(client =>
public async Task SendWithResponseAsync_VerifyReturnsExpectedValueGivenApiResponseIsSuccess() =>
await this.VerifyReturnsExpectedValueGivenApiResponseIsSuccess(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer)
new VonageHttpClient<VideoApiError>(configuration, this.serializer)
.SendWithResponseAsync<FakeRequest, FakeResponse>(
this.request));

[Fact]
public async Task SendWithResponseAsync_VerifyReturnsFailureGivenApiResponseCannotBeParsed() =>
await this.VerifyReturnsFailureGivenApiResponseCannotBeParsed(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer)
new VonageHttpClient<StandardApiError>(configuration, this.serializer)
.SendWithResponseAsync<FakeRequest, FakeResponse>(
this.request));

[Property]
public Property SendWithResponseAsync_VerifyReturnsFailureGivenApiResponseIsError() =>
this.VerifyReturnsFailureGivenApiResponseIsError(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer)
new VonageHttpClient<VideoApiError>(configuration, this.serializer)
.SendWithResponseAsync<FakeRequest, FakeResponse>(
this.request));

[Property]
public Property SendWithResponseAsync_VerifyReturnsFailureGivenErrorCannotBeParsed() =>
this.VerifyReturnsFailureGivenErrorCannotBeParsed(BuildExpectedRequest(),
configuration =>
new VonageHttpClient(configuration, this.serializer)
new VonageHttpClient<VideoApiError>(configuration, this.serializer)
.SendWithResponseAsync<FakeRequest, FakeResponse>(
this.request));

[Fact]
public async Task SendWithResponseAsync_VerifyReturnsFailureGivenRequestIsFailure() =>
await this.VerifyReturnsFailureGivenRequestIsFailure((configuration, failureRequest) =>
new VonageHttpClient(configuration, this.serializer).SendWithResponseAsync<FakeRequest, FakeResponse>(
failureRequest));
new VonageHttpClient<VideoApiError>(configuration, this.serializer)
.SendWithResponseAsync<FakeRequest, FakeResponse>(
failureRequest));

[Fact]
public async Task SendWithResponseAsync_VerifyReturnsFailureGivenTokenGenerationFails() =>
await this.VerifyReturnsFailureGivenTokenGenerationFails(configuration =>
new VonageHttpClient(configuration, this.serializer).SendWithResponseAsync<FakeRequest, FakeResponse>(
this.request));
new VonageHttpClient<VideoApiError>(configuration, this.serializer)
.SendWithResponseAsync<FakeRequest, FakeResponse>(
this.request));

private static ExpectedRequest BuildExpectedRequest() =>
new ExpectedRequest
Expand Down Expand Up @@ -246,18 +261,18 @@ private Property VerifyReturnsFailureGivenErrorCannotBeParsed<TResponse>(
.Result
.Should()
.BeFailure(HttpFailure.From(statusCode,
DeserializationFailure.From(typeof(ErrorResponse), jsonError).GetFailureMessage(),
DeserializationFailure.From(typeof(VideoApiError), jsonError).GetFailureMessage(),
jsonError));
});

private async Task VerifyReturnsFailureGivenOperationExceedsTimeout<TResponse>(
Func<VonageHttpClient, Task<Result<TResponse>>> operation)
Func<VonageHttpClient<VideoApiError>, Task<Result<TResponse>>> operation)
{
var httpClient = FakeHttpRequestHandler.Build(HttpStatusCode.OK).WithDelay(TimeSpan.FromSeconds(5))
.ToHttpClient();
httpClient.Timeout = TimeSpan.FromMilliseconds(100);
var client =
new VonageHttpClient(
new VonageHttpClient<VideoApiError>(
new VonageHttpClientConfiguration(httpClient, new AuthenticationHeaderValue("Anonymous"),
this.fixture.Create<string>()), this.serializer);
await operation(client).Should().BeFailureAsync();
Expand Down Expand Up @@ -296,7 +311,7 @@ private async Task VerifyReturnsRawContentGivenApiResponseIsSuccess(ExpectedRequ
result.Should().BeSuccess(expectedResponse);
}

private struct FakeRequest : IVonageRequest
internal struct FakeRequest : IVonageRequest
{
public Guid Id { get; set; }

Expand Down
4 changes: 2 additions & 2 deletions Vonage.Test/Common/Extensions/FsCheckExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public static class FsCheckExtensions
/// Retrieves a generator that produces error responses with invalid status codes.
/// </summary>
/// <returns>An Arbitrary of ErrorResponse.</returns>
internal static Arbitrary<ErrorResponse> GetErrorResponses() =>
internal static Arbitrary<VideoApiError> GetErrorResponses() =>
Arb.From(from message in GetAny<string>().Generator
from code in GetInvalidStatusCodes().Generator
select new ErrorResponse(code, message));
select new VideoApiError(code, message));

/// <summary>
/// Retrieves a HttpStatusCode generator that produces only invalid codes.
Expand Down
Loading

0 comments on commit f11e417

Please sign in to comment.