Skip to content

Commit 261fec0

Browse files
committed
added integration tests
1 parent 54035e4 commit 261fec0

File tree

5 files changed

+140
-34
lines changed

5 files changed

+140
-34
lines changed

client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,6 @@ public void rewindInstance(String instanceId, @Nullable String reason) {
422422
try {
423423
this.sidecarClient.rewindInstance(rewindRequestBuilder.build());
424424
} catch (StatusRuntimeException e) {
425-
if (e.getStatus().getCode() == Status.Code.NOT_FOUND) {
426-
throw new IllegalArgumentException(
427-
"No orchestration instance with ID '" + instanceId + "' was found.", e);
428-
}
429425
if (e.getStatus().getCode() == Status.Code.FAILED_PRECONDITION) {
430426
throw new IllegalStateException(
431427
"Orchestration instance '" + instanceId + "' is not in a failed state and cannot be rewound.", e);

client/src/main/java/com/microsoft/durabletask/StatusRuntimeExceptionHelper.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import io.grpc.Status;
66
import io.grpc.StatusRuntimeException;
77

8-
import java.util.NoSuchElementException;
98
import java.util.concurrent.CancellationException;
109
import java.util.concurrent.TimeoutException;
1110

@@ -19,7 +18,7 @@
1918
* <li>{@code DEADLINE_EXCEEDED} → {@link TimeoutException} (via {@link #toException})</li>
2019
* <li>{@code INVALID_ARGUMENT} → {@link IllegalArgumentException}</li>
2120
* <li>{@code FAILED_PRECONDITION} → {@link IllegalStateException}</li>
22-
* <li>{@code NOT_FOUND} → {@link NoSuchElementException}</li>
21+
* <li>{@code NOT_FOUND} → {@link IllegalArgumentException}</li>
2322
* <li>{@code UNIMPLEMENTED} → {@link UnsupportedOperationException}</li>
2423
* <li>All other codes → {@link RuntimeException}</li>
2524
* </ul>
@@ -44,7 +43,7 @@ static RuntimeException toRuntimeException(StatusRuntimeException e, String oper
4443
case FAILED_PRECONDITION:
4544
return new IllegalStateException(message, e);
4645
case NOT_FOUND:
47-
return createNoSuchElementException(e, message);
46+
return new IllegalArgumentException(message, e);
4847
case UNIMPLEMENTED:
4948
return new UnsupportedOperationException(message, e);
5049
default:
@@ -77,7 +76,7 @@ static Exception toException(StatusRuntimeException e, String operationName) {
7776
case FAILED_PRECONDITION:
7877
return new IllegalStateException(message, e);
7978
case NOT_FOUND:
80-
return createNoSuchElementException(e, message);
79+
return new IllegalArgumentException(message, e);
8180
case UNIMPLEMENTED:
8281
return new UnsupportedOperationException(message, e);
8382
default:
@@ -93,13 +92,6 @@ private static CancellationException createCancellationException(
9392
return ce;
9493
}
9594

96-
private static NoSuchElementException createNoSuchElementException(
97-
StatusRuntimeException e, String message) {
98-
NoSuchElementException ne = new NoSuchElementException(message);
99-
ne.initCause(e);
100-
return ne;
101-
}
102-
10395
private static String formatMessage(String operationName, Status.Code code, String description) {
10496
return "The " + operationName + " operation failed with a " + code + " gRPC status: " + description;
10597
}

client/src/test/java/com/microsoft/durabletask/DurableTaskGrpcClientTest.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import java.io.IOException;
3939
import java.time.Duration;
4040
import java.time.Instant;
41-
import java.util.NoSuchElementException;
4241
import java.util.concurrent.CancellationException;
4342
import java.util.concurrent.TimeoutException;
4443

@@ -123,7 +122,7 @@ public void startInstance(CreateInstanceRequest request, StreamObserver<CreateIn
123122
}
124123

125124
@Test
126-
void raiseEvent_notFound_throwsNoSuchElementException() throws IOException {
125+
void raiseEvent_notFound_throwsIllegalArgumentException() throws IOException {
127126
DurableTaskClient client = createClientWithFakeService(new TaskHubSidecarServiceGrpc.TaskHubSidecarServiceImplBase() {
128127
@Override
129128
public void raiseEvent(RaiseEventRequest request, StreamObserver<RaiseEventResponse> responseObserver) {
@@ -132,15 +131,15 @@ public void raiseEvent(RaiseEventRequest request, StreamObserver<RaiseEventRespo
132131
}
133132
});
134133

135-
NoSuchElementException ex = assertThrows(NoSuchElementException.class, () ->
134+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
136135
client.raiseEvent("test-instance", "testEvent"));
137136

138137
assertGrpcCause(ex, Status.Code.NOT_FOUND);
139138
assertTrue(ex.getMessage().contains("raiseEvent"));
140139
}
141140

142141
@Test
143-
void getInstanceMetadata_notFound_throwsNoSuchElementException() throws IOException {
142+
void getInstanceMetadata_notFound_throwsIllegalArgumentException() throws IOException {
144143
DurableTaskClient client = createClientWithFakeService(new TaskHubSidecarServiceGrpc.TaskHubSidecarServiceImplBase() {
145144
@Override
146145
public void getInstance(GetInstanceRequest request, StreamObserver<GetInstanceResponse> responseObserver) {
@@ -149,7 +148,7 @@ public void getInstance(GetInstanceRequest request, StreamObserver<GetInstanceRe
149148
}
150149
});
151150

152-
NoSuchElementException ex = assertThrows(NoSuchElementException.class, () ->
151+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
153152
client.getInstanceMetadata("test-instance", false));
154153

155154
assertGrpcCause(ex, Status.Code.NOT_FOUND);
@@ -224,7 +223,7 @@ public void deleteTaskHub(DeleteTaskHubRequest request, StreamObserver<DeleteTas
224223
}
225224

226225
@Test
227-
void purgeInstance_notFound_throwsNoSuchElementException() throws IOException {
226+
void purgeInstance_notFound_throwsIllegalArgumentException() throws IOException {
228227
DurableTaskClient client = createClientWithFakeService(new TaskHubSidecarServiceGrpc.TaskHubSidecarServiceImplBase() {
229228
@Override
230229
public void purgeInstances(PurgeInstancesRequest request, StreamObserver<PurgeInstancesResponse> responseObserver) {
@@ -233,7 +232,7 @@ public void purgeInstances(PurgeInstancesRequest request, StreamObserver<PurgeIn
233232
}
234233
});
235234

236-
NoSuchElementException ex = assertThrows(NoSuchElementException.class, () ->
235+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
237236
client.purgeInstance("test-instance"));
238237

239238
assertGrpcCause(ex, Status.Code.NOT_FOUND);
@@ -364,7 +363,7 @@ public void waitForInstanceCompletion(GetInstanceRequest request, StreamObserver
364363
}
365364

366365
@Test
367-
void purgeInstances_notFound_throwsNoSuchElementException() throws IOException {
366+
void purgeInstances_notFound_throwsIllegalArgumentException() throws IOException {
368367
DurableTaskClient client = createClientWithFakeService(new TaskHubSidecarServiceGrpc.TaskHubSidecarServiceImplBase() {
369368
@Override
370369
public void purgeInstances(PurgeInstancesRequest request, StreamObserver<PurgeInstancesResponse> responseObserver) {
@@ -376,7 +375,7 @@ public void purgeInstances(PurgeInstancesRequest request, StreamObserver<PurgeIn
376375
PurgeInstanceCriteria criteria = new PurgeInstanceCriteria()
377376
.setCreatedTimeFrom(Instant.parse("2026-01-01T00:00:00Z"));
378377

379-
NoSuchElementException ex = assertThrows(NoSuchElementException.class, () ->
378+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
380379
client.purgeInstances(criteria));
381380

382381
assertGrpcCause(ex, Status.Code.NOT_FOUND);
@@ -425,7 +424,7 @@ public void rewindInstance(RewindInstanceRequest request, StreamObserver<RewindI
425424
}
426425

427426
@Test
428-
void rewindInstance_notFound_throwsIllegalArgumentExceptionWithCustomMessage() throws IOException {
427+
void rewindInstance_notFound_throwsIllegalArgumentExceptionThroughHelper() throws IOException {
429428
DurableTaskClient client = createClientWithFakeService(new TaskHubSidecarServiceGrpc.TaskHubSidecarServiceImplBase() {
430429
@Override
431430
public void rewindInstance(RewindInstanceRequest request, StreamObserver<RewindInstanceResponse> responseObserver) {
@@ -438,8 +437,8 @@ public void rewindInstance(RewindInstanceRequest request, StreamObserver<RewindI
438437
client.rewindInstance("test-instance", null));
439438

440439
assertGrpcCause(ex, Status.Code.NOT_FOUND);
441-
// rewindInstance has its own custom message for NOT_FOUND
442-
assertTrue(ex.getMessage().contains("test-instance"));
440+
// Now goes through the helper, so message contains the operation name
441+
assertTrue(ex.getMessage().contains("rewindInstance"));
443442
}
444443

445444
// -----------------------------------------------------------------------
@@ -526,7 +525,7 @@ public void raiseEvent(RaiseEventRequest request, StreamObserver<RaiseEventRespo
526525
}
527526

528527
@Test
529-
void terminate_notFound_throwsNoSuchElementException() throws IOException {
528+
void terminate_notFound_throwsIllegalArgumentException() throws IOException {
530529
DurableTaskClient client = createClientWithFakeService(new TaskHubSidecarServiceGrpc.TaskHubSidecarServiceImplBase() {
531530
@Override
532531
public void terminateInstance(TerminateRequest request, StreamObserver<TerminateResponse> responseObserver) {
@@ -535,7 +534,7 @@ public void terminateInstance(TerminateRequest request, StreamObserver<Terminate
535534
}
536535
});
537536

538-
NoSuchElementException ex = assertThrows(NoSuchElementException.class, () ->
537+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
539538
client.terminate("test-instance", null));
540539

541540
assertGrpcCause(ex, Status.Code.NOT_FOUND);
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.durabletask;
5+
6+
import org.junit.jupiter.api.Tag;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.time.Duration;
10+
import java.util.concurrent.TimeoutException;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
/**
15+
* Integration tests that verify gRPC {@code StatusRuntimeException} codes are correctly
16+
* translated into standard Java exceptions at the SDK boundary.
17+
* <p/>
18+
* These tests require the DTS emulator sidecar to be running on localhost:4001.
19+
*/
20+
@Tag("integration")
21+
public class GrpcStatusMappingIntegrationTests extends IntegrationTestBase {
22+
23+
// -----------------------------------------------------------------------
24+
// NOT_FOUND → IllegalArgumentException
25+
// -----------------------------------------------------------------------
26+
27+
@Test
28+
void raiseEvent_nonExistentInstance_throwsIllegalArgumentException() {
29+
DurableTaskClient client = this.createClientBuilder().build();
30+
try (client) {
31+
// The emulator returns NOT_FOUND when raising an event on an instance that doesn't exist.
32+
// The SDK should translate this to IllegalArgumentException.
33+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
34+
client.raiseEvent("definitely-missing-id", "testEvent", null));
35+
assertNotNull(ex.getCause(), "Should preserve original gRPC exception as cause");
36+
}
37+
}
38+
39+
@Test
40+
void suspendInstance_nonExistentInstance_throwsIllegalArgumentException() {
41+
DurableTaskClient client = this.createClientBuilder().build();
42+
try (client) {
43+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
44+
client.suspendInstance("definitely-missing-id", "test suspend"));
45+
assertNotNull(ex.getCause(), "Should preserve original gRPC exception as cause");
46+
}
47+
}
48+
49+
@Test
50+
void terminateInstance_nonExistentInstance_throwsIllegalArgumentException() {
51+
DurableTaskClient client = this.createClientBuilder().build();
52+
try (client) {
53+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
54+
client.terminate("definitely-missing-id", "test terminate"));
55+
assertNotNull(ex.getCause(), "Should preserve original gRPC exception as cause");
56+
}
57+
}
58+
59+
// -----------------------------------------------------------------------
60+
// NOT_FOUND: getInstanceMetadata returns isInstanceFound==false (no throw)
61+
// -----------------------------------------------------------------------
62+
63+
@Test
64+
void getInstanceMetadata_nonExistentInstance_returnsNotFound() {
65+
DurableTaskClient client = this.createClientBuilder().build();
66+
try (client) {
67+
// The DTS emulator returns an empty response (not NOT_FOUND gRPC status) for
68+
// missing instances, so getInstanceMetadata does not throw — it returns metadata
69+
// with isInstanceFound == false.
70+
OrchestrationMetadata metadata = client.getInstanceMetadata("definitely-missing-id", false);
71+
assertNotNull(metadata);
72+
assertFalse(metadata.isInstanceFound());
73+
}
74+
}
75+
76+
// -----------------------------------------------------------------------
77+
// DEADLINE_EXCEEDED → TimeoutException
78+
// -----------------------------------------------------------------------
79+
80+
@Test
81+
void waitForInstanceCompletion_tinyTimeout_throwsTimeoutException() throws TimeoutException {
82+
final String orchestratorName = "SlowOrchestrator";
83+
84+
// Orchestrator that waits far longer than our timeout
85+
DurableTaskGrpcWorker worker = this.createWorkerBuilder()
86+
.addOrchestrator(orchestratorName, ctx -> {
87+
ctx.createTimer(Duration.ofMinutes(10)).await();
88+
ctx.complete("done");
89+
})
90+
.buildAndStart();
91+
92+
DurableTaskClient client = this.createClientBuilder().build();
93+
try (worker; client) {
94+
String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName);
95+
// Wait for the instance to actually start before applying the tiny timeout
96+
client.waitForInstanceStart(instanceId, defaultTimeout, false);
97+
98+
Duration tinyTimeout = Duration.ofSeconds(1);
99+
assertThrows(TimeoutException.class, () ->
100+
client.waitForInstanceCompletion(instanceId, tinyTimeout, false));
101+
}
102+
}
103+
104+
// -----------------------------------------------------------------------
105+
// UNIMPLEMENTED → UnsupportedOperationException (rewind not supported by emulator)
106+
// -----------------------------------------------------------------------
107+
108+
@Test
109+
void rewindInstance_throwsUnsupportedOperationException() {
110+
DurableTaskClient client = this.createClientBuilder().build();
111+
try (client) {
112+
// The DTS emulator does not support the rewind operation and returns UNIMPLEMENTED.
113+
// The SDK should translate this to UnsupportedOperationException.
114+
UnsupportedOperationException ex = assertThrows(UnsupportedOperationException.class, () ->
115+
client.rewindInstance("any-instance-id", "test rewind"));
116+
assertNotNull(ex.getCause(), "Should preserve original gRPC exception as cause");
117+
assertTrue(ex.getMessage().contains("UNIMPLEMENTED"));
118+
}
119+
}
120+
}

client/src/test/java/com/microsoft/durabletask/StatusRuntimeExceptionHelperTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import io.grpc.StatusRuntimeException;
88
import org.junit.jupiter.api.Test;
99

10-
import java.util.NoSuchElementException;
1110
import java.util.concurrent.CancellationException;
1211
import java.util.concurrent.TimeoutException;
1312

@@ -76,14 +75,14 @@ void toRuntimeException_failedPreconditionStatus_returnsIllegalStateException()
7675
}
7776

7877
@Test
79-
void toRuntimeException_notFoundStatus_returnsNoSuchElementException() {
78+
void toRuntimeException_notFoundStatus_returnsIllegalArgumentException() {
8079
StatusRuntimeException grpcException = new StatusRuntimeException(
8180
Status.NOT_FOUND.withDescription("instance not found"));
8281

8382
RuntimeException result = StatusRuntimeExceptionHelper.toRuntimeException(
8483
grpcException, "getInstanceMetadata");
8584

86-
assertInstanceOf(NoSuchElementException.class, result);
85+
assertInstanceOf(IllegalArgumentException.class, result);
8786
assertTrue(result.getMessage().contains("getInstanceMetadata"));
8887
assertTrue(result.getMessage().contains("NOT_FOUND"));
8988
assertTrue(result.getMessage().contains("instance not found"));
@@ -243,14 +242,14 @@ void toException_failedPreconditionStatus_returnsIllegalStateException() {
243242
}
244243

245244
@Test
246-
void toException_notFoundStatus_returnsNoSuchElementException() {
245+
void toException_notFoundStatus_returnsIllegalArgumentException() {
247246
StatusRuntimeException grpcException = new StatusRuntimeException(
248247
Status.NOT_FOUND.withDescription("not found"));
249248

250249
Exception result = StatusRuntimeExceptionHelper.toException(
251250
grpcException, "purgeInstances");
252251

253-
assertInstanceOf(NoSuchElementException.class, result);
252+
assertInstanceOf(IllegalArgumentException.class, result);
254253
assertTrue(result.getMessage().contains("purgeInstances"));
255254
assertTrue(result.getMessage().contains("NOT_FOUND"));
256255
assertSame(grpcException, result.getCause());

0 commit comments

Comments
 (0)