Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,24 @@ await httpClient.PostAsync("/api/infos", new StringContent("test", Encoding.ASCI

the response returned from the handler will be `415 Unsupported Media Type`


### Handle requests only if they match the authorization header

For some tests you might want to configure the handler to return when the correct authorization token is provided.

To do this you can configure the handler like so:

```csharp
handler
.RespondTo()
.Post()
.ForUrl("/search")
.AndAuthorization(new AuthenticationHeaderValue("Scheme", "Value"))
.With(HttpStatusCode.OK);
```

This allows you to configure multiple responses to a url depending on the authorization of a request.

### Handle requests only if they match the request body

For some tests you might want to configure the handler to return certain responses based on the request content.
Expand Down
8 changes: 8 additions & 0 deletions src/Codenizer.HttpClient.Testable/IRequestBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Net;
using System.Net.Http.Headers;

namespace Codenizer.HttpClient.Testable
{
Expand Down Expand Up @@ -87,6 +88,13 @@ public interface IRequestBuilder
/// <param name="contentType">A MIME content type (for example text/plain)</param>
/// <returns>The current <see cref="IRequestBuilder"/> instance</returns>
IRequestBuilder AndContentType(string contentType);

/// <summary>
/// Respond to a request that matches the authorization header
/// </summary>
/// <param name="authenticationHeader">A authorization header value</param>
/// <returns>The current <see cref="IRequestBuilder"/> instance</returns>
IRequestBuilder AndAuthorization(AuthenticationHeaderValue authenticationHeader);

/// <summary>
/// Respond to a request that matches the accept header
Expand Down
17 changes: 17 additions & 0 deletions src/Codenizer.HttpClient.Testable/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;

Expand Down Expand Up @@ -118,6 +119,12 @@ internal RequestBuilder(HttpMethod method, string pathAndQuery, string? contentT
/// Optional. The value of the Accept header to match.
/// </summary>
public string? Accept { get; private set; }


/// <summary>
/// Optional. The value of the Accept header to match.
/// </summary>
public AuthenticationHeaderValue? AuthorizationHeader { get; private set; }

/// <summary>
/// Optional. The expected content of the request.
Expand Down Expand Up @@ -245,6 +252,11 @@ public IRequestBuilder AndContentType(string contentType)
return this;
}

public IRequestBuilder AndAuthorization(AuthenticationHeaderValue authenticationHeader) {
AuthorizationHeader = authenticationHeader;
return this;
}

/// <inheritdoc />
public IRequestBuilder Accepting(string mimeType)
{
Expand Down Expand Up @@ -396,6 +408,11 @@ internal Dictionary<string, string> BuildRequestHeaders()
{
var headers = new Dictionary<string, string>();

if (AuthorizationHeader is not null)
{
headers.Add("Authorization", AuthorizationHeader.ToString());
}

if (!string.IsNullOrEmpty(Accept))
{
headers.Add("Accept", Accept!);
Expand Down
118 changes: 104 additions & 14 deletions test/Codenizer.HttpClient.Testable.Tests.Unit/WhenMatchingRoutes.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using FluentAssertions;
using Xunit;

Expand All @@ -9,14 +13,18 @@ namespace Codenizer.HttpClient.Testable.Tests.Unit
public static class ConfiguredRequestsExtensions
{
internal static RequestBuilder? Match(this ConfiguredRequests configuredRequests, HttpMethod method, string uri,
string? accept)
string? accept, AuthenticationHeaderValue? authorization)
{
var requestMessage = new HttpRequestMessage(method, uri);
if (!string.IsNullOrEmpty(accept))
{
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(accept));
}

if (authorization != null) {
requestMessage.Headers.Authorization = authorization;
}

return configuredRequests.Match(requestMessage);
}
}
Expand All @@ -39,7 +47,7 @@ public void GivenPathWithQueryParameters_ReturnedRequestBuilderMatches()
.Match(
HttpMethod.Get,
"/api/foo/bar?blah=blurb",
null)
null, null)
.Should()
.Be(requestBuilder);
}
Expand All @@ -60,10 +68,57 @@ public void GivenNonConfigured_NoResultIsReturned()
.Match(
HttpMethod.Get,
"/api/baz",
null)
null, null)
.Should()
.BeNull();
}


[Fact]
public void GivenRouteWithAuthorizationHeader_RequestBuilderIsReturned()
{
var requestBuilder = new RequestBuilder(HttpMethod.Get, "/api/foos/{id}", null);
requestBuilder.AndAuthorization(new AuthenticationHeaderValue("Bearer"));

var routes = new List<RequestBuilder>
{
requestBuilder
};

var dictionary = ConfiguredRequests.FromRequestBuilders(routes);

dictionary
.Match(
HttpMethod.Get,
"/api/foos/1234",
null,
new AuthenticationHeaderValue("Bearer"))
.Should()
.Be(requestBuilder);
}


