Skip to content

Commit d5a26a1

Browse files
committed
Fix ExportJobQueryPageable to skip empty filtered pages
1 parent c6467bd commit d5a26a1

2 files changed

Lines changed: 47 additions & 6 deletions

File tree

exporthistory/src/main/java/com/microsoft/durabletask/exporthistory/client/ExportJobQueryPageable.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ public Iterator<ExportJobDescription> iterator() {
3737

3838
@Override
3939
public boolean hasNext() {
40-
if (this.currentPage.hasNext()) {
41-
return true;
40+
// Loop across pages because the backend page may filter down to
41+
// empty (e.g. status / createdFrom / createdTo filters in
42+
// DefaultExportHistoryClient.listJobs) while still returning a
43+
// continuation token. Stopping after a single empty page would
44+
// skip matching jobs on later backend pages.
45+
while (!this.currentPage.hasNext() && this.hasMorePages) {
46+
fetchNextPage();
4247
}
43-
if (!this.hasMorePages) {
44-
return false;
45-
}
46-
fetchNextPage();
4748
return this.currentPage.hasNext();
4849
}
4950

exporthistory/src/test/java/com/microsoft/durabletask/exporthistory/client/DefaultExportHistoryClientTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,46 @@ void listJobs_noTimeFilters_returnsAll() {
196196
assertEquals(2, results.size());
197197
}
198198

199+
@Test
200+
void listJobs_filteredPageEmptyButContinuationTokenSet_followsToNextPage() {
201+
// Page 1: two ACTIVE jobs that filter to empty for COMPLETED, with continuation token.
202+
ExportJobState active1 = createState(ExportJobStatus.ACTIVE,
203+
Instant.parse("2026-03-01T00:00:00Z"));
204+
ExportJobState active2 = createState(ExportJobStatus.ACTIVE,
205+
Instant.parse("2026-03-02T00:00:00Z"));
206+
EntityMetadata activeMeta1 = createMockMetadata("active-1", active1);
207+
EntityMetadata activeMeta2 = createMockMetadata("active-2", active2);
208+
List<EntityMetadata> page1Entities = Arrays.asList(activeMeta1, activeMeta2);
209+
EntityQueryResult page1 = mock(EntityQueryResult.class);
210+
when(page1.getEntities()).thenReturn(page1Entities);
211+
when(page1.getContinuationToken()).thenReturn("token-page-2");
212+
213+
// Page 2: two COMPLETED jobs that match the filter, no further pages.
214+
ExportJobState completed1 = createState(ExportJobStatus.COMPLETED,
215+
Instant.parse("2026-03-03T00:00:00Z"));
216+
ExportJobState completed2 = createState(ExportJobStatus.COMPLETED,
217+
Instant.parse("2026-03-04T00:00:00Z"));
218+
EntityMetadata completedMeta1 = createMockMetadata("completed-1", completed1);
219+
EntityMetadata completedMeta2 = createMockMetadata("completed-2", completed2);
220+
List<EntityMetadata> page2Entities = Arrays.asList(completedMeta1, completedMeta2);
221+
EntityQueryResult page2 = mock(EntityQueryResult.class);
222+
when(page2.getEntities()).thenReturn(page2Entities);
223+
when(page2.getContinuationToken()).thenReturn(null);
224+
225+
when(durableEntityClient.queryEntities(any(EntityQuery.class)))
226+
.thenReturn(page1, page2);
227+
228+
ExportJobQuery query = new ExportJobQuery();
229+
query.setStatus(ExportJobStatus.COMPLETED);
230+
query.setPageSize(2);
231+
232+
List<ExportJobDescription> results = collectResults(client.listJobs(query));
233+
234+
assertEquals(2, results.size());
235+
assertEquals("completed-1", results.get(0).getJobId());
236+
assertEquals("completed-2", results.get(1).getJobId());
237+
}
238+
199239
// region Helpers
200240

201241
private ExportJobState createState(ExportJobStatus status, Instant createdAt) {

0 commit comments

Comments
 (0)