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,121 @@
|
||||
using System.Reflection;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests.Historian;
|
||||
|
||||
/// <summary>
|
||||
/// Structural contract tests for the historian data-source surface added in PR 1.1.
|
||||
/// Asserts the type shape — implementations are tested in their own projects.
|
||||
/// </summary>
|
||||
public sealed class IHistorianDataSourceContractTests
|
||||
{
|
||||
[Fact]
|
||||
public void Interface_LivesInRootNamespace()
|
||||
{
|
||||
typeof(IHistorianDataSource).Namespace
|
||||
.ShouldBe("ZB.MOM.WW.OtOpcUa.Core.Abstractions");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interface_IsPublic()
|
||||
{
|
||||
typeof(IHistorianDataSource).IsPublic.ShouldBeTrue();
|
||||
typeof(IHistorianDataSource).IsInterface.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interface_ExtendsIDisposable()
|
||||
{
|
||||
typeof(IDisposable).IsAssignableFrom(typeof(IHistorianDataSource))
|
||||
.ShouldBeTrue("data sources own backend connections; the server disposes them on shutdown");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("ReadRawAsync", typeof(Task<HistoryReadResult>))]
|
||||
[InlineData("ReadProcessedAsync", typeof(Task<HistoryReadResult>))]
|
||||
[InlineData("ReadAtTimeAsync", typeof(Task<HistoryReadResult>))]
|
||||
[InlineData("ReadEventsAsync", typeof(Task<HistoricalEventsResult>))]
|
||||
public void ReadMethods_ReturnExpectedTaskShape(string methodName, Type expectedReturnType)
|
||||
{
|
||||
var method = typeof(IHistorianDataSource).GetMethod(methodName);
|
||||
method.ShouldNotBeNull();
|
||||
method!.ReturnType.ShouldBe(expectedReturnType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHealthSnapshot_IsSynchronous()
|
||||
{
|
||||
var method = typeof(IHistorianDataSource).GetMethod("GetHealthSnapshot");
|
||||
method.ShouldNotBeNull();
|
||||
method!.ReturnType.ShouldBe(typeof(HistorianHealthSnapshot));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HealthSnapshot_AcceptsEmptyClusterNodeList()
|
||||
{
|
||||
var snapshot = new HistorianHealthSnapshot(
|
||||
TotalQueries: 0,
|
||||
TotalSuccesses: 0,
|
||||
TotalFailures: 0,
|
||||
ConsecutiveFailures: 0,
|
||||
LastSuccessTime: null,
|
||||
LastFailureTime: null,
|
||||
LastError: null,
|
||||
ProcessConnectionOpen: false,
|
||||
EventConnectionOpen: false,
|
||||
ActiveProcessNode: null,
|
||||
ActiveEventNode: null,
|
||||
Nodes: Array.Empty<HistorianClusterNodeState>());
|
||||
|
||||
snapshot.Nodes.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HealthSnapshot_PreservesClusterNodes()
|
||||
{
|
||||
var node = new HistorianClusterNodeState(
|
||||
Name: "hist-01",
|
||||
IsHealthy: true,
|
||||
CooldownUntil: null,
|
||||
FailureCount: 0,
|
||||
LastError: null,
|
||||
LastFailureTime: null);
|
||||
|
||||
var snapshot = new HistorianHealthSnapshot(
|
||||
TotalQueries: 5,
|
||||
TotalSuccesses: 5,
|
||||
TotalFailures: 0,
|
||||
ConsecutiveFailures: 0,
|
||||
LastSuccessTime: new DateTime(2026, 4, 29, 12, 0, 0, DateTimeKind.Utc),
|
||||
LastFailureTime: null,
|
||||
LastError: null,
|
||||
ProcessConnectionOpen: true,
|
||||
EventConnectionOpen: true,
|
||||
ActiveProcessNode: "hist-01",
|
||||
ActiveEventNode: "hist-01",
|
||||
Nodes: new[] { node });
|
||||
|
||||
snapshot.Nodes.Count.ShouldBe(1);
|
||||
snapshot.Nodes[0].ShouldBe(node);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClusterNodeState_RecordEqualityByValue()
|
||||
{
|
||||
var a = new HistorianClusterNodeState("hist-01", true, null, 0, null, null);
|
||||
var b = new HistorianClusterNodeState("hist-01", true, null, 0, null, null);
|
||||
|
||||
a.ShouldBe(b);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClusterNodeState_DistinctByAnyField()
|
||||
{
|
||||
var healthy = new HistorianClusterNodeState("hist-01", true, null, 0, null, null);
|
||||
var unhealthy = new HistorianClusterNodeState("hist-01", false, null, 1, "boom", null);
|
||||
|
||||
healthy.ShouldNotBe(unhealthy);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user