Skip to content

Commit 1995f5e

Browse files
committed
adding checks for list page size and completedTime for listInstanceIds
1 parent d5a26a1 commit 1995f5e

3 files changed

Lines changed: 112 additions & 1 deletion

File tree

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,12 @@ public List<OrchestrationHistoryEvent> getOrchestrationHistory(String instanceId
523523
* @param runtimeStatus optional set of runtime statuses to filter by; if {@code null}, all statuses are included
524524
* @param completedTimeFrom inclusive lower bound of the orchestration completed time filter
525525
* @param completedTimeTo inclusive upper bound of the orchestration completed time filter
526-
* @param pageSize maximum number of instance IDs to return in a single page
526+
* @param pageSize maximum number of instance IDs to return in a single page; must be between 1 and 1000
527527
* @param lastInstanceKey continuation key from the previous page; {@code null} to start from the beginning
528528
* @return a page of orchestration instance IDs along with a continuation token
529+
* @throws IllegalArgumentException if {@code pageSize} is outside the supported range, or if both
530+
* {@code completedTimeFrom} and {@code completedTimeTo} are supplied and
531+
* {@code completedTimeTo} is not strictly after {@code completedTimeFrom}
529532
* @throws UnsupportedOperationException if this client implementation does not support instance ID listing
530533
*/
531534
public InstanceIdPage listInstanceIds(

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
*/
2727
public final class DurableTaskGrpcClient extends DurableTaskClient {
2828
private static final int DEFAULT_PORT = 4001;
29+
private static final int MIN_LIST_PAGE_SIZE = 1;
30+
private static final int MAX_LIST_PAGE_SIZE = 1000;
2931
private static final Logger logger = Logger.getLogger(DurableTaskGrpcClient.class.getPackage().getName());
3032

3133
private final DataConverter dataConverter;
@@ -454,6 +456,19 @@ public InstanceIdPage listInstanceIds(
454456
int pageSize,
455457
@Nullable String lastInstanceKey) {
456458

459+
// Validate defensively before making a remote call: the sidecar would otherwise have to
460+
// reject malformed requests (or worse, accept Integer.MAX_VALUE and try to allocate a
461+
// page of that size). Range matches the export-history limits.
462+
if (pageSize < MIN_LIST_PAGE_SIZE || pageSize > MAX_LIST_PAGE_SIZE) {
463+
throw new IllegalArgumentException(
464+
"pageSize must be between " + MIN_LIST_PAGE_SIZE +
465+
" and " + MAX_LIST_PAGE_SIZE + ". Got: " + pageSize);
466+
}
467+
if (completedTimeFrom != null && completedTimeTo != null
468+
&& !completedTimeTo.isAfter(completedTimeFrom)) {
469+
throw new IllegalArgumentException("completedTimeTo must be after completedTimeFrom.");
470+
}
471+
457472
ListInstanceIdsRequest.Builder builder = ListInstanceIdsRequest.newBuilder()
458473
.setPageSize(pageSize);
459474

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.microsoft.durabletask;
4+
5+
import io.grpc.ManagedChannel;
6+
import io.grpc.ManagedChannelBuilder;
7+
import org.junit.jupiter.api.AfterEach;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
11+
import java.time.Instant;
12+
13+
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
15+
16+
/**
17+
* Unit tests for {@link DurableTaskGrpcClient#listInstanceIds} input validation.
18+
*
19+
* <p>Validation runs before any gRPC call, so an unconnected channel is sufficient
20+
* to construct the client.
21+
*/
22+
public class DurableTaskGrpcClientListInstanceIdsTest {
23+
24+
private ManagedChannel channel;
25+
private DurableTaskClient client;
26+
27+
@BeforeEach
28+
void setUp() {
29+
// gRPC channels connect lazily, so this never actually opens a socket as long as
30+
// we never invoke an RPC.
31+
this.channel = ManagedChannelBuilder.forAddress("localhost", 0)
32+
.usePlaintext()
33+
.build();
34+
this.client = new DurableTaskGrpcClientBuilder().grpcChannel(this.channel).build();
35+
}
36+
37+
@AfterEach
38+
void tearDown() {
39+
if (this.client != null) {
40+
this.client.close();
41+
}
42+
if (this.channel != null) {
43+
this.channel.shutdownNow();
44+
}
45+
}
46+
47+
@Test
48+
void listInstanceIds_pageSizeZero_throwsIllegalArgumentException() {
49+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
50+
this.client.listInstanceIds(null, null, null, 0, null));
51+
assertTrue(ex.getMessage().contains("pageSize"));
52+
}
53+
54+
@Test
55+
void listInstanceIds_pageSizeNegative_throwsIllegalArgumentException() {
56+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
57+
this.client.listInstanceIds(null, null, null, -1, null));
58+
assertTrue(ex.getMessage().contains("pageSize"));
59+
}
60+
61+
@Test
62+
void listInstanceIds_pageSizeMaxInt_throwsIllegalArgumentException() {
63+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
64+
this.client.listInstanceIds(null, null, null, Integer.MAX_VALUE, null));
65+
assertTrue(ex.getMessage().contains("pageSize"));
66+
}
67+
68+
@Test
69+
void listInstanceIds_pageSizeAboveMax_throwsIllegalArgumentException() {
70+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
71+
this.client.listInstanceIds(null, null, null, 1001, null));
72+
assertTrue(ex.getMessage().contains("pageSize"));
73+
}
74+
75+
@Test
76+
void listInstanceIds_completedTimeToBeforeFrom_throwsIllegalArgumentException() {
77+
Instant from = Instant.parse("2026-03-01T00:00:00Z");
78+
Instant to = Instant.parse("2026-02-01T00:00:00Z");
79+
80+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
81+
this.client.listInstanceIds(null, from, to, 100, null));
82+
assertTrue(ex.getMessage().contains("completedTimeTo"));
83+
}
84+
85+
@Test
86+
void listInstanceIds_completedTimeToEqualsFrom_throwsIllegalArgumentException() {
87+
Instant ts = Instant.parse("2026-03-01T00:00:00Z");
88+
89+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
90+
this.client.listInstanceIds(null, ts, ts, 100, null));
91+
assertTrue(ex.getMessage().contains("completedTimeTo"));
92+
}
93+
}

0 commit comments

Comments
 (0)