using AVEVA.Historian.Client.Wcf; namespace AVEVA.Historian.Client.Tests; public sealed class WcfDataQueryProtocolTests { [Fact] public void SerializerMatchesInstrumentedNativeFullHistoryRequest() { byte[] actual = HistorianDataQueryProtocol.SerializeFullHistoryRequest(new HistorianDataQueryRequest( ["OtOpcUaParityTest_001.Counter"], new DateTime(2026, 5, 1, 14, 17, 5, 659, DateTimeKind.Utc).AddTicks(3154), new DateTime(2026, 5, 2, 14, 17, 5, 659, DateTimeKind.Utc).AddTicks(3154), MaxStates: 100, BatchSize: 1, Option: string.Empty)); byte[] expected = Convert.FromBase64String( "CQACAAAAAAAAAAAAAAAC4ScwddncAQKhkVo+2twBAAAAAAAAAAAAAAAAAAAAAAMAAABVAFQAQwABAAAAAAABAP8BAAAAAAgAAABOAG8ARgBpAGwAdABlAHIAAQADAAEA/4IHAIKBAAABAAAAHQAAAE8AdABPAHAAYwBVAGEAUABhAHIAaQB0AHkAVABlAHMAdABfADAAMAAxAC4AQwBvAHUAbgB0AGUAcgBkAAEBAAABAAABAAAJAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); Assert.Equal(expected, actual); } [Fact] public void SerializerMatchesInstrumentedNativeTimeWeightedAverageRequest() { byte[] actual = HistorianDataQueryProtocol.SerializeFullHistoryRequest(new HistorianDataQueryRequest( ["OtOpcUaParityTest_001.Counter"], new DateTime(2026, 5, 1, 14, 29, 2, 223, DateTimeKind.Utc).AddTicks(2955), new DateTime(2026, 5, 2, 14, 29, 2, 223, DateTimeKind.Utc).AddTicks(2955), MaxStates: 100, BatchSize: 3, Option: string.Empty) { QueryType = 5, Resolution = TimeSpan.FromMinutes(1) }); byte[] expected = Convert.FromBase64String( "CQAFAAAAAAAAAAAAAAB73ULbdtncAXudrAVA2twBAAAAAKPhwUEAAAAAAAAAAAMAAABVAFQAQwABAAAAAAABAP8BAAAAAAgAAABOAG8ARgBpAGwAdABlAHIAAQADAAEA/4IHAIKBAAABAAAAHQAAAE8AdABPAHAAYwBVAGEAUABhAHIAaQB0AHkAVABlAHMAdABfADAAMAAxAC4AQwBvAHUAbgB0AGUAcgBkAAEBAAABAAABAAAJAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAABg3vt0BQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); Assert.Equal(expected, actual); } [Fact] public void SerializerMatchesInstrumentedNativeInterpolatedRequest() { byte[] actual = HistorianDataQueryProtocol.SerializeFullHistoryRequest(new HistorianDataQueryRequest( ["OtOpcUaParityTest_001.Counter"], new DateTime(2026, 5, 1, 14, 32, 12, 72, DateTimeKind.Utc).AddTicks(8924), new DateTime(2026, 5, 2, 14, 32, 12, 72, DateTimeKind.Utc).AddTicks(8924), MaxStates: 100, BatchSize: 3, Option: string.Empty) { QueryType = 3, Resolution = TimeSpan.FromMinutes(1) }); byte[] expected = Convert.FromBase64String( "CQADAAAAAAAAAAAAAABcnWtMd9ncAVxd1XZA2twBAAAAAKPhwUEAAAAAAAAAAAMAAABVAFQAQwABAAAAAAABAP8BAAAAAAgAAABOAG8ARgBpAGwAdABlAHIAAQADAAEA/4IHAIKBAAABAAAAHQAAAE8AdABPAHAAYwBVAGEAUABhAHIAaQB0AHkAVABlAHMAdABfADAAMAAxAC4AQwBvAHUAbgB0AGUAcgBkAAEBAAABAAABAAAJAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAABg3vt0BQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); Assert.Equal(expected, actual); } [Fact] public void SerializerUsesDecompiledEmptyMetadataAndAutoSummaryLayout() { byte[] actual = HistorianDataQueryProtocol.SerializeFullHistoryRequest(new HistorianDataQueryRequest( ["T"], new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc), new DateTime(2026, 1, 1, 0, 1, 0, DateTimeKind.Utc), MaxStates: 100, BatchSize: 1, Option: string.Empty)); byte[] expectedMiddle = [ 0x64, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 ]; AssertContains(expectedMiddle, actual); AssertEndsWith(ExpectedEmptyEndpointAndAutoSummarySuffix(), actual); } [Fact] public void SerializerWritesPackedCqtiFlagsSeparatelyFromColumnSelectorFlags() { byte[] actual = HistorianDataQueryProtocol.SerializeFullHistoryRequest(new HistorianDataQueryRequest( ["T"], new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc), new DateTime(2026, 1, 1, 0, 1, 0, DateTimeKind.Utc), MaxStates: 100, BatchSize: 1, Option: "NoOption") { InterpolationType = 255, TimestampRule = 1, QualityRule = 0, ColumnSelectorFlags = 0x0000_0000_0003_FFFF }); int resultBufferOffset = 2 + 4 + 4 + 4 + 8 + 8 + 8 + 4 + 4 + 10 + 4; Assert.Equal([0x00, 0x00, 0x01, 0x00, 0xFF, 0x01], actual[resultBufferOffset..(resultBufferOffset + 6)]); AssertContains([0x01, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00], actual); } private static byte[] ExpectedEmptyEndpointAndAutoSummarySuffix() { List expected = []; AppendEmptyEndpoint(expected); AppendEmptyEndpoint(expected); expected.AddRange(new byte[8]); expected.AddRange([0x00, 0x00, 0x00, 0x00]); expected.AddRange([0x00, 0x00, 0x00, 0x00]); expected.AddRange([0x01, 0x00]); expected.AddRange(new byte[16]); expected.AddRange(new byte[5]); expected.AddRange([0x00, 0x00, 0x00, 0x00]); return expected.ToArray(); } private static void AppendEmptyEndpoint(List bytes) { bytes.AddRange([0x01, 0x00]); bytes.AddRange([0x00, 0x00, 0x00, 0x00]); bytes.AddRange([0x00, 0x00]); } private static void AssertContains(byte[] expected, byte[] actual) { for (int index = 0; index <= actual.Length - expected.Length; index++) { if (actual.AsSpan(index, expected.Length).SequenceEqual(expected)) { return; } } Assert.Fail($"Expected byte sequence {Convert.ToHexString(expected)} was not found."); } private static void AssertEndsWith(byte[] expectedSuffix, byte[] actual) { Assert.True(actual.Length >= expectedSuffix.Length); Assert.Equal(expectedSuffix, actual[^expectedSuffix.Length..]); } }