Files
WorkClub Automation f8f3e0f01e test(harness): stabilize backend+frontend QA test suite (12/12+63/63 unit+integration, 45/45 frontend)
Stabilize test harness across full stack:

Backend integration tests:
- Fix Auth/Club/Migration/RLS/Member/Tenant/RLS Isolation/Shift/Task test suites
- Add AssemblyInfo.cs for test configuration
- Enhance CustomWebApplicationFactory + TestAuthHandler for stable test environment
- Expand RlsIsolationTests with comprehensive multi-tenant RLS verification

Frontend test harness:
- Align vitest.config.ts with backend API changes
- Add bunfig.toml for bun test environment stability
- Enhance api.test.ts with proper test setup integration
- Expand test/setup.ts with fixture initialization

All tests now passing: backend 12/12 unit + 63/63 integration, frontend 45/45
2026-03-06 09:19:32 +01:00

89 lines
3.5 KiB
C#

using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace WorkClub.Tests.Integration.Infrastructure;
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public TestAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder)
: base(options, logger, encoder)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Support explicit unauthenticated test scenarios
var unauthenticatedHeader = Context.Request.Headers["X-Test-Unauthenticated"].ToString();
if (!string.IsNullOrEmpty(unauthenticatedHeader) && unauthenticatedHeader.Equals("true", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(AuthenticateResult.NoResult());
}
var clubsClaim = Context.Request.Headers["X-Test-Clubs"].ToString();
var emailClaim = Context.Request.Headers["X-Test-Email"].ToString();
var userIdClaim = Context.Request.Headers["X-Test-UserId"].ToString();
var clubRolesJson = Context.Request.Headers["X-Test-ClubRoles"].ToString();
// If no test auth headers are present, return NoResult (unauthenticated)
if (string.IsNullOrEmpty(emailClaim) && string.IsNullOrEmpty(userIdClaim) && string.IsNullOrEmpty(clubsClaim))
{
return Task.FromResult(AuthenticateResult.NoResult());
}
var resolvedEmail = string.IsNullOrEmpty(emailClaim) ? "test@test.com" : emailClaim;
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, "test-user"),
new Claim("sub", string.IsNullOrEmpty(userIdClaim) ? Guid.NewGuid().ToString() : userIdClaim),
new Claim(ClaimTypes.Email, resolvedEmail),
new Claim("preferred_username", resolvedEmail),
};
if (!string.IsNullOrEmpty(clubsClaim))
{
claims.Add(new Claim("clubs", clubsClaim));
}
// Parse tenant-specific role from X-Test-ClubRoles if provided
if (!string.IsNullOrEmpty(clubRolesJson))
{
var tenantId = Context.Request.Headers["X-Tenant-Id"].ToString();
if (!string.IsNullOrEmpty(tenantId))
{
try
{
var clubRoles = JsonSerializer.Deserialize<Dictionary<string, string>>(clubRolesJson);
if (clubRoles != null)
{
var tenantRole = clubRoles.FirstOrDefault(kvp =>
kvp.Key.Equals(tenantId, StringComparison.OrdinalIgnoreCase)).Value;
if (!string.IsNullOrEmpty(tenantRole))
{
claims.Add(new Claim(ClaimTypes.Role, tenantRole));
}
}
}
catch (JsonException)
{
// Invalid JSON in X-Test-ClubRoles header, proceed without role claim
}
}
}
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}