We're trying to implement unit tests for handling the SameSite mode for cookies which requires probing the User-Agent header. It turns out to be impossible to do via TestServer, because the header gets chopped up into separate values by the HttpClient. It's an array when it gets forwarded to the TestServer here:
lt's impossible to get the value back in a consistent manner, since TestServer behaves differently as an actual server (e.g. IIS); we would like to avoid doing string.Join(" ", request.Headers[HeaderNames.UserAgent]) in our server-side code, only to make the tests work.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Net.Http.Headers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net.Http;
using System.Threading.Tasks;
namespace TestServerHostHEader
{
[TestClass]
public class UnitTest
{
[TestMethod]
public async Task ShouldRecieveCorrectUserAgent()
{
// Build Proxy TestServer
var proxyWebHostBuilder = new WebHostBuilder()
.UseStartup<ProxyStartup>()
.UseUrls("http://proxy.com:5000");
var proxyTestServer = new TestServer(proxyWebHostBuilder);
// Get HttpClient make a request to the proxy
var httpClient = new HttpClient(proxyTestServer.CreateHandler());
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0";
httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, userAgent);
var response = await httpClient.GetAsync("http://proxy.com:5000/");
var responseText = await response.Content.ReadAsStringAsync();
StringAssert.Contains(responseText, userAgent);
}
public class ProxyStartup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Run(async context =>
{
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(context.Request.Headers[HeaderNames.UserAgent]);
}
}
}
}
Fails with:
StringAssert.Contains failed. String
'Mozilla/5.0,(Windows NT 10.0; Win64; x64; rv:71.0),Gecko/20100101,Firefox/71.0' does not contain string
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0'.
> dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 3.1.100
Commit: cd82f021f4
Runtime Environment:
OS Name: Windows
OS Version: 10.0.18363
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\3.1.100\
Host (useful for support):
Version: 3.1.0
Commit: 65f04fb6db
Looks like something we should fix. We'll take a look.
As a workaround for now, we think using the TestServer.SendAsync API that lets you directly set properties on HttpContext might work.
Thanks for the workaround. It's indeed sufficient for what we want to test!
Here's a SendAsync example:
[Fact]
public async Task UserAgentHeaderWorks()
{
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0";
var builder = new WebHostBuilder().Configure(app => { });
var server = new TestServer(builder);
server.BaseAddress = new Uri("https://example.com/");
var context = await server.SendAsync(c =>
{
c.Request.Headers[HeaderNames.UserAgent] = userAgent;
});
var actualResult = context.Request.Headers[HeaderNames.UserAgent];
Assert.Equal(userAgent, actualResult);
}
@m0sa excellent, glad you're unblocked!
User-Agent defies the normal HTTP rules as it's explicitly space delimited. HttpClient normally deals with this by overriding the separator (only user-agent does this).
https://github.com/dotnet/runtime/blob/aa13fb88d7640f1ccee952976ea45345c97cd7a3/src/libraries/System.Net.Http/src/System/Net/Http/Headers/ProductInfoHeaderParser.cs#L13-L14
https://github.com/dotnet/runtime/blob/4f9ae42d861fcb4be2fcd5d3d55d5f227d30e723/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs#L319-L323
Unfortionately those APIs are internal, we'll have to add our own special case.