From d880f4be6d7852e7222fd57524669e5f4e7c5f31 Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Thu, 24 Oct 2024 09:50:10 -0500 Subject: [PATCH] Test improvements - xunit.core and xunit.abstractions instead of full xunit (don't need the assertions) - add Meziantou.Extensions.Logging.Xunit to include logs in test output when failures occur in integration tests - stop dumping all integration test output to console all the time --- Directory.Packages.props | 71 +++++++++---------- ...Core.Authentication.JwtBearer.Tests.csproj | 9 ++- .../DPoPIntegrationTests.cs | 19 ++--- test/TestFramework/ApiHost.cs | 5 +- test/TestFramework/AppHost.cs | 6 +- test/TestFramework/GenericHost.cs | 24 +++++-- test/TestFramework/IdentityServerHost.cs | 9 +-- test/TestFramework/TestFramework.csproj | 3 +- test/TestFramework/TestLoggerProvider.cs | 49 ------------- 9 files changed, 78 insertions(+), 117 deletions(-) delete mode 100644 test/TestFramework/TestLoggerProvider.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 03ccdfa..9ad06a0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,41 +1,38 @@ - - - 8.0.1 - 8.0.0 - 7.1.2 - - - - - 9.0.0-rc.2.24474.3 - 9.0.0-rc.2.24473.5 - 8.0.1 - - - - - - - - - - - - - - - - - - - - - - - - - + 9.0.0-rc.2.24474.3 + 9.0.0-rc.2.24473.5 + 8.0.1 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/AspNetCore.Authentication.JwtBearer.Tests/AspNetCore.Authentication.JwtBearer.Tests.csproj b/test/AspNetCore.Authentication.JwtBearer.Tests/AspNetCore.Authentication.JwtBearer.Tests.csproj index c6c563a..467a8de 100644 --- a/test/AspNetCore.Authentication.JwtBearer.Tests/AspNetCore.Authentication.JwtBearer.Tests.csproj +++ b/test/AspNetCore.Authentication.JwtBearer.Tests/AspNetCore.Authentication.JwtBearer.Tests.csproj @@ -18,16 +18,15 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs b/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs index 7fa58fb..274c804 100644 --- a/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs +++ b/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs @@ -16,10 +16,11 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.IdentityModel.Tokens; using Shouldly; +using Xunit.Abstractions; namespace Duende.AspNetCore.Authentication.JwtBearer; -public class DPoPIntegrationTests +public class DPoPIntegrationTests(ITestOutputHelper testOutputHelper) { Client DPoPOnlyClient = new() { @@ -66,8 +67,8 @@ public async Task valid_token_and_proof_succeeds() var jwk = CreateJwk(); var api = await CreateDPoPApi(); - var app = new AppHost(identityServer, api, "client1", configureUserTokenManagementOptions: opt => - opt.DPoPJsonWebKey = jwk); + var app = new AppHost(identityServer, api, "client1", testOutputHelper, + configureUserTokenManagementOptions: opt => opt.DPoPJsonWebKey = jwk); await app.Initialize(); // Login and get token for api call @@ -110,8 +111,8 @@ public async Task excessively_large_proof_fails() var maxLength = 50; var api = await CreateDPoPApi(opt => opt.ProofTokenMaxLength = maxLength); - var app = new AppHost(identityServer, api, "client1", configureUserTokenManagementOptions: opt => - opt.DPoPJsonWebKey = jwk); + var app = new AppHost(identityServer, api, "client1", testOutputHelper, + configureUserTokenManagementOptions: opt => opt.DPoPJsonWebKey = jwk); await app.Initialize(); // Login and get token for api call @@ -142,19 +143,19 @@ public async Task excessively_large_proof_fails() result.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); } - public static async Task CreateIdentityServer(Action? setup = null) + public async Task CreateIdentityServer(Action? setup = null) { - var host = new IdentityServerHost(); + var host = new IdentityServerHost(testOutputHelper); setup?.Invoke(host); await host.Initialize(); return host; } - private static async Task CreateDPoPApi(Action? configureDPoP = null) + private async Task CreateDPoPApi(Action? configureDPoP = null) { var baseAddress = "https://api"; var identityServer = await CreateIdentityServer(); - var api = new ApiHost(identityServer, baseAddress); + var api = new ApiHost(identityServer, testOutputHelper, baseAddress); api.OnConfigureServices += services => services.ConfigureDPoPTokensForScheme(ApiHost.AuthenticationScheme, opt => diff --git a/test/TestFramework/ApiHost.cs b/test/TestFramework/ApiHost.cs index a683fc9..e86febb 100644 --- a/test/TestFramework/ApiHost.cs +++ b/test/TestFramework/ApiHost.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Xunit.Abstractions; namespace Duende.AspNetCore.TestFramework; @@ -17,8 +18,8 @@ public class ApiHost : GenericHost private readonly IdentityServerHost _identityServerHost; public event Action ApiInvoked = ctx => { }; - public ApiHost(IdentityServerHost identityServerHost, string baseAddress = "https://api") - : base(baseAddress) + public ApiHost(IdentityServerHost identityServerHost, ITestOutputHelper testOutputHelper, string baseAddress = "https://api") + : base(testOutputHelper, baseAddress) { _identityServerHost = identityServerHost; diff --git a/test/TestFramework/AppHost.cs b/test/TestFramework/AppHost.cs index ecc021c..bc9b552 100644 --- a/test/TestFramework/AppHost.cs +++ b/test/TestFramework/AppHost.cs @@ -1,4 +1,4 @@ -// Copyright (c) Duende Software. All rights reserved. +// Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. using System.Net; @@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection; using RichardSzalay.MockHttp; using Shouldly; +using Xunit.Abstractions; namespace Duende.AspNetCore.TestFramework; @@ -25,9 +26,10 @@ public AppHost( IdentityServerHost identityServerHost, ApiHost apiHost, string clientId, + ITestOutputHelper testOutputHelper, string baseAddress = "https://app", Action? configureUserTokenManagementOptions = default) - : base(baseAddress) + : base(testOutputHelper, baseAddress) { _identityServerHost = identityServerHost; _apiHost = apiHost; diff --git a/test/TestFramework/GenericHost.cs b/test/TestFramework/GenericHost.cs index e967357..44a3e78 100644 --- a/test/TestFramework/GenericHost.cs +++ b/test/TestFramework/GenericHost.cs @@ -4,21 +4,24 @@ using System.Net; using System.Reflection; using System.Security.Claims; +using Meziantou.Extensions.Logging.Xunit; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Shouldly; +using Xunit.Abstractions; namespace Duende.AspNetCore.TestFramework; public class GenericHost { - public GenericHost(string baseAddress = "https://server") + public GenericHost(ITestOutputHelper testOutputHelper, string baseAddress = "https://server") { if (baseAddress.EndsWith("/")) baseAddress = baseAddress.Substring(0, baseAddress.Length - 1); _baseAddress = baseAddress; + _testOutputHelper = testOutputHelper; } protected readonly string _baseAddress; @@ -55,8 +58,7 @@ public HttpClient HttpClient private set => _httpClient = value; } - public TestLoggerProvider Logger { get; set; } = new TestLoggerProvider(); - + private readonly ITestOutputHelper _testOutputHelper; public T Resolve() where T : notnull @@ -75,7 +77,7 @@ public string Url(string? path = null) public async Task Initialize() { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions + var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions { EnvironmentName = IsDevelopment ? "Development" : "Production", ApplicationName = HostAssembly?.GetName()?.Name @@ -99,10 +101,20 @@ public async Task Initialize() void ConfigureServices(IServiceCollection services) { + // This adds log messages to the output of our tests when they fail. + // See https://www.meziantou.net/how-to-view-logs-from-ilogger-in-xunitdotnet.htm services.AddLogging(options => { - options.SetMinimumLevel(LogLevel.Debug); - options.AddProvider(Logger); + // If you need different log output to understand a test failure, configure it here + options.SetMinimumLevel(LogLevel.Error); + options.AddFilter("Duende", LogLevel.Information); + options.AddFilter("Duende.IdentityServer.License", LogLevel.Error); + options.AddFilter("Duende.IdentityServer.Startup", LogLevel.Error); + + options.AddProvider(new XUnitLoggerProvider(_testOutputHelper, new XUnitLoggerOptions + { + IncludeCategory = true, + })); }); OnConfigureServices(services); diff --git a/test/TestFramework/IdentityServerHost.cs b/test/TestFramework/IdentityServerHost.cs index a027e4b..4945551 100644 --- a/test/TestFramework/IdentityServerHost.cs +++ b/test/TestFramework/IdentityServerHost.cs @@ -10,13 +10,14 @@ using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; +using Xunit.Abstractions; namespace Duende.AspNetCore.TestFramework; public class IdentityServerHost : GenericHost { - public IdentityServerHost(string baseAddress = "https://identityserver") - : base(baseAddress) + public IdentityServerHost(ITestOutputHelper testOutputHelper, string baseAddress = "https://identityserver") + : base(testOutputHelper, baseAddress) { OnConfigureServices += ConfigureServices; OnConfigure += Configure; @@ -44,10 +45,6 @@ private void ConfigureServices(IServiceCollection services) services.AddRouting(); services.AddAuthorization(); - services.AddLogging(logging => { - logging.AddFilter("Duende", LogLevel.Debug); - }); - services.AddIdentityServer(options=> { options.EmitStaticAudienceClaim = true; diff --git a/test/TestFramework/TestFramework.csproj b/test/TestFramework/TestFramework.csproj index 67a7431..fee201a 100644 --- a/test/TestFramework/TestFramework.csproj +++ b/test/TestFramework/TestFramework.csproj @@ -14,11 +14,12 @@ + - + \ No newline at end of file diff --git a/test/TestFramework/TestLoggerProvider.cs b/test/TestFramework/TestLoggerProvider.cs deleted file mode 100644 index 07ac623..0000000 --- a/test/TestFramework/TestLoggerProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.Extensions.Logging; - -namespace Duende.AspNetCore.TestFramework; - -public class TestLoggerProvider : ILoggerProvider -{ - public class DebugLogger(TestLoggerProvider parent, string category) : ILogger, IDisposable - { - public void Dispose() - { - } - - public IDisposable BeginScope(TState state) - where TState : notnull - { - return this; - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - var msg = $"[{logLevel}] {category} : {formatter(state, exception)}"; - parent.Log(msg); - } - } - - public List LogEntries = new List(); - - private void Log(string msg) - { - LogEntries.Add(msg); - } - - public ILogger CreateLogger(string categoryName) - { - return new DebugLogger(this, categoryName); - } - - public void Dispose() - { - } -} \ No newline at end of file