using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Ipc; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Tests; /// /// Phase 7 follow-up #247 — covers the wire-format translation between the /// the SQLite sink hands to the writer + the /// the Galaxy.Host IPC contract expects, plus /// the per-event outcome enum mapping. Pure functions; the round-trip over a real /// pipe is exercised by the live Host suite (task #240). /// [Trait("Category", "Unit")] public sealed class GalaxyHistorianWriterMappingTests { [Fact] public void ToDto_round_trips_every_field() { var ts = new DateTime(2026, 4, 20, 14, 30, 0, DateTimeKind.Utc); var e = new AlarmHistorianEvent( AlarmId: "al-7", EquipmentPath: "/Site/Line/Cell", AlarmName: "HighTemp", AlarmTypeName: "LimitAlarm", Severity: AlarmSeverity.High, EventKind: "RaiseEvent", Message: "Temp 92°C exceeded 90°C", User: "operator-7", Comment: "ack with reason", TimestampUtc: ts); var dto = GalaxyHistorianWriter.ToDto(e); dto.AlarmId.ShouldBe("al-7"); dto.EquipmentPath.ShouldBe("/Site/Line/Cell"); dto.AlarmName.ShouldBe("HighTemp"); dto.AlarmTypeName.ShouldBe("LimitAlarm"); dto.Severity.ShouldBe((int)AlarmSeverity.High); dto.EventKind.ShouldBe("RaiseEvent"); dto.Message.ShouldBe("Temp 92°C exceeded 90°C"); dto.User.ShouldBe("operator-7"); dto.Comment.ShouldBe("ack with reason"); dto.TimestampUtcUnixMs.ShouldBe(new DateTimeOffset(ts, TimeSpan.Zero).ToUnixTimeMilliseconds()); } [Fact] public void ToDto_preserves_null_Comment() { var e = new AlarmHistorianEvent( "a", "/p", "n", "AlarmCondition", AlarmSeverity.Low, "RaiseEvent", "m", User: "system", Comment: null, TimestampUtc: DateTime.UtcNow); GalaxyHistorianWriter.ToDto(e).Comment.ShouldBeNull(); } [Theory] [InlineData(HistorianAlarmEventOutcomeDto.Ack, HistorianWriteOutcome.Ack)] [InlineData(HistorianAlarmEventOutcomeDto.RetryPlease, HistorianWriteOutcome.RetryPlease)] [InlineData(HistorianAlarmEventOutcomeDto.PermanentFail, HistorianWriteOutcome.PermanentFail)] public void MapOutcome_round_trips_every_byte( HistorianAlarmEventOutcomeDto wire, HistorianWriteOutcome expected) { GalaxyHistorianWriter.MapOutcome(wire).ShouldBe(expected); } [Fact] public void MapOutcome_unknown_byte_throws() { Should.Throw( () => GalaxyHistorianWriter.MapOutcome((HistorianAlarmEventOutcomeDto)0xFF)); } [Fact] public void Null_client_rejected() { Should.Throw(() => new GalaxyHistorianWriter(null!)); } }