- Driver.Historian.Wonderware-004: ToHistorianEvent synthesises a fresh
Guid when the upstream EventId is unparseable and logs the substitution
instead of writing the historian with Guid.Empty.
- Driver.Historian.Wonderware-005: GetHealthSnapshot derives the
connection-open booleans from the active-node fields so the snapshot
is self-consistent without depending on the secondary lock.
- Driver.Historian.Wonderware-007: SID-mismatch branch in PipeServer now
sends a HelloAck { Accepted=false, RejectReason } so the client sees a
symmetric rejection.
- Driver.Historian.Wonderware-008: classify StartQuery failures —
connection-class codes drop the connection, query-class codes throw
QueryClassStartQueryException so the IPC layer surfaces Success=false.
- Driver.Historian.Wonderware-010: RequestTimeoutSeconds now enforced
via BuildRequestCts linked to the caller's CancellationToken.
- Driver.Historian.Wonderware-011: refreshed XML docs to describe the
current sidecar / named-pipe architecture (Galaxy.Host / Proxy
references reframed as historical context).
- Driver.Historian.Wonderware-012: pinned the previously-uncovered
HistorianDataSource behaviours with five new test files; also removed
the stale empty tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests
directory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
51 lines
2.6 KiB
C#
51 lines
2.6 KiB
C#
using ArchestrA;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.Backend;
|
|
|
|
/// <summary>
|
|
/// Driver.Historian.Wonderware-008 regression. The previous implementation unconditionally
|
|
/// called <c>HandleConnectionError()</c> whenever <c>StartQuery</c> returned <c>false</c>,
|
|
/// which tore down the (relatively expensive) shared SDK connection on a query-class error
|
|
/// such as a bad tag name. A burst of bad-tag queries could therefore push an otherwise
|
|
/// healthy cluster node into cooldown via the picker's <c>MarkFailed</c>. The fix
|
|
/// classifies the SDK error code: connection-class codes drop the connection; query-class
|
|
/// codes leave it intact.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class HistorianDataSourceStartQueryClassificationTests
|
|
{
|
|
// ── Connection-class codes — the connection should be reset ───────────
|
|
|
|
[Theory]
|
|
[InlineData(HistorianAccessError.ErrorValue.FailedToConnect)]
|
|
[InlineData(HistorianAccessError.ErrorValue.FailedToCreateSession)]
|
|
[InlineData(HistorianAccessError.ErrorValue.NoReply)]
|
|
[InlineData(HistorianAccessError.ErrorValue.NotReady)]
|
|
[InlineData(HistorianAccessError.ErrorValue.NotInitialized)]
|
|
[InlineData(HistorianAccessError.ErrorValue.Stopping)]
|
|
[InlineData(HistorianAccessError.ErrorValue.Win32Exception)]
|
|
[InlineData(HistorianAccessError.ErrorValue.InvalidResponse)]
|
|
public void Connection_class_codes_are_classified_as_connection_errors(HistorianAccessError.ErrorValue code)
|
|
{
|
|
HistorianDataSource.IsConnectionClassError(code).ShouldBeTrue(
|
|
$"{code} is a connection/server failure — the SDK connection should be reset");
|
|
}
|
|
|
|
// ── Query-class codes — the connection should NOT be reset ────────────
|
|
|
|
[Theory]
|
|
[InlineData(HistorianAccessError.ErrorValue.InvalidArgument)] // bad tag name, etc.
|
|
[InlineData(HistorianAccessError.ErrorValue.ValidationFailed)] // bad query args
|
|
[InlineData(HistorianAccessError.ErrorValue.NotApplicable)] // wrong tag kind for query
|
|
[InlineData(HistorianAccessError.ErrorValue.NotImplemented)] // unsupported aggregate
|
|
[InlineData(HistorianAccessError.ErrorValue.NoData)] // empty range
|
|
public void Query_class_codes_are_NOT_classified_as_connection_errors(HistorianAccessError.ErrorValue code)
|
|
{
|
|
HistorianDataSource.IsConnectionClassError(code).ShouldBeFalse(
|
|
$"{code} is a query payload problem — must NOT tear down the SDK connection");
|
|
}
|
|
}
|