Files
histsdk/tests/AVEVA.Historian.Client.Tests/WcfHistoricalWriteProtocolTests.cs
T
Joseph Doherty 95b924cdbe fix(write): re-gate UInt1 — historian creates degenerate UInt1 analog tags
Live evidence: EnsureTags(UInt1) returns success but the server stores a
degenerate tag (descriptor type byte 0x00, no GUID/name), so GetTagInfo
truncates and writes fail. Not client-fixable on the analog path. Int8/UInt8
stay GREEN (live-proven). UInt1 reverts to ProtocolEvidenceMissingException
(fail-closed); golden rows removed; negative-gate tests added. Removed the
one-off capture diagnostic.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 15:27:04 -04:00

73 lines
4.2 KiB
C#

using System.Buffers.Binary;
using AVEVA.Historian.Client.Models;
using AVEVA.Historian.Client.Wcf;
namespace AVEVA.Historian.Client.Tests;
public sealed class WcfHistoricalWriteProtocolTests
{
// Exact HistoryService.AddStreamValues "values" buffers captured live from the native 2023 R2
// client writing one historical sample to a sandbox tag of each analog type — see
// docs/plans/revision-write-path.md §"R3.1 CAPTURED". The value descriptor (C0 10 01 00) is
// constant across types; only the value width differs.
[Theory]
[InlineData(HistorianDataType.Float, 124.5,
"4f4e0100380000002e00d51d3107e9f8664793b155d3d1aef54470df38b1d101dd01c000c0100100e64bcb74e201dd01000000000000f942")]
[InlineData(HistorianDataType.Double, 124.5,
"4f4e01003c0000003200" + "7f970bb0b5a7344bb7bf6899ff06d027" + "c07d5f23d701dd01" + "c000" + "c0100100" + "08eff1e6e701dd01" + "000000000000000000205f40")]
[InlineData(HistorianDataType.Int2, 1234d,
"4f4e0100360000002c00" + "35bb0ca5865a7f4498f06bf4e4d9ab23" + "f012f356d701dd01" + "c000" + "c0100100" + "9546841ae801dd01" + "00000000d204")]
[InlineData(HistorianDataType.Int4, 12345d,
"4f4e0100380000002e00" + "ca9735f7f841b244b56f9c14ccfeac32" + "b09bc72fd701dd01" + "c000" + "c0100100" + "104b59f3e701dd01" + "0000000039300000")]
[InlineData(HistorianDataType.UInt4, 305419896d,
"4f4e0100380000002e00" + "e7ae22d8e4cc65439ebd8bcb09402974" + "602d6663d701dd01" + "c000" + "c0100100" + "498af726e801dd01" + "0000000078563412")]
// Int8/UInt8 (H1 un-gate): prefix is the captured Double buffer; only the value bytes differ. Live round-trip confirms (gateway HistorianTypeRoundTripTests).
[InlineData(HistorianDataType.Int8, 100000d,
"4f4e01003c0000003200" + "7f970bb0b5a7344bb7bf6899ff06d027" + "c07d5f23d701dd01" + "c000" + "c0100100" + "08eff1e6e701dd01" + "00000000a086010000000000")]
[InlineData(HistorianDataType.UInt8, 100001d,
"4f4e01003c0000003200" + "7f970bb0b5a7344bb7bf6899ff06d027" + "c07d5f23d701dd01" + "c000" + "c0100100" + "08eff1e6e701dd01" + "00000000a186010000000000")]
public void SerializeAddStreamValuesBuffer_MatchesCapturedNativeBuffer(HistorianDataType dataType, double value, string capturedHex)
{
byte[] captured = Convert.FromHexString(capturedHex);
// The GUID + both FILETIMEs sit at fixed offsets (the value width varies after them).
var tagGuid = new Guid(captured.AsSpan(10, 16).ToArray());
long sampleFt = BinaryPrimitives.ReadInt64LittleEndian(captured.AsSpan(26, 8));
long receivedFt = BinaryPrimitives.ReadInt64LittleEndian(captured.AsSpan(40, 8));
byte[] actual = HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer(
tagGuid,
dataType,
DateTime.FromFileTimeUtc(sampleFt),
value,
DateTime.FromFileTimeUtc(receivedFt),
quality: 192);
Assert.Equal(captured, actual);
}
[Fact]
public void SerializeAddStreamValuesBuffer_UnsupportedType_Throws()
{
Assert.Throws<ProtocolEvidenceMissingException>(() =>
HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer(
Guid.NewGuid(), HistorianDataType.SingleByteString, DateTime.UtcNow, 1.0, DateTime.UtcNow));
// UInt1 re-gated: historian creates a degenerate UInt1 analog tag (null type descriptor) — see pending notes.
Assert.Throws<ProtocolEvidenceMissingException>(() =>
HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer(
Guid.NewGuid(), HistorianDataType.UInt1, DateTime.UtcNow, 1.0, DateTime.UtcNow));
}
[Fact]
public void SerializeAddStreamValuesBuffer_HeaderDeclaresLengths()
{
byte[] buffer = HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer(
Guid.NewGuid(), HistorianDataType.Float, DateTime.UtcNow, value: 1.0, DateTime.UtcNow);
Assert.Equal(0x4E4F, BitConverter.ToUInt16(buffer, 0)); // "ON"
Assert.Equal(1, BitConverter.ToUInt16(buffer, 2)); // sampleCount
Assert.Equal((uint)buffer.Length, BitConverter.ToUInt32(buffer, 4));
Assert.Equal(buffer.Length - 10, BitConverter.ToUInt16(buffer, 8));
}
}