22
33import static org .junit .Assume .assumeTrue ;
44
5- import io .nexusrpc .handler .OperationHandler ;
6- import io .nexusrpc .handler .OperationImpl ;
7- import io .nexusrpc .handler .ServiceImpl ;
85import io .temporal .api .nexus .v1 .Endpoint ;
96import io .temporal .client .NexusClient ;
107import io .temporal .client .NexusOperationExecutionCount ;
1310import io .temporal .client .UntypedNexusOperationHandle ;
1411import io .temporal .client .UntypedNexusServiceClient ;
1512import io .temporal .testing .internal .SDKTestWorkflowRule ;
13+ import io .temporal .workflow .shared .EchoNexusServiceImpl ;
1614import io .temporal .workflow .shared .TestNexusServices ;
1715import io .temporal .workflow .shared .TestWorkflows ;
1816import java .time .Duration ;
17+ import java .util .List ;
1918import java .util .UUID ;
2019import java .util .concurrent .TimeUnit ;
20+ import java .util .stream .Collectors ;
2121import org .junit .Assert ;
2222import org .junit .BeforeClass ;
2323import org .junit .Rule ;
@@ -29,7 +29,7 @@ public class NexusClientTest {
2929 public SDKTestWorkflowRule testWorkflowRule =
3030 SDKTestWorkflowRule .newBuilder ()
3131 .setWorkflowTypes (NexusClientTest .PlaceholderWorkflowImpl .class )
32- .setNexusServiceImplementation (new TestNexusServiceImpl ())
32+ .setNexusServiceImplementation (new EchoNexusServiceImpl ())
3333 .build ();
3434
3535 @ BeforeClass
@@ -112,6 +112,114 @@ public void runStandaloneNexusOperation() throws Exception {
112112 Assert .assertTrue (countNexusOperations () > initialCount );
113113 }
114114
115+ @ Test
116+ public void listNexusOperationExecutionsWithQueryFiltersResults () throws Exception {
117+ // Run a known operation through to completion, then assert that an OperationId-scoped query
118+ // narrows the list to exactly that one row. Uses a built-in visibility field (OperationId), so
119+ // the async search-attribute registration race that affects custom SAs doesn't apply.
120+ String operationId = startAndAwaitSyncOperation ("list-query" );
121+ NexusClient client = testWorkflowRule .getNexusClient ();
122+
123+ // Sync on the unfiltered list first so the visibility index has indexed our operation; the
124+ // filtered query reads from the same index.
125+ Assert .assertNotNull (
126+ "expected operation to appear in visibility before filtered query" ,
127+ waitForListedOperation (client , operationId , Duration .ofSeconds (15 )));
128+
129+ String query = "OperationId='" + operationId + "'" ;
130+ List <NexusOperationExecutionMetadata > results =
131+ client .listNexusOperationExecutions (query ).collect (Collectors .toList ());
132+
133+ // OperationId is unique server-side, so the filter must produce exactly one row — proving the
134+ // query string actually narrowed results rather than being a no-op passthrough.
135+ Assert .assertEquals ("expected exactly one match for query: " + query , 1 , results .size ());
136+ Assert .assertEquals (operationId , results .get (0 ).getOperationId ());
137+ }
138+
139+ @ Test
140+ public void countNexusOperationExecutionsWithQueryFiltersResults () throws Exception {
141+ String operationId = startAndAwaitSyncOperation ("count-query" );
142+ NexusClient client = testWorkflowRule .getNexusClient ();
143+
144+ Assert .assertNotNull (
145+ "expected operation to appear in visibility before filtered count" ,
146+ waitForListedOperation (client , operationId , Duration .ofSeconds (15 )));
147+
148+ String query = "OperationId='" + operationId + "'" ;
149+ NexusOperationExecutionCount count = client .countNexusOperationExecutions (query );
150+
151+ Assert .assertEquals ("expected exactly one match for query: " + query , 1L , count .getCount ());
152+ }
153+
154+ /**
155+ * Starts a sync echo operation with a unique input, blocks until it completes, and returns the
156+ * operation ID. Used by the filtered list/count tests to obtain a known operation to query for.
157+ */
158+ private String startAndAwaitSyncOperation (String label ) throws Exception {
159+ Endpoint endpoint = testWorkflowRule .getNexusEndpoint ();
160+ UntypedNexusServiceClient svcClient =
161+ testWorkflowRule
162+ .getNexusClient ()
163+ .newUntypedNexusServiceClient (
164+ endpoint .getSpec ().getName (),
165+ TestNexusServices .TestNexusService1 .class .getSimpleName ());
166+ StartNexusOperationOptions opts =
167+ StartNexusOperationOptions .newBuilder ()
168+ .setScheduleToCloseTimeout (Duration .ofSeconds (30 ))
169+ .build ();
170+ UntypedNexusOperationHandle handle =
171+ svcClient .start ("operation" , opts , label + "-" + UUID .randomUUID ());
172+ handle .getResult (60 , TimeUnit .SECONDS , String .class );
173+ return handle .getNexusOperationId ();
174+ }
175+
176+ @ Test
177+ public void untypedExecuteByClassReturnsResult () {
178+ Endpoint endpoint = testWorkflowRule .getNexusEndpoint ();
179+ UntypedNexusServiceClient svcClient =
180+ testWorkflowRule
181+ .getNexusClient ()
182+ .newUntypedNexusServiceClient (
183+ endpoint .getSpec ().getName (),
184+ TestNexusServices .TestNexusService1 .class .getSimpleName ());
185+
186+ String result =
187+ svcClient .execute (
188+ "operation" ,
189+ String .class ,
190+ StartNexusOperationOptions .newBuilder ()
191+ .setScheduleToCloseTimeout (Duration .ofSeconds (30 ))
192+ .build (),
193+ "untyped-exec" );
194+
195+ Assert .assertEquals ("echo:untyped-exec" , result );
196+ }
197+
198+ @ Test
199+ public void untypedExecuteByClassAndTypeReturnsResult () {
200+ Endpoint endpoint = testWorkflowRule .getNexusEndpoint ();
201+ UntypedNexusServiceClient svcClient =
202+ testWorkflowRule
203+ .getNexusClient ()
204+ .newUntypedNexusServiceClient (
205+ endpoint .getSpec ().getName (),
206+ TestNexusServices .TestNexusService1 .class .getSimpleName ());
207+
208+ // The Type overload exists for generic results (e.g. List<String>); exercising it with the same
209+ // class/type here proves the path is wired through to the data converter.
210+ String result =
211+ svcClient .execute (
212+ "operation" ,
213+ String .class ,
214+ String .class ,
215+ StartNexusOperationOptions .newBuilder ()
216+ .setScheduleToCloseTimeout (Duration .ofSeconds (30 ))
217+ .build (),
218+ "untyped-exec-typed" );
219+
220+ Assert .assertEquals ("echo:untyped-exec-typed" , result );
221+ }
222+
115223 private NexusOperationExecutionMetadata waitForListedOperation (
116224 NexusClient client , String operationId , Duration timeout ) throws InterruptedException {
117225 long deadlineNanos = System .nanoTime () + timeout .toNanos ();
@@ -136,13 +244,4 @@ public String execute(String input) {
136244 return input ;
137245 }
138246 }
139-
140- @ ServiceImpl (service = TestNexusServices .TestNexusService1 .class )
141- public static class TestNexusServiceImpl {
142- @ OperationImpl
143- public OperationHandler <String , String > operation () {
144- return OperationHandler .sync (
145- (context , details , input ) -> "echo:" + (input == null ? "<null>" : input ));
146- }
147- }
148247}
0 commit comments