Skip to content

Commit 9e8edba

Browse files
committed
Adding tests
1 parent 3b45b4e commit 9e8edba

3 files changed

Lines changed: 205 additions & 13 deletions

File tree

temporal-sdk/src/test/java/io/temporal/client/nexus/NexusClientTest.java

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
import static org.junit.Assume.assumeTrue;
44

5-
import io.nexusrpc.handler.OperationHandler;
6-
import io.nexusrpc.handler.OperationImpl;
7-
import io.nexusrpc.handler.ServiceImpl;
85
import io.temporal.api.nexus.v1.Endpoint;
96
import io.temporal.client.NexusClient;
107
import io.temporal.client.NexusOperationExecutionCount;
@@ -13,11 +10,14 @@
1310
import io.temporal.client.UntypedNexusOperationHandle;
1411
import io.temporal.client.UntypedNexusServiceClient;
1512
import io.temporal.testing.internal.SDKTestWorkflowRule;
13+
import io.temporal.workflow.shared.EchoNexusServiceImpl;
1614
import io.temporal.workflow.shared.TestNexusServices;
1715
import io.temporal.workflow.shared.TestWorkflows;
1816
import java.time.Duration;
17+
import java.util.List;
1918
import java.util.UUID;
2019
import java.util.concurrent.TimeUnit;
20+
import java.util.stream.Collectors;
2121
import org.junit.Assert;
2222
import org.junit.BeforeClass;
2323
import 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
}

temporal-sdk/src/test/java/io/temporal/client/nexus/NexusOperationHandleTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import static org.junit.Assume.assumeTrue;
44

5+
import io.temporal.api.enums.v1.NexusOperationExecutionStatus;
56
import io.temporal.api.nexus.v1.Endpoint;
67
import io.temporal.client.NexusClient;
78
import io.temporal.client.NexusOperationException;
89
import io.temporal.client.NexusOperationExecutionDescription;
910
import io.temporal.client.NexusOperationFailedException;
1011
import io.temporal.client.NexusOperationHandle;
12+
import io.temporal.client.NexusOperationNotFoundException;
1113
import io.temporal.client.StartNexusOperationOptions;
1214
import io.temporal.client.UntypedNexusOperationHandle;
1315
import io.temporal.client.UntypedNexusServiceClient;
@@ -56,6 +58,44 @@ public void describeReturnsDescriptionForStartedOperation() {
5658
Assert.assertNotNull(description.getRawResponse());
5759
}
5860

