feat(kg): 实现同步时间窗口过滤(P0-03)

为 DataManagementClient 的 5 个 listAll* 方法添加时间窗口过滤:
- listAllDatasets(updatedFrom, updatedTo)
- listAllWorkflows(updatedFrom, updatedTo)
- listAllJobs(updatedFrom, updatedTo)
- listAllLabelTasks(updatedFrom, updatedTo)
- listAllKnowledgeSets(updatedFrom, updatedTo)

特性:
- 时间参数构建为 HTTP 查询参数(ISO_LOCAL_DATE_TIME 格式)
- 客户端侧双重过滤(兼容上游未支持的场景)
- 参数校验:updatedFrom > updatedTo 时抛出异常
- null 元素安全处理:过滤列表中的 null 项
- 无参版本保留向后兼容

测试:
- 新增 DataManagementClientTest.java(28 个测试用例)
- 覆盖 URL 参数拼接、参数校验、本地过滤、null 安全、分页等场景
- 测试结果:162 tests, 0 failures

代码审查:
- Codex 两轮审查通过
- 修复 P2 问题:null 元素安全处理
This commit is contained in:
2026-02-18 14:00:01 +08:00
parent 75db6daeb5
commit 74daed1c25
2 changed files with 838 additions and 5 deletions

View File

