Skip to content

Commit 8a62400

Browse files
authored
🔧 Fix HttpContent equality check. (#25)
1 parent b243bc5 commit 8a62400

File tree

2 files changed

+76
-19
lines changed

2 files changed

+76
-19
lines changed

src/HttpClient.Helpers/FakeMessageHandler.cs

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,21 @@ private HttpMessageOptions GetExpectedOption(HttpMessageOptions option)
138138
throw new ArgumentNullException(nameof(option));
139139
}
140140

141-
return _lotsOfOptions.Values.SingleOrDefault(x => (x.RequestUri.Equals(option.RequestUri, StringComparison.OrdinalIgnoreCase) ||
142-
x.RequestUri == HttpMessageOptions.NoValue) &&
143-
(x.HttpMethod == option.HttpMethod ||
144-
x.HttpMethod == null) &&
145-
(x.HttpContent == option.HttpContent ||
146-
x.HttpContent == null) &&
147-
(x.Headers == null ||
148-
x.Headers.Count == 0) ||
149-
(x.Headers != null &&
150-
HeaderExists(x.Headers, option.Headers)));
141+
// NOTE: We only compare the *setup* HttpMessageOptions properties if they were provided.
142+
// So if there was no HEADERS provided ... but the real 'option' has some, we still ignore
143+
// and don't compare.
144+
return _lotsOfOptions.Values.SingleOrDefault(x => (x.RequestUri == HttpMessageOptions.NoValue || // Don't care about the Request Uri.
145+
x.RequestUri.Equals(option.RequestUri, StringComparison.OrdinalIgnoreCase)) &&
146+
147+
(x.HttpMethod == null || // Don't care about the HttpMethod.
148+
x.HttpMethod == option.HttpMethod) &&
149+
150+
(x.HttpContent == null || // Don't care about the Content.
151+
ContentAreEqual(x.HttpContent, option.HttpContent)) &&
152+
153+
(x.Headers == null || // Don't care about the Header.
154+
x.Headers.Count == 0 || // No header's were supplied, so again don't care/
155+
HeadersAreEqual(x.Headers, option.Headers)));
151156
}
152157

153158
private static void IncrementCalls(HttpMessageOptions options)
@@ -168,17 +173,51 @@ private static void IncrementCalls(HttpMessageOptions options)
168173
propertyInfo.SetValue(options, ++existingValue);
169174
}
170175

171-
private static bool HeaderExists(IDictionary<string, IEnumerable<string>> source,
172-
IDictionary<string, IEnumerable<string>> destination)
176+
private static bool ContentAreEqual(HttpContent source, HttpContent destination)
177+
{
178+
if (source == null &&
179+
destination == null)
180+
{
181+
// Both are null - so they match :P
182+
return true;
183+
}
184+
185+
if (source == null ||
186+
destination == null)
187+
{
188+
return false;
189+
}
190+
191+
// Extract the content from both HttpContent's.
192+
var sourceContentTask = source.ReadAsStringAsync();
193+
var destinationContentTask = destination.ReadAsStringAsync();
194+
var tasks = new List<Task>
195+
{
196+
sourceContentTask,
197+
destinationContentTask
198+
};
199+
Task.WaitAll(tasks.ToArray());
200+
201+
// Now compare both results.
202+
// NOTE: Case sensitive.
203+
return sourceContentTask.Result == destinationContentTask.Result;
204+
}
205+
206+
private static bool HeadersAreEqual(IDictionary<string, IEnumerable<string>> source,
207+
IDictionary<string, IEnumerable<string>> destination)
173208
{
174-
if (source == null)
209+
if (source == null &&
210+
destination == null)
175211
{
176-
throw new ArgumentNullException(nameof(source));
212+
// Nothing from both .. so that's ok!
213+
return true;
177214
}
178215

179-
if (destination == null)
216+
if (source == null ||
217+
destination == null)
180218
{
181-
throw new ArgumentNullException(nameof(destination));
219+
// At least one is different so don't bother checking.
220+
return false;
182221
}
183222

184223
// Both sides are not the same size.

tests/HttpClient.Helpers.Tests/PostAsyncTests.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,25 @@ public static IEnumerable<object[]> ValidPostHttpContent
1818
{
1919
get
2020
{
21+
// Source content, Expected Content.
22+
// NOTE: we have to duplicate the source/expected below so we
23+
// test that they are **separate memory references** and not the same
24+
// memory reference.
2125
yield return new object[]
2226
{
2327
// Sample json.
28+
new StringContent("{\"id\":1}", Encoding.UTF8),
2429
new StringContent("{\"id\":1}", Encoding.UTF8)
2530
};
2631

2732
yield return new object[]
2833
{
2934
// Form key/values.
35+
new FormUrlEncodedContent(new[]
36+
{
37+
new KeyValuePair<string, string>("a", "b"),
38+
new KeyValuePair<string, string>("c", "1")
39+
}),
3040
new FormUrlEncodedContent(new[]
3141
{
3242
new KeyValuePair<string, string>("a", "b"),
@@ -47,6 +57,13 @@ public static IEnumerable<object[]> InvalidPostHttpContent
4757
new StringContent("{\"id\":2}", Encoding.UTF8)
4858
};
4959

60+
yield return new object[]
61+
{
62+
// Sample json.
63+
new StringContent("{\"id\":1}", Encoding.UTF8),
64+
new StringContent("{\"ID\":1}", Encoding.UTF8) // Case has changed.
65+
};
66+
5067
yield return new object[]
5168
{
5269
// Form key/values.
@@ -65,7 +82,8 @@ public static IEnumerable<object[]> InvalidPostHttpContent
6582

6683
[Theory]
6784
[MemberData(nameof(ValidPostHttpContent))]
68-
public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent httpContent)
85+
public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent expectedHttpContent,
86+
HttpContent sentHttpContent)
6987
{
7088
// Arrange.
7189
const string requestUri = "http://www.something.com/some/website";
@@ -74,7 +92,7 @@ public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent h
7492
{
7593
HttpMethod = HttpMethod.Post,
7694
RequestUri = requestUri,
77-
HttpContent = httpContent,
95+
HttpContent = expectedHttpContent, // This makes sure it's two separate memory references.
7896
HttpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
7997
{
8098
Content = new StringContent(responseContent)
@@ -88,7 +106,7 @@ public async Task GivenAPostRequest_PostAsync_ReturnsAFakeResponse(HttpContent h
88106
using (var httpClient = new System.Net.Http.HttpClient(messageHandler))
89107
{
90108
// Act.
91-
message = await httpClient.PostAsync(requestUri, httpContent);
109+
message = await httpClient.PostAsync(requestUri, sentHttpContent);
92110
content = await message.Content.ReadAsStringAsync();
93111
}
94112

0 commit comments

Comments
 (0)