Skip to content

Commit 301832b

Browse files
Updated Dependencies, Added Patch as a Method, Ability to add Cust… (#3)
Adds PATCH request type and headers without validation for more use cases. Co-authored-by: Nick Craver <[email protected]>
1 parent 4e92d83 commit 301832b

File tree

4 files changed

+123
-1
lines changed

4 files changed

+123
-1
lines changed

src/StackExchange.Utils.Http/Extensions.Modifier.cs

+27
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using System.Collections.Immutable;
55
using System.Net;
6+
using System.Net.Http;
67
using System.Net.Http.Headers;
78

89
namespace StackExchange.Utils
@@ -124,6 +125,32 @@ public static IRequestBuilder AddHeader(this IRequestBuilder builder, string nam
124125
}
125126
return builder;
126127
}
128+
129+
/// <summary>
130+
/// Adds a single header without Validation against known Header types.
131+
/// (ideal if you have different interpretation to the spec for any known types)
132+
/// </summary>
133+
/// <param name="builder">The builder we're working on.</param>
134+
/// <param name="name">The auth scheme to add to this request.</param>
135+
/// <param name="value">The key value (for <paramref name="name"/>) to add to this request.</param>
136+
/// <returns>The request builder for chaining.</returns>
137+
public static IRequestBuilder AddHeaderWithoutValidation(this IRequestBuilder builder, string name, string value)
138+
{
139+
if (!string.IsNullOrEmpty(name))
140+
{
141+
try
142+
{
143+
builder.Message.Headers.TryAddWithoutValidation(name, value);
144+
145+
}
146+
catch (Exception e)
147+
{
148+
var wrapper = new HttpClientException("Unable to set header using: " + name + " to '" + value + "'", builder.Message.RequestUri, e);
149+
builder.GetSettings().OnException(builder, new HttpExceptionArgs(builder, wrapper));
150+
}
151+
}
152+
return builder;
153+
}
127154

128155
/// <summary>
129156
/// Adds headers to this request.

src/StackExchange.Utils.Http/Extensions.Verbs.cs

+10
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,15 @@ public static Task<HttpCallResponse<T>> PostAsync<T>(this IRequestBuilder<T> bui
4545
/// <returns>A <see cref="HttpCallResponse{T}"/> to consume.</returns>
4646
public static Task<HttpCallResponse<T>> PutAsync<T>(this IRequestBuilder<T> builder, CancellationToken cancellationToken = default) =>
4747
Http.SendAsync(builder, HttpMethod.Put, cancellationToken);
48+
49+
/// <summary>
50+
/// Issue the request as a PATCH.
51+
/// </summary>
52+
/// <typeparam name="T">The return type.</typeparam>
53+
/// <param name="builder">The builder used for this request.</param>
54+
/// <param name="cancellationToken">The cancellation token for stopping the request.</param>
55+
/// <returns>A <see cref="HttpCallResponse{T}"/> to consume.</returns>
56+
public static Task<HttpCallResponse<T>> PatchAsync<T>(this IRequestBuilder<T> builder, CancellationToken cancellationToken = default) =>
57+
Http.SendAsync(builder, new HttpMethod("PATCH"), cancellationToken);
4858
}
4959
}

tests/Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
1313
<PackageReference Include="xunit" Version="$(xUnitVersion)" />
1414
<PackageReference Include="xunit.runner.visualstudio" Version="$(xUnitVersion)" />
1515
</ItemGroup>

tests/StackExchange.Utils.Tests/HttpTests.cs

+85
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Net;
34
using System.Net.Http;
5+
using System.Net.Http.Headers;
46
using System.Threading.Tasks;
57
using Xunit;
68

@@ -122,5 +124,88 @@ public async Task Timeouts()
122124
Assert.Equal("https://httpbin.org/delay/10", err.Uri.ToString());
123125
Assert.Null(err.StatusCode);
124126
}
127+
128+
[Fact]
129+
public async Task AddHeaderWithoutValidation()
130+
{
131+
var result = await Http.Request("https://httpbin.org/bearer")
132+
.AddHeaderWithoutValidation("Authorization","abcd")
133+
.ExpectJson<HttpBinResponse>()
134+
.GetAsync();
135+
Assert.True(result.RawRequest.Headers.Contains("Authorization"));
136+
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
137+
}
138+
139+
[Fact]
140+
public async Task AddHeaders()
141+
{
142+
var result = await Http.Request("https://httpbin.org/headers")
143+
.AddHeaders(new Dictionary<string, string>()
144+
{
145+
{"Content-Type", "application/json"},
146+
{"Custom", "Test"}
147+
})
148+
.SendJson("{}")
149+
.ExpectJson<HttpBinResponse>()
150+
.GetAsync();
151+
152+
Assert.Equal(HttpStatusCode.OK,result.StatusCode);
153+
Assert.Equal("Test", result.Data.Headers["Custom"]);
154+
// Content-Type should be present because we're sending a body up
155+
Assert.StartsWith("application/json", result.Data.Headers["Content-Type"]);
156+
}
157+
158+
[Fact]
159+
public async Task TestAddHeadersWhereClientAddsHeaderBeforeContent()
160+
{
161+
var result = await Http.Request("https://httpbin.org/headers")
162+
.AddHeaders(new Dictionary<string, string>()
163+
{
164+
{"Content-Type", "application/json"},
165+
{"Custom", "Test"}
166+
})
167+
.SendJson("")
168+
.ExpectJson<HttpBinResponse>()
169+
.GetAsync();
170+
171+
Assert.Equal(HttpStatusCode.OK,result.StatusCode);
172+
Assert.Equal("Test", result.Data.Headers["Custom"]);
173+
Assert.Equal("application/json; charset=utf-8", result.Data.Headers["Content-Type"]);
174+
}
175+
176+
[Fact]
177+
public async Task TestAddHeadersWhereClientAddsHeaderAndNoContent()
178+
{
179+
var result = await Http.Request("https://httpbin.org/headers")
180+
.AddHeaders(new Dictionary<string, string>()
181+
{
182+
{"Content-Type", "application/json"},
183+
{"Custom", "Test"}
184+
})
185+
.ExpectJson<HttpBinResponse>()
186+
.GetAsync();
187+
188+
Assert.Equal(HttpStatusCode.OK,result.StatusCode);
189+
Assert.Equal("Test", result.Data.Headers["Custom"]);
190+
// Content-Type is NOT sent when there's no body - this is correct behavior
191+
Assert.DoesNotContain("Content-Type", result.Data.Headers.Keys);
192+
}
193+
194+
[Fact]
195+
public async Task PatchRequest()
196+
{
197+
var guid = Guid.NewGuid().ToString();
198+
var result = await Http.Request("https://httpbin.org/patch")
199+
.SendPlaintext(guid)
200+
.ExpectJson<HttpBinResponse>()
201+
.PatchAsync();
202+
203+
Assert.True(result.Success);
204+
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
205+
Assert.NotNull(result);
206+
Assert.True(result.Data.Form.ContainsKey(guid));
207+
Assert.Equal("https://httpbin.org/patch", result.Data.Url);
208+
Assert.Equal(Http.DefaultSettings.UserAgent, result.Data.Headers["User-Agent"]);
209+
}
125210
}
126211
}

0 commit comments

Comments
 (0)