test: prove trusted channels PKI and scoped audit compatibility
This commit is contained in:
+28
-10
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests;
|
||||
public sealed class Sdk2AggregateCompatibilityTests
|
||||
{
|
||||
[Fact]
|
||||
public void All_37_factories_preserve_the_4144_baseline_vectors()
|
||||
public void All_37_factories_preserve_the_6144_baseline_vectors()
|
||||
{
|
||||
var baseline = JsonSerializer.Deserialize<Baseline>(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "opcua-1.5.378.182-aggregates.json")), new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;
|
||||
var telemetry = DefaultTelemetry.Create(_ => { });
|
||||
@@ -22,22 +22,29 @@ public sealed class Sdk2AggregateCompatibilityTests
|
||||
{
|
||||
var id = (NodeId)typeof(ObjectIds).GetProperty(vector.Aggregate)!.GetValue(null)!;
|
||||
var start = epoch.AddSeconds(vector.Reverse ? 70 : 0); var end = epoch.AddSeconds(vector.Reverse ? 0 : 70);
|
||||
var config = new AggregateConfiguration { TreatUncertainAsBad = vector.UncertainAsBad, PercentDataBad = 100, PercentDataGood = 100, UseSlopedExtrapolation = false, UseServerCapabilitiesDefaults = false };
|
||||
var profile = vector.Configuration;
|
||||
var config = new AggregateConfiguration
|
||||
{
|
||||
TreatUncertainAsBad = profile?.TreatUncertainAsBad ?? vector.UncertainAsBad,
|
||||
PercentDataBad = profile?.PercentDataBad ?? 100, PercentDataGood = profile?.PercentDataGood ?? 100,
|
||||
UseSlopedExtrapolation = profile?.UseSlopedExtrapolation ?? false,
|
||||
UseServerCapabilitiesDefaults = profile?.UseServerCapabilitiesDefaults ?? false,
|
||||
};
|
||||
var calculator = CompatibilityAggregateFactory.Create(id, start, end, baseline.IntervalMilliseconds, vector.Stepped, config, telemetry);
|
||||
var output = new List<Value>(); string? error = null;
|
||||
var output = new List<Value>(); var accepted = new List<bool>(); string? error = null;
|
||||
try
|
||||
{
|
||||
var samples = baseline.Data[vector.DataSet].OrderBy(x => x.Seconds);
|
||||
foreach (var sample in vector.Reverse ? samples.Reverse() : samples)
|
||||
{
|
||||
var time = epoch.AddSeconds(sample.Seconds);
|
||||
calculator.QueueRawValue(new DataValue(SdkValueBoundary.FromClr(Payload(sample)), sample.Status, time, time));
|
||||
accepted.Add(calculator.QueueRawValue(new DataValue(SdkValueBoundary.FromClr(Payload(sample)), sample.Status, time, time.AddTicks(sample.ServerOffsetTicks), sample.SourcePicoseconds, sample.ServerPicoseconds)));
|
||||
if (vector.Incremental) Drain(false);
|
||||
}
|
||||
Drain(true);
|
||||
}
|
||||
catch (Exception ex) { error = ex.GetType().FullName; }
|
||||
if (error != vector.Exception || !output.SequenceEqual(vector.Output))
|
||||
if (error != vector.Exception || !output.SequenceEqual(vector.Output) || (vector.QueueAccepted is { } expectedAccepted && !accepted.SequenceEqual(expectedAccepted)))
|
||||
{
|
||||
failures.Add($"{vector.Aggregate}/{vector.DataSet}/rev={vector.Reverse}/step={vector.Stepped}/uncertainBad={vector.UncertainAsBad}/incremental={vector.Incremental}: error {vector.Exception} vs {error}; expected {JsonSerializer.Serialize(vector.Output)} actual {JsonSerializer.Serialize(output)}");
|
||||
}
|
||||
@@ -47,20 +54,31 @@ public sealed class Sdk2AggregateCompatibilityTests
|
||||
{
|
||||
if (!calculator.TryGetProcessedValue(partial, out var value)) return;
|
||||
var payload = SdkValueBoundary.ToClr(value.WrappedValue);
|
||||
output.Add(new(partial, payload?.GetType().Name, Format(payload), value.StatusCode.Code, ((DateTime)value.SourceTimestamp).Ticks, ((DateTime)value.ServerTimestamp).Ticks, value.SourcePicoseconds, value.ServerPicoseconds));
|
||||
output.Add(new(partial, payload?.GetType().Name, Format(payload), value.StatusCode.Code, LegacyTimestampTicks(value.SourceTimestamp), LegacyTimestampTicks(value.ServerTimestamp), value.SourcePicoseconds, value.ServerPicoseconds));
|
||||
}
|
||||
throw new InvalidOperationException("Baseline calculator did not terminate.");
|
||||
}
|
||||
}
|
||||
baseline.Vectors.Length.ShouldBe(4144);
|
||||
baseline.Vectors.Length.ShouldBe(6144);
|
||||
baseline.Vectors.Select(x => x.Aggregate).Distinct().Count().ShouldBe(37);
|
||||
failures.ShouldBeEmpty("The application must retain baseline values, CLR types, complete status bits, timestamps and incremental processing.");
|
||||
}
|
||||
|
||||
static object? Payload(Sample sample) => sample.Type switch { "Double" => double.Parse(sample.Payload!, CultureInfo.InvariantCulture), "Int64" => long.Parse(sample.Payload!, CultureInfo.InvariantCulture), "Boolean" => bool.Parse(sample.Payload!), _ => null };
|
||||
// UA FILETIME zero is the old CLR unset sentinel, not a different historical instant.
|
||||
private static long LegacyTimestampTicks(DateTimeUtc time) => time.IsNull ? DateTime.MinValue.Ticks : ((DateTime)time).Ticks;
|
||||
static object? Payload(Sample sample) => sample.Type switch { "SByte" => sbyte.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"Byte" => byte.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"Int16" => short.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"UInt16" => ushort.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"Int32" => int.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"UInt32" => uint.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"UInt64" => ulong.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"Single" => float.Parse(sample.Payload!, CultureInfo.InvariantCulture),
|
||||
"Double" => double.Parse(sample.Payload!, CultureInfo.InvariantCulture), "Int64" => long.Parse(sample.Payload!, CultureInfo.InvariantCulture), "Boolean" => bool.Parse(sample.Payload!), _ => null };
|
||||
static string? Format(object? value) => value switch { null => null, double number => number.ToString("R", CultureInfo.InvariantCulture), float number => number.ToString("R", CultureInfo.InvariantCulture), IFormattable f => f.ToString(null, CultureInfo.InvariantCulture), _ => value.ToString() };
|
||||
record Baseline(long EpochTicks, int IntervalMilliseconds, Dictionary<string,Sample[]> Data, Vector[] Vectors);
|
||||
record Sample(double Seconds,string Type,string? Payload,uint Status);
|
||||
record Sample(double Seconds,string Type,string? Payload,uint Status,ushort SourcePicoseconds = 0,ushort ServerPicoseconds = 0,long ServerOffsetTicks = 0);
|
||||
record Value(bool PartialRequested,string? Type,string? Payload,uint Status,long SourceTicks,long ServerTicks,ushort SourcePicoseconds,ushort ServerPicoseconds);
|
||||
record Vector(string Aggregate,string DataSet,bool Reverse,bool Stepped,bool UncertainAsBad,bool Incremental,Value[] Output,string? Exception);
|
||||
record Vector(string Aggregate,string DataSet,bool Reverse,bool Stepped,bool UncertainAsBad,bool Incremental,Value[] Output,string? Exception, Configuration? Configuration = null,bool[]? QueueAccepted = null);
|
||||
record Configuration(bool TreatUncertainAsBad,byte PercentDataBad,byte PercentDataGood,bool UseSlopedExtrapolation,bool UseServerCapabilitiesDefaults);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user