Skip to content

Commit

Permalink
🔧 Fix HttpContent equality check. (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
PureKrome authored Mar 31, 2017
1 parent b243bc5 commit 8a62400
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 19 deletions.
71 changes: 55 additions & 16 deletions src/HttpClient.Helpers/FakeMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,21 @@ private HttpMessageOptions GetExpectedOption(HttpMessageOptions option)
throw new ArgumentNullException(nameof(option));
}

return _lotsOfOptions.Values.SingleOrDefault(x => (x.RequestUri.Equals(option.RequestUri, StringComparison.OrdinalIgnoreCase) ||
x.RequestUri == HttpMessageOptions.NoValue) &&
(x.HttpMethod == option.HttpMethod ||
x.HttpMethod == null) &&
(x.HttpContent == option.HttpContent ||
x.HttpContent == null) &&
(x.Headers == null ||
x.Headers.Count == 0) ||
(x.Headers != null &&
HeaderExists(x.Headers, option.Headers)));
// NOTE: We only compare the *setup* HttpMessageOptions properties if they were provided.
// So if there was no HEADERS provided ... but the real 'option' has some, we still ignore
// and don't compare.
return _lotsOfOptions.Values.SingleOrDefault(x => (x.RequestUri == HttpMessageOptions.NoValue || // Don't care about the Request Uri.
x.RequestUri.Equals(option.RequestUri, StringComparison.OrdinalIgnoreCase)) &&

(x.HttpMethod == null || // Don't care about the HttpMethod.
x.HttpMethod == option.HttpMethod) &&

(x.HttpContent == null || // Don't care about the Content.
ContentAreEqual(x.HttpContent, option.HttpContent)) &&

(x.Headers == null || // Don't care about the Header.
x.Headers.Count == 0 || // No header's were supplied, so again don't care/
HeadersAreEqual(x.Headers, option.Headers)));
}

private static void IncrementCalls(HttpMessageOptions options)
Expand All @@ -168,17 +173,51 @@ private static void IncrementCalls(HttpMessageOptions options)
propertyInfo.SetValue(options, ++existingValue);
}

private static bool HeaderExists(IDictionary<string, IEnumerable<string>> source,
IDictionary<string, IEnumerable<string>> destination)
private static bool ContentAreEqual(HttpContent source, HttpContent destination)
{
if (source == null &&
destination == null)
{
// Both are null - so they match :P
return true;
}

if (source == null ||
destination == null)
{
return false;
}

// Extract the content from both HttpContent's.
var sourceContentTask = source.ReadAsStringAsync();
var destinationContentTask = destination.ReadAsStringAsync();
var tasks = new List<Task>
{
sourceContentTask,
destinationContentTask
};
Task.WaitAll(tasks.ToArray());

// Now compare both results.
// NOTE: Case sensitive.
return sourceContentTask.Result == destinationContentTask.Result;
}

private static bool HeadersAreEqual(IDictionary<string, IEnumerable<string>> source,
IDictionary<string, IEnumerable<string>> destination)
{
if (source == null)
if (source == null &&
destination == null)
{
throw new ArgumentNullException(nameof(source));
// Nothing from both .. so that's ok!
return true;
}

if (destination == null)
if (source == null ||
destination == null)
{
throw new ArgumentNullException(nameof(destination));
// At least one is different so don't bother checking.
return false;
}

// Both sides are not the same size.
Expand Down
24 changes: 21 additions & 3 deletions tests/HttpClient.Helpers.Tests/PostAsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,25 @@ public static IEnumerable<object[]> ValidPostHttpContent
{
get
{
// Source content, Expected Content.
// NOTE: we have to duplicate the source/expected below so we
// test that they are **separate memory references** and not the same
// memory reference.
yield return new object[]
{
// Sample json.
new StringContent("{\"id\":1}", Encoding.UTF8),
new StringContent("{\"id\":1}", Encoding.UTF8)
};

yield return new object[]
{
// Form key/values.
new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("a", "b"),
new KeyValuePair<string, string>("c", "1")
}),
new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("a", "b"),
Expand All @@ -47,6 +57,13 @@ public static IEnumerable<object[]> InvalidPostHttpContent
new StringContent("{\"id\":2}", Encoding.UTF8)
};

yield return new object[]
{
// Sample json.
new StringContent("{\"id\":1}", Encoding.UTF8),
new StringContent("{\"ID\":1}", Encoding.UTF8) // Case has changed.
};

yield return new object[]
{
// Form key/values.
Expand All @@ -65,7 +82,8 @@ public static IEnumerable<object[]> InvalidPostHttpContent

[Theory]
[MemberData(nameof(ValidPostHttpContent))]
public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent httpContent)
public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent expectedHttpContent,
HttpContent sentHttpContent)
{
// Arrange.
const string requestUri = "http://www.something.com/some/website";
Expand All @@ -74,7 +92,7 @@ public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent h
{
HttpMethod = HttpMethod.Post,
RequestUri = requestUri,
HttpContent = httpContent,
HttpContent = expectedHttpContent, // This makes sure it's two separate memory references.
HttpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(responseContent)
Expand All @@ -88,7 +106,7 @@ public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent h
using (var httpClient = new System.Net.Http.HttpClient(messageHandler))
{
// Act.
message = await httpClient.PostAsync(requestUri, httpContent);
message = await httpClient.PostAsync(requestUri, sentHttpContent);
content = await message.Content.ReadAsStringAsync();
}

Expand Down

0 comments on commit 8a62400

Please sign in to comment.