Skip to content

Commit 7216d7c

Browse files
Support dynamic response headers
- Add support for dynamically matching and producing HTTP headers. - Add missing null check. Relates to #473.
1 parent 85ac699 commit 7216d7c

File tree

4 files changed

+107
-10
lines changed

4 files changed

+107
-10
lines changed

src/HttpClientInterception/HttpRequestInterceptionBuilder.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,29 @@ public HttpRequestInterceptionBuilder WithContentHeaders(IDictionary<string, str
499499
return this;
500500
}
501501

502+
/// <summary>
503+
/// Sets a delegate to a method that generates any custom HTTP content headers to use.
504+
/// </summary>
505+
/// <param name="headerFactory">Any delegate that creates any custom HTTP content headers to use.</param>
506+
/// <returns>
507+
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
508+
/// </returns>
509+
/// <exception cref="ArgumentNullException">
510+
/// <paramref name="headerFactory"/> is <see langword="null"/>.
511+
/// </exception>
512+
public HttpRequestInterceptionBuilder WithContentHeaders(
513+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory)
514+
{
515+
if (headerFactory is null)
516+
{
517+
throw new ArgumentNullException(nameof(headerFactory));
518+
}
519+
520+
_contentHeaders = new DynamicDictionary(headerFactory);
521+
IncrementRevision();
522+
return this;
523+
}
524+
502525
/// <summary>
503526
/// Sets a custom HTTP response header to use with a single value.
504527
/// </summary>
@@ -947,16 +970,50 @@ public HttpRequestInterceptionBuilder ForRequestHeaders(IDictionary<string, stri
947970
/// <returns>
948971
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
949972
/// </returns>
973+
/// <exception cref="ArgumentNullException">
974+
/// <paramref name="headers"/> is <see langword="null"/>.
975+
/// </exception>
950976
/// <remarks>
951977
/// HTTP request headers are only tested for interception if the URI requested was registered for interception.
952978
/// </remarks>
953979
public HttpRequestInterceptionBuilder ForRequestHeaders(IDictionary<string, ICollection<string>> headers)
954980
{
981+
if (headers is null)
982+
{
983+
throw new ArgumentNullException(nameof(headers));
984+
}
985+
955986
_requestHeaders = new Dictionary<string, ICollection<string>>(headers, StringComparer.OrdinalIgnoreCase);
956987
IncrementRevision();
957988
return this;
958989
}
959990

991+
/// <summary>
992+
/// Sets a delegate to a method that generates the HTTP request headers to intercept.
993+
/// </summary>
994+
/// <param name="headerFactory">Any delegate that returns the HTTP request headers to intercept..</param>
995+
/// <returns>
996+
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
997+
/// </returns>
998+
/// <exception cref="ArgumentNullException">
999+
/// <paramref name="headerFactory"/> is <see langword="null"/>.
1000+
/// </exception>
1001+
/// <remarks>
1002+
/// HTTP request headers are only tested for interception if the URI requested was registered for interception.
1003+
/// </remarks>
1004+
public HttpRequestInterceptionBuilder ForRequestHeaders(
1005+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory)
1006+
{
1007+
if (headerFactory is null)
1008+
{
1009+
throw new ArgumentNullException(nameof(headerFactory));
1010+
}
1011+
1012+
_requestHeaders = new DynamicDictionary(headerFactory);
1013+
IncrementRevision();
1014+
return this;
1015+
}
1016+
9601017
/// <summary>
9611018
/// Configures the builder to match any request whose HTTP content meets the criteria defined by the specified predicate.
9621019
/// </summary>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
#nullable enable
2+
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForRequestHeaders(System.Func<System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, System.Collections.Generic.ICollection<string!>!>>!>! headerFactory) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
3+
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentHeaders(System.Func<System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, System.Collections.Generic.ICollection<string!>!>>!>! headerFactory) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
24
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithResponseHeaders(System.Func<System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, System.Collections.Generic.ICollection<string!>!>>!>! headerFactory) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!

tests/HttpClientInterception.Tests/Examples.cs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -757,43 +757,73 @@ static bool IsHttpGetForJustEatGitHubOrg(HttpRequestMessage request)
757757
}
758758

