Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Audit/ConfigurationAuditLogDisposeTests.cs
T

61 lines
2.8 KiB
C#

using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using AuditLogPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Audit.ConfigurationAuditLog;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Audit;
/// <summary>Review 2026-10-21 07/M-1: the browser-time JS module must be disposed with the page.</summary>
public class ConfigurationAuditLogDisposeTests : BunitContext
{
private sealed class StubAuth(AuthenticationState state) : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync() => Task.FromResult(state);
}
private (IJSObjectReference module, IRenderedComponent<AuditLogPage> cut) Arrange(bool moduleThrowsOnDispose)
{
var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "u") }, "T");
var auth = new StubAuth(new AuthenticationState(new ClaimsPrincipal(identity)));
Services.AddSingleton<AuthenticationStateProvider>(auth);
Services.AddScoped(_ => new SiteScopeService(auth));
Services.AddScoped<IDialogService, DialogService>();
Services.AddSingleton(Substitute.For<ICentralUiRepository>());
var module = Substitute.For<IJSObjectReference>();
module.InvokeAsync<int>("getTimezoneOffsetMinutes", Arg.Any<object?[]>()).Returns(new ValueTask<int>(0));
if (moduleThrowsOnDispose)
module.DisposeAsync().Returns(_ => ValueTask.FromException(new JSDisconnectedException("gone")));
var js = Substitute.For<IJSRuntime>();
js.InvokeAsync<IJSObjectReference>("import", Arg.Any<object?[]>())
.Returns(new ValueTask<IJSObjectReference>(module));
Services.AddSingleton(js);
return (module, Render<AuditLogPage>());
}
/// <summary>Disposing the page disposes the imported module.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Dispose_DisposesBrowserTimeModule()
{
var (module, cut) = Arrange(moduleThrowsOnDispose: false);
await cut.InvokeAsync(async () => await ((IAsyncDisposable)cut.Instance).DisposeAsync());
await module.Received(1).DisposeAsync();
}
/// <summary>A disconnected circuit's dispose failure is swallowed.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Dispose_SwallowsJSDisconnected()
{
var (_, cut) = Arrange(moduleThrowsOnDispose: true);
await cut.InvokeAsync(async () => await ((IAsyncDisposable)cut.Instance).DisposeAsync());
}
}