Skip to content

🔧 Fix HttpContent equality check. #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 31, 2017
Merged
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