chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Health;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Health;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="HostStatusAggregator"/> — the merge + diff logic for the
|
||||
/// transport entry plus per-platform probe entries that
|
||||
/// <c>IHostConnectivityProbe.GetHostStatuses()</c> surfaces.
|
||||
/// </summary>
|
||||
public sealed class HostStatusAggregatorTests
|
||||
{
|
||||
private static HostConnectivityStatus Status(string host, HostState state) =>
|
||||
new(host, state, DateTime.UtcNow);
|
||||
|
||||
[Fact]
|
||||
public void Snapshot_Empty_WhenNothingTracked()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
agg.Snapshot().ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_NewHost_FiresChange_PreviousIsUnknown()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
var captured = new List<HostStatusChangedEventArgs>();
|
||||
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
|
||||
|
||||
agg.Update(Status("PlatformA", HostState.Running));
|
||||
|
||||
captured.Count.ShouldBe(1);
|
||||
captured[0].HostName.ShouldBe("PlatformA");
|
||||
captured[0].OldState.ShouldBe(HostState.Unknown);
|
||||
captured[0].NewState.ShouldBe(HostState.Running);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_SameState_DoesNotFire()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
agg.Update(Status("PlatformA", HostState.Running));
|
||||
|
||||
var captured = new List<HostStatusChangedEventArgs>();
|
||||
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
|
||||
|
||||
agg.Update(Status("PlatformA", HostState.Running));
|
||||
|
||||
captured.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_StateTransition_FiresChangeWithCorrectPreviousAndNew()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
agg.Update(Status("PlatformA", HostState.Running));
|
||||
|
||||
var captured = new List<HostStatusChangedEventArgs>();
|
||||
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
|
||||
|
||||
agg.Update(Status("PlatformA", HostState.Stopped));
|
||||
|
||||
captured.Count.ShouldBe(1);
|
||||
captured[0].OldState.ShouldBe(HostState.Running);
|
||||
captured[0].NewState.ShouldBe(HostState.Stopped);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Snapshot_ReflectsEveryUpsertedHost()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
agg.Update(Status("Transport", HostState.Running));
|
||||
agg.Update(Status("PlatformA", HostState.Running));
|
||||
agg.Update(Status("PlatformB", HostState.Stopped));
|
||||
|
||||
var snap = agg.Snapshot();
|
||||
|
||||
snap.Count.ShouldBe(3);
|
||||
snap.Select(s => s.HostName).OrderBy(x => x).ShouldBe(new[] { "PlatformA", "PlatformB", "Transport" });
|
||||
snap.First(s => s.HostName == "PlatformB").State.ShouldBe(HostState.Stopped);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_HostNameComparison_IsCaseInsensitive()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
var captured = new List<HostStatusChangedEventArgs>();
|
||||
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
|
||||
|
||||
agg.Update(Status("PlatformA", HostState.Running));
|
||||
agg.Update(Status("platforma", HostState.Stopped)); // same host, different case
|
||||
|
||||
captured.Count.ShouldBe(2);
|
||||
captured[1].OldState.ShouldBe(HostState.Running);
|
||||
captured[1].NewState.ShouldBe(HostState.Stopped);
|
||||
agg.Snapshot().Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_TrackedHost_ReturnsTrue_AndDropsFromSnapshot()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
agg.Update(Status("PlatformA", HostState.Running));
|
||||
agg.Remove("PlatformA").ShouldBeTrue();
|
||||
agg.Snapshot().ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_UnknownHost_ReturnsFalse()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
agg.Remove("Nope").ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConcurrentUpdates_DoNotCorruptDictionary()
|
||||
{
|
||||
var agg = new HostStatusAggregator();
|
||||
const int threadCount = 8;
|
||||
const int updatesPerThread = 250;
|
||||
|
||||
var tasks = Enumerable.Range(0, threadCount).Select(t => Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < updatesPerThread; i++)
|
||||
{
|
||||
var hostName = $"Host{(t * updatesPerThread + i) % 32}";
|
||||
var state = i % 2 == 0 ? HostState.Running : HostState.Stopped;
|
||||
agg.Update(Status(hostName, state));
|
||||
}
|
||||
})).ToArray();
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
agg.Snapshot().Count.ShouldBeLessThanOrEqualTo(32);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user