Skip to content

Commit

Permalink
Added some useful helper classes for testing http stuff and handling …
Browse files Browse the repository at this point in the history
…embedded test resources
  • Loading branch information
karl-sjogren committed Dec 31, 2022
1 parent 84022d0 commit 2546723
Show file tree
Hide file tree
Showing 17 changed files with 397 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Karls.Templates.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<PackageType>Template</PackageType>
<PackageVersion>1.0.0</PackageVersion>
<PackageVersion>1.1.0</PackageVersion>
<PackageId>Karls.Templates</PackageId>
<Title>Karls Templates</Title>
<Authors>Karl-Johan Sjögren</Authors>
Expand Down
4 changes: 0 additions & 4 deletions templates/opinionated-solution/src/BASE_NAME.Core/Class1.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Net;
using System.Text.Json;
using BASE_NAME.Core.Exceptions;

namespace BASE_NAME.Core.Common;

public abstract class HttpClientBase {
private static readonly JsonSerializerOptions _serializerOptions = new(JsonSerializerDefaults.Web);

protected HttpClientBase(HttpClient httpClient) {
HttpClient = httpClient;
}

protected virtual JsonSerializerOptions SerializerOptions => _serializerOptions;

protected HttpClient HttpClient { get; }

protected virtual async Task EnsureSuccessStatusCodeAsync(HttpResponseMessage response, CancellationToken cancellationToken) {
if(response.IsSuccessStatusCode)
return;

var errorMessage = await GetErrorMessageAsync(response, cancellationToken);

if(response.StatusCode == HttpStatusCode.BadRequest)
throw new HttpBadRequestException(errorMessage, $"400 Bad Request was returned for call to {response.RequestMessage?.RequestUri}.");

if(response.StatusCode == HttpStatusCode.Unauthorized)
throw new HttpUnauthorizedException(errorMessage, $"401 Unauthorized was returned for call to {response.RequestMessage?.RequestUri}.");

if(response.StatusCode == HttpStatusCode.NotFound)
throw new HttpNotFoundException(errorMessage, $"404 Not Found was returned for call to {response.RequestMessage?.RequestUri}.");

if(response.StatusCode == HttpStatusCode.InternalServerError)
throw new HttpInternalServerErrorException(errorMessage, $"500 Internal Server Error was returned for call to {response.RequestMessage?.RequestUri}.");

if(response.StatusCode == HttpStatusCode.ServiceUnavailable)
throw new HttpServiceUnavailableException(errorMessage, $"503 Service Unavailable was returned for call to {response.RequestMessage?.RequestUri}.");

throw new HttpStatusCodeException(response.StatusCode, errorMessage, $"A non-successful status code was returned for call to {response.RequestMessage?.RequestUri}.");
}

protected virtual async Task<string> GetErrorMessageAsync(HttpResponseMessage response, CancellationToken cancellationToken) {
try {
var content = await response.Content.ReadAsStringAsync(cancellationToken);
return content ?? string.Empty;
} catch {
return string.Empty;
}
}

protected virtual async Task<T?> DeserializeResponseAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken) {
var stream = await response.Content.ReadAsStreamAsync(cancellationToken);

return await JsonSerializer.DeserializeAsync<T>(stream, SerializerOptions, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;

namespace BASE_NAME.Core.Exceptions;

[ExcludeFromCodeCoverage]
public class HttpBadRequestException : HttpExceptionBase {
public HttpBadRequestException() : base(HttpStatusCode.BadRequest, null) {
}

public HttpBadRequestException(string? responseMessage) : base(HttpStatusCode.BadRequest, responseMessage) {
}

public HttpBadRequestException(string? responseMessage, string message) : base(HttpStatusCode.BadRequest, responseMessage, message) {
}

public HttpBadRequestException(string? responseMessage, string message, Exception innerException) : base(HttpStatusCode.BadRequest, responseMessage, message, innerException) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Net;

[assembly: SuppressMessage("Readability", "RCS1194", Justification = "Exceptions inheriting from HttpExceptionBase needs a HttpStatusCode in their constructors.", Scope = "NamespaceAndDescendants", Target = "~N:BASE_NAME.Core.Exceptions")]

namespace BASE_NAME.Core.Exceptions;

[ExcludeFromCodeCoverage]
public abstract class HttpExceptionBase : HttpRequestException {
public new HttpStatusCode? StatusCode { get; }
public string? ResponseMessage { get; }

protected HttpExceptionBase(HttpStatusCode statusCode, string? responseMessage) {
StatusCode = statusCode;
ResponseMessage = responseMessage;
}

protected HttpExceptionBase(HttpStatusCode statusCode, string? responseMessage, string message) : base(message) {
StatusCode = statusCode;
ResponseMessage = responseMessage;
}

protected HttpExceptionBase(HttpStatusCode statusCode, string? responseMessage, string message, Exception innerException) : base(message, innerException) {
StatusCode = statusCode;
ResponseMessage = responseMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;

namespace BASE_NAME.Core.Exceptions;

[ExcludeFromCodeCoverage]
public class HttpInternalServerErrorException : HttpExceptionBase {
public HttpInternalServerErrorException() : base(HttpStatusCode.InternalServerError, null) {
}

public HttpInternalServerErrorException(string? responseMessage) : base(HttpStatusCode.InternalServerError, responseMessage) {
}

public HttpInternalServerErrorException(string? responseMessage, string message) : base(HttpStatusCode.InternalServerError, responseMessage, message) {
}

public HttpInternalServerErrorException(string? responseMessage, string message, Exception innerException) : base(HttpStatusCode.InternalServerError, responseMessage, message, innerException) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;

namespace BASE_NAME.Core.Exceptions;

[ExcludeFromCodeCoverage]
public class HttpNotFoundException : HttpExceptionBase {
public HttpNotFoundException() : base(HttpStatusCode.NotFound, null) {
}

public HttpNotFoundException(string? responseMessage) : base(HttpStatusCode.NotFound, responseMessage) {
}

public HttpNotFoundException(string? responseMessage, string message) : base(HttpStatusCode.NotFound, responseMessage, message) {
}

public HttpNotFoundException(string? responseMessage, string message, Exception innerException) : base(HttpStatusCode.NotFound, responseMessage, message, innerException) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;

namespace BASE_NAME.Core.Exceptions;

[ExcludeFromCodeCoverage]
public class HttpServiceUnavailableException : HttpExceptionBase {
public HttpServiceUnavailableException() : base(HttpStatusCode.ServiceUnavailable, null) {
}

public HttpServiceUnavailableException(string? responseMessage) : base(HttpStatusCode.ServiceUnavailable, responseMessage) {
}

public HttpServiceUnavailableException(string? responseMessage, string message) : base(HttpStatusCode.ServiceUnavailable, responseMessage, message) {
}

public HttpServiceUnavailableException(string? responseMessage, string message, Exception innerException) : base(HttpStatusCode.ServiceUnavailable, responseMessage, message, innerException) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;

namespace BASE_NAME.Core.Exceptions;

[ExcludeFromCodeCoverage]
public class HttpStatusCodeException : HttpExceptionBase {
public HttpStatusCodeException(HttpStatusCode httpStatusCode) : base(httpStatusCode, null) {
}

public HttpStatusCodeException(HttpStatusCode httpStatusCode, string? responseMessage) : base(httpStatusCode, responseMessage) {
}

public HttpStatusCodeException(HttpStatusCode httpStatusCode, string? responseMessage, string message) : base(httpStatusCode, responseMessage, message) {
}

public HttpStatusCodeException(HttpStatusCode httpStatusCode, string? responseMessage, string message, Exception innerException) : base(httpStatusCode, responseMessage, message, innerException) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;

namespace BASE_NAME.Core.Exceptions;

[ExcludeFromCodeCoverage]
public class HttpUnauthorizedException : HttpExceptionBase {
public HttpUnauthorizedException() : base(HttpStatusCode.Unauthorized, null) {
}

public HttpUnauthorizedException(string? responseMessage) : base(HttpStatusCode.Unauthorized, responseMessage) {
}

public HttpUnauthorizedException(string? responseMessage, string message) : base(HttpStatusCode.Unauthorized, responseMessage, message) {
}

public HttpUnauthorizedException(string? responseMessage, string message, Exception innerException) : base(HttpStatusCode.Unauthorized, responseMessage, message, innerException) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@
<IsTestProject>false</IsTestProject>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\BASE_NAME.Core\BASE_NAME.Core.csproj" />
</ItemGroup>

</Project>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Net;
using System.Text;

namespace BASE_NAME.TestHelpers.Http;

public class FakeHttpContent : HttpContent {
public string Content { get; set; }

public FakeHttpContent(string content) {
Content = content ?? throw new ArgumentNullException(nameof(content));
}

protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) {
var byteArray = Encoding.UTF8.GetBytes(Content);
await stream.WriteAsync(byteArray);
}

protected override bool TryComputeLength(out long length) {
length = Encoding.UTF8.GetBytes(Content).LongLength;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace BASE_NAME.TestHelpers.Http;

public class FakeHttpMessageHandler : HttpMessageHandler {
private readonly HttpResponseMessage? _response;
private readonly Func<HttpRequestMessage, CancellationToken, HttpResponseMessage>? _responseFunc;
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>>? _asyncResponseFunc;

public FakeHttpMessageHandler(HttpResponseMessage response) {
_response = response;
}

public FakeHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> responseFunc) {
_responseFunc = responseFunc;
}

public FakeHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> asyncResponseFunc) {
_asyncResponseFunc = asyncResponseFunc;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
if(_response != null) {
return _response;
}

if(_responseFunc != null) {
return _responseFunc(request, cancellationToken);
}

if(_asyncResponseFunc != null) {
var response = await _asyncResponseFunc(request, cancellationToken);
return response;
}

throw new InvalidOperationException("This shouldn't be able to happen.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace BASE_NAME.TestHelpers.Http;

public class FuncHttpMessageHandler : HttpMessageHandler {
private readonly Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> _func;

public FuncHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> func) {
_func = func;
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var responseTask = new TaskCompletionSource<HttpResponseMessage>();
responseTask.SetResult(_func(request, cancellationToken));
return responseTask.Task;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Net;
using BASE_NAME.Core.Common;

namespace BASE_NAME.TestHelpers.Http;

public static class HttpClientActivator<T> where T : HttpClientBase {
public static async Task<T> GetClientWithResourceResponseAsync(HttpStatusCode httpStatusCode, string resourceName, Func<HttpClient, T> createClient) {
var responseJson = await Resources.GetStringAsync(resourceName);
var dummyResponse = new HttpResponseMessage(httpStatusCode) { Content = new FakeHttpContent(responseJson) };
var client = new HttpClient(new FakeHttpMessageHandler(dummyResponse)) {
BaseAddress = new Uri("http://www.example.com/")
};

return createClient(client);
}

public static T GetClient(HttpStatusCode httpStatusCode, Func<HttpClient, T> createClient) {
var dummyResponse = new HttpResponseMessage(httpStatusCode);
var client = new HttpClient(new FakeHttpMessageHandler(dummyResponse)) {
BaseAddress = new Uri("http://www.example.com/")
};

return createClient(client);
}

public static T GetClient(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> responseFunc, Func<HttpClient, T> createClient) {
var client = new HttpClient(new FakeHttpMessageHandler(responseFunc)) {
BaseAddress = new Uri("http://www.example.com/")
};

return createClient(client);
}

public static T GetClient(Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> responseFunc, Func<HttpClient, T> createClient) {
var client = new HttpClient(new FakeHttpMessageHandler(responseFunc)) {
BaseAddress = new Uri("http://www.example.com/")
};

return createClient(client);
}
}
Loading

0 comments on commit 2546723

Please sign in to comment.