test(historian): gateway writer acks an all-non-numeric batch without a call; fix stale writer docs
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// One numeric sample the continuous-historization recorder drains to the historian's SQL
|
||||
/// live-value write path: an optional UTC timestamp, the typed value, and the OPC UA status code
|
||||
/// the sample was recorded with.
|
||||
/// One sample the continuous-historization recorder drains to the backend's live-value write path: an
|
||||
/// optional UTC timestamp, the typed value (any <see cref="HistorianValueKind"/> the backend supports — the
|
||||
/// Gateway backend takes numeric kinds only, SqlServer takes all of them), and the OPC UA status code the
|
||||
/// sample was recorded with.
|
||||
/// </summary>
|
||||
/// <param name="TimestampUtc">
|
||||
/// UTC source timestamp of the sample, or <c>null</c> to defer to the historian's server-stamped
|
||||
/// current-time path (the SQL writer uses <c>SYSDATETIME()</c> when the timestamp is absent).
|
||||
/// UTC source timestamp of the sample, or <c>null</c> to have the backend stamp the current time.
|
||||
/// </param>
|
||||
/// <param name="Value">The typed sample value.</param>
|
||||
/// <param name="StatusCode">OPC UA status code of the sample (0 = Good).</param>
|
||||
@@ -16,20 +16,20 @@ public readonly record struct HistorizationValue(DateTime? TimestampUtc, Histori
|
||||
/// <summary>
|
||||
/// Seam over the historian's live-value write path used by the continuous-historization recorder.
|
||||
/// Lives in the abstraction layer so the Runtime recorder depends on it without taking a hard
|
||||
/// reference on the gRPC gateway driver; the gateway driver supplies the concrete adapter
|
||||
/// (<c>GatewayHistorianValueWriter</c>).
|
||||
/// reference on a backend driver; the selected backend provider supplies the concrete writer
|
||||
/// (<c>GatewayHistorianValueWriter</c> or <c>SqlServerHistorianValueWriter</c>).
|
||||
/// </summary>
|
||||
public interface IHistorianValueWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a batch of live values for a single tag through the historian's SQL live-write path.
|
||||
/// Writes a batch of live values for a single tag through the backend's live-write path.
|
||||
/// Implementations are expected to be non-throwing: a transport/gateway error is surfaced as a
|
||||
/// <c>false</c> result so the recorder retains the entries and retries, rather than as an
|
||||
/// exception.
|
||||
/// </summary>
|
||||
/// <param name="tag">Fully-qualified historian tag the values are recorded against.</param>
|
||||
/// <param name="values">The numeric samples to write, in append order.</param>
|
||||
/// <param name="values">The samples to write, in append order.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns><c>true</c> on a successful (or durably-queued) gateway ack; <c>false</c> on a retryable failure.</returns>
|
||||
/// <returns><c>true</c> when the backend stored (or durably queued) the batch; <c>false</c> on a retryable failure.</returns>
|
||||
Task<bool> WriteLiveValuesAsync(string tag, IReadOnlyList<HistorizationValue> values, CancellationToken ct);
|
||||
}
|
||||
|
||||
+18
@@ -73,6 +73,24 @@ public sealed class GatewayHistorianValueWriterTests
|
||||
Assert.False(ok);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task All_non_numeric_batch_is_acked_without_calling_the_gateway()
|
||||
{
|
||||
// A soft-failure ack: had the writer called the gateway, the result would be false.
|
||||
var fake = new FakeHistorianGatewayClient { WriteLiveValuesResult = new WriteAck { Success = false, Queued = false } };
|
||||
var values = new[]
|
||||
{
|
||||
new HistorizationValue(DateTime.UtcNow, HistorianValue.FromString("text"), 0u),
|
||||
new HistorizationValue(DateTime.UtcNow, HistorianValue.FromDateTime(DateTime.UtcNow), 0u),
|
||||
new HistorizationValue(DateTime.UtcNow, HistorianValue.Empty, 0x80310000u),
|
||||
};
|
||||
|
||||
var ok = await Writer(fake).WriteLiveValuesAsync("T", values, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(ok);
|
||||
Assert.Equal(0, fake.WriteLiveValuesCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Empty_batch_is_trivially_acked_without_calling_the_gateway()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user