test: preserve server SDK 2.0 value security and history contracts
This commit is contained in:
+29
-27
@@ -71,26 +71,26 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
fake.LastEnd.ShouldBe(end);
|
||||
fake.LastMaxEvents.ShouldBe(50);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
results[0].StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
|
||||
results[0].StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
|
||||
|
||||
var history = (HistoryEvent)ExtensionObject.ToEncodeable(results[0].HistoryData);
|
||||
var history = (HistoryEvent)ExtensionObject.ToEncodeable(results[0].HistoryData)!;
|
||||
history.Events.Count.ShouldBe(1);
|
||||
var fields = history.Events[0].EventFields;
|
||||
fields.Count.ShouldBe(6);
|
||||
|
||||
// EventId ⇒ ByteString (UTF-8 of "evt-42").
|
||||
fields[0].Value.ShouldBeOfType<byte[]>().ShouldBe(System.Text.Encoding.UTF8.GetBytes("evt-42"));
|
||||
SdkValueBoundary.ToClr(fields[0])!.ShouldBeOfType<byte[]>().ShouldBe(System.Text.Encoding.UTF8.GetBytes("evt-42"));
|
||||
// SourceName ⇒ string.
|
||||
fields[1].Value.ShouldBe("Pump_001");
|
||||
SdkValueBoundary.ToClr(fields[1])!.ShouldBe("Pump_001");
|
||||
// Time ⇒ DateTime (event occurrence time).
|
||||
fields[2].Value.ShouldBe(evtTime);
|
||||
SdkValueBoundary.ToClr(fields[2])!.ShouldBe(evtTime);
|
||||
// ReceiveTime ⇒ DateTime (server receipt time = ReceivedTimeUtc).
|
||||
fields[3].Value.ShouldBe(rcvTime);
|
||||
SdkValueBoundary.ToClr(fields[3])!.ShouldBe(rcvTime);
|
||||
// Message ⇒ LocalizedText.
|
||||
fields[4].Value.ShouldBeOfType<LocalizedText>().Text.ShouldBe("Pump tripped");
|
||||
SdkValueBoundary.ToClr(fields[4])!.ShouldBeOfType<LocalizedText>().Text.ShouldBe("Pump tripped");
|
||||
// Severity ⇒ UInt16.
|
||||
fields[5].Value.ShouldBe((ushort)700);
|
||||
SdkValueBoundary.ToClr(fields[5])!.ShouldBe((ushort)700);
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
|
||||
var (_, errors) = InvokeHistoryRead(server, nm, details, notifierNodeId);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
|
||||
// The backend saw the unbounded cap, NOT the 0/default-cap sentinel.
|
||||
fake.LastMaxEvents.ShouldBe(int.MaxValue);
|
||||
|
||||
@@ -163,14 +163,14 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
|
||||
var (results, errors) = InvokeHistoryRead(server, nm, details, notifierNodeId);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
var history = (HistoryEvent)ExtensionObject.ToEncodeable(results[0].HistoryData);
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
|
||||
var history = (HistoryEvent)ExtensionObject.ToEncodeable(results[0].HistoryData)!;
|
||||
var fields = history.Events[0].EventFields;
|
||||
fields.Count.ShouldBe(2);
|
||||
// LocalTime ⇒ Variant.Null.
|
||||
fields[0].Value.ShouldBeNull();
|
||||
SdkValueBoundary.ToClr(fields[0])!.ShouldBeNull();
|
||||
// EventId still projects to a ByteString.
|
||||
fields[1].Value.ShouldBeOfType<byte[]>().ShouldBe(System.Text.Encoding.UTF8.GetBytes("evt-1"));
|
||||
SdkValueBoundary.ToClr(fields[1])!.ShouldBeOfType<byte[]>().ShouldBe(System.Text.Encoding.UTF8.GetBytes("evt-1"));
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
@@ -203,10 +203,10 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
|
||||
var (results, errors) = InvokeHistoryRead(server, nm, details, notifierNodeId);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
results[0].StatusCode.Code.ShouldBe(StatusCodes.GoodNoData);
|
||||
var history = (HistoryEvent)ExtensionObject.ToEncodeable(results[0].HistoryData);
|
||||
history.Events.ShouldBeEmpty();
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
|
||||
results[0].StatusCode.Code.ShouldBe(StatusCodes.GoodNoData.Code);
|
||||
var history = (HistoryEvent)ExtensionObject.ToEncodeable(results[0].HistoryData)!;
|
||||
history.Events.Count.ShouldBe(0);
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
@@ -236,14 +236,14 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
};
|
||||
|
||||
var (_, unsupported) = InvokeHistoryRead(server, nm, details, notifierNodeId);
|
||||
unsupported[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported);
|
||||
unsupported[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported.Code);
|
||||
|
||||
var fake = new RecordingHistorianDataSource();
|
||||
nm.HistorianDataSource = fake;
|
||||
|
||||
var (_, errors) = InvokeHistoryRead(server, nm, details, notifierNodeId);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
|
||||
fake.EventsCalled.ShouldBeTrue();
|
||||
fake.LastSourceName.ShouldBe(equipmentId);
|
||||
|
||||
@@ -282,8 +282,8 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
};
|
||||
var (results, errors) = InvokeHistoryRead(server, nm, details, folder.NodeId);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported);
|
||||
results[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported, "set by the override's guard");
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported.Code);
|
||||
results[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported.Code, "set by the override's guard");
|
||||
fake.EventsCalled.ShouldBeFalse();
|
||||
|
||||
await host.DisposeAsync();
|
||||
@@ -327,7 +327,7 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
|
||||
var (_, errors) = InvokeHistoryRead(server, nm, details, nodeId);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported);
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.BadHistoryOperationUnsupported.Code);
|
||||
fake.EventsCalled.ShouldBeFalse();
|
||||
|
||||
await host.DisposeAsync();
|
||||
@@ -365,10 +365,10 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
|
||||
// Authoritative per-node signal: errors[0] must be Bad.
|
||||
ServiceResult.IsBad(errors[0]).ShouldBeTrue();
|
||||
errors[0].StatusCode.Code.ShouldBe(communication ? StatusCodes.BadNoCommunication : StatusCodes.BadHistoryOperationUnsupported);
|
||||
errors[0].StatusCode.Code.ShouldBe(communication ? StatusCodes.BadNoCommunication.Code : StatusCodes.BadHistoryOperationUnsupported.Code);
|
||||
// The production catch now sets results[0] explicitly — assert it directly (not relying on base seeding).
|
||||
results[0].ShouldNotBeNull();
|
||||
results[0].StatusCode.Code.ShouldBe(communication ? StatusCodes.BadNoCommunication : StatusCodes.BadHistoryOperationUnsupported);
|
||||
results[0].StatusCode.Code.ShouldBe(communication ? StatusCodes.BadNoCommunication.Code : StatusCodes.BadHistoryOperationUnsupported.Code);
|
||||
// The source WAS entered (proving the override reached the bridge and the throw was swallowed).
|
||||
fake.EventsEntered.ShouldBeTrue();
|
||||
// ThrowOnRead fires before EventsCalled/LastSourceName are set — that's the throw path.
|
||||
@@ -382,15 +382,17 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
private static EventFilter SelectFilter(params string[] leafFieldNames)
|
||||
{
|
||||
var filter = new EventFilter();
|
||||
var clauses = new List<SimpleAttributeOperand>();
|
||||
foreach (var name in leafFieldNames)
|
||||
{
|
||||
filter.SelectClauses.Add(
|
||||
clauses.Add(
|
||||
new SimpleAttributeOperand(ObjectTypeIds.BaseEventType, new QualifiedName(name))
|
||||
{
|
||||
AttributeId = Attributes.Value,
|
||||
});
|
||||
}
|
||||
|
||||
filter.SelectClauses = clauses;
|
||||
return filter;
|
||||
}
|
||||
|
||||
@@ -400,7 +402,7 @@ public sealed class NodeManagerHistoryReadEventsTests : IDisposable
|
||||
OtOpcUaSdkServer server, OtOpcUaNodeManager nm, HistoryReadDetails details, NodeId nodeId)
|
||||
{
|
||||
var context = new OperationContext(
|
||||
new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, identity: null);
|
||||
new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, requestLifetime: RequestLifetime.None, identity: null);
|
||||
|
||||
var nodesToRead = new List<HistoryReadValueId> { new() { NodeId = nodeId } };
|
||||
var results = new List<SdkHistoryReadResult> { null! };
|
||||
|
||||
Reference in New Issue
Block a user