From 72cf2f4091b7cae13c6b633c99b27c99d671d516 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 18 Jun 2026 03:14:14 -0400 Subject: [PATCH] fix(client-rust): correct outer MxValue data_type and add end-to-end write_array_elements test --- clients/rust/src/session.rs | 2 - clients/rust/tests/client_behavior.rs | 100 +++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/clients/rust/src/session.rs b/clients/rust/src/session.rs index 7f59d71..bd04339 100644 --- a/clients/rust/src/session.rs +++ b/clients/rust/src/session.rs @@ -584,8 +584,6 @@ impl Session { .collect(); let sparse_value = ProtoMxValue { - data_type: element_data_type as i32, - variant_type: String::new(), kind: Some(Kind::SparseArrayValue(MxSparseArray { element_data_type: element_data_type as i32, total_length, diff --git a/clients/rust/tests/client_behavior.rs b/clients/rust/tests/client_behavior.rs index 565a7ad..ab53e07 100644 --- a/clients/rust/tests/client_behavior.rs +++ b/clients/rust/tests/client_behavior.rs @@ -17,6 +17,7 @@ use tonic::{Request, Response, Status}; use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::mx_access_gateway_server::{ MxAccessGateway, MxAccessGatewayServer, }; +use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::mx_command; use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::mx_command_reply; use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::mx_value::Kind; use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::{ @@ -27,8 +28,8 @@ use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::{ MxEventFamily, MxSparseArray, MxSparseElement, MxStatusCategory, MxStatusProxy, MxStatusSource, MxValue, OnAlarmTransitionEvent, OpenSessionReply, OpenSessionRequest, ProtocolStatus, ProtocolStatusCode, QueryActiveAlarmsRequest, RegisterReply, SessionState, StreamAlarmsRequest, - StreamEventsRequest, SubscribeResult, Write2BulkEntry, WriteBulkEntry, WriteSecured2BulkEntry, - WriteSecuredBulkEntry, + StreamEventsRequest, SubscribeResult, Write2BulkEntry, WriteBulkEntry, WriteCommand, + WriteSecured2BulkEntry, WriteSecuredBulkEntry, }; use zb_mom_ww_mxgateway_client::{ next_correlation_id, ApiKey, ClientOptions, CommandError, Error, GatewayClient, MxStatus, @@ -659,6 +660,9 @@ struct FakeState { authorization: Mutex>, last_command_kind: Mutex>, last_correlation_id: Mutex>, + /// Captures the last `WriteCommand` payload received, populated when the + /// `WriteOk` override is active. Used by `write_array_elements` e2e test. + last_write_command: Mutex>, stream_dropped: Arc, /// Optional per-test override that pins the fake's `Invoke` handler to /// a specific reply shape (or `Err(Status)`). The default of `None` @@ -683,6 +687,10 @@ enum InvokeOverride { /// Fail the unary call with `Status::unavailable(...)` so the client's /// `Code::Unavailable` -> `Error::Unavailable` mapping is exercised. Unavailable(String), + /// Accept a `Write` command (return `protocol_status = Ok`, no payload) + /// and capture the decoded `WriteCommand` in + /// `FakeState::last_write_command` for inspection. + WriteOk, } #[derive(Clone)] @@ -764,6 +772,23 @@ impl MxAccessGateway for FakeGateway { ..MxCommandReply::default() })), InvokeOverride::Unavailable(message) => Err(Status::unavailable(message)), + InvokeOverride::WriteOk => { + // Extract and capture the WriteCommand payload so the test + // can assert on server_handle, item_handle, user_id, and value. + if let Some(mx_command::Payload::Write(write_cmd)) = + request.command.and_then(|c| c.payload) + { + *self.state.last_write_command.lock().await = Some(write_cmd); + } + Ok(Response::new(MxCommandReply { + session_id: request.session_id, + correlation_id: "fake-correlation".to_owned(), + kind, + protocol_status: Some(ok_status("write ok")), + payload: None, + ..MxCommandReply::default() + })) + } }; } @@ -1092,6 +1117,77 @@ fn case_by_id<'a>(cases: &'a [Value], id: &str) -> &'a Value { .unwrap_or_else(|| panic!("missing fixture case {id}")) } +// --------------------------------------------------------------------------- +// write_array_elements — end-to-end fake-server test +// --------------------------------------------------------------------------- + +#[tokio::test] +async fn write_array_elements_routes_sparse_array_write_through_fake_gateway() { + // Arrange: stand up the fake gateway with WriteOk so the Write command + // succeeds and the sent WriteCommand is captured for inspection. + let state = Arc::new(FakeState::default()); + *state.invoke_override.lock().await = Some(InvokeOverride::WriteOk); + let endpoint = spawn_fake_gateway(state.clone()).await; + let client = GatewayClient::connect(ClientOptions::new(endpoint)) + .await + .unwrap(); + let session = client.session("session-fixture"); + + // Act: call the public write_array_elements helper. + session + .write_array_elements( + 12, + 34, + MxDataType::Integer, + 10, + [(2u32, ClientMxValue::int32(42))], + 7, + ) + .await + .unwrap(); + + // Assert: the fake captured a Write command with the expected handles and + // a SparseArrayValue whose total_length and element index/value are correct. + let captured = state + .last_write_command + .lock() + .await + .take() + .expect("fake should have captured a WriteCommand"); + + assert_eq!(captured.server_handle, 12, "server_handle must round-trip"); + assert_eq!(captured.item_handle, 34, "item_handle must round-trip"); + assert_eq!(captured.user_id, 7, "user_id must round-trip"); + + let value = captured.value.expect("WriteCommand must carry a value"); + assert_eq!( + value.data_type, 0, + "outer MxValue.data_type must be Unspecified (0), not the element type" + ); + + let Kind::SparseArrayValue(ref sparse) = value.kind.as_ref().unwrap() else { + panic!( + "expected SparseArrayValue kind on the outer MxValue, got {:?}", + value.kind + ); + }; + assert_eq!( + sparse.element_data_type, + MxDataType::Integer as i32, + "element_data_type must carry the element type" + ); + assert_eq!(sparse.total_length, 10, "total_length must round-trip"); + assert_eq!(sparse.elements.len(), 1, "one element supplied"); + + let elem = &sparse.elements[0]; + assert_eq!(elem.index, 2, "element index must round-trip"); + assert_eq!( + elem.value.as_ref().unwrap().kind, + Some(Kind::Int32Value(42)), + "element value must round-trip" + ); +} + // --------------------------------------------------------------------------- // write_array_elements — proto shape unit tests // ---------------------------------------------------------------------------