759759
[Fact]
760-
public static async Task Dynamic_Headers()
760+
public static async Task Dynamically_Compute_Http_Headers()
761761
{
762762
// Arrange
763-
int counter = 0;
763+
int contentHeadersCounter = 0;
764+
int requestHeadersCounter = 0;
765+
int responseHeadersCounter = 0;
764766

765767
var builder = new HttpRequestInterceptionBuilder()
766768
.ForHost("service.local")
767769
.ForPath("resource")
768-
.WithJsonContent(new object())
770+
.ForRequestHeaders(() =>
771+
{
772+
return new Dictionary<string, ICollection<string>>()
773+
{
774+
["x-sequence"] = new[] { (++requestHeadersCounter).ToString(CultureInfo.InvariantCulture) },
775+
};
776+
})
777+
.WithContentHeaders(() =>
778+
{
779+
return new Dictionary<string, ICollection<string>>()
780+
{
781+
["content-type"] = new[] { "application/json; v=" + (++contentHeadersCounter).ToString(CultureInfo.InvariantCulture) },
782+
};
783+
})
769784
.WithResponseHeaders(() =>
770785
{
771786
return new Dictionary<string, ICollection<string>>()
772787
{
773-
["x-count"] = new[] { (++counter).ToString(CultureInfo.InvariantCulture) },
788+
["x-count"] = new[] { (++responseHeadersCounter).ToString(CultureInfo.InvariantCulture) },
774789
};
775790
});
776791

777792
var options = new HttpClientInterceptorOptions()
778-
.Register(builder);
793+
.Register(builder)
794+
.ThrowsOnMissingRegistration();
795+
796+
var method = HttpMethod.Get;
797+
string requestUri = "http://service.local/resource";
779798

780799
using var client = options.CreateHttpClient();
781-
using var body = new StringContent(@"{ ""FirstName"": ""John"" }");
782800

783801
// Act
784-
using var response1 = await client.GetAsync("http://service.local/resource");
802+
using var request1 = new HttpRequestMessage(method, requestUri);
803+
request1.Headers.Add("x-sequence", "1");
804+
using var response1 = await client.SendAsync(request1);
785805

786806
// Assert
787807
response1.Headers.TryGetValues("x-count", out var values).ShouldBeTrue();
788808
values.ShouldNotBeNull();
789809
values.ShouldBe(new[] { "1" });
790810

811+
response1.Content.Headers.TryGetValues("content-type", out values).ShouldBeTrue();
812+
values.ShouldNotBeNull();
813+
values.ShouldBe(new[] { "application/json; v=1" });
814+
791815
// Act
792-
using var response2 = await client.GetAsync("http://service.local/resource");
816+
using var request2 = new HttpRequestMessage(method, requestUri);
817+
request2.Headers.Add("x-sequence", "2");
818+
using var response2 = await client.SendAsync(request2);
793819

794820
// Assert
795821
response2.Headers.TryGetValues("x-count", out values).ShouldBeTrue();
796822
values.ShouldNotBeNull();
797823
values.ShouldBe(new[] { "2" });
824+
825+
response2.Content.Headers.TryGetValues("content-type", out values).ShouldBeTrue();
826+
values.ShouldNotBeNull();
827+
values.ShouldBe(new[] { "application/json; v=2" });
798828
}
799829
}

tests/HttpClientInterception.Tests/HttpRequestInterceptionBuilderTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,10 +1707,14 @@ public static void ForRequestHeaders_Validates_Parameters()
17071707
{
17081708
// Arrange
17091709
var builder = new HttpRequestInterceptionBuilder();
1710-
IDictionary<string, string> headers = null;
1710+
IDictionary<string, string> headersOfString = null;
1711+
IDictionary<string, ICollection<string>> headersOfStrings = null;
1712+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory = null;
17111713

17121714
// Act and Assert
1713-
Should.Throw<ArgumentNullException>(() => builder.ForRequestHeaders(headers), "headers");
1715+
Should.Throw<ArgumentNullException>(() => builder.ForRequestHeaders(headersOfString), "headers");
1716+
Should.Throw<ArgumentNullException>(() => builder.ForRequestHeaders(headersOfStrings), "headers");
1717+
Should.Throw<ArgumentNullException>(() => builder.ForRequestHeaders(headerFactory), "headerFactory");
17141718
}
17151719

17161720
[Fact]
@@ -1736,10 +1740,12 @@ public static void WithContentHeaders_Validates_Parameters()
17361740
var builder = new HttpRequestInterceptionBuilder();
17371741
IDictionary<string, string> headers = null;
17381742
IDictionary<string, ICollection<string>> headerValues = null;
1743+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory = null;
17391744

17401745
// Act and Assert
17411746
Should.Throw<ArgumentNullException>(() => builder.WithContentHeaders(headers), "headers");
17421747
Should.Throw<ArgumentNullException>(() => builder.WithContentHeaders(headerValues), "headers");
1748+
Should.Throw<ArgumentNullException>(() => builder.WithContentHeaders(headerFactory), "headerFactory");
17431749
}
17441750

17451751
[Fact]
@@ -1764,9 +1770,11 @@ public static void WithResponseHeaders_Validates_Parameters()
17641770
// Arrange
17651771
var builder = new HttpRequestInterceptionBuilder();
17661772
IDictionary<string, string> headers = null;
1773+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory = null;
17671774

17681775
// Act and Assert
17691776
Should.Throw<ArgumentNullException>(() => builder.WithResponseHeaders(headers), "headers");
1777+
Should.Throw<ArgumentNullException>(() => builder.WithResponseHeaders(headerFactory), "headerFactory");
17701778
}
17711779

17721780
[Fact]

0 commit comments

Comments
 (0)