@@ -0,0 +1,649 @@
package com.datamate.knowledgegraph.infrastructure.client;
import com.datamate.knowledgegraph.infrastructure.client.DataManagementClient.DatasetDTO;
import com.datamate.knowledgegraph.infrastructure.client.DataManagementClient.JobDTO;
import com.datamate.knowledgegraph.infrastructure.client.DataManagementClient.KnowledgeSetDTO;
import com.datamate.knowledgegraph.infrastructure.client.DataManagementClient.LabelTaskDTO;
import com.datamate.knowledgegraph.infrastructure.client.DataManagementClient.PagedResult;
import com.datamate.knowledgegraph.infrastructure.client.DataManagementClient.WorkflowDTO;
import com.datamate.knowledgegraph.infrastructure.neo4j.KnowledgeGraphProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DataManagementClientTest {
private static final String BASE_URL = "http://dm-service:8080";
private static final String ANNOTATION_URL = "http://annotation-service:8081";
private static final int PAGE_SIZE = 10;
@Mock
private RestTemplate restTemplate;
private DataManagementClient client;
@BeforeEach
void setUp() {
KnowledgeGraphProperties properties = new KnowledgeGraphProperties();
KnowledgeGraphProperties.Sync sync = new KnowledgeGraphProperties.Sync();
sync.setDataManagementUrl(BASE_URL);
sync.setAnnotationServiceUrl(ANNOTATION_URL);
sync.setPageSize(PAGE_SIZE);
properties.setSync(sync);
client = new DataManagementClient(restTemplate, properties);
}
// -----------------------------------------------------------------------
// Helper methods
// -----------------------------------------------------------------------
private <T> PagedResult<T> pagedResult(List<T> content, int page, int totalPages) {
PagedResult<T> result = new PagedResult<>();
result.setContent(content);
result.setPage(page);
result.setTotalPages(totalPages);
result.setTotalElements(content.size());
return result;
}
private DatasetDTO dataset(String id, LocalDateTime updatedAt) {
DatasetDTO dto = new DatasetDTO();
dto.setId(id);
dto.setName("dataset-" + id);
dto.setUpdatedAt(updatedAt);
return dto;
}
private WorkflowDTO workflow(String id, LocalDateTime updatedAt) {
WorkflowDTO dto = new WorkflowDTO();
dto.setId(id);
dto.setName("workflow-" + id);
dto.setUpdatedAt(updatedAt);
return dto;
}
private JobDTO job(String id, LocalDateTime updatedAt) {
JobDTO dto = new JobDTO();
dto.setId(id);
dto.setName("job-" + id);
dto.setUpdatedAt(updatedAt);
return dto;
}
private LabelTaskDTO labelTask(String id, LocalDateTime updatedAt) {
LabelTaskDTO dto = new LabelTaskDTO();
dto.setId(id);
dto.setName("label-task-" + id);
dto.setUpdatedAt(updatedAt);
return dto;
}
private KnowledgeSetDTO knowledgeSet(String id, LocalDateTime updatedAt) {
KnowledgeSetDTO dto = new KnowledgeSetDTO();
dto.setId(id);
dto.setName("knowledge-set-" + id);
dto.setUpdatedAt(updatedAt);
return dto;
}
@SuppressWarnings("unchecked")
private <T> void stubSinglePageResponse(List<T> content) {
PagedResult<T> paged = pagedResult(content, 0, 1);
when(restTemplate.exchange(
any(String.class), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class)))
.thenReturn(ResponseEntity.ok(paged));
}
// -----------------------------------------------------------------------
// Time window URL parameter tests
// -----------------------------------------------------------------------
@Nested
class TimeWindowUrlTests {
@Test
void listAllDatasets_withTimeWindow_passesQueryParams() {
LocalDateTime from = LocalDateTime.of(2025, 1, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 6, 30, 23, 59, 59);
DatasetDTO ds = dataset("ds-1", LocalDateTime.of(2025, 3, 15, 10, 0));
stubSinglePageResponse(List.of(ds));
client.listAllDatasets(from, to);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
String url = urlCaptor.getValue();
assertThat(url).contains("updatedFrom=2025-01-01T00%3A00%3A00");
assertThat(url).contains("updatedTo=2025-06-30T23%3A59%3A59");
assertThat(url).contains("page=0");
assertThat(url).contains("size=" + PAGE_SIZE);
}
@Test
void listAllDatasets_withOnlyUpdatedFrom_passesOnlyFromParam() {
LocalDateTime from = LocalDateTime.of(2025, 3, 1, 0, 0, 0);
DatasetDTO ds = dataset("ds-1", LocalDateTime.of(2025, 5, 1, 12, 0));
stubSinglePageResponse(List.of(ds));
client.listAllDatasets(from, null);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
String url = urlCaptor.getValue();
assertThat(url).contains("updatedFrom=2025-03-01T00%3A00%3A00");
assertThat(url).doesNotContain("updatedTo");
}
@Test
void listAllDatasets_withOnlyUpdatedTo_passesOnlyToParam() {
LocalDateTime to = LocalDateTime.of(2025, 12, 31, 23, 59, 59);
DatasetDTO ds = dataset("ds-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(ds));
client.listAllDatasets(null, to);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
String url = urlCaptor.getValue();
assertThat(url).doesNotContain("updatedFrom");
assertThat(url).contains("updatedTo=2025-12-31T23%3A59%3A59");
}
@Test
void listAllDatasets_noTimeWindow_omitsTimeParams() {
DatasetDTO ds = dataset("ds-1", LocalDateTime.of(2025, 1, 1, 0, 0));
stubSinglePageResponse(List.of(ds));
client.listAllDatasets(null, null);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
String url = urlCaptor.getValue();
assertThat(url).doesNotContain("updatedFrom");
assertThat(url).doesNotContain("updatedTo");
}
@Test
void noArgOverload_delegatesToTimeWindowVersion_omitsTimeParams() {
DatasetDTO ds = dataset("ds-1", LocalDateTime.of(2025, 1, 1, 0, 0));
stubSinglePageResponse(List.of(ds));
client.listAllDatasets();
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
String url = urlCaptor.getValue();
assertThat(url).doesNotContain("updatedFrom");
assertThat(url).doesNotContain("updatedTo");
}
}
// -----------------------------------------------------------------------
// Validation
// -----------------------------------------------------------------------
@Nested
class ValidationTests {
@Test
void listAllDatasets_updatedFromAfterUpdatedTo_throwsIllegalArgument() {
LocalDateTime from = LocalDateTime.of(2025, 12, 31, 23, 59, 59);
LocalDateTime to = LocalDateTime.of(2025, 1, 1, 0, 0, 0);
assertThatThrownBy(() -> client.listAllDatasets(from, to))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("updatedFrom must be less than or equal to updatedTo");
}
@Test
void listAllWorkflows_updatedFromAfterUpdatedTo_throwsIllegalArgument() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 1, 1, 0, 0);
assertThatThrownBy(() -> client.listAllWorkflows(from, to))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void listAllJobs_updatedFromAfterUpdatedTo_throwsIllegalArgument() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 1, 1, 0, 0);
assertThatThrownBy(() -> client.listAllJobs(from, to))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void listAllLabelTasks_updatedFromAfterUpdatedTo_throwsIllegalArgument() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 1, 1, 0, 0);
assertThatThrownBy(() -> client.listAllLabelTasks(from, to))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void listAllKnowledgeSets_updatedFromAfterUpdatedTo_throwsIllegalArgument() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 1, 1, 0, 0);
assertThatThrownBy(() -> client.listAllKnowledgeSets(from, to))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void listAllDatasets_sameFromAndTo_doesNotThrow() {
LocalDateTime ts = LocalDateTime.of(2025, 6, 1, 12, 0, 0);
DatasetDTO ds = dataset("ds-1", ts);
stubSinglePageResponse(List.of(ds));
List<DatasetDTO> result = client.listAllDatasets(ts, ts);
assertThat(result).hasSize(1);
}
}
// -----------------------------------------------------------------------
// Local filtering (client-side updatedAt filter)
// -----------------------------------------------------------------------
@Nested
class LocalFilteringTests {
@Test
void listAllDatasets_filtersItemsBeforeUpdatedFrom() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 12, 31, 23, 59, 59);
DatasetDTO old = dataset("ds-old", LocalDateTime.of(2025, 1, 1, 0, 0));
DatasetDTO recent = dataset("ds-recent", LocalDateTime.of(2025, 7, 1, 0, 0));
stubSinglePageResponse(List.of(old, recent));
List<DatasetDTO> result = client.listAllDatasets(from, to);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("ds-recent");
}
@Test
void listAllDatasets_filtersItemsAfterUpdatedTo() {
LocalDateTime from = LocalDateTime.of(2025, 1, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 6, 30, 23, 59, 59);
DatasetDTO inRange = dataset("ds-in", LocalDateTime.of(2025, 3, 15, 10, 0));
DatasetDTO tooNew = dataset("ds-new", LocalDateTime.of(2025, 9, 1, 0, 0));
stubSinglePageResponse(List.of(inRange, tooNew));
List<DatasetDTO> result = client.listAllDatasets(from, to);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("ds-in");
}
@Test
void listAllDatasets_filtersItemsWithNullUpdatedAt() {
LocalDateTime from = LocalDateTime.of(2025, 1, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 12, 31, 23, 59, 59);
DatasetDTO withTime = dataset("ds-with", LocalDateTime.of(2025, 6, 1, 0, 0));
DatasetDTO noTime = dataset("ds-null", null);
stubSinglePageResponse(List.of(withTime, noTime));
List<DatasetDTO> result = client.listAllDatasets(from, to);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("ds-with");
}
@Test
void listAllDatasets_includesBoundaryValues() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 6, 30, 23, 59, 59);
DatasetDTO exactFrom = dataset("ds-from", from);
DatasetDTO exactTo = dataset("ds-to", to);
DatasetDTO inBetween = dataset("ds-mid", LocalDateTime.of(2025, 6, 15, 12, 0));
stubSinglePageResponse(List.of(exactFrom, exactTo, inBetween));
List<DatasetDTO> result = client.listAllDatasets(from, to);
assertThat(result).hasSize(3);
assertThat(result).extracting(DatasetDTO::getId)
.containsExactly("ds-from", "ds-to", "ds-mid");
}
@Test
void listAllDatasets_noTimeWindow_returnsAllItems() {
DatasetDTO ds1 = dataset("ds-1", LocalDateTime.of(2020, 1, 1, 0, 0));
DatasetDTO ds2 = dataset("ds-2", null);
stubSinglePageResponse(List.of(ds1, ds2));
List<DatasetDTO> result = client.listAllDatasets(null, null);
assertThat(result).hasSize(2);
}
@SuppressWarnings("unchecked")
@Test
void listAllDatasets_withNullItemInList_doesNotThrowNPE() {
LocalDateTime from = LocalDateTime.of(2025, 1, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 12, 31, 23, 59, 59);
DatasetDTO valid = dataset("ds-valid", LocalDateTime.of(2025, 6, 1, 0, 0));
// Build a list that contains a null element to simulate upstream returning null items
List<DatasetDTO> contentWithNull = new java.util.ArrayList<>();
contentWithNull.add(valid);
contentWithNull.add(null);
contentWithNull.add(dataset("ds-old", LocalDateTime.of(2024, 1, 1, 0, 0)));
PagedResult<DatasetDTO> paged = pagedResult(contentWithNull, 0, 1);
when(restTemplate.exchange(
any(String.class), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class)))
.thenReturn(ResponseEntity.ok(paged));
List<DatasetDTO> result = client.listAllDatasets(from, to);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("ds-valid");
}
@Test
void listAllWorkflows_filtersCorrectly() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0, 0);
WorkflowDTO old = workflow("wf-old", LocalDateTime.of(2025, 1, 1, 0, 0));
WorkflowDTO recent = workflow("wf-recent", LocalDateTime.of(2025, 7, 1, 0, 0));
stubSinglePageResponse(List.of(old, recent));
List<WorkflowDTO> result = client.listAllWorkflows(from, null);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("wf-recent");
}
@Test
void listAllJobs_filtersCorrectly() {
LocalDateTime to = LocalDateTime.of(2025, 6, 30, 23, 59, 59);
JobDTO inRange = job("j-in", LocalDateTime.of(2025, 3, 1, 0, 0));
JobDTO outOfRange = job("j-out", LocalDateTime.of(2025, 9, 1, 0, 0));
stubSinglePageResponse(List.of(inRange, outOfRange));
List<JobDTO> result = client.listAllJobs(null, to);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("j-in");
}
@Test
void listAllLabelTasks_filtersCorrectly() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 6, 30, 23, 59, 59);
LabelTaskDTO inRange = labelTask("lt-in", LocalDateTime.of(2025, 6, 15, 0, 0));
LabelTaskDTO outOfRange = labelTask("lt-out", LocalDateTime.of(2025, 1, 1, 0, 0));
stubSinglePageResponse(List.of(inRange, outOfRange));
List<LabelTaskDTO> result = client.listAllLabelTasks(from, to);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("lt-in");
}
@Test
void listAllKnowledgeSets_filtersCorrectly() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0, 0);
KnowledgeSetDTO old = knowledgeSet("ks-old", LocalDateTime.of(2025, 1, 1, 0, 0));
KnowledgeSetDTO recent = knowledgeSet("ks-new", LocalDateTime.of(2025, 8, 1, 0, 0));
stubSinglePageResponse(List.of(old, recent));
List<KnowledgeSetDTO> result = client.listAllKnowledgeSets(from, null);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("ks-new");
}
}
// -----------------------------------------------------------------------
// Pagination with time window
// -----------------------------------------------------------------------
@Nested
class PaginationTests {
@SuppressWarnings("unchecked")
@Test
void listAllDatasets_multiplePages_fetchesAllAndFilters() {
LocalDateTime from = LocalDateTime.of(2025, 6, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.of(2025, 12, 31, 23, 59, 59);
DatasetDTO ds1 = dataset("ds-1", LocalDateTime.of(2025, 7, 1, 0, 0));
DatasetDTO ds2 = dataset("ds-2", LocalDateTime.of(2025, 3, 1, 0, 0)); // outside
DatasetDTO ds3 = dataset("ds-3", LocalDateTime.of(2025, 9, 1, 0, 0));
PagedResult<DatasetDTO> page0 = pagedResult(List.of(ds1, ds2), 0, 2);
PagedResult<DatasetDTO> page1 = pagedResult(List.of(ds3), 1, 2);
when(restTemplate.exchange(
(String) argThat(url -> url != null && url.toString().contains("page=0")),
eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class)))
.thenReturn(ResponseEntity.ok(page0));
when(restTemplate.exchange(
(String) argThat(url -> url != null && url.toString().contains("page=1")),
eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class)))
.thenReturn(ResponseEntity.ok(page1));
List<DatasetDTO> result = client.listAllDatasets(from, to);
// ds2 is outside time window, filtered out client-side
assertThat(result).hasSize(2);
assertThat(result).extracting(DatasetDTO::getId)
.containsExactly("ds-1", "ds-3");
}
@SuppressWarnings("unchecked")
@Test
void listAllDatasets_emptyFirstPage_returnsEmptyList() {
PagedResult<DatasetDTO> emptyPage = pagedResult(List.of(), 0, 0);
when(restTemplate.exchange(
any(String.class), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class)))
.thenReturn(ResponseEntity.ok(emptyPage));
List<DatasetDTO> result = client.listAllDatasets(
LocalDateTime.of(2025, 1, 1, 0, 0),
LocalDateTime.of(2025, 12, 31, 23, 59, 59));
assertThat(result).isEmpty();
}
}
// -----------------------------------------------------------------------
// Error propagation
// -----------------------------------------------------------------------
@Nested
class ErrorTests {
@Test
void listAllDatasets_restClientException_propagates() {
when(restTemplate.exchange(
any(String.class), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class)))
.thenThrow(new RestClientException("connection refused"));
assertThatThrownBy(() -> client.listAllDatasets(
LocalDateTime.of(2025, 1, 1, 0, 0), null))
.isInstanceOf(RestClientException.class)
.hasMessageContaining("connection refused");
}
}
// -----------------------------------------------------------------------
// URL format for each entity type
// -----------------------------------------------------------------------
@Nested
class UrlEndpointTests {
@Test
void listAllWorkflows_usesCorrectEndpoint() {
WorkflowDTO wf = workflow("wf-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(wf));
client.listAllWorkflows(LocalDateTime.of(2025, 1, 1, 0, 0), null);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
assertThat(urlCaptor.getValue())
.startsWith(BASE_URL + "/data-management/workflows?");
}
@Test
void listAllJobs_usesCorrectEndpoint() {
JobDTO j = job("j-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(j));
client.listAllJobs(LocalDateTime.of(2025, 1, 1, 0, 0), null);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
assertThat(urlCaptor.getValue())
.startsWith(BASE_URL + "/data-management/jobs?");
}
@Test
void listAllLabelTasks_usesAnnotationServiceUrl() {
LabelTaskDTO lt = labelTask("lt-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(lt));
client.listAllLabelTasks(LocalDateTime.of(2025, 1, 1, 0, 0), null);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
assertThat(urlCaptor.getValue())
.startsWith(ANNOTATION_URL + "/annotation/label-tasks?");
}
@Test
void listAllKnowledgeSets_usesCorrectEndpoint() {
KnowledgeSetDTO ks = knowledgeSet("ks-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(ks));
client.listAllKnowledgeSets(LocalDateTime.of(2025, 1, 1, 0, 0), null);
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplate).exchange(
urlCaptor.capture(), eq(HttpMethod.GET), isNull(),
any(ParameterizedTypeReference.class));
assertThat(urlCaptor.getValue())
.startsWith(BASE_URL + "/data-management/knowledge-sets?");
}
}
// -----------------------------------------------------------------------
// No-arg overloads (backward compatibility)
// -----------------------------------------------------------------------
@Nested
class NoArgOverloadTests {
@Test
void listAllWorkflows_noArgs_returnsAll() {
WorkflowDTO wf = workflow("wf-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(wf));
List<WorkflowDTO> result = client.listAllWorkflows();
assertThat(result).hasSize(1);
}
@Test
void listAllJobs_noArgs_returnsAll() {
JobDTO j = job("j-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(j));
List<JobDTO> result = client.listAllJobs();
assertThat(result).hasSize(1);
}
@Test
void listAllLabelTasks_noArgs_returnsAll() {
LabelTaskDTO lt = labelTask("lt-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(lt));
List<LabelTaskDTO> result = client.listAllLabelTasks();
assertThat(result).hasSize(1);
}
@Test
void listAllKnowledgeSets_noArgs_returnsAll() {
KnowledgeSetDTO ks = knowledgeSet("ks-1", LocalDateTime.of(2025, 6, 1, 0, 0));
stubSinglePageResponse(List.of(ks));
List<KnowledgeSetDTO> result = client.listAllKnowledgeSets();
assertThat(result).hasSize(1);
}
}
}