61+
@Test
62+
public void describeReturnsTerminalStateAfterSyncOperationCompletes() {
63+
// Drive a sync echo through to completion, then assert describe surfaces the terminal state.
64+
UntypedNexusOperationHandle handle = startOperation();
65+
String expected = handle.getResult(String.class);
66+
67+
NexusOperationExecutionDescription description = handle.describe();
68+
69+
Assert.assertEquals(
70+
NexusOperationExecutionStatus.NEXUS_OPERATION_EXECUTION_STATUS_COMPLETED,
71+
description.getStatus());
72+
Assert.assertNotNull("expected closeTime once terminal", description.getCloseTime());
73+
Assert.assertNotNull(
74+
"expected executionDuration once terminal", description.getExecutionDuration());
75+
// describe() defaults to includeOutcome=true, so the success payload should be present.
76+
Assert.assertTrue(
77+
"expected description.hasResult() after a successful sync operation",
78+
description.hasResult());
79+
Assert.assertEquals(expected, description.getResult(String.class).orElse(null));
80+
Assert.assertNull("expected no failure on a successful operation", description.getFailure());
81+
}
82+
83+
@Test
84+
public void describeThrowsForUnknownOperationId() {
85+
// Mint an operation ID that the server has never seen; describe must surface the typed
86+
// NOT_FOUND-mapped exception rather than a raw gRPC status.
87+
String bogusOperationId = "does-not-exist-" + UUID.randomUUID();
88+
UntypedNexusOperationHandle handle =
89+
testWorkflowRule.getNexusClient().getHandle(bogusOperationId);
90+
91+
try {
92+
handle.describe();
93+
Assert.fail("expected NexusOperationNotFoundException for an unknown operation ID");
94+
} catch (NexusOperationNotFoundException expected) {
95+
Assert.assertEquals(bogusOperationId, expected.getOperationId());
96+
}
97+
}
98+
5999
@Test
60100
public void describeWithoutRunIdTargetsLatest() {
61101
UntypedNexusOperationHandle started = startOperation();
@@ -122,6 +162,20 @@ public void terminateSucceedsForStartedOperation() {
122162
assertTerminalFailure(handle);
123163
}
124164

165+
@Test
166+
public void terminateTransitionsOperationToTerminatedStatus() {
167+
UntypedNexusOperationHandle handle = startPendingOperation();
168+
handle.terminate("status-assertion");
169+
// assertTerminalFailure proves getResult observed terminality; describe must agree that the
170+
// server-side status was specifically TERMINATED (not CANCELED/FAILED/TIMED_OUT).
171+
assertTerminalFailure(handle);
172+
NexusOperationExecutionDescription description = handle.describe();
173+
Assert.assertEquals(
174+
NexusOperationExecutionStatus.NEXUS_OPERATION_EXECUTION_STATUS_TERMINATED,
175+
description.getStatus());
176+
Assert.assertNotNull(description.getCloseTime());
177+
}
178+
125179
@Test
126180
public void terminateWithReasonSucceedsForStartedOperation() {
127181
UntypedNexusOperationHandle handle = startPendingOperation();

temporal-sdk/src/test/java/io/temporal/client/nexus/NexusServiceClientTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import io.temporal.workflow.shared.EchoNexusServiceImpl;
1414
import io.temporal.workflow.shared.TestNexusServices;
1515
import io.temporal.workflow.shared.TestWorkflows;
16+
import java.time.Duration;
17+
import java.util.UUID;
1618
import org.junit.Assert;
1719
import org.junit.BeforeClass;
1820
import org.junit.Rule;
@@ -61,6 +63,43 @@ public void startReturnsTypedHandleAndPollsResult() {
6163
Assert.assertEquals("echo:world", handle.getResult());
6264
}
6365

66+
@Test
67+
public void executeWithOptionsReturnsResult() {
68+
// Covers the 3-arg execute(op, input, options) overload — the no-options variant is already
69+
// covered by executeReturnsTypedResult.
70+
StartNexusOperationOptions options =
71+
StartNexusOperationOptions.newBuilder()
72+
.setScheduleToCloseTimeout(Duration.ofSeconds(30))
73+
.build();
74+
75+
String result =
76+
buildServiceClient(testWorkflowRule.getNexusEndpoint())
77+
.execute(TestNexusServices.TestNexusService1::operation, "with-opts", options);
78+
79+
Assert.assertEquals("echo:with-opts", result);
80+
}
81+
82+
@Test
83+
public void startWithExplicitIdHonoursId() {
84+
String explicitId = "explicit-id-" + UUID.randomUUID();
85+
StartNexusOperationOptions options =
86+
StartNexusOperationOptions.newBuilder()
87+
.setId(explicitId)
88+
.setScheduleToCloseTimeout(Duration.ofSeconds(30))
89+
.build();
90+
91+
NexusOperationHandle<String> handle =
92+
buildServiceClient(testWorkflowRule.getNexusEndpoint())
93+
.start(TestNexusServices.TestNexusService1::operation, "id-test", options);
94+
95+
Assert.assertEquals(
96+
"explicit ID supplied via StartNexusOperationOptions.setId must round-trip on the handle",
97+
explicitId,
98+
handle.getNexusOperationId());
99+
// Sanity-check: the operation still completes normally with the explicit ID.
100+
Assert.assertEquals("echo:id-test", handle.getResult());
101+
}
102+
64103
@Test
65104
public void clientSummaryReachesServer() {
66105
NexusServiceClient<TestNexusServices.TestNexusService1> client =

0 commit comments

Comments
 (0)