refactor: extract WebHostFixture + TestAuthPolicyProvider to shared lib
Some checks failed
Dotnet build and test / log-the-inputs (pull_request) Has been cancelled
Dotnet build and test / build (pull_request) Has been cancelled

Yavsc.Blogs.Tests will need a fixture too. Lifting the cross-cutting
plumbing (Kestrel + self-signed cert + address discovery + lazy init)
into a new Yavsc.Tests.Shared project lets the next fixture inherit
from it without copying 200+ lines of setup boilerplate, and keeps
the Org.Tests fixture focused on its IdentityServer + SMTP seed.

* New project src/Yavsc.Tests.Shared with WebHostFixture (abstract)
  and TestAuthPolicyProvider (test auth bypass via X-Test-Role).
* WebServerFixture in Org.Tests now inherits from WebHostFixture;
  BuildApp + ConfigurePipelineAsync hold only Org-specific work.
* Two shared package versions promoted to the root Directory.Packages.props.
* Tests still 30/30 green.
This commit is contained in:
Paul Schneider 2026-07-06 21:33:57 +01:00
commit 349ddc03f5
12 changed files with 460 additions and 355 deletions

View file

@ -1,70 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace Yavsc.Org.Tests;
/// <summary>
/// Authorization policy provider used by integration tests. Replaces the
/// production provider in the WebApplicationFactory so that any
/// policy-protected controller can be exercised by sending a
/// <c>X-Test-Role: Administrator</c> header — no login roundtrip, no
/// cookie, no database user.
///
/// The role names accepted in the header are the same as the
/// production <see cref="YavscConstants.AdminGroupName"/>. Any
/// policy that requires one of those roles short-circuits to success
/// when the matching header is present; otherwise the production
/// policy is preserved.
/// </summary>
public sealed class TestAuthPolicyProvider : IAuthorizationPolicyProvider
{
public const string HeaderName = "X-Test-Role";
public const string AdminRole = "Administrator";
private readonly DefaultAuthorizationPolicyProvider _fallback;
public TestAuthPolicyProvider(IOptions<AuthorizationOptions> options)
{
_fallback = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => _fallback.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => _fallback.GetFallbackPolicyAsync();
public async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
var policy = await _fallback.GetPolicyAsync(policyName);
if (policy is null) return null;
return new AuthorizationPolicyBuilder()
.RequireAssertion(ctx =>
{
// ASP.NET Core sets ctx.Resource to the HttpContext when
// the authorization middleware invokes the policy. Use
// the request headers directly to honour X-Test-Role.
var http = ctx.Resource as Microsoft.AspNetCore.Http.HttpContext;
if (http is null) return false;
var role = http.Request.Headers[HeaderName].ToString();
if (string.IsNullOrEmpty(role)) return false;
// The test does not perform a real login, so the
// authenticated user has no claims. Attach an
// in-memory identity carrying the role claim to the
// HttpContext (ctx.User is read-only) so the
// production policy's claim requirement is satisfied.
if (http.User.Identity is null || !http.User.Identity.IsAuthenticated)
{
var identity = new System.Security.Claims.ClaimsIdentity(
new[]
{
new System.Security.Claims.Claim(
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
role),
},
authenticationType: "TestAuth");
http.User = new System.Security.Claims.ClaimsPrincipal(identity);
}
return true;
})
.Build();
}
}