[Fact]
public void GivenRouteAuthorizationHeaderAndEmptyRequestAuthorization_RequestBuilderIsNotReturned()
{
var requestBuilder = new RequestBuilder(HttpMethod.Get, "/api/foos/{id}", null);
requestBuilder.AndAuthorization(new AuthenticationHeaderValue("Bearer"));

var routes = new List<RequestBuilder>
{
requestBuilder
};

var dictionary = ConfiguredRequests.FromRequestBuilders(routes);

dictionary
.Match(
HttpMethod.Get,
"/api/foos/1234",
null, null)
.Should()
.Be(null);
}

[Fact]
public void GivenRouteWithParameter_RequestBuilderIsReturned()
Expand All @@ -81,7 +136,7 @@ public void GivenRouteWithParameter_RequestBuilderIsReturned()
.Match(
HttpMethod.Get,
"/api/foos/1234",
null)
null, null)
.Should()
.Be(requestBuilder);
}
Expand All @@ -102,7 +157,7 @@ public void GivenRouteWithParameterAndQueryString_RequestBuilderIsReturned()
.Match(
HttpMethod.Get,
"/api/foos/1234/?blah=baz",
null)
null, null)
.Should()
.Be(requestBuilder);
}
Expand All @@ -123,7 +178,7 @@ public void GivenRouteWithParameterAndQueryStringWithoutSeparator_RequestBuilder
.Match(
HttpMethod.Get,
"/api/foos/1234?blah=baz",
null)
null, null)
.Should()
.Be(requestBuilder);
}
Expand All @@ -143,7 +198,7 @@ public void GivenRouteWithParameterAndQueryStringWithoutSeparatorX_RequestBuilde
.Match(
HttpMethod.Get,
"/api/foos/1234?blah=qux",
null)
null, null)
.Should()
.Be(routes[1]);
}
Expand All @@ -165,7 +220,7 @@ public void GivenResponseWithAcceptHeaderAndNoAcceptHeaderInRequest_RequestDoesN
.Match(
HttpMethod.Get,
"/api/foo",
null)
null, null)
.Should()
.BeNull();
}
Expand All @@ -187,7 +242,7 @@ public void GivenResponseWithAcceptHeaderAndAcceptHeaderInRequestMatches_Respons
.Match(
HttpMethod.Get,
"/api/foo",
"foo/bar")
"foo/bar", null)
.Should()
.NotBeNull();
}
Expand All @@ -209,7 +264,7 @@ public void GivenResponseWithAcceptHeaderAndAcceptHeaderInRequestDoesNotMatch_Re
.Match(
HttpMethod.Get,
"/api/foo",
"derp/derp")
"derp/derp", null)
.Should()
.BeNull();
}
Expand All @@ -234,7 +289,7 @@ public void GivenTwoResponsesWithDifferentAcceptHeaderAndAcceptHeaderInRequestMa
.Match(
HttpMethod.Get,
"/api/foo",
"baz/quux")
"baz/quux", null)
.Should()
.BeOfType<RequestBuilder>()
.Which
Expand Down Expand Up @@ -263,7 +318,7 @@ public void GivenTwoResponsesWithDifferentAcceptHeaderAndAcceptHeaderInRequestMa
.Match(
HttpMethod.Get,
"/api/foo?bar=baz",
"baz/quux")
"baz/quux", null)
.Should()
.BeOfType<RequestBuilder>()
.Which
Expand All @@ -272,6 +327,41 @@ public void GivenTwoResponsesWithDifferentAcceptHeaderAndAcceptHeaderInRequestMa
.Be("baz/quux");
}

[Fact]
public void GivenTwoResponsesWithDifferentAuthorizationInRequest_ResponseBuilderIsReturned() {
var requestBuilderOne = new RequestBuilder(HttpMethod.Get, "/api/foo?bar=baz", null)
.AndAuthorization(new AuthenticationHeaderValue("BEARER", "Value"));
var requestBuilderTwo = new RequestBuilder(HttpMethod.Get, "/api/foo?bar=baz", null)
.AndAuthorization(new AuthenticationHeaderValue("BEARER"));

var routes = new List<RequestBuilder>
{
(RequestBuilder)requestBuilderOne,
(RequestBuilder)requestBuilderTwo
};

var dictionary = ConfiguredRequests.FromRequestBuilders(routes);

dictionary
.Match(
HttpMethod.Get,
"/api/foo?bar=baz",
"baz/quux",
new AuthenticationHeaderValue("BEARER", "Value"))
.Should()
.Be(requestBuilderOne);


dictionary
.Match(
HttpMethod.Get,
"/api/foo?bar=baz",
"baz/quux",
new AuthenticationHeaderValue("BEARER"))
.Should()
.Be(requestBuilderTwo);
}

[Fact]
public void GivenRouteHasExtraPartInPath_ShouldNotReturnAMatch()
{
Expand All @@ -282,7 +372,7 @@ public void GivenRouteHasExtraPartInPath_ShouldNotReturnAMatch()

var dictionary = ConfiguredRequests.FromRequestBuilders(routes);

dictionary.Match(HttpMethod.Post, "/api/v2/foos/1/bla-bla", "application/json")
dictionary.Match(HttpMethod.Post, "/api/v2/foos/1/bla-bla", "application/json", null)
.Should()
.BeNull();
}
Expand Down
Loading