Skip to content

Commit f072480

Browse files
committed
Add support for header matching
1 parent 94b460b commit f072480

File tree

2 files changed

+143
-2
lines changed

2 files changed

+143
-2
lines changed

src/TestableHttpClient/Utils/HttpRequestMessagePattern.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ internal sealed class HttpRequestMessagePattern
55
public Value<HttpMethod> Method { get; init; } = Value.Any<HttpMethod>();
66
public UriPattern RequestUri { get; init; } = UriPattern.Any;
77
public Value<Version> Version { get; init; } = Value.Any<Version>();
8-
9-
// public ??? Headers { get; init; }
8+
public Dictionary<Value<string>, Value<string>> Headers { get; init; } = new() { [Value.Any<string>()] = Value.Any<string>() };
109
public Value<string> Content { get; init; } = Value.Any<string>();
1110

1211
public HttpRequestMessagePatternMatchingResult Matches(HttpRequestMessage httpRequestMessage, HttpRequestMessagePatternMatchingOptions options) =>
@@ -15,9 +14,46 @@ public HttpRequestMessagePatternMatchingResult Matches(HttpRequestMessage httpRe
1514
Method = Method.Matches(httpRequestMessage.Method, false),
1615
RequestUri = RequestUri.Matches(httpRequestMessage.RequestUri, options.RequestUriMatchingOptions),
1716
Version = Version.Matches(httpRequestMessage.Version, false),
17+
Headers = MatchesHeaders(httpRequestMessage),
1818
Content = MatchesContent(httpRequestMessage.Content)
1919
};
2020

21+
private bool MatchesHeaders(HttpRequestMessage httpRequestMessage)
22+
{
23+
IEnumerable<KeyValuePair<string, IEnumerable<string>>> requestHeaders = httpRequestMessage.Headers;
24+
if (httpRequestMessage.Content is not null)
25+
{
26+
requestHeaders = requestHeaders.Concat(httpRequestMessage.Content.Headers);
27+
}
28+
29+
return MatchesHeaders(requestHeaders.ToDictionary(x => x.Key, x => string.Join(", ", x.Value), StringComparer.OrdinalIgnoreCase));
30+
}
31+
32+
private bool MatchesHeaders(Dictionary<string, string> requestHeaders)
33+
{
34+
if (Headers.Count == 0 && requestHeaders.Count > 0)
35+
{
36+
return false;
37+
}
38+
39+
foreach (var header in Headers)
40+
{
41+
bool matched = false;
42+
43+
foreach (var requestHeader in requestHeaders)
44+
{
45+
matched |= header.Key.Matches(requestHeader.Key, true) && header.Value.Matches(requestHeader.Value, true);
46+
}
47+
48+
if (!matched)
49+
{
50+
return false;
51+
}
52+
}
53+
54+
return true;
55+
}
56+
2157
private bool MatchesContent(HttpContent? requestContent)
2258
{
2359
if (requestContent == null)
@@ -41,6 +77,7 @@ internal sealed class HttpRequestMessagePatternMatchingResult
4177
public bool Method { get; init; }
4278
public bool RequestUri { get; init; }
4379
public bool Version { get; init; }
80+
public bool Headers { get; init; }
4481
public bool Content { get; init; }
4582
}
4683

test/TestableHttpClient.Tests/Utils/HttpRequestMessagePatternTests.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public void Matches_HttpRequestMessageWithAnyHttpMethod_MatchesAllHttpMethods(st
2727

2828
[Theory]
2929
[InlineData("GET", true)]
30+
[InlineData("get", true)]
31+
[InlineData("Get", true)]
3032
[InlineData("POST", true)]
3133
[InlineData("PATCH", false)]
3234
[InlineData("PUT", false)]
@@ -127,4 +129,106 @@ public void Matches_HttpRequestMessageWithNotMatchingBody_DoesNotMatchExactBody(
127129

128130
Assert.False(sut.Matches(matchingRequest, defaultOptions).Content);
129131
}
132+
133+
[Fact]
134+
public void Matches_HttpRequestMessageWithNoHeaders_DoesMatchNoHeaders()
135+
{
136+
HttpRequestMessagePattern sut = new()
137+
{
138+
Headers = new()
139+
};
140+
141+
using HttpRequestMessage matchingRequest = new();
142+
143+
Assert.True(sut.Matches(matchingRequest, defaultOptions).Headers);
144+
}
145+
146+
[Fact]
147+
public void Matches_HttpRequestMessageWithHeaders_DoesNotMatchNoHeaders()
148+
{
149+
HttpRequestMessagePattern sut = new()
150+
{
151+
Headers = new()
152+
};
153+
154+
using HttpRequestMessage matchingRequest = new();
155+
matchingRequest.Headers.Host = "localhost";
156+
157+
Assert.False(sut.Matches(matchingRequest, defaultOptions).Headers);
158+
}
159+
160+
[Fact]
161+
public void Matches_HttpRequestMessageWithHeaders_DoesMatchAnyHeaders()
162+
{
163+
HttpRequestMessagePattern sut = new();
164+
165+
using HttpRequestMessage matchingRequest = new();
166+
matchingRequest.Headers.Host = "localhost";
167+
168+
Assert.True(sut.Matches(matchingRequest, defaultOptions).Headers);
169+
}
170+
171+
[Theory]
172+
[InlineData("Host")]
173+
[InlineData("host")]
174+
[InlineData("HOST")]
175+
public void Matches_HttpRequestMessageWithHeaders_DoesMatchExactHeaders(string headerName)
176+
{
177+
HttpRequestMessagePattern sut = new()
178+
{
179+
Headers = new() { [Value.Exact(headerName)] = Value.Exact("localhost") }
180+
};
181+
182+
using HttpRequestMessage matchingRequest = new();
183+
matchingRequest.Headers.Host = "localhost";
184+
185+
Assert.True(sut.Matches(matchingRequest, defaultOptions).Headers);
186+
187+
matchingRequest.Headers.Host = "localhost2";
188+
Assert.False(sut.Matches(matchingRequest, defaultOptions).Headers);
189+
}
190+
191+
[Fact]
192+
public void Matches_HttpRequestMessageWithHeaders_DoesMatchExactContentHeaders()
193+
{
194+
HttpRequestMessagePattern sut = new()
195+
{
196+
Headers = new() { [Value.Exact("Content-Disposition")] = Value.Exact("inline") }
197+
};
198+
199+
using HttpRequestMessage matchingRequest = new()
200+
{
201+
Content = new StringContent("")
202+
};
203+
matchingRequest.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("inline");
204+
205+
Assert.True(sut.Matches(matchingRequest, defaultOptions).Headers);
206+
207+
matchingRequest.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
208+
Assert.False(sut.Matches(matchingRequest, defaultOptions).Headers);
209+
}
210+
211+
[Fact]
212+
public void Matches_HttpRequestMessageWithHeaders_AnyDoesNotOverrideExactHeader()
213+
{
214+
HttpRequestMessagePattern sut = new()
215+
{
216+
Headers = new()
217+
{
218+
[Value.Any<string>()] = Value.Any<string>(),
219+
[Value.Exact("Content-Disposition")] = Value.Exact("inline"),
220+
}
221+
};
222+
223+
using HttpRequestMessage matchingRequest = new()
224+
{
225+
Content = new StringContent("")
226+
};
227+
matchingRequest.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("inline");
228+
229+
Assert.True(sut.Matches(matchingRequest, defaultOptions).Headers);
230+
231+
matchingRequest.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
232+
Assert.False(sut.Matches(matchingRequest, defaultOptions).Headers);
233+
}
130234
}

0 commit comments

Comments
 (0)