up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,257 +1,257 @@
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Concelier.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common.Fetch;
|
||||
using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Connector.Cve.Configuration;
|
||||
using StellaOps.Concelier.Connector.Cve.Internal;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Cve.Tests;
|
||||
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Concelier.Documents;
|
||||
using StellaOps.Concelier.InMemoryDriver;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common.Fetch;
|
||||
using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Connector.Cve.Configuration;
|
||||
using StellaOps.Concelier.Connector.Cve.Internal;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Cve.Tests;
|
||||
|
||||
[Collection(ConcelierFixtureCollection.Name)]
|
||||
public sealed class CveConnectorTests : IAsyncLifetime
|
||||
{
|
||||
private readonly ConcelierPostgresFixture _fixture;
|
||||
private readonly ITestOutputHelper _output;
|
||||
private ConnectorTestHarness? _harness;
|
||||
|
||||
public CveConnectorTests(ConcelierPostgresFixture fixture, ITestOutputHelper output)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchParseMap_EmitsCanonicalAdvisory()
|
||||
{
|
||||
var initialTime = new DateTimeOffset(2024, 10, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
await EnsureHarnessAsync(initialTime);
|
||||
var harness = _harness!;
|
||||
|
||||
var since = initialTime - TimeSpan.FromDays(30);
|
||||
var listUri = new Uri($"https://cve.test/api/cve?time_modified.gte={Uri.EscapeDataString(since.ToString("O"))}&time_modified.lte={Uri.EscapeDataString(initialTime.ToString("O"))}&page=1&size=5");
|
||||
harness.Handler.AddJsonResponse(listUri, ReadFixture("Fixtures/cve-list.json"));
|
||||
harness.Handler.SetFallback(request =>
|
||||
{
|
||||
if (request.RequestUri is null)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
if (request.RequestUri.AbsoluteUri.Equals("https://cve.test/api/cve/CVE-2024-0001", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(ReadFixture("Fixtures/cve-CVE-2024-0001.json"), Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
});
|
||||
|
||||
var metrics = new Dictionary<string, long>(StringComparer.Ordinal);
|
||||
using var listener = new MeterListener
|
||||
{
|
||||
InstrumentPublished = (instrument, meterListener) =>
|
||||
{
|
||||
if (instrument.Meter.Name == CveDiagnostics.MeterName)
|
||||
{
|
||||
meterListener.EnableMeasurementEvents(instrument);
|
||||
}
|
||||
}
|
||||
};
|
||||
listener.SetMeasurementEventCallback<long>((instrument, value, tags, state) =>
|
||||
{
|
||||
if (metrics.TryGetValue(instrument.Name, out var existing))
|
||||
{
|
||||
metrics[instrument.Name] = existing + value;
|
||||
}
|
||||
else
|
||||
{
|
||||
metrics[instrument.Name] = value;
|
||||
}
|
||||
});
|
||||
listener.Start();
|
||||
|
||||
var connector = new CveConnectorPlugin().Create(harness.ServiceProvider);
|
||||
|
||||
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
await connector.ParseAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
await connector.MapAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
|
||||
listener.Dispose();
|
||||
|
||||
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
|
||||
var advisory = await advisoryStore.FindAsync("CVE-2024-0001", CancellationToken.None);
|
||||
Assert.NotNull(advisory);
|
||||
|
||||
var snapshot = SnapshotSerializer.ToSnapshot(advisory!).Replace("\r\n", "\n").TrimEnd();
|
||||
var expected = ReadFixture("Fixtures/expected-CVE-2024-0001.json").Replace("\r\n", "\n").TrimEnd();
|
||||
|
||||
if (!string.Equals(expected, snapshot, StringComparison.Ordinal))
|
||||
{
|
||||
var actualPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "expected-CVE-2024-0001.actual.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(actualPath)!);
|
||||
File.WriteAllText(actualPath, snapshot);
|
||||
}
|
||||
|
||||
Assert.Equal(expected, snapshot);
|
||||
harness.Handler.AssertNoPendingResponses();
|
||||
|
||||
_output.WriteLine("CVE connector smoke metrics:");
|
||||
foreach (var entry in metrics.OrderBy(static pair => pair.Key, StringComparer.Ordinal))
|
||||
{
|
||||
_output.WriteLine($" {entry.Key} = {entry.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchWithoutCredentials_SeedsFromDirectory()
|
||||
{
|
||||
var initialTime = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
var projectRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", ".."));
|
||||
var repositoryRoot = Path.GetFullPath(Path.Combine(projectRoot, "..", ".."));
|
||||
var seedDirectory = Path.Combine(repositoryRoot, "seed-data", "cve", "2025-10-15");
|
||||
Assert.True(Directory.Exists(seedDirectory), $"Seed directory '{seedDirectory}' was not found.");
|
||||
|
||||
await using var harness = new ConnectorTestHarness(_fixture, initialTime, CveOptions.HttpClientName);
|
||||
await harness.EnsureServiceProviderAsync(services =>
|
||||
{
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddProvider(new TestOutputLoggerProvider(_output, LogLevel.Information));
|
||||
builder.SetMinimumLevel(LogLevel.Information);
|
||||
});
|
||||
services.AddCveConnector(options =>
|
||||
{
|
||||
options.BaseEndpoint = new Uri("https://cve.test/api/", UriKind.Absolute);
|
||||
options.SeedDirectory = seedDirectory;
|
||||
options.PageSize = 5;
|
||||
options.MaxPagesPerFetch = 1;
|
||||
options.InitialBackfill = TimeSpan.FromDays(30);
|
||||
options.RequestDelay = TimeSpan.Zero;
|
||||
});
|
||||
});
|
||||
|
||||
var connector = new CveConnectorPlugin().Create(harness.ServiceProvider);
|
||||
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
|
||||
Assert.Empty(harness.Handler.Requests);
|
||||
|
||||
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
|
||||
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
|
||||
var keys = advisories.Select(advisory => advisory.AdvisoryKey).ToArray();
|
||||
|
||||
Assert.Contains("CVE-2024-0001", keys);
|
||||
Assert.Contains("CVE-2024-4567", keys);
|
||||
}
|
||||
|
||||
private async Task EnsureHarnessAsync(DateTimeOffset initialTime)
|
||||
{
|
||||
if (_harness is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var harness = new ConnectorTestHarness(_fixture, initialTime, CveOptions.HttpClientName);
|
||||
await harness.EnsureServiceProviderAsync(services =>
|
||||
{
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddProvider(new TestOutputLoggerProvider(_output, LogLevel.Information));
|
||||
builder.SetMinimumLevel(LogLevel.Information);
|
||||
});
|
||||
services.AddCveConnector(options =>
|
||||
{
|
||||
options.BaseEndpoint = new Uri("https://cve.test/api/", UriKind.Absolute);
|
||||
options.ApiOrg = "test-org";
|
||||
options.ApiUser = "test-user";
|
||||
options.ApiKey = "test-key";
|
||||
options.InitialBackfill = TimeSpan.FromDays(30);
|
||||
options.PageSize = 5;
|
||||
options.MaxPagesPerFetch = 2;
|
||||
options.RequestDelay = TimeSpan.Zero;
|
||||
});
|
||||
});
|
||||
|
||||
_harness = harness;
|
||||
}
|
||||
|
||||
private static string ReadFixture(string relativePath)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, relativePath);
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_harness is not null)
|
||||
{
|
||||
await _harness.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestOutputLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly LogLevel _minLevel;
|
||||
|
||||
public TestOutputLoggerProvider(ITestOutputHelper output, LogLevel minLevel)
|
||||
{
|
||||
_output = output;
|
||||
_minLevel = minLevel;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName) => new TestOutputLogger(_output, _minLevel);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class TestOutputLogger : ILogger
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly LogLevel _minLevel;
|
||||
|
||||
public TestOutputLogger(ITestOutputHelper output, LogLevel minLevel)
|
||||
{
|
||||
_output = output;
|
||||
_minLevel = minLevel;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullLogger.Instance.BeginScope(state);
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLevel;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (IsEnabled(logLevel))
|
||||
{
|
||||
_output.WriteLine(formatter(state, exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class CveConnectorTests : IAsyncLifetime
|
||||
{
|
||||
private readonly ConcelierPostgresFixture _fixture;
|
||||
private readonly ITestOutputHelper _output;
|
||||
private ConnectorTestHarness? _harness;
|
||||
|
||||
public CveConnectorTests(ConcelierPostgresFixture fixture, ITestOutputHelper output)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchParseMap_EmitsCanonicalAdvisory()
|
||||
{
|
||||
var initialTime = new DateTimeOffset(2024, 10, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
await EnsureHarnessAsync(initialTime);
|
||||
var harness = _harness!;
|
||||
|
||||
var since = initialTime - TimeSpan.FromDays(30);
|
||||
var listUri = new Uri($"https://cve.test/api/cve?time_modified.gte={Uri.EscapeDataString(since.ToString("O"))}&time_modified.lte={Uri.EscapeDataString(initialTime.ToString("O"))}&page=1&size=5");
|
||||
harness.Handler.AddJsonResponse(listUri, ReadFixture("Fixtures/cve-list.json"));
|
||||
harness.Handler.SetFallback(request =>
|
||||
{
|
||||
if (request.RequestUri is null)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
if (request.RequestUri.AbsoluteUri.Equals("https://cve.test/api/cve/CVE-2024-0001", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(ReadFixture("Fixtures/cve-CVE-2024-0001.json"), Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
});
|
||||
|
||||
var metrics = new Dictionary<string, long>(StringComparer.Ordinal);
|
||||
using var listener = new MeterListener
|
||||
{
|
||||
InstrumentPublished = (instrument, meterListener) =>
|
||||
{
|
||||
if (instrument.Meter.Name == CveDiagnostics.MeterName)
|
||||
{
|
||||
meterListener.EnableMeasurementEvents(instrument);
|
||||
}
|
||||
}
|
||||
};
|
||||
listener.SetMeasurementEventCallback<long>((instrument, value, tags, state) =>
|
||||
{
|
||||
if (metrics.TryGetValue(instrument.Name, out var existing))
|
||||
{
|
||||
metrics[instrument.Name] = existing + value;
|
||||
}
|
||||
else
|
||||
{
|
||||
metrics[instrument.Name] = value;
|
||||
}
|
||||
});
|
||||
listener.Start();
|
||||
|
||||
var connector = new CveConnectorPlugin().Create(harness.ServiceProvider);
|
||||
|
||||
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
await connector.ParseAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
await connector.MapAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
|
||||
listener.Dispose();
|
||||
|
||||
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
|
||||
var advisory = await advisoryStore.FindAsync("CVE-2024-0001", CancellationToken.None);
|
||||
Assert.NotNull(advisory);
|
||||
|
||||
var snapshot = SnapshotSerializer.ToSnapshot(advisory!).Replace("\r\n", "\n").TrimEnd();
|
||||
var expected = ReadFixture("Fixtures/expected-CVE-2024-0001.json").Replace("\r\n", "\n").TrimEnd();
|
||||
|
||||
if (!string.Equals(expected, snapshot, StringComparison.Ordinal))
|
||||
{
|
||||
var actualPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "expected-CVE-2024-0001.actual.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(actualPath)!);
|
||||
File.WriteAllText(actualPath, snapshot);
|
||||
}
|
||||
|
||||
Assert.Equal(expected, snapshot);
|
||||
harness.Handler.AssertNoPendingResponses();
|
||||
|
||||
_output.WriteLine("CVE connector smoke metrics:");
|
||||
foreach (var entry in metrics.OrderBy(static pair => pair.Key, StringComparer.Ordinal))
|
||||
{
|
||||
_output.WriteLine($" {entry.Key} = {entry.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchWithoutCredentials_SeedsFromDirectory()
|
||||
{
|
||||
var initialTime = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
var projectRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", ".."));
|
||||
var repositoryRoot = Path.GetFullPath(Path.Combine(projectRoot, "..", ".."));
|
||||
var seedDirectory = Path.Combine(repositoryRoot, "seed-data", "cve", "2025-10-15");
|
||||
Assert.True(Directory.Exists(seedDirectory), $"Seed directory '{seedDirectory}' was not found.");
|
||||
|
||||
await using var harness = new ConnectorTestHarness(_fixture, initialTime, CveOptions.HttpClientName);
|
||||
await harness.EnsureServiceProviderAsync(services =>
|
||||
{
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddProvider(new TestOutputLoggerProvider(_output, LogLevel.Information));
|
||||
builder.SetMinimumLevel(LogLevel.Information);
|
||||
});
|
||||
services.AddCveConnector(options =>
|
||||
{
|
||||
options.BaseEndpoint = new Uri("https://cve.test/api/", UriKind.Absolute);
|
||||
options.SeedDirectory = seedDirectory;
|
||||
options.PageSize = 5;
|
||||
options.MaxPagesPerFetch = 1;
|
||||
options.InitialBackfill = TimeSpan.FromDays(30);
|
||||
options.RequestDelay = TimeSpan.Zero;
|
||||
});
|
||||
});
|
||||
|
||||
var connector = new CveConnectorPlugin().Create(harness.ServiceProvider);
|
||||
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
|
||||
Assert.Empty(harness.Handler.Requests);
|
||||
|
||||
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
|
||||
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
|
||||
var keys = advisories.Select(advisory => advisory.AdvisoryKey).ToArray();
|
||||
|
||||
Assert.Contains("CVE-2024-0001", keys);
|
||||
Assert.Contains("CVE-2024-4567", keys);
|
||||
}
|
||||
|
||||
private async Task EnsureHarnessAsync(DateTimeOffset initialTime)
|
||||
{
|
||||
if (_harness is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var harness = new ConnectorTestHarness(_fixture, initialTime, CveOptions.HttpClientName);
|
||||
await harness.EnsureServiceProviderAsync(services =>
|
||||
{
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddProvider(new TestOutputLoggerProvider(_output, LogLevel.Information));
|
||||
builder.SetMinimumLevel(LogLevel.Information);
|
||||
});
|
||||
services.AddCveConnector(options =>
|
||||
{
|
||||
options.BaseEndpoint = new Uri("https://cve.test/api/", UriKind.Absolute);
|
||||
options.ApiOrg = "test-org";
|
||||
options.ApiUser = "test-user";
|
||||
options.ApiKey = "test-key";
|
||||
options.InitialBackfill = TimeSpan.FromDays(30);
|
||||
options.PageSize = 5;
|
||||
options.MaxPagesPerFetch = 2;
|
||||
options.RequestDelay = TimeSpan.Zero;
|
||||
});
|
||||
});
|
||||
|
||||
_harness = harness;
|
||||
}
|
||||
|
||||
private static string ReadFixture(string relativePath)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, relativePath);
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_harness is not null)
|
||||
{
|
||||
await _harness.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestOutputLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly LogLevel _minLevel;
|
||||
|
||||
public TestOutputLoggerProvider(ITestOutputHelper output, LogLevel minLevel)
|
||||
{
|
||||
_output = output;
|
||||
_minLevel = minLevel;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName) => new TestOutputLogger(_output, _minLevel);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class TestOutputLogger : ILogger
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly LogLevel _minLevel;
|
||||
|
||||
public TestOutputLogger(ITestOutputHelper output, LogLevel minLevel)
|
||||
{
|
||||
_output = output;
|
||||
_minLevel = minLevel;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullLogger.Instance.BeginScope(state);
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLevel;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (IsEnabled(logLevel))
|
||||
{
|
||||
_output.WriteLine(formatter(state, exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user