Skip to content

Commit

Permalink
Support matching requests using headers (#72)
Browse files Browse the repository at this point in the history
* Support matching requests using headers

* Tidy up matching on request headers

- Add null check for RequestHeaders
- Make HeaderMatch and QueryParamMatch fields of EndpointMatchingRule
  • Loading branch information
adamreeve authored and hibri committed May 19, 2017
1 parent bef94b5 commit 81bd0b9
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Compile Include="SystemUnderTest.cs" />
<Compile Include="UsingMultipleServersTests.cs" />
<Compile Include="UsingTheSameStubServerAndDifferentQueryRequestParams.cs" />
<Compile Include="UsingTheSameStubServerAndDifferentRequestHeaders.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="App.config">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using NUnit.Framework;

namespace HttpMock.Integration.Tests
{
[TestFixture]
public class UsingTheSameStubServerAndDifferentRequestHeaders
{
private string _endpointToHit;
private IHttpServer _httpMockRepository;

private readonly IDictionary<string, string> _firstSetOfHeaders = new Dictionary<string, string>
{
{"X-HeaderOne", "one"},
{"X-HeaderTwo", "a"}
};

private readonly IDictionary<string, string> _secondSetOfHeaders = new Dictionary<string, string>
{
{"X-HeaderOne", "one"},
{"X-HeaderTwo", "b"}
};

private readonly IDictionary<string, string> _thirdSetOfHeaders = new Dictionary<string, string>
{
{"X-HeaderOne", "two"},
{"X-HeaderTwo", "a"}
};

[SetUp]
public void SetUp()
{
var hostUrl = HostHelper.GenerateAHostUrlForAStubServer();
_endpointToHit = hostUrl + "/endpoint";
_httpMockRepository = HttpMockRepository.At(hostUrl);

_httpMockRepository.Stub(x => x.Get("/endpoint"))
.WithHeaders(_firstSetOfHeaders)
.Return("I was the first one")
.OK();
_httpMockRepository.Stub(x => x.Get("/endpoint"))
.WithHeaders(_secondSetOfHeaders)
.Return("I was the second one")
.OK();
_httpMockRepository.Stub(x => x.Get("/endpoint"))
.WithHeaders(_thirdSetOfHeaders)
.Return("I was the third one")
.OK();
}

[Test]
public void Should_return_first_one()
{
AssertResponse("I was the first one", _firstSetOfHeaders);
}

[Test]
public void Should_return_second_one()
{
AssertResponse("I was the second one", _secondSetOfHeaders);
}

[Test]
public void Should_return_third_one()
{
AssertResponse("I was the third one", _thirdSetOfHeaders);
}

private void AssertResponse(string expected, IEnumerable<KeyValuePair<string, string>> headers)
{
var webRequest =
(HttpWebRequest) WebRequest.Create(string.Format("{0}?abirdinthehand=twointhebush", _endpointToHit));
foreach (var header in headers)
{
webRequest.Headers.Add(header.Key, header.Value);
}
using (var response = webRequest.GetResponse())
{
using (var sr = new StreamReader(response.GetResponseStream()))
{
var readToEnd = sr.ReadToEnd();
Assert.That(readToEnd, Is.EqualTo(expected));
}
}
}
}
}
79 changes: 79 additions & 0 deletions src/HttpMock.Unit.Tests/EndpointMatchingRuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,62 @@ public void urls_and_methods_and_queryparams_match_it_returns_true() {
Assert.That(endpointMatchingRule.IsEndpointMatch(requestHandler, httpRequestHead));
}

[Test]
public void urls_and_methods_match_headers_differ_it_returns_false() {
var requestHandler = MockRepository.GenerateStub<IRequestHandler>();
requestHandler.Path = "test";
requestHandler.Method = "GET";
requestHandler.QueryParams = new Dictionary<string, string>();
requestHandler.RequestHeaders = new Dictionary<string, string> { { "myHeader", "one" } };

var httpRequestHead = new HttpRequestHead
{
Uri = "test",
Method = "GET",
Headers = new Dictionary<string, string>
{
{ "myHeader", "two" }
}
};
var endpointMatchingRule = new EndpointMatchingRule();
Assert.That(endpointMatchingRule.IsEndpointMatch(requestHandler, httpRequestHead), Is.False);
}

[Test]
public void urls_and_methods_match_and_headers_match_it_returns_true() {
var requestHandler = MockRepository.GenerateStub<IRequestHandler>();
requestHandler.Path = "test";
requestHandler.Method = "GET";
requestHandler.QueryParams = new Dictionary<string, string>();
requestHandler.RequestHeaders = new Dictionary<string, string> { { "myHeader", "one" } };

var httpRequestHead = new HttpRequestHead
{
Uri = "test",
Method = "GET",
Headers = new Dictionary<string, string>
{
{ "myHeader", "one" },
{ "anotherHeader", "two" }
}
};
var endpointMatchingRule = new EndpointMatchingRule();
Assert.That(endpointMatchingRule.IsEndpointMatch(requestHandler, httpRequestHead));
}

[Test]
public void urls_and_methods_match_and_header_does_not_exist_it_returns_false() {
var requestHandler = MockRepository.GenerateStub<IRequestHandler>();
requestHandler.Path = "test";
requestHandler.Method = "GET";
requestHandler.QueryParams = new Dictionary<string, string>();
requestHandler.RequestHeaders = new Dictionary<string, string> { { "myHeader", "one" } };

var httpRequestHead = new HttpRequestHead { Uri = "test", Method = "GET" };
var endpointMatchingRule = new EndpointMatchingRule();
Assert.That(endpointMatchingRule.IsEndpointMatch(requestHandler, httpRequestHead), Is.False);
}

[Test]
public void should_do_a_case_insensitive_match_on_query_string_parameter_values() {

Expand All @@ -131,6 +187,29 @@ public void should_do_a_case_insensitive_match_on_query_string_parameter_values(
Assert.That(endpointMatchingRule.IsEndpointMatch(requestHandler, httpRequestHead));
}

[Test]
public void should_do_a_case_insensitive_match_on_header_names_and_values() {

var requestHandler = MockRepository.GenerateStub<IRequestHandler>();
requestHandler.Path = "test";
requestHandler.Method = "GET";
requestHandler.QueryParams = new Dictionary<string, string>();
requestHandler.RequestHeaders = new Dictionary<string, string> { { "myHeader", "one" } };

var httpRequestHead = new HttpRequestHead
{
Uri = "test",
Method = "GET",
Headers = new Dictionary<string, string>
{
{ "MYheaDER", "OnE" }
}
};

var endpointMatchingRule = new EndpointMatchingRule();
Assert.That(endpointMatchingRule.IsEndpointMatch(requestHandler, httpRequestHead));
}

[Test]
public void should_match_when_the_query_string_has_a_trailing_ampersand()
{
Expand Down
30 changes: 26 additions & 4 deletions src/HttpMock/EndpointMatchingRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,42 @@ namespace HttpMock
{
public class EndpointMatchingRule : IMatchingRule
{
private readonly HeaderMatch _headerMatch;
private readonly QueryParamMatch _queryParamMatch;

public EndpointMatchingRule()
{
_headerMatch = new HeaderMatch();
_queryParamMatch = new QueryParamMatch();
}

public bool IsEndpointMatch(IRequestHandler requestHandler, HttpRequestHead request) {
if (requestHandler.QueryParams == null)
throw new ArgumentException("requestHandler QueryParams cannot be null");

var requestQueryParams = GetQueryParams(request);
var requestHeaders = GetHeaders(request);

bool uriStartsWith = MatchPath(requestHandler, request);

bool uriStartsWith = MatchPath(requestHandler, request);

bool httpMethodsMatch = requestHandler.Method == request.Method;

bool queryParamMatch = true;
bool shouldMatchQueryParams = (requestHandler.QueryParams.Count > 0);

if (shouldMatchQueryParams) {
queryParamMatch = new QueryParamMatch().MatchQueryParams(requestHandler, requestQueryParams);
queryParamMatch = _queryParamMatch.MatchQueryParams(requestHandler, requestQueryParams);
}

bool headerMatch = true;
bool shouldMatchHeaders = requestHandler.RequestHeaders != null
&& requestHandler.RequestHeaders.Count > 0;

if (shouldMatchHeaders) {
headerMatch = _headerMatch.MatchHeaders(requestHandler, requestHeaders);
}

return uriStartsWith && httpMethodsMatch && queryParamMatch;
return uriStartsWith && httpMethodsMatch && queryParamMatch && headerMatch;
}

private static bool MatchPath(IRequestHandler requestHandler, HttpRequestHead request)
Expand Down Expand Up @@ -60,5 +77,10 @@ private static Dictionary<string, string> GetQueryParams(HttpRequestHead request
.ToDictionary(k => k, k => valueCollection[k]);
return requestQueryParams;
}

private static IDictionary<string, string> GetHeaders(HttpRequestHead request)
{
return request.Headers ?? new Dictionary<string, string>();
}
}
}
23 changes: 23 additions & 0 deletions src/HttpMock/HeaderMatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace HttpMock
{
public class HeaderMatch {
internal bool MatchHeaders(IRequestHandler requestHandler, IDictionary<string, string> requestHeaders)
{
return requestHandler.RequestHeaders.All(
expectedHeader => requestHeaders.Any(header => HeadersMatch(expectedHeader, header)));
}

private static bool HeadersMatch(KeyValuePair<string, string> expectedHeader, KeyValuePair<string, string> header)
{
if (!string.Equals(expectedHeader.Key, header.Key, StringComparison.OrdinalIgnoreCase))
{
return false;
}
return string.Equals(expectedHeader.Value, header.Value, StringComparison.OrdinalIgnoreCase);
}
}
}
1 change: 1 addition & 0 deletions src/HttpMock/HttpMock.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<ItemGroup>
<Compile Include="IRequestProcessor.cs" />
<Compile Include="IRequestVerify.cs" />
<Compile Include="HeaderMatch.cs" />
<Compile Include="RequestMatcher.cs" />
<Compile Include="ReceivedRequest.cs" />
<Compile Include="RequestHandlerFactory.cs" />
Expand Down
1 change: 1 addition & 0 deletions src/HttpMock/IRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public interface IRequestHandler {
string Method { get; set; }
IRequestProcessor RequestProcessor { get; set; }
IDictionary<string, string> QueryParams { get; set; }
IDictionary<string, string> RequestHeaders { get; set; }
ResponseBuilder ResponseBuilder { get; }
bool CanVerifyConstraintsFor(string url);
void RecordRequest(HttpRequestHead request, string body);
Expand Down
1 change: 1 addition & 0 deletions src/HttpMock/IRequestStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface IRequestStub
IRequestStub Return(Func<string> responseBody);
IRequestStub ReturnFile(string pathToFile);
IRequestStub WithParams(IDictionary<string, string> nameValueCollection);
IRequestStub WithHeaders(IDictionary<string, string> nameValueCollection);
void OK();
void WithStatus( HttpStatusCode httpStatusCode);
void NotFound();
Expand Down
9 changes: 8 additions & 1 deletion src/HttpMock/RequestHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
Expand All @@ -18,12 +17,14 @@ public RequestHandler(string path, IRequestProcessor requestProcessor) {
Path = path;
RequestProcessor = requestProcessor;
QueryParams = new Dictionary<string, string>();
RequestHeaders = new Dictionary<string, string>();
}

public string Path { get; set; }
public string Method { get; set; }
public IRequestProcessor RequestProcessor { get; set; }
public IDictionary<string, string> QueryParams { get; set; }
public IDictionary<string, string> RequestHeaders { get; set; }

public ResponseBuilder ResponseBuilder {
get { return _webResponseBuilder; }
Expand Down Expand Up @@ -57,6 +58,12 @@ public IRequestStub WithParams(IDictionary<string, string> nameValueCollection)
return this;
}

public IRequestStub WithHeaders(IDictionary<string, string> nameValueCollection)
{
RequestHeaders = nameValueCollection;
return this;
}

public void OK() {
WithStatus(HttpStatusCode.OK);
}
Expand Down

0 comments on commit 81bd0b9

Please sign in to comment.