Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Browsing/BrowserSessionServiceTests.cs
T
2026-05-28 15:44:20 -04:00

143 lines
5.1 KiB
C#

using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Browsing;
using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Browsing;
/// <summary>Unit tests for <see cref="BrowserSessionService"/> — driver-type dispatch
/// on open, NotFound semantics on unknown tokens, exception swallowing, and per-call
/// timeout enforcement.</summary>
public sealed class BrowserSessionServiceTests
{
private static BrowserSessionService NewService(
BrowseSessionRegistry registry, params IDriverBrowser[] browsers) =>
new(browsers, registry, NullLogger<BrowserSessionService>.Instance);
[Fact]
public async Task OpenAsync_unknown_driver_type_returns_Ok_false_with_message()
{
var registry = new BrowseSessionRegistry();
var service = NewService(registry, new FakeDriverBrowser("Known"));
var result = await service.OpenAsync("Unknown", "{}", CancellationToken.None);
result.Ok.ShouldBeFalse();
result.Token.ShouldBe(Guid.Empty);
result.Message.ShouldNotBeNull();
result.Message!.ShouldContain("Unknown");
}
[Fact]
public async Task OpenAsync_happy_path_returns_token_and_registers()
{
var registry = new BrowseSessionRegistry();
var session = new FakeBrowseSession();
var browser = new FakeDriverBrowser("Galaxy")
{
OpenHandler = (_, _) => Task.FromResult<IBrowseSession>(session),
};
var service = NewService(registry, browser);
var result = await service.OpenAsync("Galaxy", "{}", CancellationToken.None);
result.Ok.ShouldBeTrue();
result.Message.ShouldBeNull();
result.Token.ShouldNotBe(Guid.Empty);
result.Token.ShouldBe(session.Token);
registry.TryGet(result.Token, out var registered).ShouldBeTrue();
registered.ShouldBeSameAs((IBrowseSession)session);
}
[Fact]
public async Task OpenAsync_swallows_driver_throws_returns_Ok_false()
{
var registry = new BrowseSessionRegistry();
var browser = new FakeDriverBrowser("Galaxy")
{
OpenHandler = (_, _) => throw new InvalidOperationException("boom"),
};
var service = NewService(registry, browser);
var result = await service.OpenAsync("Galaxy", "{}", CancellationToken.None);
result.Ok.ShouldBeFalse();
result.Token.ShouldBe(Guid.Empty);
result.Message.ShouldNotBeNull();
result.Message!.ShouldContain("boom");
}
[Fact]
public async Task RootAsync_unknown_token_throws_BrowseSessionNotFoundException()
{
var registry = new BrowseSessionRegistry();
var service = NewService(registry);
await Should.ThrowAsync<BrowseSessionNotFoundException>(
() => service.RootAsync(Guid.NewGuid(), CancellationToken.None));
}
[Fact]
public async Task RootAsync_invokes_session_Root()
{
var registry = new BrowseSessionRegistry();
IReadOnlyList<BrowseNode> expected = new[]
{
new BrowseNode("ns=2;s=A", "A", BrowseNodeKind.Folder, true),
new BrowseNode("ns=2;s=B", "B", BrowseNodeKind.Leaf, false),
};
var session = new FakeBrowseSession { RootHandler = _ => Task.FromResult(expected) };
registry.Register(session);
var service = NewService(registry);
var actual = await service.RootAsync(session.Token, CancellationToken.None);
actual.ShouldBe(expected);
}
[Fact]
public async Task RootAsync_enforces_PerCallTimeout()
{
var registry = new BrowseSessionRegistry();
var session = new FakeBrowseSession
{
// Awaits the linked CTS — the service must cancel it via PerCallTimeout (20s).
RootHandler = ct => Task.Delay(TimeSpan.FromSeconds(40), ct)
.ContinueWith<IReadOnlyList<BrowseNode>>(_ => Array.Empty<BrowseNode>(), ct),
};
registry.Register(session);
var service = NewService(registry);
var sw = System.Diagnostics.Stopwatch.StartNew();
await Should.ThrowAsync<OperationCanceledException>(
() => service.RootAsync(session.Token, CancellationToken.None));
sw.Stop();
// Real timeout is 20s; allow generous slack but cap well below the 40s task delay.
sw.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(35));
}
[Fact]
public async Task CloseAsync_removes_and_disposes_session()
{
var registry = new BrowseSessionRegistry();
var session = new FakeBrowseSession();
var browser = new FakeDriverBrowser("Galaxy")
{
OpenHandler = (_, _) => Task.FromResult<IBrowseSession>(session),
};
var service = NewService(registry, browser);
var opened = await service.OpenAsync("Galaxy", "{}", CancellationToken.None);
opened.Ok.ShouldBeTrue();
await service.CloseAsync(opened.Token);
registry.TryGet(opened.Token, out _).ShouldBeFalse();
session.Disposed.ShouldBeTrue();
// Unknown token is a no-op.
await Should.NotThrowAsync(() => service.CloseAsync(Guid.NewGuid()));
}
}