fix(central-ui): resolve CentralUI-015..019 — pager windowing, logout CSRF, narrowed catch blocks, coverage; CentralUI-015 re-triaged Won't Fix
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
using Bunit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.JSInterop;
|
||||
using ScadaLink.CentralUI.Components.Shared;
|
||||
|
||||
namespace ScadaLink.CentralUI.Tests.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Regression tests for CentralUI-018. <c>MonacoEditor</c> wrapped every JS
|
||||
/// interop call in a bare <c>try { ... } catch { }</c> with no logging — a
|
||||
/// genuine Monaco init failure became invisible. The fix narrows the catch to
|
||||
/// the expected prerender / disconnect cases and logs any real
|
||||
/// <see cref="JSException"/> via <c>ILogger</c>.
|
||||
/// </summary>
|
||||
public class MonacoEditorLoggingTests : BunitContext
|
||||
{
|
||||
/// <summary>Captures log entries so the test can assert on them.</summary>
|
||||
private sealed class CapturingLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public List<(LogLevel Level, string Message, Exception? Exception)> Entries { get; } = new();
|
||||
|
||||
public ILogger CreateLogger(string categoryName) => new CapturingLogger(Entries);
|
||||
public void Dispose() { }
|
||||
|
||||
private sealed class CapturingLogger : ILogger
|
||||
{
|
||||
private readonly List<(LogLevel, string, Exception?)> _entries;
|
||||
public CapturingLogger(List<(LogLevel, string, Exception?)> entries) => _entries = entries;
|
||||
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||
Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
=> _entries.Add((logLevel, formatter(state, exception), exception));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateEditor_GenuineJsException_IsLogged_NotSwallowed()
|
||||
{
|
||||
var provider = new CapturingLoggerProvider();
|
||||
Services.AddLogging(b => b.AddProvider(provider));
|
||||
|
||||
// createEditor is an InvokeVoidAsync call — configure it to throw a
|
||||
// genuine JSException so we exercise the real-failure path.
|
||||
JSInterop.Mode = JSRuntimeMode.Strict;
|
||||
JSInterop.SetupVoid("MonacoBlazor.createEditor", _ => true)
|
||||
.SetException(new JSException("Monaco failed to load"));
|
||||
|
||||
// Pre-fix: the bare catch {} swallowed this with no trace. Post-fix:
|
||||
// the component renders fine but the failure is logged.
|
||||
var cut = Render<MonacoEditor>(p => p.Add(c => c.ShowToolbar, false));
|
||||
|
||||
var errors = provider.Entries.Where(e => e.Level == LogLevel.Error).ToList();
|
||||
Assert.NotEmpty(errors);
|
||||
Assert.Contains(errors, e => e.Exception is JSException);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateEditor_Prerender_DoesNotLog()
|
||||
{
|
||||
// When JS interop is unavailable (prerender), createEditor throws
|
||||
// InvalidOperationException — that is expected and must NOT be logged.
|
||||
var provider = new CapturingLoggerProvider();
|
||||
Services.AddLogging(b => b.AddProvider(provider));
|
||||
|
||||
JSInterop.Mode = JSRuntimeMode.Strict;
|
||||
JSInterop.SetupVoid("MonacoBlazor.createEditor", _ => true)
|
||||
.SetException(new InvalidOperationException("JS interop not available during prerender"));
|
||||
|
||||
var cut = Render<MonacoEditor>(p => p.Add(c => c.ShowToolbar, false));
|
||||
|
||||
Assert.DoesNotContain(provider.Entries, e => e.Level >= LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user