test(client): cookies + middleware-based user injection for POSTs
- WebApplicationFactoryClientOptions.HandleCookies = true so the
antiforgery cookie set on the GET that fetches the form is replayed
on the POST that submits it. Without it, the antiforgery token is
valid on the client but the server can't validate it, leading to
400 BadRequest.
- Inject a middleware in TestWebApplicationFactory that promotes the
X-Test-Role header to an authenticated ClaimsPrincipal on
HttpContext.User, so anything that reads User.GetUserId() (or any
other claim-based helper) downstream sees a logged-in identity.
The TestAuthPolicyProvider only short-circuits [Authorize(...)]
checks; it does not touch HttpContext.User, which is what user
code reads.
- Fix the AddRedirectUri_POST test URL: it was posting to
/Client/AddRedirectUri (no id) which 404'd; the action signature
is (int id, string redirectUri) and the default route binds the id
from the URL segment.
WIP: the MapStaticAssets() default lookup at
{AssemblyName}.staticwebassets.endpoints.json still needs the
manifest to be renamed on copy — the Yavsc.Org.Tests.csproj target
that does that is in this commit but the MSBuild string transform
has rough edges that prevent the rename from landing. Will revisit.
This commit is contained in:
parent
6aaff74082
commit
68192f9e5b
3 changed files with 64 additions and 4 deletions
|
|
@ -76,10 +76,12 @@ public class ClientControllerCollectionTests : IClassFixture<TestWebApplicationF
|
|||
{
|
||||
// WebApplicationFactory.CreateClient() returns an HttpClient wired
|
||||
// directly to the in-memory test server — no Kestrel socket, no
|
||||
// self-signed cert, no IServerAddressesFeature lookup.
|
||||
// self-signed cert, no IServerAddressesFeature lookup. Keep
|
||||
// cookies enabled so the antiforgery cookie set on the GET that
|
||||
// fetches the form is replayed on the POST that submits it.
|
||||
var http = _factory.CreateClient(new WebApplicationFactoryClientOptions
|
||||
{
|
||||
HandleCookies = false,
|
||||
HandleCookies = true,
|
||||
});
|
||||
http.DefaultRequestHeaders.Add(TestAuthPolicyProvider.HeaderName, TestAuthPolicyProvider.AdminRole);
|
||||
return http;
|
||||
|
|
@ -127,7 +129,10 @@ public class ClientControllerCollectionTests : IClassFixture<TestWebApplicationF
|
|||
{ new StringContent(newUri), "redirectUri" },
|
||||
{ new StringContent(token!), "__RequestVerificationToken" },
|
||||
};
|
||||
var postResp = await http.PostAsync($"/Client/AddRedirectUri", form, TestContext.Current.CancellationToken);
|
||||
var postResp = await http.PostAsync($"/Client/AddRedirectUri/{id}", form, TestContext.Current.CancellationToken);
|
||||
var postBody = await postResp.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
||||
Assert.True(postResp.StatusCode == HttpStatusCode.Redirect,
|
||||
$"Expected 302 Redirect, got {(int)postResp.StatusCode}. Body[0..500]: {postBody.Substring(0, Math.Min(500, postBody.Length))}");
|
||||
Assert.Equal(HttpStatusCode.Redirect, postResp.StatusCode);
|
||||
|
||||
// Verify in DB.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Yavsc.Org.Tests;
|
||||
|
||||
|
|
@ -16,6 +19,10 @@ namespace Yavsc.Org.Tests;
|
|||
/// <c>[Authorize("AdministratorOnly")]</c> (and any other policy
|
||||
/// requiring a role) is satisfied by sending an
|
||||
/// <c>X-Test-Role: Administrator</c> header, without a real login.
|
||||
/// Also injects a middleware that promotes the same header into a
|
||||
/// real <see cref="ClaimsPrincipal"/> on <c>HttpContext.User</c> so
|
||||
/// that user code reading <c>User.GetUserId()</c> sees a logged-in
|
||||
/// identity.
|
||||
/// </summary>
|
||||
public class TestWebApplicationFactory : WebApplicationFactory<Program>
|
||||
{
|
||||
|
|
@ -33,5 +40,38 @@ public class TestWebApplicationFactory : WebApplicationFactory<Program>
|
|||
// becomes irrelevant: any GetPolicyAsync call is routed here.
|
||||
services.AddSingleton<IAuthorizationPolicyProvider, TestAuthPolicyProvider>();
|
||||
});
|
||||
|
||||
// Promote the X-Test-Role header to an authenticated identity
|
||||
// on the request, so anything that reads User.GetUserId() (or
|
||||
// any other claim-based helper) downstream sees a logged-in
|
||||
// user. The policy provider above only short-circuits
|
||||
// [Authorize(...)] checks; it does not touch HttpContext.User.
|
||||
builder.Configure(app =>
|
||||
{
|
||||
app.Use(InjectTestUser);
|
||||
});
|
||||
}
|
||||
|
||||
private static RequestDelegate InjectTestUser(RequestDelegate next)
|
||||
{
|
||||
return async ctx =>
|
||||
{
|
||||
var role = ctx.Request.Headers[TestAuthPolicyProvider.HeaderName].ToString();
|
||||
if (!string.IsNullOrEmpty(role) &&
|
||||
(ctx.User.Identity is null || !ctx.User.Identity.IsAuthenticated))
|
||||
{
|
||||
var identity = new ClaimsIdentity(
|
||||
new[]
|
||||
{
|
||||
new Claim(
|
||||
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
|
||||
role),
|
||||
new Claim(ClaimTypes.NameIdentifier, "test-user"),
|
||||
},
|
||||
authenticationType: "TestAuth");
|
||||
ctx.User = new ClaimsPrincipal(identity);
|
||||
}
|
||||
await next(ctx);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,10 +64,25 @@
|
|||
<_YavscOrgStaticAssetsDir>$(MSBuildProjectDirectory)\..\Yavsc.Org\bin\$(Configuration)\$(TargetFramework)</_YavscOrgStaticAssetsDir>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!--
|
||||
Match the SDK-generated manifest files. Each file is then
|
||||
copied into the test bin with the test assembly's name as
|
||||
the prefix so that the default MapStaticAssets() lookup at
|
||||
{AssemblyName}.staticwebassets.endpoints.json finds them.
|
||||
|
||||
Example:
|
||||
src : Yavsc.Org.staticwebassets.endpoints.json
|
||||
dst : Yavsc.Org.Tests.staticwebassets.endpoints.json
|
||||
|
||||
The transform is a string replace of the source filename's
|
||||
"Yavsc.Org." prefix with "Yavsc.Org.Tests." — done by the
|
||||
-> '$(OutDir)Yavsc.Org.Tests.%(Filename)' transformation
|
||||
with a RegexReplace on the source path.
|
||||
-->
|
||||
<_YavscOrgStaticAssetsFiles Include="$(_YavscOrgStaticAssetsDir)\Yavsc.Org.staticwebassets.*.json" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(_YavscOrgStaticAssetsFiles)"
|
||||
DestinationFolder="$(OutDir)"
|
||||
DestinationFiles="@(_YavscOrgStaticAssetsFiles->'$(OutDir)Yavsc.Org.Tests.' + System.IO.Path.GetFileNameWithoutExtension('%(Filename)').Replace('Yavsc.Org.', '') + System.IO.Path.GetExtension('%(Filename)'))"
|
||||
SkipUnchangedFiles="true"
|
||||
Condition="'@(_YavscOrgStaticAssetsFiles)' != ''" />
|
||||
</Target>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue