Skip to content

Commit b9e7392

Browse files
committed
Adding async tests
1 parent ab48a94 commit b9e7392

1 file changed

Lines changed: 238 additions & 0 deletions

File tree

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package io.temporal.client.nexus;
2+
3+
import static org.junit.Assume.assumeTrue;
4+
5+
import io.nexusrpc.OperationException;
6+
import io.nexusrpc.handler.OperationCancelDetails;
7+
import io.nexusrpc.handler.OperationContext;
8+
import io.nexusrpc.handler.OperationHandler;
9+
import io.nexusrpc.handler.OperationImpl;
10+
import io.nexusrpc.handler.OperationStartDetails;
11+
import io.nexusrpc.handler.OperationStartResult;
12+
import io.nexusrpc.handler.ServiceImpl;
13+
import io.temporal.api.nexus.v1.Endpoint;
14+
import io.temporal.client.NexusClient;
15+
import io.temporal.client.NexusClientOptions;
16+
import io.temporal.client.NexusOperationException;
17+
import io.temporal.client.NexusOperationFailedException;
18+
import io.temporal.client.NexusOperationHandle;
19+
import io.temporal.client.NexusServiceClient;
20+
import io.temporal.client.StartNexusOperationOptions;
21+
import io.temporal.client.UntypedNexusOperationHandle;
22+
import io.temporal.client.UntypedNexusServiceClient;
23+
import io.temporal.testing.internal.SDKTestWorkflowRule;
24+
import io.temporal.workflow.shared.TestNexusServices;
25+
import io.temporal.workflow.shared.TestWorkflows;
26+
import java.time.Duration;
27+
import java.util.concurrent.CompletableFuture;
28+
import java.util.concurrent.ExecutionException;
29+
import java.util.concurrent.TimeUnit;
30+
import org.junit.Assert;
31+
import org.junit.BeforeClass;
32+
import org.junit.Rule;
33+
import org.junit.Test;
34+
35+
/**
36+
* Coverage tests for the {@link CompletableFuture}-returning surface on the standalone Nexus
37+
* client: {@link NexusServiceClient#executeAsync executeAsync} on the typed service client, plus
38+
* the {@code getResultAsync} overloads on both {@link NexusOperationHandle} and {@link
39+
* UntypedNexusOperationHandle}. Each overload is asserted against the existing sync echo handler so
40+
* the Java async API is exercised without depending on server-side async completion.
41+
*/
42+
public class NexusAsyncApiTest {
43+
44+
private static final Duration FUTURE_GET_TIMEOUT = Duration.ofSeconds(30);
45+
private static final String FAIL_PREFIX = "FAIL:";
46+
47+
@Rule
48+
public SDKTestWorkflowRule testWorkflowRule =
49+
SDKTestWorkflowRule.newBuilder()
50+
.setWorkflowTypes(PlaceholderWorkflowImpl.class)
51+
.setNexusServiceImplementation(new TestNexusServiceImpl())
52+
.build();
53+
54+
@BeforeClass
55+
public static void requireExternalService() {
56+
// The time-skipping test server does not implement standalone Nexus operation RPCs.
57+
assumeTrue(
58+
"standalone Nexus operations require a real server",
59+
SDKTestWorkflowRule.useExternalService);
60+
}
61+
62+
// --- NexusServiceClient.executeAsync ---
63+
64+
@Test
65+
public void serviceClientExecuteAsyncReturnsResult() throws Exception {
66+
String result =
67+
buildServiceClient()
68+
.executeAsync(TestNexusServices.TestNexusService1::operation, "hello")
69+
.get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
70+
71+
Assert.assertEquals("echo:hello", result);
72+
}
73+
74+
@Test
75+
public void serviceClientExecuteAsyncWithOptionsReturnsResult() throws Exception {
76+
StartNexusOperationOptions options =
77+
StartNexusOperationOptions.newBuilder()
78+
.setScheduleToCloseTimeout(Duration.ofSeconds(30))
79+
.build();
80+
81+
String result =
82+
buildServiceClient()
83+
.executeAsync(TestNexusServices.TestNexusService1::operation, "world", options)
84+
.get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
85+
86+
Assert.assertEquals("echo:world", result);
87+
}
88+
89+
// --- NexusOperationHandle (typed) getResultAsync overloads ---
90+
91+
@Test
92+
public void typedHandleGetResultAsyncReturnsResult() throws Exception {
93+
NexusOperationHandle<String> handle =
94+
buildServiceClient().start(TestNexusServices.TestNexusService1::operation, "typed");
95+
96+
String result = handle.getResultAsync().get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
97+
98+
Assert.assertEquals("echo:typed", result);
99+
}
100+
101+
@Test
102+
public void typedHandleGetResultAsyncWithTimeoutReturnsResult() throws Exception {
103+
NexusOperationHandle<String> handle =
104+
buildServiceClient().start(TestNexusServices.TestNexusService1::operation, "typed-tm");
105+
106+
String result =
107+
handle
108+
.getResultAsync(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS)
109+
.get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
110+
111+
Assert.assertEquals("echo:typed-tm", result);
112+
}
113+
114+
// --- UntypedNexusOperationHandle getResultAsync overloads ---
115+
116+
@Test
117+
public void untypedHandleGetResultAsyncByClassReturnsResult() throws Exception {
118+
UntypedNexusOperationHandle handle = startUntyped("untyped");
119+
120+
String result =
121+
handle.getResultAsync(String.class).get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
122+
123+
Assert.assertEquals("echo:untyped", result);
124+
}
125+
126+
@Test
127+
public void untypedHandleGetResultAsyncByClassAndTypeReturnsResult() throws Exception {
128+
UntypedNexusOperationHandle handle = startUntyped("untyped-gen");
129+
130+
String result =
131+
handle
132+
.getResultAsync(String.class, String.class)
133+
.get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
134+
135+
Assert.assertEquals("echo:untyped-gen", result);
136+
}
137+
138+
@Test
139+
public void untypedHandleGetResultAsyncWithTimeoutByClassReturnsResult() throws Exception {
140+
UntypedNexusOperationHandle handle = startUntyped("untyped-tm");
141+
142+
String result =
143+
handle
144+
.getResultAsync(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS, String.class)
145+
.get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
146+
147+
Assert.assertEquals("echo:untyped-tm", result);
148+
}
149+
150+
@Test
151+
public void untypedHandleGetResultAsyncWithTimeoutByClassAndTypeReturnsResult() throws Exception {
152+
UntypedNexusOperationHandle handle = startUntyped("untyped-tm-gen");
153+
154+
String result =
155+
handle
156+
.getResultAsync(
157+
FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS, String.class, String.class)
158+
.get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
159+
160+
Assert.assertEquals("echo:untyped-tm-gen", result);
161+
}
162+
163+
// --- Failure path ---
164+
165+
@Test
166+
public void executeAsyncPropagatesOperationFailure() throws Exception {
167+
CompletableFuture<String> future =
168+
buildServiceClient()
169+
.executeAsync(TestNexusServices.TestNexusService1::operation, FAIL_PREFIX + "boom");
170+
171+
try {
172+
future.get(FUTURE_GET_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
173+
Assert.fail("expected future to complete exceptionally");
174+
} catch (ExecutionException e) {
175+
Throwable cause = e.getCause();
176+
Assert.assertTrue(
177+
"expected NexusOperationException, got "
178+
+ (cause == null ? "null" : cause.getClass().getSimpleName()),
179+
cause instanceof NexusOperationException);
180+
Assert.assertTrue(
181+
"expected NexusOperationFailedException, got " + cause.getClass().getSimpleName(),
182+
cause instanceof NexusOperationFailedException);
183+
}
184+
}
185+
186+
// --- helpers ---
187+
188+
private NexusServiceClient<TestNexusServices.TestNexusService1> buildServiceClient() {
189+
Endpoint endpoint = testWorkflowRule.getNexusEndpoint();
190+
return NexusServiceClient.newInstance(
191+
TestNexusServices.TestNexusService1.class,
192+
endpoint.getSpec().getName(),
193+
testWorkflowRule.getWorkflowServiceStubs(),
194+
NexusClientOptions.newBuilder()
195+
.setNamespace(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
196+
.build());
197+
}
198+
199+
private UntypedNexusOperationHandle startUntyped(String input) {
200+
NexusClient client = testWorkflowRule.getNexusClient();
201+
Endpoint endpoint = testWorkflowRule.getNexusEndpoint();
202+
UntypedNexusServiceClient svcClient =
203+
client.newUntypedNexusServiceClient(
204+
endpoint.getSpec().getName(),
205+
TestNexusServices.TestNexusService1.class.getSimpleName());
206+
return svcClient.start("operation", StartNexusOperationOptions.newBuilder().build(), input);
207+
}
208+
209+
public static class PlaceholderWorkflowImpl implements TestWorkflows.TestWorkflow1 {
210+
@Override
211+
public String execute(String input) {
212+
return input;
213+
}
214+
}
215+
216+
@ServiceImpl(service = TestNexusServices.TestNexusService1.class)
217+
public static class TestNexusServiceImpl {
218+
@OperationImpl
219+
public OperationHandler<String, String> operation() {
220+
return new OperationHandler<String, String>() {
221+
@Override
222+
public OperationStartResult<String> start(
223+
OperationContext context, OperationStartDetails details, String input)
224+
throws OperationException {
225+
if (input != null && input.startsWith(FAIL_PREFIX)) {
226+
throw OperationException.failed("intentional failure: " + input);
227+
}
228+
return OperationStartResult.sync("echo:" + (input == null ? "<null>" : input));
229+
}
230+
231+
@Override
232+
public void cancel(OperationContext context, OperationCancelDetails details) {
233+
// Unused in these tests.
234+
}
235+
};
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)