sidecar: wire IAlarmEventWriter into Program.cs (PR C.2)
Fifth PR of the alarms-over-gateway epic (docs/plans/alarms-over-gateway.md). Depends on PR C.1 (AahClientManagedAlarmEventWriter), already merged. Today HistorianFrameHandler is constructed at Program.cs line 57 without an alarmWriter, so every WriteAlarmEvents frame replies "Sidecar not configured with an alarm-event writer" and the lmxopcua side keeps the row queued. C.2 wires a real writer behind a new OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED toggle. - Program.BuildAlarmWriter — gated on the env var (default true, fail-open under accidental misconfiguration). Constructs an AahClientManagedAlarmEventWriter wrapping a SdkAlarmHistorianWriteBackend with the same connection config the read path uses. - Install-Services.ps1 — appends OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true to the OtOpcUaWonderwareHistorian service env block when the sidecar is installed. Read-only deployments flip it to false at service-config edit time without re-installing. - HistorianFrameHandler signature already accepts IAlarmEventWriter? — supplying non-null at line 57 lights up the WriteAlarmEvents reply path that's been dormant since PR 3.3. Until PR D.1 pins the live aahClientManaged entry point, the SdkAlarmHistorianWriteBackend reports RetryPlease for every event with a structured diagnostic. The lmxopcua-side SqliteStoreAndForwardSink retains queued events; same effective behaviour as today's NullAlarmHistorianSink fallback but with visible diagnostics rather than silent discard. Tests: - 6 BuildAlarmWriter env-var cases — unset / true / false / unrecognized → default-on / capitalization variants. - Full sidecar test suite: 56 passed (was 48; 8 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// PR C.2 — pins the env-var contract that gates whether the sidecar boots an
|
||||
/// alarm-event writer. Default-on (when the historian itself is enabled) so a
|
||||
/// fresh deploy picks up the writer without a service-config edit; explicit
|
||||
/// <c>false</c> opts a read-only deployment out.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ProgramAlarmWriterTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildAlarmWriter_returns_writer_when_env_unset()
|
||||
{
|
||||
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", null);
|
||||
|
||||
var writer = Program.BuildAlarmWriter();
|
||||
|
||||
writer.ShouldNotBeNull();
|
||||
writer.ShouldBeOfType<AahClientManagedAlarmEventWriter>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("true")]
|
||||
[InlineData("True")]
|
||||
[InlineData("TRUE")]
|
||||
public void BuildAlarmWriter_returns_writer_when_env_explicitly_true(string value)
|
||||
{
|
||||
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", value);
|
||||
|
||||
var writer = Program.BuildAlarmWriter();
|
||||
|
||||
writer.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("false")]
|
||||
[InlineData("False")]
|
||||
[InlineData("FALSE")]
|
||||
public void BuildAlarmWriter_returns_null_when_env_false(string value)
|
||||
{
|
||||
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", value);
|
||||
|
||||
var writer = Program.BuildAlarmWriter();
|
||||
|
||||
writer.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmWriter_treats_unrecognized_value_as_enabled()
|
||||
{
|
||||
// Anything other than the literal "false" (case-insensitive) keeps the writer
|
||||
// wired — fail-open under accidental misconfiguration so an alarm-write deploy
|
||||
// doesn't silently lose alarms because of a typo.
|
||||
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", "yes");
|
||||
|
||||
var writer = Program.BuildAlarmWriter();
|
||||
|
||||
writer.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
private static IDisposable ScopedEnv(string name, string? value)
|
||||
{
|
||||
var prior = Environment.GetEnvironmentVariable(name);
|
||||
Environment.SetEnvironmentVariable(name, value);
|
||||
return new DisposableAction(() => Environment.SetEnvironmentVariable(name, prior));
|
||||
}
|
||||
|
||||
private sealed class DisposableAction : IDisposable
|
||||
{
|
||||
private readonly Action _action;
|
||||
public DisposableAction(Action action) { _action = action; }
|
||||
public void Dispose() => _action();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user