From 3bb4d5a0825202da2e42f1d5518d8f4af8580a7a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 16 Jun 2026 17:09:44 -0400 Subject: [PATCH] test(java-cli): cover stream-events over in-process harness --- .../ww/mxgateway/cli/MxGatewayCliTests.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java b/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java index 2651723..70a0f10 100644 --- a/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java +++ b/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.zb.mom.ww.mxgateway.client.MxGatewayAlarmFeedSubscription; +import com.zb.mom.ww.mxgateway.client.MxGatewayClient; import com.zb.mom.ww.mxgateway.client.MxGatewayClientOptions; import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyObject; import io.grpc.stub.StreamObserver; @@ -31,6 +32,7 @@ import mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest; import mxaccess_gateway.v1.MxaccessGateway.MxCommandKind; import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply; import mxaccess_gateway.v1.MxaccessGateway.MxEvent; +import mxaccess_gateway.v1.MxaccessGateway.MxEventFamily; import mxaccess_gateway.v1.MxaccessGateway.MxValue; import mxaccess_gateway.v1.MxaccessGateway.OnAlarmTransitionEvent; import mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply; @@ -693,6 +695,60 @@ final class MxGatewayCliTests { + " out=\n" + run.output() + "\nerr=\n" + run.errors()); } + @Test + void streamEventsRendersScriptedEventsIncludingHighUint64Sequence() { + // Drive the REAL MxGatewayClient / MxGatewaySession / MxEventStream + // path over the in-process harness (Task 4), so the production + // stream-events command exercises the real streaming wrapper instead + // of a hand-written FakeSession seam. + // + // The high worker-sequence (-1L == 18446744073709551615 unsigned) + // covers the unsigned-rendering regression: worker_sequence is a + // proto uint64 carried as a Java long with the top bit set. The CLI's + // --json path renders it through protobuf's JsonFormat, which prints + // uint64 as an unsigned decimal STRING; a naive %d render would print + // a negative number instead. + MxEvent dataChange = MxEvent.newBuilder() + .setFamily(MxEventFamily.MX_EVENT_FAMILY_ON_DATA_CHANGE) + .setSessionId("session-cli") + .setServerHandle(7) + .setItemHandle(42) + .setWorkerSequence(5L) + .build(); + MxEvent highSequence = MxEvent.newBuilder() + .setFamily(MxEventFamily.MX_EVENT_FAMILY_OPERATION_COMPLETE) + .setSessionId("session-cli") + .setServerHandle(9) + .setItemHandle(99) + // -1L unsigned == 18446744073709551615 (top bit set). + .setWorkerSequence(-1L) + .build(); + + try (InProcessGatewayHarness harness = new InProcessGatewayHarness()) { + harness.setScriptedEvents(List.of(dataChange, highSequence)); + CliRun run = execute( + new HarnessClientFactory(harness), + "stream-events", + "--session-id", + "session-cli", + "--json"); + + assertEquals(0, run.exitCode(), "errors:\n" + run.errors()); + String out = run.output(); + // Scripted event fields surface in the JSON render. + assertTrue(out.contains("\"family\":\"MX_EVENT_FAMILY_ON_DATA_CHANGE\""), out); + assertTrue(out.contains("\"family\":\"MX_EVENT_FAMILY_OPERATION_COMPLETE\""), out); + assertTrue(out.contains("\"serverHandle\":7"), out); + assertTrue(out.contains("\"itemHandle\":42"), out); + // The low sequence renders as the unsigned decimal string "5". + assertTrue(out.contains("\"workerSequence\":\"5\""), out); + // The high sequence renders as the FULL unsigned decimal, not -1. + assertTrue(out.contains("\"workerSequence\":\"18446744073709551615\""), out); + assertFalse(out.contains("\"workerSequence\":\"-1\""), out); + assertFalse(out.contains("\"workerSequence\":-1"), out); + } + } + @Test void batchCommandExecutesVersionAndEmitsEorMarker() { CliRun run = executeBatch(new FakeClientFactory(), "version --json\n"); @@ -849,6 +905,28 @@ final class MxGatewayCliTests { } } + /** + * Factory that wires the production {@link MxGatewayCli.GrpcMxGatewayCliClient} + * adapter around the harness's REAL {@link MxGatewayClient}, so the + * stream-events command runs against the in-process scripted gateway over + * a real channel (exercising the real {@code MxEventStream}). Mirrors the + * production {@code GrpcMxGatewayCliClientFactory}, swapping only the + * client construction for the harness-backed client. + */ + private static final class HarnessClientFactory implements MxGatewayCli.MxGatewayCliClientFactory { + private final InProcessGatewayHarness harness; + + private HarnessClientFactory(InProcessGatewayHarness harness) { + this.harness = harness; + } + + @Override + public MxGatewayCli.MxGatewayCliClient connect(MxGatewayCli.CommonOptions options) { + return new MxGatewayCli.GrpcMxGatewayCliClient( + harness.gatewayClient(), options.spec.commandLine().getOut()); + } + } + private static final class OverflowingFakeClient implements MxGatewayCli.MxGatewayCliClient { private final PrintWriter out;