This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 1ac74beca fix(cloud): consolidate inventory pagination (#2956)
1ac74beca is described below
commit 1ac74becacad1457130c1cd521c1b0545cd24242
Author: aias00 <[email protected]>
AuthorDate: Wed Sep 2 15:03:45 2026 +0800
fix(cloud): consolidate inventory pagination (#2956)
* fix(tencent): complete cloud inventory pagination
(cherry picked from commit e0bcd31a785e7d115f8fb7513984258a46896004)
Signed-off-by: liuhy <[email protected]>
* fix(studio): page Tencent ACL inventories by total count
Tencent Cloud exposes TotalCount for ACL roles and topic subscriptions, so
Studio should not silently stop at the old 100-page inventory ceiling.
Constraint: Preserve short-page termination when Tencent omits TotalCount.
Rejected: Keep MAX_PAGES guard | it still truncates valid inventories
beyond 10,000 records.
Confidence: high
Scope-risk: narrow
Tested:
JAVA_HOME=/Users/aias/Library/Java/JavaVirtualMachines/openjdk-21.0.2/Contents/Home
mvn -Dtest=TencentAclServiceTest,TencentInstanceProviderTest test
Tested:
JAVA_HOME=/Users/aias/Library/Java/JavaVirtualMachines/openjdk-21.0.2/Contents/Home
mvn -Dtest='org.apache.rocketmq.studio.provider.tencent.*Test' test
Not-tested: Full mvn test is blocked by pre-existing origin/rocketmq-studio
AlertSchemaMigrationTest failure: rmq_instance_message table not found.
Signed-off-by: liuhy <[email protected]>
(cherry picked from commit 712b6c6ee190f5edbbb90adfd08614e1524520aa)
Signed-off-by: liuhy <[email protected]>
* fix(studio): fetch all Aliyun consumer groups
Aliyun consumer group inventories must not stop at the legacy five-page cap
when the OpenAPI response reports more data. Continue until totalCount is
satisfied or the service returns a short page so counts, exports, and
management screens see the full group set.\n\nConstraint: refreshed against
origin/rocketmq-studio at 899561e56.\nRejected: keep MAX_PAGES fallback | it
still returns a successful partial inventory for known totals above
500.\nConfidence: high\nScope-risk: narrow\nTest [...]
Signed-off-by: liuhy <[email protected]>
(cherry picked from commit e59ac28decda2400d3075ba431b6733aa2f13ee0)
Signed-off-by: liuhy <[email protected]>
---------
Signed-off-by: liuhy <[email protected]>
---
.../provider/alibaba/AliyunInstanceProvider.java | 5 +-
.../studio/provider/tencent/TencentAclService.java | 53 +++++++-------
.../provider/tencent/TencentCatalogService.java | 11 +--
.../provider/tencent/TencentInstanceProvider.java | 18 +++--
.../alibaba/AliyunInstanceProviderTest.java | 73 ++++++++++++++++++++
.../provider/tencent/TencentAclServiceTest.java | 80 ++++++++++++++++++++++
.../tencent/TencentCatalogServiceTest.java | 54 +++++++++++++++
.../tencent/TencentInstanceProviderTest.java | 80 ++++++++++++++++++++++
8 files changed, 337 insertions(+), 37 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java
index 3cf687d62..08d7e8070 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java
@@ -301,7 +301,7 @@ public class AliyunInstanceProvider implements
InstanceProvider {
public List<ConsumerGroupVO> listConsumerGroups(String instanceId, String
search) {
Context ctx = resolve(instanceId);
List<ListConsumerGroupsResponseBody.List> all = new ArrayList<>();
- for (int page = 1; page <= AliyunConverters.MAX_PAGES; page++) {
+ for (int page = 1; ; page++) {
ListConsumerGroupsRequest.Builder builder =
ListConsumerGroupsRequest.builder()
.instanceId(ctx.cloudInstanceId())
.pageNumber(page)
@@ -319,7 +319,8 @@ public class AliyunInstanceProvider implements
InstanceProvider {
break;
}
all.addAll(list);
- if (list.size() < AliyunConverters.PAGE_SIZE) {
+ if (hasFetchedAll(page, AliyunConverters.PAGE_SIZE,
data.getTotalCount())
+ || list.size() < AliyunConverters.PAGE_SIZE) {
break;
}
}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentAclService.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentAclService.java
index 03b858e28..b656056e6 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentAclService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentAclService.java
@@ -53,7 +53,6 @@ import java.util.List;
public class TencentAclService {
static final int PAGE_SIZE = 100;
- static final int MAX_PAGES = 100;
static final String ACL_VERSION = "1.0";
static final String RESOURCE_TYPE = "Cluster";
static final String RESOURCE = "*";
@@ -67,23 +66,20 @@ public class TencentAclService {
public List<AclUserVO> listUsers(String instanceId) {
Context context = resolve(instanceId);
List<AclUserVO> users = new ArrayList<>();
- for (int page = 0; page < MAX_PAGES; page++) {
- DescribeRoleListRequest request = new DescribeRoleListRequest();
- request.setInstanceId(context.cloudInstanceId());
- request.setOffset((long) page * PAGE_SIZE);
- request.setLimit((long) PAGE_SIZE);
- DescribeRoleListResponse response =
clientFactory.call(context.credentialId(),
- context.regionId(), client ->
client.DescribeRoleList(request));
+ long fetched = 0L;
+ for (long offset = 0L; ; offset += PAGE_SIZE) {
+ DescribeRoleListResponse response = describeRoles(context, offset);
RoleItem[] data = response == null ? null : response.getData();
if (data == null || data.length == 0) {
break;
}
+ fetched += data.length;
for (RoleItem role : data) {
if (role != null && StringUtils.hasText(role.getRoleName())) {
users.add(toUser(role, context.cloudInstanceId()));
}
}
- if (data.length < PAGE_SIZE) {
+ if (isLastRolePage(data.length, fetched,
response.getTotalCount())) {
break;
}
}
@@ -94,17 +90,14 @@ public class TencentAclService {
Context context = resolve(instanceId);
String requestedPrincipal = StringUtils.hasText(principal) ?
principal.trim() : null;
List<AclRuleVO> rules = new ArrayList<>();
- for (int page = 0; page < MAX_PAGES; page++) {
- DescribeRoleListRequest request = new DescribeRoleListRequest();
- request.setInstanceId(context.cloudInstanceId());
- request.setOffset((long) page * PAGE_SIZE);
- request.setLimit((long) PAGE_SIZE);
- DescribeRoleListResponse response =
clientFactory.call(context.credentialId(),
- context.regionId(), client ->
client.DescribeRoleList(request));
+ long fetched = 0L;
+ for (long offset = 0L; ; offset += PAGE_SIZE) {
+ DescribeRoleListResponse response = describeRoles(context, offset);
RoleItem[] data = response == null ? null : response.getData();
if (data == null || data.length == 0) {
break;
}
+ fetched += data.length;
for (RoleItem role : data) {
if (role == null || !StringUtils.hasText(role.getRoleName())) {
continue;
@@ -115,7 +108,7 @@ public class TencentAclService {
}
rules.add(toRule(role));
}
- if (data.length < PAGE_SIZE) {
+ if (isLastRolePage(data.length, fetched,
response.getTotalCount())) {
break;
}
}
@@ -178,29 +171,39 @@ public class TencentAclService {
}
private RoleItem findRole(Context context, String roleName) {
- for (int page = 0; page < MAX_PAGES; page++) {
- DescribeRoleListRequest request = new DescribeRoleListRequest();
- request.setInstanceId(context.cloudInstanceId());
- request.setOffset((long) page * PAGE_SIZE);
- request.setLimit((long) PAGE_SIZE);
- DescribeRoleListResponse response =
clientFactory.call(context.credentialId(),
- context.regionId(), client ->
client.DescribeRoleList(request));
+ long fetched = 0L;
+ for (long offset = 0L; ; offset += PAGE_SIZE) {
+ DescribeRoleListResponse response = describeRoles(context, offset);
RoleItem[] data = response == null ? null : response.getData();
if (data == null || data.length == 0) {
break;
}
+ fetched += data.length;
for (RoleItem role : data) {
if (role != null && roleName.equals(role.getRoleName())) {
return role;
}
}
- if (data.length < PAGE_SIZE) {
+ if (isLastRolePage(data.length, fetched,
response.getTotalCount())) {
break;
}
}
throw new BusinessException(404, "ACL user not found: " + roleName);
}
+ private DescribeRoleListResponse describeRoles(Context context, long
offset) {
+ DescribeRoleListRequest request = new DescribeRoleListRequest();
+ request.setInstanceId(context.cloudInstanceId());
+ request.setOffset(offset);
+ request.setLimit((long) PAGE_SIZE);
+ return clientFactory.call(context.credentialId(),
+ context.regionId(), client ->
client.DescribeRoleList(request));
+ }
+
+ private static boolean isLastRolePage(int returned, long fetched, Long
totalCount) {
+ return returned < PAGE_SIZE || totalCount != null && totalCount >= 0L
&& fetched >= totalCount;
+ }
+
public void deleteUser(String instanceId, String username) {
Context context = resolve(instanceId);
String roleName = requireRoleName(username, "ACL username");
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogService.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogService.java
index 886b907bc..744980ed7 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogService.java
@@ -43,7 +43,6 @@ import java.util.Locale;
public class TencentCatalogService implements CloudCatalogProvider {
static final int PAGE_SIZE = 100;
- static final int MAX_PAGES = 100;
static final List<CloudRegionVO> SUPPORTED_REGIONS = List.of(
region("ap-guangzhou", "Guangzhou"),
region("ap-shenzhen-fsi", "Shenzhen Finance"),
@@ -83,9 +82,9 @@ public class TencentCatalogService implements
CloudCatalogProvider {
requireId(credentialId, "credentialId");
requireNonBlank(regionId, "regionId");
List<CloudInstanceOptionVO> instances = new ArrayList<>();
- for (int page = 0; page < MAX_PAGES; page++) {
+ for (long offset = 0L; ; offset += PAGE_SIZE) {
DescribeInstanceListRequest request = new
DescribeInstanceListRequest();
- request.setOffset((long) page * PAGE_SIZE);
+ request.setOffset(offset);
request.setLimit((long) PAGE_SIZE);
DescribeInstanceListResponse response =
clientFactory.call(credentialId, regionId,
client -> client.DescribeInstanceList(request));
@@ -102,13 +101,17 @@ public class TencentCatalogService implements
CloudCatalogProvider {
instances.add(option);
}
}
- if (data.length < PAGE_SIZE) {
+ if (data.length < PAGE_SIZE || hasFetchedAll(offset,
response.getTotalCount())) {
break;
}
}
return instances;
}
+ private static boolean hasFetchedAll(long offset, Long totalCount) {
+ return totalCount != null && totalCount >= 0L && offset + PAGE_SIZE >=
totalCount;
+ }
+
@Override
public CloudInstanceDetailVO getCloudInstance(Long credentialId, String
regionId, String cloudInstanceId) {
requireId(credentialId, "credentialId");
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
index 79560ea4e..110834bfd 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
@@ -276,6 +276,10 @@ public class TencentInstanceProvider implements
InstanceProvider {
return totalCount != null && totalCount >= 0L && offset + pageSize >=
totalCount;
}
+ private static boolean hasFetchedAll(long fetched, Long totalCount) {
+ return totalCount != null && totalCount >= 0L && fetched >= totalCount;
+ }
+
private static PageResult<TopicVO> paginate(List<TopicVO> topics, int
page, int pageSize) {
int total = topics.size();
long offset = Pagination.pageOffset(page, pageSize);
@@ -409,10 +413,10 @@ public class TencentInstanceProvider implements
InstanceProvider {
private List<ConsumerGroupVO> listConsumerGroups(String instanceId, String
search, boolean enrichTimes) {
Context context = resolve(instanceId);
List<ConsumerGroupVO> groups = new ArrayList<>();
- for (int page = 0; page < MAX_PAGES; page++) {
+ for (long offset = 0L; ; offset += PAGE_SIZE) {
DescribeConsumerGroupListRequest request = new
DescribeConsumerGroupListRequest();
request.setInstanceId(context.cloudInstanceId());
- request.setOffset((long) page * PAGE_SIZE);
+ request.setOffset(offset);
request.setLimit((long) PAGE_SIZE);
DescribeConsumerGroupListResponse response =
clientFactory.call(context.credentialId(), context.regionId(),
client -> client.DescribeConsumerGroupList(request));
@@ -432,7 +436,7 @@ public class TencentInstanceProvider implements
InstanceProvider {
groups.add(group);
}
}
- if (data.length < PAGE_SIZE) {
+ if (data.length < PAGE_SIZE || hasFetchedAll(offset, PAGE_SIZE,
response.getTotalCount())) {
break;
}
}
@@ -935,11 +939,12 @@ public class TencentInstanceProvider implements
InstanceProvider {
private List<SubscriptionData> listTopicSubscriptionsByGroup(Context
context, String groupName) {
List<SubscriptionData> all = new ArrayList<>();
- for (int page = 0; page < MAX_PAGES; page++) {
+ long fetched = 0L;
+ for (long offset = 0L; ; offset += PAGE_SIZE) {
DescribeTopicListByGroupRequest request = new
DescribeTopicListByGroupRequest();
request.setInstanceId(context.cloudInstanceId());
request.setConsumerGroup(groupName);
- request.setOffset((long) page * PAGE_SIZE);
+ request.setOffset(offset);
request.setLimit((long) PAGE_SIZE);
DescribeTopicListByGroupResponse response =
clientFactory.call(context.credentialId(), context.regionId(),
client -> client.DescribeTopicListByGroup(request));
@@ -947,8 +952,9 @@ public class TencentInstanceProvider implements
InstanceProvider {
if (data == null || data.length == 0) {
break;
}
+ fetched += data.length;
all.addAll(Arrays.asList(data));
- if (data.length < PAGE_SIZE) {
+ if (data.length < PAGE_SIZE || hasFetchedAll(fetched,
response.getTotalCount())) {
break;
}
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
index 5b43f55c6..fb45c4936 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
@@ -224,6 +224,73 @@ class AliyunInstanceProviderTest {
assertThat(groups.get(0).getConsumeType()).isEqualTo(ConsumeType.CLUSTERING);
}
+ @Test
+ void listConsumerGroupsShouldFetchExactlyFiveFullPagesTest() {
+ stubInstance();
+ stubCallThrough();
+
when(asyncClient.listConsumerGroups(any(ListConsumerGroupsRequest.class))).thenAnswer(invocation
-> {
+ ListConsumerGroupsRequest request = invocation.getArgument(0);
+ return CompletableFuture.completedFuture(groupsResponse(500L,
+ groupIdsForPage(request.getPageNumber(),
AliyunConverters.PAGE_SIZE)));
+ });
+
+ List<ConsumerGroupVO> groups =
provider.listConsumerGroups(STUDIO_INSTANCE_ID, null);
+
+ assertThat(groups).hasSize(500);
+ ArgumentCaptor<ListConsumerGroupsRequest> captor =
+ ArgumentCaptor.forClass(ListConsumerGroupsRequest.class);
+ verify(asyncClient, times(5)).listConsumerGroups(captor.capture());
+
assertThat(captor.getAllValues()).extracting(ListConsumerGroupsRequest::getPageNumber)
+ .containsExactly(1, 2, 3, 4, 5);
+ }
+
+ @Test
+ void listConsumerGroupsShouldTraversePastLegacyFivePageCapTest() {
+ stubInstance();
+ stubCallThrough();
+
when(asyncClient.listConsumerGroups(any(ListConsumerGroupsRequest.class))).thenAnswer(invocation
-> {
+ ListConsumerGroupsRequest request = invocation.getArgument(0);
+ int pageNumber = request.getPageNumber();
+ if (pageNumber <= 5) {
+ return CompletableFuture.completedFuture(groupsResponse(501L,
+ groupIdsForPage(pageNumber,
AliyunConverters.PAGE_SIZE)));
+ }
+ return CompletableFuture.completedFuture(groupsResponse(501L,
"GID_500"));
+ });
+
+ List<ConsumerGroupVO> groups =
provider.listConsumerGroups(STUDIO_INSTANCE_ID, null);
+
+ assertThat(groups).hasSize(501);
+ ArgumentCaptor<ListConsumerGroupsRequest> captor =
+ ArgumentCaptor.forClass(ListConsumerGroupsRequest.class);
+ verify(asyncClient, times(6)).listConsumerGroups(captor.capture());
+
assertThat(captor.getAllValues()).extracting(ListConsumerGroupsRequest::getPageNumber)
+ .containsExactly(1, 2, 3, 4, 5, 6);
+ }
+
+ @Test
+ void listConsumerGroupsShouldStopOnShortPageWhenTotalCountIsMissingTest() {
+ stubInstance();
+ stubCallThrough();
+
when(asyncClient.listConsumerGroups(any(ListConsumerGroupsRequest.class))).thenAnswer(invocation
-> {
+ ListConsumerGroupsRequest request = invocation.getArgument(0);
+ if (request.getPageNumber() == 1) {
+ return CompletableFuture.completedFuture(groupsResponse(null,
+ groupIdsForPage(1, AliyunConverters.PAGE_SIZE)));
+ }
+ return CompletableFuture.completedFuture(groupsResponse(null,
"GID_100"));
+ });
+
+ List<ConsumerGroupVO> groups =
provider.listConsumerGroups(STUDIO_INSTANCE_ID, null);
+
+ assertThat(groups).hasSize(101);
+ ArgumentCaptor<ListConsumerGroupsRequest> captor =
+ ArgumentCaptor.forClass(ListConsumerGroupsRequest.class);
+ verify(asyncClient, times(2)).listConsumerGroups(captor.capture());
+
assertThat(captor.getAllValues()).extracting(ListConsumerGroupsRequest::getPageNumber)
+ .containsExactly(1, 2);
+ }
+
@Test
void getGroupProgressShouldMapLagRowsTest() {
stubInstance();
@@ -736,6 +803,12 @@ class AliyunInstanceProviderTest {
.build();
}
+ private static String[] groupIdsForPage(int pageNumber, int pageSize) {
+ return IntStream.range(0, pageSize)
+ .mapToObj(index -> "GID_" + ((pageNumber - 1) * pageSize +
index))
+ .toArray(String[]::new);
+ }
+
@Test
void normalizeDeliveryOrderTypeShouldMapFifoToOrderlyTest() {
org.junit.jupiter.api.Assertions.assertEquals("Orderly",
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentAclServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentAclServiceTest.java
index 4304752ff..482277c9b 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentAclServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentAclServiceTest.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.studio.provider.tencent;
import com.tencentcloudapi.trocket.v20230308.TrocketClient;
+import com.tencentcloudapi.trocket.v20230308.models.DescribeRoleListRequest;
import com.tencentcloudapi.trocket.v20230308.models.DescribeRoleListResponse;
import com.tencentcloudapi.trocket.v20230308.models.ModifyRoleRequest;
import com.tencentcloudapi.trocket.v20230308.models.RoleItem;
@@ -34,6 +35,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import java.util.Optional;
+import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -41,6 +43,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -110,6 +113,66 @@ class TencentAclServiceTest {
.isEqualTo("reader-role");
}
+ @Test
+ void listUsersShouldFetchExactlyTenThousandTencentRolesTest() throws
Exception {
+ when(client.DescribeRoleList(any())).thenAnswer(invocation -> {
+ DescribeRoleListRequest request = invocation.getArgument(0);
+ DescribeRoleListResponse response = new DescribeRoleListResponse();
+ response.setTotalCount(10000L);
+ response.setData(rolePage(request.getOffset(), request.getLimit(),
10000));
+ return response;
+ });
+
+ assertThat(service.listUsers(INSTANCE_ID)).hasSize(10000);
+ verify(client, times(100)).DescribeRoleList(any());
+ }
+
+ @Test
+ void listRulesShouldFetchPastLegacyTenThousandTencentRoleCapTest() throws
Exception {
+ when(client.DescribeRoleList(any())).thenAnswer(invocation -> {
+ DescribeRoleListRequest request = invocation.getArgument(0);
+ DescribeRoleListResponse response = new DescribeRoleListResponse();
+ response.setTotalCount(10001L);
+ response.setData(rolePage(request.getOffset(), request.getLimit(),
10001));
+ return response;
+ });
+
+ assertThat(service.listRules(INSTANCE_ID, null)).hasSize(10001);
+ verify(client, times(101)).DescribeRoleList(any());
+ }
+
+ @Test
+ void updateUserShouldFindRolePastLegacyTenThousandTencentRoleCapTest()
throws Exception {
+ when(client.DescribeRoleList(any())).thenAnswer(invocation -> {
+ DescribeRoleListRequest request = invocation.getArgument(0);
+ DescribeRoleListResponse response = new DescribeRoleListResponse();
+ response.setTotalCount(10001L);
+ response.setData(rolePage(request.getOffset(), request.getLimit(),
10001));
+ return response;
+ });
+
+ AclUserVO updated = service.updateUser(INSTANCE_ID, AclUserVO.builder()
+ .username("role-10000")
+ .build());
+
+ assertThat(updated.getUsername()).isEqualTo("role-10000");
+ verify(client, times(101)).DescribeRoleList(any());
+ verify(client).ModifyRole(any());
+ }
+
+ @Test
+ void listUsersShouldStopOnShortPageWhenTencentTotalCountIsMissingTest()
throws Exception {
+ when(client.DescribeRoleList(any())).thenAnswer(invocation -> {
+ DescribeRoleListRequest request = invocation.getArgument(0);
+ DescribeRoleListResponse response = new DescribeRoleListResponse();
+ response.setData(rolePage(request.getOffset(), request.getLimit(),
150));
+ return response;
+ });
+
+ assertThat(service.listUsers(INSTANCE_ID)).hasSize(150);
+ verify(client, times(2)).DescribeRoleList(any());
+ }
+
@Test
void createUserShouldRejectNullPayloadTest() {
assertThatThrownBy(() -> service.createUser(INSTANCE_ID, null))
@@ -211,4 +274,21 @@ class TencentAclServiceTest {
.hasMessage("Tencent Cloud roles only support ALLOW ACL rules")
.satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(400));
}
+
+ private static RoleItem[] rolePage(Long offset, Long limit, int total) {
+ int start = offset == null ? 0 : offset.intValue();
+ int size = Math.min(limit == null ? TencentAclService.PAGE_SIZE :
limit.intValue(),
+ Math.max(total - start, 0));
+ return IntStream.range(0, size)
+ .mapToObj(index -> role("role-" + (start + index)))
+ .toArray(RoleItem[]::new);
+ }
+
+ private static RoleItem role(String name) {
+ RoleItem role = new RoleItem();
+ role.setRoleName(name);
+ role.setPermRead(true);
+ role.setPermWrite(false);
+ return role;
+ }
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogServiceTest.java
index 1d4714a1d..03d15b621 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentCatalogServiceTest.java
@@ -17,9 +17,11 @@
package org.apache.rocketmq.studio.provider.tencent;
import
com.tencentcloudapi.trocket.v20230308.models.DescribeInstanceListResponse;
+import
com.tencentcloudapi.trocket.v20230308.models.DescribeInstanceListRequest;
import com.tencentcloudapi.trocket.v20230308.models.DescribeInstanceResponse;
import com.tencentcloudapi.trocket.v20230308.models.Endpoint;
import com.tencentcloudapi.trocket.v20230308.models.InstanceItem;
+import com.tencentcloudapi.trocket.v20230308.TrocketClient;
import org.apache.rocketmq.studio.provider.CloudInstanceDetailVO;
import org.apache.rocketmq.studio.provider.CloudInstanceOptionVO;
import org.apache.rocketmq.studio.provider.CloudRegionVO;
@@ -34,6 +36,9 @@ import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@@ -45,11 +50,18 @@ class TencentCatalogServiceTest {
@Mock
private TencentClientFactory clientFactory;
+ @Mock
+ private TrocketClient client;
+
private TencentCatalogService service;
@BeforeEach
void setUp() {
service = new TencentCatalogService(clientFactory);
+ lenient().when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION),
any())).thenAnswer(invocation -> {
+ TencentClientFactory.TencentCall<Object> action =
invocation.getArgument(2);
+ return action.execute(client);
+ });
}
@Test
@@ -121,6 +133,48 @@ class TencentCatalogServiceTest {
.isEqualTo("rmq-valid");
}
+ @Test
+ void
listCloudInstancesShouldContinuePastTenThousandRecordsWhenTotalCountRequiresItTest()
throws Exception {
+ InstanceItem item = new InstanceItem();
+ item.setInstanceId("rmq-page");
+ item.setInstanceName("page");
+ when(client.DescribeInstanceList(any())).thenAnswer(invocation -> {
+ DescribeInstanceListRequest request = invocation.getArgument(0);
+ DescribeInstanceListResponse response = new
DescribeInstanceListResponse();
+ response.setTotalCount(10_001L);
+ response.setData(request.getOffset() < 10_000L
+ ? java.util.stream.IntStream.range(0, 100).mapToObj(index
-> item).toArray(InstanceItem[]::new)
+ : new InstanceItem[]{item});
+ return response;
+ });
+
+ List<CloudInstanceOptionVO> instances =
service.listCloudInstances(CREDENTIAL_ID, REGION, null);
+
+ assertThat(instances).hasSize(10_001);
+ org.mockito.ArgumentCaptor<DescribeInstanceListRequest> captor =
+
org.mockito.ArgumentCaptor.forClass(DescribeInstanceListRequest.class);
+ verify(client, times(101)).DescribeInstanceList(captor.capture());
+
assertThat(captor.getAllValues().get(100).getOffset()).isEqualTo(10_000L);
+ }
+
+ @Test
+ void
listCloudInstancesShouldStopAtExactlyTenThousandRecordsWhenTotalCountIsReachedTest()
throws Exception {
+ InstanceItem item = new InstanceItem();
+ item.setInstanceId("rmq-page");
+ item.setInstanceName("page");
+ when(client.DescribeInstanceList(any())).thenAnswer(invocation -> {
+ DescribeInstanceListResponse response = new
DescribeInstanceListResponse();
+ response.setTotalCount(10_000L);
+ response.setData(java.util.stream.IntStream.range(0, 100)
+ .mapToObj(index -> item)
+ .toArray(InstanceItem[]::new));
+ return response;
+ });
+
+ assertThat(service.listCloudInstances(CREDENTIAL_ID, REGION,
null)).hasSize(10_000);
+ verify(client, times(100)).DescribeInstanceList(any());
+ }
+
@Test
void getCloudInstanceShouldMapOnlyOpenEndpointsTest() {
Endpoint vpc = endpoint("VPC", "OPEN", "vpc.tencent:8080");
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
index 234584860..203578989 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
@@ -30,6 +30,7 @@ import
com.tencentcloudapi.trocket.v20230308.models.DescribeMessageTraceRequest;
import
com.tencentcloudapi.trocket.v20230308.models.DescribeMessageTraceResponse;
import com.tencentcloudapi.trocket.v20230308.models.MessageItem;
import com.tencentcloudapi.trocket.v20230308.models.MessageTraceItem;
+import
com.tencentcloudapi.trocket.v20230308.models.DescribeTopicListByGroupRequest;
import
com.tencentcloudapi.trocket.v20230308.models.DescribeTopicListByGroupResponse;
import
com.tencentcloudapi.trocket.v20230308.models.DescribeConsumerGroupListRequest;
import com.tencentcloudapi.trocket.v20230308.models.DescribeTopicListRequest;
@@ -537,6 +538,28 @@ class TencentInstanceProviderTest {
assertThat(groups.get(0).getInstances()).isNotNull().isEmpty();
}
+ @Test
+ void
listConsumerGroupsShouldContinuePastTenThousandRecordsWhenTotalCountRequiresItTest()
throws Exception {
+ ConsumeGroupItem item = new ConsumeGroupItem();
+ item.setConsumerGroup("GID_page");
+ when(client.DescribeConsumerGroupList(any())).thenAnswer(invocation ->
{
+ DescribeConsumerGroupListRequest request =
invocation.getArgument(0);
+ DescribeConsumerGroupListResponse response = new
DescribeConsumerGroupListResponse();
+ response.setTotalCount(10_001L);
+ response.setData(request.getOffset() < 10_000L
+ ? IntStream.range(0, 100).mapToObj(index ->
item).toArray(ConsumeGroupItem[]::new)
+ : new ConsumeGroupItem[]{item});
+ return response;
+ });
+
+ assertThat(provider.listConsumerGroups(STUDIO_INSTANCE_ID,
"does-not-match")).isEmpty();
+
+ ArgumentCaptor<DescribeConsumerGroupListRequest> captor =
+
ArgumentCaptor.forClass(DescribeConsumerGroupListRequest.class);
+ verify(client, times(101)).DescribeConsumerGroupList(captor.capture());
+
assertThat(captor.getAllValues().get(100).getOffset()).isEqualTo(10_000L);
+ }
+
@Test
void createConsumerGroupShouldCallTencentOpenApiTest() throws Exception {
when(client.CreateConsumerGroup(any())).thenReturn(null);
@@ -614,6 +637,47 @@ class TencentInstanceProviderTest {
assertThat(preview.getQueues().get(0).getRiskLevel()).isEqualTo("WARNING");
}
+ @Test
+ void
getGroupSubscriptionsShouldFetchExactlyTenThousandTencentSubscriptionsTest()
throws Exception {
+ when(client.DescribeTopicListByGroup(any())).thenAnswer(invocation -> {
+ DescribeTopicListByGroupRequest request =
invocation.getArgument(0);
+ DescribeTopicListByGroupResponse response = new
DescribeTopicListByGroupResponse();
+ response.setTotalCount(10000L);
+ response.setData(subscriptionPage(request.getOffset(),
request.getLimit(), 10000));
+ return response;
+ });
+
+ assertThat(provider.getGroupSubscriptions(STUDIO_INSTANCE_ID,
"GID_test")).hasSize(10000);
+ verify(client, times(100)).DescribeTopicListByGroup(any());
+ }
+
+ @Test
+ void
getGroupProgressShouldFetchPastLegacyTenThousandTencentSubscriptionCapTest()
throws Exception {
+ when(client.DescribeTopicListByGroup(any())).thenAnswer(invocation -> {
+ DescribeTopicListByGroupRequest request =
invocation.getArgument(0);
+ DescribeTopicListByGroupResponse response = new
DescribeTopicListByGroupResponse();
+ response.setTotalCount(10001L);
+ response.setData(subscriptionPage(request.getOffset(),
request.getLimit(), 10001));
+ return response;
+ });
+
+ assertThat(provider.getGroupProgress(STUDIO_INSTANCE_ID,
"GID_test")).hasSize(10001);
+ verify(client, times(101)).DescribeTopicListByGroup(any());
+ }
+
+ @Test
+ void
getGroupSubscriptionsShouldStopOnShortPageWhenTencentTotalCountIsMissingTest()
throws Exception {
+ when(client.DescribeTopicListByGroup(any())).thenAnswer(invocation -> {
+ DescribeTopicListByGroupRequest request =
invocation.getArgument(0);
+ DescribeTopicListByGroupResponse response = new
DescribeTopicListByGroupResponse();
+ response.setData(subscriptionPage(request.getOffset(),
request.getLimit(), 150));
+ return response;
+ });
+
+ assertThat(provider.getGroupSubscriptions(STUDIO_INSTANCE_ID,
"GID_test")).hasSize(150);
+ verify(client, times(2)).DescribeTopicListByGroup(any());
+ }
+
@Test
void resetOffsetShouldCallTencentOpenApiTest() throws Exception {
when(client.ResetConsumerGroupOffset(any())).thenReturn(null);
@@ -645,6 +709,22 @@ class TencentInstanceProviderTest {
return subscription;
}
+ private static SubscriptionData[] subscriptionPage(Long offset, Long
limit, int total) {
+ int start = offset == null ? 0 : offset.intValue();
+ int size = Math.min(limit == null ? TencentInstanceProvider.PAGE_SIZE
: limit.intValue(),
+ Math.max(total - start, 0));
+ return IntStream.range(0, size)
+ .mapToObj(index -> {
+ SubscriptionData subscription = subscription("GID_test");
+ subscription.setTopic("topic-" + (start + index));
+ subscription.setSubString("*");
+ subscription.setExpressionType("TAG");
+ subscription.setConsumerLag((long) start + index);
+ return subscription;
+ })
+ .toArray(SubscriptionData[]::new);
+ }
+
@Test
void queryMessagesByMsgIdShouldReturnDetailWithBodyTest() throws Exception
{
DescribeMessageResponse detail = new DescribeMessageResponse();