Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.CodeDom.Compiler;
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -66,6 +67,11 @@ public override void Scheme(string scheme)
_indentedWriter.WriteLine($"{scheme}://");
}

public override void When(object userData) {
_indentedWriter.Indent = 1;
_indentedWriter.WriteLine($"When: {userData}");
}

public override void Method(HttpMethod method)
{
_indentedWriter.Indent = 0;
Expand Down
24 changes: 20 additions & 4 deletions src/Codenizer.HttpClient.Testable/ConfiguredRequests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -35,8 +35,15 @@ public ConfiguredRequests(IEnumerable<RequestBuilder> requestBuilders)
.Split('?').First();

var pathNode = authorityNode.Add(path);

var queryNode = pathNode.Add(requestBuilder.QueryParameters, requestBuilder.QueryStringAssertions);

RequestQueryNode queryNode;
if (requestBuilder.Predicate != null) {
var whenNode = pathNode.Add(requestBuilder.Predicate, requestBuilder.UserObject);
queryNode = whenNode.Add(requestBuilder.QueryParameters, requestBuilder.QueryStringAssertions);
}
else
queryNode = pathNode.Add(requestBuilder.QueryParameters, requestBuilder.QueryStringAssertions);


var headers = requestBuilder.BuildRequestHeaders();

Expand Down Expand Up @@ -117,7 +124,16 @@ private static void ThrowIfRouteIsNotFullyConfigured(RequestBuilder route)
? httpRequestMessage.RequestUri.OriginalString.Split('?').Last()
: null;

var queryNode = pathNode.Match(query);

RequestQueryNode queryNode;
if (pathNode.HasWhenNodes()) {
var whenNode = pathNode.Match(httpRequestMessage);
if (whenNode == null)
return null;
queryNode = whenNode.Match(query);
}
else
queryNode = pathNode.Match(query);

if (queryNode == null)
{
Expand Down
9 changes: 9 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;

namespace Codenizer.HttpClient.Testable
{
Expand Down Expand Up @@ -88,6 +89,14 @@ public interface IRequestBuilder
/// <returns>The current <see cref="IRequestBuilder"/> instance</returns>
IRequestBuilder AndContentType(string contentType);


/// <summary>
/// Respond to a request that matches the given content type
/// </summary>
/// <param name="contentType">A MIME content type (for example text/plain)</param>
/// <returns>The current <see cref="IRequestBuilder"/> instance</returns>
IRequestBuilder AndWhen(object userData, Func<HttpRequestMessage, object, bool> predicate);

/// <summary>
/// Respond to a request that matches the accept header
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions src/Codenizer.HttpClient.Testable/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ internal RequestBuilder(HttpMethod method, string pathAndQuery, string? contentT
/// </summary>
public object? Data { get; private set; }
/// <summary>
/// Optional. A user defined function to inspect the message and make a dynamic Match decision
/// </summary>
public Func<HttpRequestMessage, object, bool>? Predicate { get; private set; }
/// <summary>
/// When Predicate is set, the UserObject is defined and passed to the predicate
/// Also used to Add in the RequestBuilders
/// </summary>
public object? UserObject { get; private set; }
/// <summary>
/// Optional. The callback to invoke when generating the response to a request.
/// </summary>
public Func<HttpRequestMessage, object>? ResponseCallback { get; private set; }
Expand Down Expand Up @@ -245,6 +254,12 @@ public IRequestBuilder AndContentType(string contentType)
return this;
}

public IRequestBuilder AndWhen(object userData, Func<HttpRequestMessage, object, bool> predicate) {
Predicate = predicate;
UserObject = userData;
return this;
}

/// <inheritdoc />
public IRequestBuilder Accepting(string mimeType)
{
Expand Down
4 changes: 3 additions & 1 deletion src/Codenizer.HttpClient.Testable/RequestNodeVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http;
using System;
using System.Net.Http;

namespace Codenizer.HttpClient.Testable
{
Expand All @@ -9,6 +10,7 @@ internal abstract class RequestNodeVisitor
public abstract void Path(string path);
public abstract void Authority(string authority);
public abstract void Scheme(string scheme);
public abstract void When(object userData);
public abstract void Method(HttpMethod method);
public abstract void Response(RequestBuilder requestBuilder);
public abstract void Content(string expectedContent);
Expand Down
30 changes: 27 additions & 3 deletions src/Codenizer.HttpClient.Testable/RequestPathNode.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;

namespace Codenizer.HttpClient.Testable
{
internal class RequestPathNode : RequestNode
{
public string Path { get; }
private readonly List<RequestQueryNode> _queryNodes = new List<RequestQueryNode>();
private readonly List<RequestWhenNode> _whenNodes = new ();
private readonly List<RequestQueryNode> _queryNodes = new ();

public RequestPathNode(string path)
{
Path = path;
}

public RequestWhenNode Add(Func<HttpRequestMessage, object, bool>? predicate, object? userValue) {

var existingWhen = _whenNodes.SingleOrDefault(node => node.Matches(userValue));

if (existingWhen is null) {
_whenNodes.Add(existingWhen = new RequestWhenNode(
predicate ?? ((_, _) => true),
userValue ?? new object()));
}

return existingWhen;
}


public RequestQueryNode Add(
List<KeyValuePair<string, string?>> queryParameters,
List<QueryStringAssertion> queryStringAssertions)
Expand All @@ -33,6 +50,13 @@ public RequestQueryNode Add(
return _queryNodes.SingleOrDefault(node => node.Matches(queryString));
}

public RequestWhenNode? Match(HttpRequestMessage request)
{
return _whenNodes.SingleOrDefault(node => node.Matches(request));
}

public bool HasWhenNodes() => _whenNodes.Any();

public bool MatchesPath(string path)
{
if (PathHasRouteParameters())
Expand Down Expand Up @@ -82,7 +106,7 @@ public override void Accept(RequestNodeVisitor visitor)
{
visitor.Path(Path);

foreach (var node in _queryNodes)
foreach (var node in _whenNodes)
{
node.Accept(visitor);
}
Expand Down
63 changes: 63 additions & 0 deletions src/Codenizer.HttpClient.Testable/RequestWhenNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Codenizer.HttpClient.Testable
{
internal class RequestWhenNode : RequestNode {
private readonly Func<HttpRequestMessage, object, bool> _predicate;
private readonly object _userObject;
private readonly List<RequestQueryNode> _queryNodes = new List<RequestQueryNode>();


public RequestWhenNode(Func<HttpRequestMessage, object, bool> predicate, object userObject) {
_predicate = predicate;
_userObject = userObject;
}


public RequestQueryNode Add(
List<KeyValuePair<string, string?>> queryParameters,
List<QueryStringAssertion> queryStringAssertions)
{
var existingQuery = _queryNodes.SingleOrDefault(node => node.Matches(queryParameters));

if (existingQuery == null)
{
existingQuery = new RequestQueryNode(queryParameters, queryStringAssertions);
_queryNodes.Add(existingQuery);
}

return existingQuery;
}

public RequestQueryNode? Match(string queryString)
{
return _queryNodes.SingleOrDefault(node => node.Matches(queryString));
}

public bool Matches(object? userObject) {
if (userObject == null)
return false;
return _userObject == userObject;
}

public bool Matches(HttpRequestMessage message) {
return _predicate(message, _userObject);
}

public override void Accept(RequestNodeVisitor visitor)
{
visitor.When(_predicate);

foreach (var node in _queryNodes)
{
node.Accept(visitor);
}
}


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,51 @@ public void GivenPathWithQueryParameters_ReturnedRequestBuilderMatches()
.Should()
.Be(requestBuilder);
}


[Fact]
public void GivenPathWithQueryParametersAndWhen_ReturnedRequestBuilderMatches()
{
var requestBuilder = new RequestBuilder(HttpMethod.Get, "/api/foo/bar?blah=blurb", null);
requestBuilder.AndWhen(new { }, (message, o) => true);

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

var dictionary = ConfiguredRequests.FromRequestBuilders(routes);

dictionary
.Match(
HttpMethod.Get,
"/api/foo/bar?blah=blurb",
null)
.Should()
.Be(requestBuilder);
}

[Fact]
public void GivenPathWithQueryParametersAndWhen_ReturnedRequestBuilderNoMatch()
{
var requestBuilder = new RequestBuilder(HttpMethod.Get, "/api/foo/bar?blah=blurb", null);
requestBuilder.AndWhen(new { }, (message, o) => true);

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

var dictionary = ConfiguredRequests.FromRequestBuilders(routes);

dictionary
.Match(
HttpMethod.Get,
"/api/foo/bar?blah=blurb",
null)
.Should()
.Be(requestBuilder);
}

[Fact]
public void GivenNonConfigured_NoResultIsReturned()
Expand Down