From ddf2d84fbc6a62bf55c2df234027ba3726a5eaab Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 15 Jun 2026 02:46:06 -0400 Subject: [PATCH] contracts: round-trip degraded provenance/watch-list/mode-changed; proto doc (Contracts-018,019) --- code-reviews/Contracts/findings.md | 71 ++++++++++- .../Generated/MxaccessGateway.cs | 22 +++- .../Protos/mxaccess_gateway.proto | 18 ++- .../ProtobufContractRoundTripTests.cs | 116 ++++++++++++++++++ 4 files changed, 221 insertions(+), 6 deletions(-) diff --git a/code-reviews/Contracts/findings.md b/code-reviews/Contracts/findings.md index df10bc8..99282bf 100644 --- a/code-reviews/Contracts/findings.md +++ b/code-reviews/Contracts/findings.md @@ -4,8 +4,8 @@ |---|---| | Module | `src/ZB.MOM.WW.MxGateway.Contracts` | | Reviewer | Claude Code | -| Review date | 2026-05-24 | -| Commit reviewed | `42b0037` | +| Review date | 2026-06-15 | +| Commit reviewed | `410acc9` | | Status | Re-reviewed | | Open findings | 0 | @@ -50,6 +50,43 @@ Python and Go descriptors. No fields renumbered or repurposed. | 9 | Testing coverage | No issues found — `ProtobufContractRoundTripTests` and `GatewayContractInfoTests` continue to pin the protocol version; new `QueryActiveAlarmsRequest` lacks a round-trip test but the RPC type is generated and exercised end-to-end by the gRPC client tests in each language. | | 10 | Documentation & comments | Issues found: Contracts-017 (the `rpc QueryActiveAlarms` comment block does not mention the `alarm_filter_prefix` request field). | +#### 2026-06-15 re-review (commit 410acc9) + +Re-review pass at `410acc9` scoped to the contract changes since `42b0037` +(`git diff 42b0037..HEAD -- src/ZB.MOM.WW.MxGateway.Contracts/`). The window +contains two unrelated additive contract features. The brief targets the +**alarm-provider fallback** surface in `mxaccess_gateway.proto`: the new +`AlarmProviderMode` enum (`UNSPECIFIED=0`/`ALARMMGR=1`/`SUBTAG=2`), the +`AlarmSubtagTarget` watch-list message, `AlarmFailoverConfig`, the three new +`SubscribeAlarmsCommand` fields (`forced_mode=2`, `watch_list=3`, `failover=4`), +the `OnAlarmProviderModeChangedEvent` (`MxEvent.body` oneof tag 25, +`MxEventFamily=6`), the `degraded=14`/`source_provider=15` provenance fields on +`OnAlarmTransitionEvent` **and** `ActiveAlarmSnapshot`, and the +`AlarmFeedMessage.provider_status=4` oneof case carrying `AlarmProviderStatus`. +The same window also adds the Galaxy `BrowseChildren` lazy-browse RPC +(`galaxy_repository.proto`) and three XML doc comments on `GatewayContractInfo` +constants — both outside the brief's alarm focus but checked for additive-only +hygiene (clean). `Generated/*.cs` is build output and was not reviewed as +hand-written. `mxaccess_worker.proto` is unchanged (the alarm additions live in +the gateway proto the worker imports — matches the design doc's Superseded note). + +Verified against `docs/plans/2026-06-13-alarm-subtag-fallback-design.md`, +`docs/plans/2026-06-15-forced-subtag-mode-fix.md`, and the worker/gateway source +(`AlarmDispatcher.cs:213`, `MxAccessEventMapper.cs:151`, `GatewayAlarmMonitor.cs`). + +| # | Category | Result | +|---|---|---| +| 1 | Correctness & logic bugs | No issues found. Field semantics are correct against source: `AlarmProviderStatus.degraded`/`OnAlarmTransitionEvent.degraded` track `mode == SUBTAG` (worker `AlarmDispatcher.cs:213` sets `SourceProvider = Degraded ? Subtag : Alarmmgr`; gateway `GatewayAlarmMonitor._providerDegraded = toMode == Subtag`). `OnAlarmProviderModeChangedEvent.hresult` "0 on failback" matches the Auto-mode failover/failback path that emits it; forced mode is seeded gateway-side and emits no worker event, so the comment is not contradicted. | +| 2 | mxaccessgw conventions | No issues found. The subtag fallback synthesizes events **inside the worker** and marks every synthesized transition `degraded`, satisfying the CLAUDE.md "gateway forwards only worker-emitted events; synthesizing is an explicit opt-in non-parity mode" rule. `snake_case` fields, `PascalCase` messages, the `ALARM_PROVIDER_MODE_`/`MX_EVENT_FAMILY_` enum-prefix discipline, and the top-of-file wire-compatibility policy block (Contracts-005) are all honoured. Generated code regenerated, not hand-edited. | +| 3 | Concurrency & thread safety | N/A — pure contract definitions plus a static constants class. | +| 4 | Error handling & resilience | No issues found. The degraded/provider-status surface lets clients distinguish the lower-fidelity subtag feed from the authoritative alarmmgr feed; `AlarmProviderStatus` is emitted on stream open and every switch so late joiners learn the mode. | +| 5 | Security | No issues found — none of the new fields carry credentials or secrets. `AlarmSubtagTarget` carries only item-address strings. | +| 6 | Performance & resource management | No issues found. `repeated AlarmSubtagTarget watch_list` is sent once at subscribe time, not per-event; provenance fields are scalars. No hot-path bloat. | +| 7 | Design-document adherence | No drift. The shipped contract matches `docs/plans/2026-06-13-alarm-subtag-fallback-design.md` (including its Superseded notes: additions in the gateway proto, not the worker proto). | +| 8 | Code organization & conventions | No issues found. Every addition uses a new, contiguous field number — `SubscribeAlarmsCommand` 2-4, `MxEvent.body` 25, `MxEventFamily` 6, `OnAlarmTransitionEvent`/`ActiveAlarmSnapshot` 14-15, `AlarmFeedMessage.payload` 4 — with no reuse, renumbering, or type narrowing of any existing field. Enum zero-values are `UNSPECIFIED`. Additive-only invariant preserved. | +| 9 | Testing coverage | Issues found: Contracts-018 — `ProtobufContractRoundTripTests` covers the new `AlarmProviderStatus` (via `AlarmFeedMessage`) and the `OnAlarmTransitionEvent` `degraded`/`source_provider` fields, but has no round-trip coverage for the `ActiveAlarmSnapshot` provenance fields, the `SubscribeAlarmsCommand` extensions (`forced_mode`/`watch_list`/`failover`), or `OnAlarmProviderModeChangedEvent`. | +| 10 | Documentation & comments | Issues found: Contracts-019 — the `ActiveAlarmSnapshot.degraded`/`source_provider` fields carry no in-proto comment while the byte-identical fields on `OnAlarmTransitionEvent` are documented; and the `AlarmProviderMode` enum doc explains `UNSPECIFIED` only for the `forced_mode` use, not for the provenance (`source_provider`) reuse. | + ## Findings ### Contracts-001 @@ -341,3 +378,33 @@ additive-only with no reuse, renumbering, or type narrowing. Re-review: no new findings. Open finding count remains 0. All seventeen recorded Contracts findings (Contracts-001..017) remain closed (Resolved / Won't Fix). + +### Contracts-018 + +| Field | Value | +|---|---| +| Severity | Low | +| Category | Testing coverage | +| Location | `src/ZB.MOM.WW.MxGateway.Tests/Contracts/ProtobufContractRoundTripTests.cs:396` (`ActiveAlarmSnapshot_RoundTripsAllFields`) | +| Status | Resolved | + +**Description:** The alarm-provider fallback feature added several new wire fields to `mxaccess_gateway.proto`. `ProtobufContractRoundTripTests` was extended with `AlarmFeedMessage_RoundTripsProviderStatus` (covers `AlarmProviderStatus` + the `provider_status` oneof case) and `Transition_RoundTripsDegradedProvenance` (covers `OnAlarmTransitionEvent.degraded`/`source_provider`), but three pieces of the new contract surface have no round-trip coverage: (a) `ActiveAlarmSnapshot.degraded` (14) / `source_provider` (15) — `ActiveAlarmSnapshot_RoundTripsAllFields` stops at `OperatorComment` (field 11) and never sets or asserts the two new provenance fields, so a future renumber/type change to them would not be caught; (b) the `SubscribeAlarmsCommand` extensions `forced_mode` (2), `watch_list` (3, `repeated AlarmSubtagTarget`), and `failover` (4, `AlarmFailoverConfig`) — no test exercises these, and the live `forced_mode` enum-drop concern that prompted the `2026-06-15-forced-subtag-mode-fix` investigation is exactly the kind of wire shape prior contract tests have been written to pin; (c) `OnAlarmProviderModeChangedEvent` (the `MxEvent.body` oneof tag 25 / `MxEventFamily=6` worker→gateway event). This is the same class of gap previously flagged for the bulk family (Contracts-007 / Contracts-010): new wire shapes shipped without round-trip pinning. + +**Recommendation:** Extend `ActiveAlarmSnapshot_RoundTripsAllFields` (or add a focused test) to set and assert `degraded = true` + `source_provider = AlarmProviderMode.Subtag`; add a round-trip test for `SubscribeAlarmsCommand` populating `forced_mode`, a `watch_list` entry (all six `AlarmSubtagTarget` string fields), and a `failover` `AlarmFailoverConfig`; and add a round-trip / `MxEvent` oneof-case test for `OnAlarmProviderModeChangedEvent` pinning `MxEvent.BodyCase == OnAlarmProviderModeChanged` for `MxEventFamily.OnAlarmProviderModeChanged`. + +**Resolution:** _(2026-06-15)_ Verified the three coverage gaps against the proto — `ActiveAlarmSnapshot.degraded`/`source_provider` (14/15), `SubscribeAlarmsCommand.forced_mode`/`watch_list`/`failover` (2/3/4), and the `MxEvent.body` oneof tag 25 / `MxEventFamily=6` `OnAlarmProviderModeChangedEvent` were all unpinned. Added three focused round-trip tests to `ProtobufContractRoundTripTests`: `ActiveAlarmSnapshot_RoundTripsDegradedProvenance` (sets/asserts `degraded = true` + `source_provider = AlarmProviderMode.Subtag`), `SubscribeAlarmsCommand_RoundTripsForcedModeWatchListAndFailover` (populates `forced_mode`, a `watch_list` entry with all six `AlarmSubtagTarget` string fields, and a `failover` `AlarmFailoverConfig`), and `MxEvent_RoundTripsOnAlarmProviderModeChangedBody` (pins `MxEvent.BodyCase == OnAlarmProviderModeChanged` + `Family == OnAlarmProviderModeChanged`). All fields round-trip — no contract bug found. The full `ProtobufContractRoundTrip` filter is 49/49 green. + +### Contracts-019 + +| Field | Value | +|---|---| +| Severity | Low | +| Category | Documentation & comments | +| Location | `src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto:850-851` (`ActiveAlarmSnapshot`), `:318-324` (`AlarmProviderMode`) | +| Status | Resolved | + +**Description:** Two in-proto documentation gaps on the new alarm-provider surface. (1) `OnAlarmTransitionEvent.degraded` (line 805-808) and `source_provider` (809-810) carry clear comments ("True when this transition came from the subtag-monitoring fallback … synthesized from data changes, reduced fidelity"; "Which provider produced this transition."), but the byte-identical `ActiveAlarmSnapshot.degraded` (850) and `source_provider` (851) are declared bare with no comment. The two messages model the same provenance concept and a reader of `ActiveAlarmSnapshot` alone gets no signal that a non-`UNSPECIFIED` `source_provider` plus `degraded = true` means the snapshot came from the lower-fidelity subtag source. (2) The `AlarmProviderMode` enum comment (318-319) documents the zero value only for one use site — "UNSPECIFIED on a SubscribeAlarmsCommand means auto: alarmmgr primary with subtag fallback" — but the same enum is reused as a provenance field on `OnAlarmTransitionEvent.source_provider`, `ActiveAlarmSnapshot.source_provider`, `OnAlarmProviderModeChangedEvent.mode`, and `AlarmProviderStatus.mode`. The worker always sets `source_provider` to `ALARMMGR` or `SUBTAG` (never `UNSPECIFIED`; `MxAccessEventMapper.cs:151` defaults to `Alarmmgr`, `AlarmDispatcher.cs:213` picks `Subtag`/`Alarmmgr`), so `UNSPECIFIED` as a provenance value has no defined meaning and the comment does not say so. The ProtobufStyleGuide rule "comment fields carrying MXAccess parity / non-obvious semantics" applies — this is a non-parity provenance marker. + +**Recommendation:** (1) Add comments to `ActiveAlarmSnapshot.degraded` / `source_provider` mirroring the wording already on `OnAlarmTransitionEvent` (or a one-line cross-reference). (2) Extend the `AlarmProviderMode` enum comment to note that as a `source_provider` / `mode` provenance value the field is always `ALARMMGR` or `SUBTAG` on the wire and `UNSPECIFIED` should be treated as "unknown / not yet determined", so the zero value is unambiguous at every use site. Comment-only changes; no wire-format impact. + +**Resolution:** _(2026-06-15)_ Confirmed both gaps in `mxaccess_gateway.proto`: `ActiveAlarmSnapshot.degraded`/`source_provider` (14/15) were bare while the byte-identical `OnAlarmTransitionEvent` fields were documented, and the `AlarmProviderMode` enum comment only explained `UNSPECIFIED` for the `forced_mode` use. (1) Added comments to `ActiveAlarmSnapshot.degraded`/`source_provider` mirroring the `OnAlarmTransitionEvent` wording (subtag-fallback / reduced-fidelity, always ALARMMGR or SUBTAG, never UNSPECIFIED). (2) Extended the `AlarmProviderMode` enum comment to distinguish its two use sites: as `forced_mode`, `UNSPECIFIED` = auto; as a provenance value (`OnAlarmTransitionEvent.source_provider`, `ActiveAlarmSnapshot.source_provider`, `OnAlarmProviderModeChangedEvent.mode`, `AlarmProviderStatus.mode`) the worker always emits ALARMMGR/SUBTAG and `UNSPECIFIED` should be read as "unknown / not yet determined". Comment-only changes; no wire-format impact. NOTE: on this dev box the `csharp` protoc generator DOES emit proto leading comments into `Generated/MxaccessGateway.cs` `` XML doc (contrary to the brief's assumption), so the build regenerated `Generated/MxaccessGateway.cs` with the new doc comments only — diff is `///`-comment lines exclusively, zero code/wire/type changes. `dotnet build -f net10.0` succeeds with 0 warnings / 0 errors. diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs b/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs index 3edfa96..e297fac 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs +++ b/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs @@ -668,8 +668,15 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto { } /// - /// Provider selection / current provider for the alarm feed. UNSPECIFIED on a - /// SubscribeAlarmsCommand means auto: alarmmgr primary with subtag fallback. + /// Provider selection / current provider for the alarm feed. The zero value + /// has two distinct meanings depending on the use site: + /// - As SubscribeAlarmsCommand.forced_mode, UNSPECIFIED means auto: alarmmgr + /// primary with subtag fallback. + /// - As a provenance value (OnAlarmTransitionEvent.source_provider, + /// ActiveAlarmSnapshot.source_provider, OnAlarmProviderModeChangedEvent.mode, + /// AlarmProviderStatus.mode), the worker always emits ALARMMGR or SUBTAG and + /// never UNSPECIFIED; clients should treat a UNSPECIFIED provenance value as + /// "unknown / not yet determined". /// public enum AlarmProviderMode { [pbr::OriginalName("ALARM_PROVIDER_MODE_UNSPECIFIED")] Unspecified = 0, @@ -26528,6 +26535,12 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto { /// Field number for the "degraded" field. public const int DegradedFieldNumber = 14; private bool degraded_; + /// + /// True when this snapshot came from the subtag-monitoring fallback rather + /// than the native alarmmgr provider — synthesized from data changes, reduced + /// fidelity (synthetic GUID, no native raise time). Mirrors + /// OnAlarmTransitionEvent.degraded. + /// [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public bool Degraded { @@ -26540,6 +26553,11 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto { /// Field number for the "source_provider" field. public const int SourceProviderFieldNumber = 15; private global::ZB.MOM.WW.MxGateway.Contracts.Proto.AlarmProviderMode sourceProvider_ = global::ZB.MOM.WW.MxGateway.Contracts.Proto.AlarmProviderMode.Unspecified; + /// + /// Which provider produced this snapshot. Mirrors + /// OnAlarmTransitionEvent.source_provider; always ALARMMGR or SUBTAG on the + /// wire (never UNSPECIFIED). + /// [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public global::ZB.MOM.WW.MxGateway.Contracts.Proto.AlarmProviderMode SourceProvider { diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto b/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto index 4394702..5ace9b5 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto +++ b/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto @@ -315,8 +315,15 @@ message SubscribeBulkCommand { repeated string tag_addresses = 2; } -// Provider selection / current provider for the alarm feed. UNSPECIFIED on a -// SubscribeAlarmsCommand means auto: alarmmgr primary with subtag fallback. +// Provider selection / current provider for the alarm feed. The zero value +// has two distinct meanings depending on the use site: +// - As SubscribeAlarmsCommand.forced_mode, UNSPECIFIED means auto: alarmmgr +// primary with subtag fallback. +// - As a provenance value (OnAlarmTransitionEvent.source_provider, +// ActiveAlarmSnapshot.source_provider, OnAlarmProviderModeChangedEvent.mode, +// AlarmProviderStatus.mode), the worker always emits ALARMMGR or SUBTAG and +// never UNSPECIFIED; clients should treat a UNSPECIFIED provenance value as +// "unknown / not yet determined". enum AlarmProviderMode { ALARM_PROVIDER_MODE_UNSPECIFIED = 0; ALARM_PROVIDER_MODE_ALARMMGR = 1; @@ -847,7 +854,14 @@ message ActiveAlarmSnapshot { string operator_comment = 11; MxValue current_value = 12; MxValue limit_value = 13; + // True when this snapshot came from the subtag-monitoring fallback rather + // than the native alarmmgr provider — synthesized from data changes, reduced + // fidelity (synthetic GUID, no native raise time). Mirrors + // OnAlarmTransitionEvent.degraded. bool degraded = 14; + // Which provider produced this snapshot. Mirrors + // OnAlarmTransitionEvent.source_provider; always ALARMMGR or SUBTAG on the + // wire (never UNSPECIFIED). AlarmProviderMode source_provider = 15; } diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ProtobufContractRoundTripTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ProtobufContractRoundTripTests.cs index 4eb3061..614c522 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ProtobufContractRoundTripTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ProtobufContractRoundTripTests.cs @@ -1427,4 +1427,120 @@ public sealed class ProtobufContractRoundTripTests Assert.Single(parsed.ReadBulk.Results); Assert.True(parsed.ReadBulk.Results[0].WasCached); } + + /// + /// Verifies that an carrying the + /// alarm-provider provenance fields degraded (14) and + /// source_provider (15) round-trips with their values preserved, + /// pinning the wire shape of the byte-identical provenance fields that + /// also appear on . + /// + [Fact] + public void ActiveAlarmSnapshot_RoundTripsDegradedProvenance() + { + var raise = Timestamp.FromDateTime(new DateTime(2026, 6, 13, 12, 0, 0, DateTimeKind.Utc)); + var original = new ActiveAlarmSnapshot + { + AlarmFullReference = "Galaxy!Area.Tank01.Level.HiHi", + SourceObjectReference = "Tank01", + AlarmTypeName = "AnalogLimitAlarm.HiHi", + Severity = 750, + OriginalRaiseTimestamp = raise, + CurrentState = AlarmConditionState.Active, + Degraded = true, + SourceProvider = AlarmProviderMode.Subtag, + }; + + var parsed = ActiveAlarmSnapshot.Parser.ParseFrom(original.ToByteArray()); + + Assert.Equal(original, parsed); + Assert.True(parsed.Degraded); + Assert.Equal(AlarmProviderMode.Subtag, parsed.SourceProvider); + } + + /// + /// Verifies that a populating the + /// alarm-provider fallback extensions — forced_mode (2), a + /// watch_list entry with all six + /// string fields (3), and a failover + /// (4) — round-trips end to end, + /// pinning the wire shape that the forced-subtag-mode fix depends on. + /// + [Fact] + public void SubscribeAlarmsCommand_RoundTripsForcedModeWatchListAndFailover() + { + var original = new SubscribeAlarmsCommand + { + SubscriptionExpression = @"\\node\Galaxy!Area", + ForcedMode = AlarmProviderMode.Subtag, + WatchList = + { + new AlarmSubtagTarget + { + AlarmFullReference = "Galaxy!Area.Tank01.Level.HiHi", + SourceObjectReference = "Tank01", + ActiveSubtag = "Tank01.Level.HiHi.InAlarm", + AckedSubtag = "Tank01.Level.HiHi.Acked", + AckCommentSubtag = "Tank01.Level.HiHi.AckMsg", + PrioritySubtag = "Tank01.Level.HiHi.Priority", + }, + }, + Failover = new AlarmFailoverConfig + { + ConsecutiveFailureThreshold = 3, + FailbackProbeIntervalSeconds = 10, + FailbackStableProbes = 5, + }, + }; + + var parsed = SubscribeAlarmsCommand.Parser.ParseFrom(original.ToByteArray()); + + Assert.Equal(original, parsed); + Assert.Equal(AlarmProviderMode.Subtag, parsed.ForcedMode); + var target = Assert.Single(parsed.WatchList); + Assert.Equal("Galaxy!Area.Tank01.Level.HiHi", target.AlarmFullReference); + Assert.Equal("Tank01", target.SourceObjectReference); + Assert.Equal("Tank01.Level.HiHi.InAlarm", target.ActiveSubtag); + Assert.Equal("Tank01.Level.HiHi.Acked", target.AckedSubtag); + Assert.Equal("Tank01.Level.HiHi.AckMsg", target.AckCommentSubtag); + Assert.Equal("Tank01.Level.HiHi.Priority", target.PrioritySubtag); + Assert.Equal(3, parsed.Failover.ConsecutiveFailureThreshold); + Assert.Equal(10, parsed.Failover.FailbackProbeIntervalSeconds); + Assert.Equal(5, parsed.Failover.FailbackStableProbes); + } + + /// + /// Verifies that an carrying an + /// body (the + /// MxEvent.body oneof tag 25 paired with + /// , family 6) + /// round-trips and resolves to + /// . + /// + [Fact] + public void MxEvent_RoundTripsOnAlarmProviderModeChangedBody() + { + var at = Timestamp.FromDateTime(new DateTime(2026, 6, 13, 9, 30, 0, DateTimeKind.Utc)); + var original = new MxEvent + { + Family = MxEventFamily.OnAlarmProviderModeChanged, + SessionId = "session-1", + WorkerSequence = 42, + OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent + { + Mode = AlarmProviderMode.Subtag, + Reason = "wnwrap poll failed 3x", + Hresult = unchecked((int)0x80004005), + At = at, + }, + }; + + var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray()); + + Assert.Equal(original, parsed); + Assert.Equal(MxEvent.BodyOneofCase.OnAlarmProviderModeChanged, parsed.BodyCase); + Assert.Equal(MxEventFamily.OnAlarmProviderModeChanged, parsed.Family); + Assert.Equal(AlarmProviderMode.Subtag, parsed.OnAlarmProviderModeChanged.Mode); + Assert.Equal(unchecked((int)0x80004005), parsed.OnAlarmProviderModeChanged.Hresult); + } }