test(adminui): browse session registry, reaper, service
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Browsing;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Browsing;
|
||||
|
||||
/// <summary>Unit tests for <see cref="BrowseSessionReaper"/>. We drive the reaper
|
||||
/// directly through the internal <c>ReapOnceAsync</c> entry point and skew
|
||||
/// <see cref="FakeBrowseSession.LastUsedUtc"/> to simulate the passage of time —
|
||||
/// no real wall-clock waits.</summary>
|
||||
public sealed class BrowseSessionReaperTests
|
||||
{
|
||||
private static BrowseSessionReaper NewReaper(BrowseSessionRegistry registry) =>
|
||||
new(registry, NullLogger<BrowseSessionReaper>.Instance);
|
||||
|
||||
[Fact]
|
||||
public async Task ReapOnceAsync_evicts_idle_session()
|
||||
{
|
||||
var registry = new BrowseSessionRegistry();
|
||||
var session = new FakeBrowseSession
|
||||
{
|
||||
LastUsedUtc = DateTime.UtcNow - TimeSpan.FromMinutes(3),
|
||||
};
|
||||
registry.Register(session);
|
||||
var reaper = NewReaper(registry);
|
||||
|
||||
await reaper.ReapOnceAsync(CancellationToken.None);
|
||||
|
||||
registry.TryGet(session.Token, out _).ShouldBeFalse();
|
||||
session.Disposed.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReapOnceAsync_preserves_recent_session()
|
||||
{
|
||||
var registry = new BrowseSessionRegistry();
|
||||
var session = new FakeBrowseSession { LastUsedUtc = DateTime.UtcNow };
|
||||
registry.Register(session);
|
||||
var reaper = NewReaper(registry);
|
||||
|
||||
await reaper.ReapOnceAsync(CancellationToken.None);
|
||||
|
||||
registry.TryGet(session.Token, out _).ShouldBeTrue();
|
||||
session.Disposed.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReapOnceAsync_handles_already_removed_session()
|
||||
{
|
||||
var registry = new BrowseSessionRegistry();
|
||||
var session = new FakeBrowseSession
|
||||
{
|
||||
LastUsedUtc = DateTime.UtcNow - TimeSpan.FromMinutes(3),
|
||||
};
|
||||
registry.Register(session);
|
||||
// Race the reaper: pull the entry out before the loop calls TryRemove.
|
||||
registry.TryRemove(session.Token, out _).ShouldBeTrue();
|
||||
var reaper = NewReaper(registry);
|
||||
|
||||
// No exception, no double dispose. Because we removed the session ourselves
|
||||
// without disposing it, Disposed should still be false.
|
||||
await reaper.ReapOnceAsync(CancellationToken.None);
|
||||
|
||||
session.Disposed.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReapOnceAsync_continues_when_one_session_dispose_throws()
|
||||
{
|
||||
var registry = new BrowseSessionRegistry();
|
||||
var staleAt = DateTime.UtcNow - TimeSpan.FromMinutes(3);
|
||||
var bad = new FakeBrowseSession { LastUsedUtc = staleAt, ThrowOnDispose = true };
|
||||
var good = new FakeBrowseSession { LastUsedUtc = staleAt };
|
||||
registry.Register(bad);
|
||||
registry.Register(good);
|
||||
var reaper = NewReaper(registry);
|
||||
|
||||
// The dispose exception is swallowed; the second session must still be reaped + disposed.
|
||||
await Should.NotThrowAsync(() => reaper.ReapOnceAsync(CancellationToken.None));
|
||||
|
||||
registry.TryGet(bad.Token, out _).ShouldBeFalse();
|
||||
registry.TryGet(good.Token, out _).ShouldBeFalse();
|
||||
good.Disposed.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user