using System.Text; using Bff.Api; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; namespace Bff.Tests; /// /// Test host for the BFF. It swaps the downstream clients for in-memory fakes and reconfigures the /// JWT bearer to validate against a local test key (no live Keycloak) — so token validation is /// exercised in-process with tokens the tests mint (ADR-0010). /// internal sealed class BffFactory : WebApplicationFactory { public static readonly SymmetricSecurityKey TestSigningKey = new(Encoding.UTF8.GetBytes("bff-test-signing-key-that-is-at-least-256-bits-long!")); public FakeDomainClient Domain { get; } = new(); public FakeProjectionClient Projection { get; } = new(); protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseSetting("Keycloak:Authority", "https://keycloak.invalid/realms/digid"); builder.UseSetting("Downstream:Domain:BaseUrl", "http://domain.invalid/"); builder.UseSetting("Downstream:Projection:BaseUrl", "http://projection.invalid/"); builder.ConfigureTestServices(services => { services.AddSingleton(Domain); services.AddSingleton(Projection); services.Configure(JwtBearerDefaults.AuthenticationScheme, options => { // Validate locally against the test key; never reach out for OIDC metadata. options.Authority = null; options.MetadataAddress = null!; options.RequireHttpsMetadata = false; options.Configuration = new OpenIdConnectConfiguration(); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = TestSigningKey, ClockSkew = TimeSpan.Zero, }; }); }); } } /// Captures the bsn the BFF forwarded and returns a canned acceptance. internal sealed class FakeDomainClient : IDomainClient { public string? SubmittedBsn { get; private set; } public SubmitAccepted Result { get; set; } = new("reg-123", "Ingediend"); public Task SubmitRegistrationAsync(string bsn, CancellationToken ct = default) { SubmittedBsn = bsn; return Task.FromResult(Result); } } /// Serves a configurable set of projection rows. internal sealed class FakeProjectionClient : IProjectionClient { public List Entries { get; } = []; public Task> GetRegisterAsync(CancellationToken ct = default) => Task.FromResult>(Entries); }