Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a02faa6ade | |||
| 1f546c46ee |
@@ -7,7 +7,7 @@
|
|||||||
| Review date | 2026-05-18 |
|
| Review date | 2026-05-18 |
|
||||||
| Commit reviewed | `6c64030` |
|
| Commit reviewed | `6c64030` |
|
||||||
| Status | Reviewed |
|
| Status | Reviewed |
|
||||||
| Open findings | 8 |
|
| Open findings | 7 |
|
||||||
|
|
||||||
## Checklist coverage
|
## Checklist coverage
|
||||||
|
|
||||||
@@ -48,13 +48,13 @@
|
|||||||
| Severity | Medium |
|
| Severity | Medium |
|
||||||
| Category | Error handling & resilience |
|
| Category | Error handling & resilience |
|
||||||
| Location | `src/MxGateway.Contracts/Protos/mxaccess_gateway.proto:384-385`, `:95` |
|
| Location | `src/MxGateway.Contracts/Protos/mxaccess_gateway.proto:384-385`, `:95` |
|
||||||
| Status | Open |
|
| Status | Resolved |
|
||||||
|
|
||||||
**Description:** `MxCommandKind` includes `MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME = 29` and `MxCommand.payload` carries `AcknowledgeAlarmByNameCommand acknowledge_alarm_by_name_command = 38`, but `MxCommandReply.payload` has only `acknowledge_alarm = 34` and `query_active_alarms = 35` — there is no by-name reply case. The by-name ack must reuse `AcknowledgeAlarmReplyPayload` or rely on the top-level `hresult`. The command/reply payload asymmetry is undocumented and easy to dispatch incorrectly.
|
**Description:** `MxCommandKind` includes `MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME = 29` and `MxCommand.payload` carries `AcknowledgeAlarmByNameCommand acknowledge_alarm_by_name_command = 38`, but `MxCommandReply.payload` has only `acknowledge_alarm = 34` and `query_active_alarms = 35` — there is no by-name reply case. The by-name ack must reuse `AcknowledgeAlarmReplyPayload` or rely on the top-level `hresult`. The command/reply payload asymmetry is undocumented and easy to dispatch incorrectly.
|
||||||
|
|
||||||
**Recommendation:** Either add an explicit comment to `MxCommandReply` stating that by-name ack reuses the `acknowledge_alarm` payload case, or add a dedicated payload case for symmetry, and document the chosen contract in `docs/Contracts.md` / `AlarmClientDiscovery.md`.
|
**Recommendation:** Either add an explicit comment to `MxCommandReply` stating that by-name ack reuses the `acknowledge_alarm` payload case, or add a dedicated payload case for symmetry, and document the chosen contract in `docs/Contracts.md` / `AlarmClientDiscovery.md`.
|
||||||
|
|
||||||
**Resolution:** _(open)_
|
**Resolution:** _(2026-05-18)_ Verified against both the `.proto` and the dispatch code. The asymmetry is intentional and the code is correct: the worker's `MxAccessCommandExecutor.ExecuteAcknowledgeAlarmByName` builds `reply.AcknowledgeAlarm = new AcknowledgeAlarmReplyPayload { NativeStatus = rc }` — deliberately reusing the `acknowledge_alarm` payload case — and the gateway's `WorkerAlarmRpcDispatcher.AcknowledgeAsync` only reads the top-level `hresult`/`protocol_status`, so both ack arms work. The gap was documentation only. Took the finding's preferred option (a) — comment-only, no wire-format or generated-type change: added explicit comments to the `acknowledge_alarm` reply-payload case and to the `AcknowledgeAlarmReplyPayload` message in `mxaccess_gateway.proto` stating both ack kinds reuse this case and consumers must dispatch on `MxCommandReply.kind`, and documented the contract in `docs/AlarmClientDiscovery.md` section 4. Added regression test `ProtobufContractRoundTripTests.MxCommandReply_AcknowledgeAlarmByName_ReusesAcknowledgeAlarmPayloadCase` pinning the by-name-ack → `acknowledge_alarm` reuse and asserting no by-name-specific reply oneof case exists.
|
||||||
|
|
||||||
### Contracts-003
|
### Contracts-003
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Each module's `findings.md` is the source of truth; this file is generated from
|
|||||||
| [Client.Java](Client.Java/findings.md) | Claude Code | 2026-05-18 | `3cc53a8` | Reviewed | 7 | 12 |
|
| [Client.Java](Client.Java/findings.md) | Claude Code | 2026-05-18 | `3cc53a8` | Reviewed | 7 | 12 |
|
||||||
| [Client.Python](Client.Python/findings.md) | Claude Code | 2026-05-18 | `3cc53a8` | Reviewed | 9 | 12 |
|
| [Client.Python](Client.Python/findings.md) | Claude Code | 2026-05-18 | `3cc53a8` | Reviewed | 9 | 12 |
|
||||||
| [Client.Rust](Client.Rust/findings.md) | Claude Code | 2026-05-18 | `3cc53a8` | Reviewed | 0 | 12 |
|
| [Client.Rust](Client.Rust/findings.md) | Claude Code | 2026-05-18 | `3cc53a8` | Reviewed | 0 | 12 |
|
||||||
| [Contracts](Contracts/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 8 | 8 |
|
| [Contracts](Contracts/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 7 | 8 |
|
||||||
| [IntegrationTests](IntegrationTests/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 4 | 10 |
|
| [IntegrationTests](IntegrationTests/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 4 | 10 |
|
||||||
| [Server](Server/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 8 | 14 |
|
| [Server](Server/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 8 | 14 |
|
||||||
| [Tests](Tests/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 6 | 12 |
|
| [Tests](Tests/findings.md) | Claude Code | 2026-05-18 | `6c64030` | Reviewed | 6 | 12 |
|
||||||
@@ -28,7 +28,6 @@ Findings with status `Open` or `In Progress`, ordered by severity.
|
|||||||
|
|
||||||
| ID | Severity | Category | Location | Description |
|
| ID | Severity | Category | Location | Description |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| Contracts-002 | Medium | Error handling & resilience | `src/MxGateway.Contracts/Protos/mxaccess_gateway.proto:384-385`, `:95` | `MxCommandKind` includes `MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME = 29` and `MxCommand.payload` carries `AcknowledgeAlarmByNameCommand acknowledge_alarm_by_name_command = 38`, but `MxCommandReply.payload` has only `acknowledge_alarm = 34… |
|
|
||||||
| Client.Dotnet-004 | Low | Error handling & resilience | `clients/dotnet/MxGateway.Client/MxGatewayClient.cs:283-294`, `clients/dotnet/MxGateway.Client/GalaxyRepositoryClient.cs:392-403` | `ExecuteSafeUnaryAsync` wraps the whole Polly retry pipeline in a single linked CTS cancelled after `Options.DefaultCallTimeout`, while `CreateCallOptions` also stamps each individual call with a `DefaultCallTimeout` gRPC deadline. The ret… |
|
| Client.Dotnet-004 | Low | Error handling & resilience | `clients/dotnet/MxGateway.Client/MxGatewayClient.cs:283-294`, `clients/dotnet/MxGateway.Client/GalaxyRepositoryClient.cs:392-403` | `ExecuteSafeUnaryAsync` wraps the whole Polly retry pipeline in a single linked CTS cancelled after `Options.DefaultCallTimeout`, while `CreateCallOptions` also stamps each individual call with a `DefaultCallTimeout` gRPC deadline. The ret… |
|
||||||
| Client.Dotnet-005 | Low | Correctness & logic bugs | `clients/dotnet/MxGateway.Client/MxGatewaySession.cs:82,124,175` | `RegisterAsync`/`AddItemAsync`/`AddItem2Async` return `reply.<Typed>?.ServerHandle ?? reply.ReturnValue.Int32Value`. After `EnsureMxAccessSuccess()` passes, a missing typed payload silently falls back to `ReturnValue.Int32Value`, which for… |
|
| Client.Dotnet-005 | Low | Correctness & logic bugs | `clients/dotnet/MxGateway.Client/MxGatewaySession.cs:82,124,175` | `RegisterAsync`/`AddItemAsync`/`AddItem2Async` return `reply.<Typed>?.ServerHandle ?? reply.ReturnValue.Int32Value`. After `EnsureMxAccessSuccess()` passes, a missing typed payload silently falls back to `ReturnValue.Int32Value`, which for… |
|
||||||
| Client.Dotnet-006 | Low | Code organization & conventions | `clients/dotnet/MxGateway.Client/MxGatewayClientOptions.cs:50`, `clients/dotnet/MxGateway.Client/MxGatewayClientContractInfo.cs:10-14` | `MxGatewayClientOptions.MaxGrpcMessageBytes` and the two `const`s in `MxGatewayClientContractInfo` are public members with no XML doc comments, inconsistent with every other public member in the assembly and with the repo's documented C# s… |
|
| Client.Dotnet-006 | Low | Code organization & conventions | `clients/dotnet/MxGateway.Client/MxGatewayClientOptions.cs:50`, `clients/dotnet/MxGateway.Client/MxGatewayClientContractInfo.cs:10-14` | `MxGatewayClientOptions.MaxGrpcMessageBytes` and the two `const`s in `MxGatewayClientContractInfo` are public members with no XML doc comments, inconsistent with every other public member in the assembly and with the repo's documented C# s… |
|
||||||
@@ -135,6 +134,7 @@ Findings with status `Resolved`, `Won't Fix`, or `Deferred`.
|
|||||||
| Client.Python-009 | Medium | Resolved | Testing coverage | `clients/python/tests/` |
|
| Client.Python-009 | Medium | Resolved | Testing coverage | `clients/python/tests/` |
|
||||||
| Client.Rust-005 | Medium | Resolved | Correctness & logic bugs | `clients/rust/src/session.rs:489-520` |
|
| Client.Rust-005 | Medium | Resolved | Correctness & logic bugs | `clients/rust/src/session.rs:489-520` |
|
||||||
| Client.Rust-006 | Medium | Resolved | Error handling & resilience | `clients/rust/src/session.rs:531-555` |
|
| Client.Rust-006 | Medium | Resolved | Error handling & resilience | `clients/rust/src/session.rs:531-555` |
|
||||||
|
| Contracts-002 | Medium | Resolved | Error handling & resilience | `src/MxGateway.Contracts/Protos/mxaccess_gateway.proto:384-385`, `:95` |
|
||||||
| IntegrationTests-003 | Medium | Resolved | Correctness & logic bugs | `src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:89-97` |
|
| IntegrationTests-003 | Medium | Resolved | Correctness & logic bugs | `src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:89-97` |
|
||||||
| IntegrationTests-004 | Medium | Resolved | Error handling & resilience | `src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:108-111` |
|
| IntegrationTests-004 | Medium | Resolved | Error handling & resilience | `src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:108-111` |
|
||||||
| IntegrationTests-005 | Medium | Resolved | Testing coverage | `src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs` |
|
| IntegrationTests-005 | Medium | Resolved | Testing coverage | `src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs` |
|
||||||
|
|||||||
@@ -762,6 +762,20 @@ in the codebase for the forward-compat shape, but the gateway-side
|
|||||||
`AcknowledgeAlarmByName` when the public RPC supplies a recognizable
|
`AcknowledgeAlarmByName` when the public RPC supplies a recognizable
|
||||||
`Provider!Group.Tag` reference.
|
`Provider!Group.Tag` reference.
|
||||||
|
|
||||||
|
**Command/reply payload reuse.** `MxCommand.payload` has a dedicated
|
||||||
|
`acknowledge_alarm_by_name_command` field, but `MxCommandReply.payload`
|
||||||
|
intentionally has **no** by-name-specific case. The by-name ack carries
|
||||||
|
no outcome detail beyond the native return code, so the worker's
|
||||||
|
`ExecuteAcknowledgeAlarmByName` sets the same `acknowledge_alarm`
|
||||||
|
(`AcknowledgeAlarmReplyPayload`) reply case used by the GUID arm, with
|
||||||
|
`native_status` = the `AlarmAckByName` return code (also echoed into the
|
||||||
|
top-level `MxCommandReply.hresult`). Reply consumers must dispatch on
|
||||||
|
`MxCommandReply.kind` (`MX_COMMAND_KIND_ACKNOWLEDGE_ALARM` vs.
|
||||||
|
`MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME`), not on the payload oneof
|
||||||
|
case, to distinguish the two acks. `WorkerAlarmRpcDispatcher` reads only
|
||||||
|
the top-level `hresult`/`protocol_status`, so it handles both arms
|
||||||
|
without unpacking the payload.
|
||||||
|
|
||||||
### 5. STA / threading — production fix needed
|
### 5. STA / threading — production fix needed
|
||||||
|
|
||||||
The wnwrap COM is `ThreadingModel=Apartment`. The consumer's
|
The wnwrap COM is `ThreadingModel=Apartment`. The consumer's
|
||||||
|
|||||||
@@ -13388,6 +13388,17 @@ namespace MxGateway.Contracts.Proto {
|
|||||||
|
|
||||||
/// <summary>Field number for the "acknowledge_alarm" field.</summary>
|
/// <summary>Field number for the "acknowledge_alarm" field.</summary>
|
||||||
public const int AcknowledgeAlarmFieldNumber = 34;
|
public const int AcknowledgeAlarmFieldNumber = 34;
|
||||||
|
/// <summary>
|
||||||
|
/// Reply payload for BOTH MX_COMMAND_KIND_ACKNOWLEDGE_ALARM (by GUID)
|
||||||
|
/// and MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME. There is intentionally
|
||||||
|
/// no by-name-specific reply case: the by-name ack carries no outcome
|
||||||
|
/// detail beyond the native ack return code, so the worker reuses this
|
||||||
|
/// `acknowledge_alarm` payload for both command kinds (the worker's
|
||||||
|
/// MxAccessCommandExecutor sets `acknowledge_alarm` for the by-name arm
|
||||||
|
/// too). Consumers must dispatch on MxCommandReply.kind, not on the
|
||||||
|
/// payload case, to tell the two acks apart. The top-level `hresult`
|
||||||
|
/// mirrors AcknowledgeAlarmReplyPayload.native_status and is preferred.
|
||||||
|
/// </summary>
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||||
public global::MxGateway.Contracts.Proto.AcknowledgeAlarmReplyPayload AcknowledgeAlarm {
|
public global::MxGateway.Contracts.Proto.AcknowledgeAlarmReplyPayload AcknowledgeAlarm {
|
||||||
@@ -17339,12 +17350,16 @@ namespace MxGateway.Contracts.Proto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reply payload for AcknowledgeAlarmCommand. Surfaces AVEVA's native
|
/// Reply payload for AcknowledgeAlarmCommand AND
|
||||||
/// AlarmAckByGUID return code; 0 means success. The MxCommandReply's
|
/// AcknowledgeAlarmByNameCommand — both ack command kinds reuse this
|
||||||
/// hresult field carries the same value and is preferred for protocol
|
/// payload case (`MxCommandReply.acknowledge_alarm`); there is no
|
||||||
/// consumers — this payload exists so the gateway-side
|
/// dedicated by-name reply case. Surfaces AVEVA's native ack return
|
||||||
/// WorkerAlarmRpcDispatcher can echo native_status into
|
/// code (AlarmAckByGUID for the GUID arm, AlarmAckByName for the
|
||||||
/// AcknowledgeAlarmReply.hresult without unpacking the outer envelope.
|
/// by-name arm); 0 means success. The MxCommandReply's hresult field
|
||||||
|
/// carries the same value and is preferred for protocol consumers —
|
||||||
|
/// this payload exists so the gateway-side WorkerAlarmRpcDispatcher
|
||||||
|
/// can echo native_status into AcknowledgeAlarmReply.hresult without
|
||||||
|
/// unpacking the outer envelope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
|
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
|
||||||
public sealed partial class AcknowledgeAlarmReplyPayload : pb::IMessage<AcknowledgeAlarmReplyPayload>
|
public sealed partial class AcknowledgeAlarmReplyPayload : pb::IMessage<AcknowledgeAlarmReplyPayload>
|
||||||
|
|||||||
@@ -381,6 +381,15 @@ message MxCommandReply {
|
|||||||
BulkSubscribeReply un_advise_item_bulk = 31;
|
BulkSubscribeReply un_advise_item_bulk = 31;
|
||||||
BulkSubscribeReply subscribe_bulk = 32;
|
BulkSubscribeReply subscribe_bulk = 32;
|
||||||
BulkSubscribeReply unsubscribe_bulk = 33;
|
BulkSubscribeReply unsubscribe_bulk = 33;
|
||||||
|
// Reply payload for BOTH MX_COMMAND_KIND_ACKNOWLEDGE_ALARM (by GUID)
|
||||||
|
// and MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME. There is intentionally
|
||||||
|
// no by-name-specific reply case: the by-name ack carries no outcome
|
||||||
|
// detail beyond the native ack return code, so the worker reuses this
|
||||||
|
// `acknowledge_alarm` payload for both command kinds (the worker's
|
||||||
|
// MxAccessCommandExecutor sets `acknowledge_alarm` for the by-name arm
|
||||||
|
// too). Consumers must dispatch on MxCommandReply.kind, not on the
|
||||||
|
// payload case, to tell the two acks apart. The top-level `hresult`
|
||||||
|
// mirrors AcknowledgeAlarmReplyPayload.native_status and is preferred.
|
||||||
AcknowledgeAlarmReplyPayload acknowledge_alarm = 34;
|
AcknowledgeAlarmReplyPayload acknowledge_alarm = 34;
|
||||||
QueryActiveAlarmsReplyPayload query_active_alarms = 35;
|
QueryActiveAlarmsReplyPayload query_active_alarms = 35;
|
||||||
SessionStateReply session_state = 100;
|
SessionStateReply session_state = 100;
|
||||||
@@ -448,12 +457,16 @@ message DrainEventsReply {
|
|||||||
repeated MxEvent events = 1;
|
repeated MxEvent events = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply payload for AcknowledgeAlarmCommand. Surfaces AVEVA's native
|
// Reply payload for AcknowledgeAlarmCommand AND
|
||||||
// AlarmAckByGUID return code; 0 means success. The MxCommandReply's
|
// AcknowledgeAlarmByNameCommand — both ack command kinds reuse this
|
||||||
// hresult field carries the same value and is preferred for protocol
|
// payload case (`MxCommandReply.acknowledge_alarm`); there is no
|
||||||
// consumers — this payload exists so the gateway-side
|
// dedicated by-name reply case. Surfaces AVEVA's native ack return
|
||||||
// WorkerAlarmRpcDispatcher can echo native_status into
|
// code (AlarmAckByGUID for the GUID arm, AlarmAckByName for the
|
||||||
// AcknowledgeAlarmReply.hresult without unpacking the outer envelope.
|
// by-name arm); 0 means success. The MxCommandReply's hresult field
|
||||||
|
// carries the same value and is preferred for protocol consumers —
|
||||||
|
// this payload exists so the gateway-side WorkerAlarmRpcDispatcher
|
||||||
|
// can echo native_status into AcknowledgeAlarmReply.hresult without
|
||||||
|
// unpacking the outer envelope.
|
||||||
message AcknowledgeAlarmReplyPayload {
|
message AcknowledgeAlarmReplyPayload {
|
||||||
int32 native_status = 1;
|
int32 native_status = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,6 +342,56 @@ public sealed class ProtobufContractRoundTripTests
|
|||||||
Assert.True(parsed.HasHresult);
|
Assert.True(parsed.HasHresult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pins the documented command/reply payload-reuse contract: an
|
||||||
|
/// <c>ACKNOWLEDGE_ALARM_BY_NAME</c> command's reply intentionally has no
|
||||||
|
/// by-name-specific payload case and instead reuses the
|
||||||
|
/// <c>acknowledge_alarm</c> (<see cref="AcknowledgeAlarmReplyPayload"/>)
|
||||||
|
/// case. A future change that adds a separate by-name reply case — or
|
||||||
|
/// drops the reuse — breaks this test. See Contracts-002 and
|
||||||
|
/// docs/AlarmClientDiscovery.md section 4.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void MxCommandReply_AcknowledgeAlarmByName_ReusesAcknowledgeAlarmPayloadCase()
|
||||||
|
{
|
||||||
|
// The reply oneof must NOT have a by-name-specific case. If a future
|
||||||
|
// edit adds one, this assertion fails and forces the doc/test contract
|
||||||
|
// to be revisited deliberately.
|
||||||
|
foreach (MxCommandReply.PayloadOneofCase value in
|
||||||
|
System.Enum.GetValues<MxCommandReply.PayloadOneofCase>())
|
||||||
|
{
|
||||||
|
Assert.NotEqual("AcknowledgeAlarmByName", value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
var original = new MxCommandReply
|
||||||
|
{
|
||||||
|
SessionId = "session-1",
|
||||||
|
CorrelationId = "gateway-correlation-7",
|
||||||
|
Kind = MxCommandKind.AcknowledgeAlarmByName,
|
||||||
|
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
|
||||||
|
Hresult = 0,
|
||||||
|
// By-name ack reuses the acknowledge_alarm payload case; see the
|
||||||
|
// worker's MxAccessCommandExecutor.ExecuteAcknowledgeAlarmByName.
|
||||||
|
AcknowledgeAlarm = new AcknowledgeAlarmReplyPayload
|
||||||
|
{
|
||||||
|
NativeStatus = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var parsed = MxCommandReply.Parser.ParseFrom(original.ToByteArray());
|
||||||
|
|
||||||
|
Assert.Equal(original, parsed);
|
||||||
|
// Kind distinguishes the by-name ack; the payload case is shared.
|
||||||
|
Assert.Equal(MxCommandKind.AcknowledgeAlarmByName, parsed.Kind);
|
||||||
|
Assert.Equal(MxCommandReply.PayloadOneofCase.AcknowledgeAlarm, parsed.PayloadCase);
|
||||||
|
Assert.Equal(0, parsed.AcknowledgeAlarm.NativeStatus);
|
||||||
|
// The by-name command has its own command payload case — the asymmetry
|
||||||
|
// with the reply oneof is the documented contract under test.
|
||||||
|
Assert.Contains(
|
||||||
|
MxCommand.PayloadOneofCase.AcknowledgeAlarmByNameCommand,
|
||||||
|
System.Enum.GetValues<MxCommand.PayloadOneofCase>());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that ActiveAlarmSnapshot round-trips with current state and operator metadata.</summary>
|
/// <summary>Verifies that ActiveAlarmSnapshot round-trips with current state and operator metadata.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ActiveAlarmSnapshot_RoundTripsAllFields()
|
public void ActiveAlarmSnapshot_RoundTripsAllFields()
|
||||||
|
|||||||
Reference in New Issue
Block a user