Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 0 additions & 125 deletions HttpClientInterception.sln

This file was deleted.

60 changes: 60 additions & 0 deletions HttpClientInterception.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<Solution>
<Folder Name="/samples/">
<File Path="samples/README.md" />
<Project Path="samples/SampleApp.Tests/SampleApp.Tests.csproj" />
<Project Path="samples/SampleApp/SampleApp.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path=".gitattributes" />
<File Path=".gitignore" />
<File Path=".vsconfig" />
<File Path="build.ps1" />
<File Path="Directory.Build.props" />
<File Path="Directory.Build.targets" />
<File Path="Directory.Packages.props" />
<File Path="global.json" />
<File Path="HttpClientInterception.ruleset" />
<File Path="LICENSE" />
<File Path="NuGet.config" />
<File Path="README.md" />
<File Path="SECURITY.md" />
<File Path="stylecop.json" />
</Folder>
<Folder Name="/Solution Items/.github/">
<File Path=".github/CODEOWNERS" />
<File Path=".github/CONTRIBUTING.md" />
<File Path=".github/dependabot.yml" />
<File Path=".github/ISSUE_TEMPLATE.md" />
<File Path=".github/PULL_REQUEST_TEMPLATE.md" />
<File Path=".github/stale.yml" />
</Folder>
<Folder Name="/Solution Items/.github/ISSUE_TEMPLATE/">
<File Path=".github/ISSUE_TEMPLATE/bug_report.md" />
<File Path=".github/ISSUE_TEMPLATE/feature_request.md" />
</Folder>
<Folder Name="/Solution Items/.github/workflows/">
<File Path=".github/workflows/approve-and-merge.yml" />
<File Path=".github/workflows/build.yml" />
<File Path=".github/workflows/bump-version.yml" />
<File Path=".github/workflows/codeql.yml" />
<File Path=".github/workflows/dependabot-approve.yml" />
<File Path=".github/workflows/dependency-review.yml" />
<File Path=".github/workflows/lint.yml" />
<File Path=".github/workflows/release.yml" />
<File Path=".github/workflows/scorecard.yml" />
<File Path=".github/workflows/update-docs.yml" />
<File Path=".github/workflows/update-dotnet-sdk.yml" />
</Folder>
<Folder Name="/Solution Items/.vscode/">
<File Path=".vscode/launch.json" />
<File Path=".vscode/tasks.json" />
</Folder>
<Folder Name="/src/">
<Project Path="src/HttpClientInterception/JustEat.HttpClientInterception.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/HttpClientInterception.Benchmarks/JustEat.HttpClientInterception.Benchmarks.csproj" />
<Project Path="tests/HttpClientInterception.Tests/JustEat.HttpClientInterception.Tests.csproj" />
</Folder>
</Solution>
12 changes: 11 additions & 1 deletion src/HttpClientInterception/HttpClientInterceptorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,17 @@ private static async Task<HttpResponseMessage> BuildResponseAsync(HttpRequestMes
{
var responses = _mappings.Values
.OrderByDescending((p) => p.Priority.HasValue)
.ThenBy((p) => p.Priority);
.ThenBy((p) => p.Priority)
.ThenByDescending((p) =>
{
// Sort by the number of request headers, so that those with more headers expected are matched first.
// Count() is not used to avoid side-effects of enumerating the headers if they are dynamically generated.
#if NET8_0_OR_GREATER
return p.RequestHeaders?.TryGetNonEnumeratedCount(out var count) is true ? count : 0;
#else
return p.RequestHeaders is ICollection<KeyValuePair<string, IEnumerable<string>>> collection ? collection.Count : 0;
#endif
});

foreach (var response in responses)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,4 +601,34 @@ public static void Can_Register_Bundle_With_Null_Header_Values_In_Bundle()
// Act
options.RegisterBundle(Path.Join("Bundles", "templated-bundle-null-headers.json"), headers);
}

[Fact]
public static async Task Can_Intercept_Http_Requests_With_Correct_Precedence_For_Http_Request_Headers()
{
// Arrange
var options = new HttpClientInterceptorOptions().ThrowsOnMissingRegistration();

var requestUrl = "https://registry.hub.docker.com/v2/user/image/manifests/latest";

var unauthorized = new Dictionary<string, string>()
{
["Accept"] = "application/vnd.oci.image.index.v1+json",
};

var authorized = new Dictionary<string, string>()
{
["Accept"] = "application/vnd.oci.image.index.v1+json",
["Authorization"] = "Bearer not-a-real-docker-hub-token",
};

options.RegisterBundle(Path.Join("Bundles", "header-matching.json"));

// Act
var first = await HttpAssert.GetAsync(options, requestUrl, headers: unauthorized);
var second = await HttpAssert.GetAsync(options, requestUrl, headers: authorized);

// Assert
first.ShouldBe("unauthorized");
second.ShouldBe("authorized");
}
}
36 changes: 36 additions & 0 deletions tests/HttpClientInterception.Tests/Bundles/header-matching.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"$schema": "https://raw.githubusercontent.com/justeattakeaway/httpclient-interception/main/src/HttpClientInterception/Bundles/http-request-bundle-schema.json",
"id": "test-http-request-bundle",
"comment": "An HTTP request bundle for testing HTTP request header matching for the same URL.",
"version": 1,
"items": [
{
"id": "authorized",
"comment": "An HTTP request with an Authorization header.",
"uri": "https://registry.hub.docker.com/v2/user/image/manifests/latest",
"requestHeaders": {
"Accept": [
"application/vnd.oci.image.index.v1+json"
],
"Authorization": [
"Bearer not-a-real-docker-hub-token"
]
},
"contentString": "authorized"
},
{
"id": "unauthorized",
"comment": "An HTTP request without an Authorization header.",
"uri": "https://registry.hub.docker.com/v2/user/image/manifests/latest",
"requestHeaders": {
"Accept": [
"application/vnd.oci.image.index.v1+json"
]
},
"contentHeaders": {
"Content-Type": [ "application/json" ]
},
"contentString": "unauthorized"
}
]
}