You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rocketmq.apache.org by li...@apache.org on 2026/10/15 09:59:28 UTC
(rocketmq-dashboard) branch rocketmq-studio updated: fix(aliyun): restore read and catalog response validation (#6565)
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 3c9b6f728 fix(aliyun): restore read and catalog response validation (#6565)
3c9b6f728 is described below
commit 3c9b6f728234d790d969405b0b04ccc0f13bf638
Author: zmuxuny <xi...@gmail.com>
AuthorDate: Thu Oct 15 02:59:22 2026 -0700
fix(aliyun): restore read and catalog response validation (#6565)
---
.../common/util/AliyunResponseValidator.java | 48 ++++++
.../provider/alibaba/AliyunCatalogService.java | 22 ++-
.../provider/alibaba/AliyunInstanceProvider.java | 64 +++++--
.../exception/GlobalExceptionHandlerTest.java | 8 +
.../common/util/AliyunResponseValidatorTest.java | 73 ++++++++
.../provider/alibaba/AliyunCatalogServiceTest.java | 98 ++++++++++-
.../alibaba/AliyunInstanceProviderTest.java | 191 ++++++++++++++++++---
7 files changed, 450 insertions(+), 54 deletions(-)
diff --git a/server/src/main/java/org/apache/rocketmq/studio/common/util/AliyunResponseValidator.java b/server/src/main/java/org/apache/rocketmq/studio/common/util/AliyunResponseValidator.java
new file mode 100644
index 000000000..38e2588cb
--- /dev/null
+++ b/server/src/main/java/org/apache/rocketmq/studio/common/util/AliyunResponseValidator.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.common.util;
+
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.springframework.util.StringUtils;
+
+import java.util.function.Function;
+
+/** Shared, null-tolerant business flag validation for Aliyun read and catalog responses. */
+public final class AliyunResponseValidator {
+ private AliyunResponseValidator() {
+ }
+
+ /**
+ * Missing response bodies are malformed upstream responses. A present body with an
+ * omitted success flag remains compatible; only explicit false is a business rejection.
+ * Business rejections use 422 rather than a transport/outage 502 or a Studio-session 401.
+ */
+ public static <T> T requireReadableBody(String operation, T body, Function<T, Boolean> success,
+ Function<T, String> code, Function<T, String> message) {
+ if (body == null) {
+ throw new BusinessException(502, "Aliyun " + operation + " returned an empty response body");
+ }
+ if (Boolean.FALSE.equals(success.apply(body))) {
+ String providerMessage = message.apply(body);
+ String providerCode = code.apply(body);
+ String detail = StringUtils.hasText(providerMessage) ? providerMessage
+ : StringUtils.hasText(providerCode) ? providerCode : "provider rejected the request";
+ throw new BusinessException(422, "Aliyun " + operation + " rejected: " + detail);
+ }
+ return body;
+ }
+}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java
index 69696884c..561a85b28 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java
@@ -27,6 +27,7 @@ import com.aliyun.sdk.service.rocketmq20220801.models.ListRegionsResponse;
import com.aliyun.sdk.service.rocketmq20220801.models.ListRegionsResponseBody;
import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.common.util.AliyunResponseValidator;
import org.apache.rocketmq.studio.provider.CloudCatalogProvider;
import org.apache.rocketmq.studio.provider.CloudInstanceDetailVO;
import org.apache.rocketmq.studio.provider.CloudInstanceOptionVO;
@@ -63,8 +64,11 @@ public class AliyunCatalogService implements CloudCatalogProvider {
requireId(credentialId, "credentialId");
ListRegionsResponse response = clientFactory.call(credentialId, DEFAULT_REGION,
client -> client.listRegions(ListRegionsRequest.builder().build()));
- ListRegionsResponseBody body = response == null ? null : response.getBody();
- List<ListRegionsResponseBody.Data> data = body == null ? null : body.getData();
+ ListRegionsResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "region listing", response == null ? null : response.getBody(),
+ ListRegionsResponseBody::getSuccess, ListRegionsResponseBody::getCode,
+ ListRegionsResponseBody::getMessage);
+ List<ListRegionsResponseBody.Data> data = body.getData();
List<CloudRegionVO> regions = new ArrayList<>();
if (data == null) {
return regions;
@@ -109,8 +113,11 @@ public class AliyunCatalogService implements CloudCatalogProvider {
GetInstanceRequest request = GetInstanceRequest.builder().instanceId(normalizedCloudInstanceId).build();
GetInstanceResponse response = clientFactory.call(credentialId, normalizedRegionId,
client -> client.getInstance(request));
- GetInstanceResponseBody body = response == null ? null : response.getBody();
- GetInstanceResponseBody.Data data = body == null ? null : body.getData();
+ GetInstanceResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "instance lookup", response == null ? null : response.getBody(),
+ GetInstanceResponseBody::getSuccess, GetInstanceResponseBody::getCode,
+ GetInstanceResponseBody::getMessage);
+ GetInstanceResponseBody.Data data = body.getData();
if (data == null) {
throw new BusinessException(404, "Aliyun instance not found: " + normalizedCloudInstanceId);
}
@@ -126,8 +133,11 @@ public class AliyunCatalogService implements CloudCatalogProvider {
.build();
ListInstancesResponse response = clientFactory.call(credentialId, regionId,
client -> client.listInstances(request));
- ListInstancesResponseBody body = response == null ? null : response.getBody();
- ListInstancesResponseBody.Data data = body == null ? null : body.getData();
+ ListInstancesResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "instance listing", response == null ? null : response.getBody(),
+ ListInstancesResponseBody::getSuccess, ListInstancesResponseBody::getCode,
+ ListInstancesResponseBody::getMessage);
+ ListInstancesResponseBody.Data data = body.getData();
List<ListInstancesResponseBody.List> list = data == null ? null : data.getList();
if (list == null || list.isEmpty()) {
break;
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 d1c10c0c3..800d011bb 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
@@ -54,6 +54,7 @@ import org.springframework.util.StringUtils;
import org.apache.rocketmq.studio.common.domain.PageResult;
import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.common.util.AliyunResponseValidator;
import org.apache.rocketmq.studio.common.util.Pagination;
import org.apache.rocketmq.studio.instance.InstanceRepository;
import org.apache.rocketmq.studio.instance.InstanceVO;
@@ -129,8 +130,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
.build();
ListTopicsResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.listTopics(request));
- ListTopicsResponseBody body = response == null ? null : response.getBody();
- ListTopicsResponseBody.Data data = body == null ? null : body.getData();
+ ListTopicsResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "topic listing", response == null ? null : response.getBody(),
+ ListTopicsResponseBody::getSuccess, ListTopicsResponseBody::getCode,
+ ListTopicsResponseBody::getMessage);
+ ListTopicsResponseBody.Data data = body.getData();
Long totalCount = data == null ? null : data.getTotalCount();
return totalCount == null || totalCount < 0
? listTopics(instanceId, null, null).size()
@@ -147,8 +151,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
.build();
ListConsumerGroupsResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.listConsumerGroups(request));
- ListConsumerGroupsResponseBody body = response == null ? null : response.getBody();
- ListConsumerGroupsResponseBody.Data data = body == null ? null : body.getData();
+ ListConsumerGroupsResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "consumer group listing", response == null ? null : response.getBody(),
+ ListConsumerGroupsResponseBody::getSuccess, ListConsumerGroupsResponseBody::getCode,
+ ListConsumerGroupsResponseBody::getMessage);
+ ListConsumerGroupsResponseBody.Data data = body.getData();
Long totalCount = data == null ? null : data.getTotalCount();
return totalCount == null || totalCount < 0
? listConsumerGroups(instanceId, null).size()
@@ -194,8 +201,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
ListTopicsRequest request = buildTopicRequest(ctx.cloudInstanceId(), type, search, page, pageSize);
ListTopicsResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.listTopics(request));
- ListTopicsResponseBody body = response == null ? null : response.getBody();
- return body == null ? null : body.getData();
+ ListTopicsResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "topic listing", response == null ? null : response.getBody(),
+ ListTopicsResponseBody::getSuccess, ListTopicsResponseBody::getCode,
+ ListTopicsResponseBody::getMessage);
+ return body.getData();
}
private ListTopicsRequest buildTopicRequest(String cloudInstanceId, String type, String search,
@@ -296,8 +306,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
.build();
ListTopicSubscriptionsResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.listTopicSubscriptions(request));
- ListTopicSubscriptionsResponseBody body = response == null ? null : response.getBody();
- List<ListTopicSubscriptionsResponseBody.Data> data = body == null ? null : body.getData();
+ ListTopicSubscriptionsResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "topic subscription query", response == null ? null : response.getBody(),
+ ListTopicSubscriptionsResponseBody::getSuccess, ListTopicSubscriptionsResponseBody::getCode,
+ ListTopicSubscriptionsResponseBody::getMessage);
+ List<ListTopicSubscriptionsResponseBody.Data> data = body.getData();
List<TopicConsumerVO> consumers = new ArrayList<>();
if (data == null) {
return consumers;
@@ -326,8 +339,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
ListConsumerGroupsRequest request = builder.build();
ListConsumerGroupsResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.listConsumerGroups(request));
- ListConsumerGroupsResponseBody body = response == null ? null : response.getBody();
- ListConsumerGroupsResponseBody.Data data = body == null ? null : body.getData();
+ ListConsumerGroupsResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "consumer group listing", response == null ? null : response.getBody(),
+ ListConsumerGroupsResponseBody::getSuccess, ListConsumerGroupsResponseBody::getCode,
+ ListConsumerGroupsResponseBody::getMessage);
+ ListConsumerGroupsResponseBody.Data data = body.getData();
List<ListConsumerGroupsResponseBody.List> list = data == null ? null : data.getList();
if (list == null || list.isEmpty()) {
break;
@@ -423,8 +439,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
.build();
GetConsumerGroupLagResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.getConsumerGroupLag(request));
- GetConsumerGroupLagResponseBody body = response == null ? null : response.getBody();
- GetConsumerGroupLagResponseBody.Data data = body == null ? null : body.getData();
+ GetConsumerGroupLagResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "consumer lag query", response == null ? null : response.getBody(),
+ GetConsumerGroupLagResponseBody::getSuccess, GetConsumerGroupLagResponseBody::getCode,
+ GetConsumerGroupLagResponseBody::getMessage);
+ GetConsumerGroupLagResponseBody.Data data = body.getData();
if (data == null) {
return new ArrayList<>();
}
@@ -440,8 +459,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
.build();
ListConsumerGroupSubscriptionsResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.listConsumerGroupSubscriptions(request));
- ListConsumerGroupSubscriptionsResponseBody body = response == null ? null : response.getBody();
- List<ListConsumerGroupSubscriptionsResponseBody.Data> data = body == null ? null : body.getData();
+ ListConsumerGroupSubscriptionsResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "consumer subscription query", response == null ? null : response.getBody(),
+ ListConsumerGroupSubscriptionsResponseBody::getSuccess, ListConsumerGroupSubscriptionsResponseBody::getCode,
+ ListConsumerGroupSubscriptionsResponseBody::getMessage);
+ List<ListConsumerGroupSubscriptionsResponseBody.Data> data = body.getData();
List<SubscriptionEntryVO> subscriptions = new ArrayList<>();
if (data == null) {
return subscriptions;
@@ -534,8 +556,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
ListMessagesRequest request = builder.build();
ListMessagesResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.listMessages(request));
- ListMessagesResponseBody body = response == null ? null : response.getBody();
- ListMessagesResponseBody.Data data = body == null ? null : body.getData();
+ ListMessagesResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "message query", response == null ? null : response.getBody(),
+ ListMessagesResponseBody::getSuccess, ListMessagesResponseBody::getCode,
+ ListMessagesResponseBody::getMessage);
+ ListMessagesResponseBody.Data data = body.getData();
List<ListMessagesResponseBody.List> list = data == null ? null : data.getList();
if (list == null || list.isEmpty()) {
break;
@@ -602,8 +627,11 @@ public class AliyunInstanceProvider implements InstanceProvider {
.build();
GetTraceResponse response = clientFactory.call(ctx.credentialId(), ctx.regionId(),
client -> client.getTrace(request));
- GetTraceResponseBody body = response == null ? null : response.getBody();
- GetTraceResponseBody.Data data = body == null ? null : body.getData();
+ GetTraceResponseBody body = AliyunResponseValidator.requireReadableBody(
+ "message trace query", response == null ? null : response.getBody(),
+ GetTraceResponseBody::getSuccess, GetTraceResponseBody::getCode,
+ GetTraceResponseBody::getMessage);
+ GetTraceResponseBody.Data data = body.getData();
if (data == null) {
return emptyTraceRecord();
}
diff --git a/server/src/test/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandlerTest.java b/server/src/test/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandlerTest.java
index 61b7b832e..583434710 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandlerTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandlerTest.java
@@ -118,4 +118,12 @@ class GlobalExceptionHandlerTest {
throw new HttpMediaTypeNotAcceptableException(List.of(MediaType.APPLICATION_JSON));
}
}
+ @Test
+ void preservesBusinessRejectionWithoutPromotingItToGatewayFailureTest() throws Exception {
+ mockMvc.perform(get("/test/business/422"))
+ .andExpect(status().is(422))
+ .andExpect(jsonPath("$.code").value(422))
+ .andExpect(jsonPath("$.message").value("failure-422"));
+ }
+
}
diff --git a/server/src/test/java/org/apache/rocketmq/studio/common/util/AliyunResponseValidatorTest.java b/server/src/test/java/org/apache/rocketmq/studio/common/util/AliyunResponseValidatorTest.java
new file mode 100644
index 000000000..d4ecbaac0
--- /dev/null
+++ b/server/src/test/java/org/apache/rocketmq/studio/common/util/AliyunResponseValidatorTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.common.util;
+
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class AliyunResponseValidatorTest {
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(booleans = {true})
+ void presentBodyWithTrueOrOmittedFlagRemainsReadableTest(Boolean success) {
+ Body body = new Body(success, "ignored-code", "ignored-message");
+ assertThat(validate(body)).isSameAs(body);
+ }
+
+ @Test
+ void explicitFailureIsBusinessRejectionWithProviderMessageTest() {
+ assertThatThrownBy(() -> validate(new Body(false, "Denied", "permission denied")))
+ .isInstanceOf(BusinessException.class).hasMessage("Aliyun test read rejected: permission denied")
+ .extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void blankMessageFallsBackToProviderCodeTest() {
+ assertThatThrownBy(() -> validate(new Body(false, "Denied", " ")))
+ .isInstanceOf(BusinessException.class).hasMessage("Aliyun test read rejected: Denied")
+ .extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void missingMessageAndCodeUseBusinessFallbackTest() {
+ assertThatThrownBy(() -> validate(new Body(false, null, null)))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("Aliyun test read rejected: provider rejected the request")
+ .extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void missingBodyIsMalformedUpstreamResponseTest() {
+ assertThatThrownBy(() -> validate(null)).isInstanceOf(BusinessException.class)
+ .hasMessage("Aliyun test read returned an empty response body")
+ .extracting("code").isEqualTo(502);
+ }
+
+ private static Body validate(Body body) {
+ return AliyunResponseValidator.requireReadableBody("test read", body,
+ Body::success, Body::code, Body::message);
+ }
+
+ private record Body(Boolean success, String code, String message) {
+ }
+}
diff --git a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogServiceTest.java b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogServiceTest.java
index b0ce00c28..ab82d0355 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogServiceTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogServiceTest.java
@@ -64,7 +64,7 @@ class AliyunCatalogServiceTest {
void listRegionsShouldSkipNullSdkRecords() {
ListRegionsResponse response = ListRegionsResponse.create().toBuilder()
.statusCode(200)
- .body(ListRegionsResponseBody.builder()
+ .body(ListRegionsResponseBody.builder().success(true)
.data(Arrays.asList(null, ListRegionsResponseBody.Data.builder()
.regionId("cn-hangzhou")
.supportRocketmqV5(true)
@@ -83,7 +83,7 @@ class AliyunCatalogServiceTest {
void listRegionsShouldKeepOnlyRocketmqV5RegionsTest() {
ListRegionsResponse response = ListRegionsResponse.create().toBuilder()
.statusCode(200)
- .body(ListRegionsResponseBody.builder()
+ .body(ListRegionsResponseBody.builder().success(true)
.data(List.of(
ListRegionsResponseBody.Data.builder()
.regionId("cn-shanghai").regionName("shanghai")
@@ -191,7 +191,7 @@ class AliyunCatalogServiceTest {
void getCloudInstanceShouldMapEndpointsTest() {
GetInstanceResponse response = GetInstanceResponse.create().toBuilder()
.statusCode(200)
- .body(GetInstanceResponseBody.builder()
+ .body(GetInstanceResponseBody.builder().success(true)
.data(GetInstanceResponseBody.Data.builder()
.instanceId("rmq-cn-001")
.instanceName("prod")
@@ -227,7 +227,8 @@ class AliyunCatalogServiceTest {
@Test
void getCloudInstanceShouldNormalizeLookupIdentifiersTest() {
- when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(null);
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(
+ GetInstanceResponse.create().toBuilder().body(GetInstanceResponseBody.builder().build()).build());
assertThatThrownBy(() -> service.getCloudInstance(
CREDENTIAL_ID, " cn-hangzhou ", " rmq-missing "))
@@ -236,6 +237,46 @@ class AliyunCatalogServiceTest {
verify(clientFactory).call(eq(CREDENTIAL_ID), eq(REGION), any());
}
+ @Test
+ void listRegionsShouldRejectUnsuccessfulBusinessResponseTest() {
+ ListRegionsResponse response = ListRegionsResponse.create().toBuilder()
+ .statusCode(200)
+ .body(ListRegionsResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build())
+ .build();
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(AliyunCatalogService.DEFAULT_REGION), any()))
+ .thenReturn(response);
+
+ assertThatThrownBy(() -> service.listRegions(CREDENTIAL_ID))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void listCloudInstancesShouldRejectUnsuccessfulBusinessResponseTest() {
+ ListInstancesResponse response = ListInstancesResponse.create().toBuilder()
+ .statusCode(200)
+ .body(ListInstancesResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build())
+ .build();
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(response);
+
+ assertThatThrownBy(() -> service.listCloudInstances(CREDENTIAL_ID, REGION, null))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void getCloudInstanceShouldRejectUnsuccessfulBusinessResponseTest() {
+ GetInstanceResponse response = GetInstanceResponse.create().toBuilder()
+ .statusCode(200)
+ .body(GetInstanceResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build())
+ .build();
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(response);
+
+ assertThatThrownBy(() -> service.getCloudInstance(CREDENTIAL_ID, REGION, "rmq-cn-001"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
private static List<ListInstancesResponseBody.List> instanceRows(int count, int idOffset) {
List<ListInstancesResponseBody.List> rows = new ArrayList<>();
for (int i = 0; i < count; i++) {
@@ -263,7 +304,7 @@ class AliyunCatalogServiceTest {
private static ListInstancesResponse instancesResponse(List<ListInstancesResponseBody.List> rows, long totalCount) {
return ListInstancesResponse.create().toBuilder()
.statusCode(200)
- .body(ListInstancesResponseBody.builder()
+ .body(ListInstancesResponseBody.builder().success(true)
.data(ListInstancesResponseBody.Data.builder()
.list(rows)
.pageNumber(1L)
@@ -273,4 +314,51 @@ class AliyunCatalogServiceTest {
.build())
.build();
}
+ @Test
+ void listCloudInstancesShouldTolerateOmittedSuccessFlagTest() {
+ ListInstancesResponse fixture = instancesResponse(List.of(instanceRow("rmq-a", "A")));
+ ListInstancesResponse response = ListInstancesResponse.create().toBuilder()
+ .body(ListInstancesResponseBody.builder().data(fixture.getBody().getData()).build()).build();
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(response);
+ assertThat(service.listCloudInstances(CREDENTIAL_ID, REGION, null)).singleElement()
+ .extracting(CloudInstanceOptionVO::getInstanceId).isEqualTo("rmq-a");
+ }
+
+ @Test
+ void listRegionsShouldTolerateOmittedSuccessFlagTest() {
+ ListRegionsResponse response = ListRegionsResponse.create().toBuilder()
+ .body(ListRegionsResponseBody.builder().data(List.of(ListRegionsResponseBody.Data.builder()
+ .regionId(REGION).supportRocketmqV5(true).build())).build()).build();
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(AliyunCatalogService.DEFAULT_REGION), any()))
+ .thenReturn(response);
+ assertThat(service.listRegions(CREDENTIAL_ID)).singleElement()
+ .extracting(CloudRegionVO::getRegionId).isEqualTo(REGION);
+ }
+
+ @Test
+ void getCloudInstanceShouldTolerateOmittedSuccessFlagTest() {
+ GetInstanceResponse response = GetInstanceResponse.create().toBuilder()
+ .body(GetInstanceResponseBody.builder().data(GetInstanceResponseBody.Data.builder()
+ .instanceId("rmq-a").instanceName("A").regionId(REGION).build()).build()).build();
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(response);
+ assertThat(service.getCloudInstance(CREDENTIAL_ID, REGION, "rmq-a").getInstanceId()).isEqualTo("rmq-a");
+ }
+
+ @org.junit.jupiter.params.ParameterizedTest
+ @org.junit.jupiter.params.provider.ValueSource(booleans = {true, false})
+ void catalogReadsShouldRejectMissingBodiesConsistentlyTest(boolean nullResponse) {
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(nullResponse ? null
+ : ListRegionsResponse.create().toBuilder().build());
+ assertThatThrownBy(() -> service.listRegions(CREDENTIAL_ID))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(502);
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(nullResponse ? null
+ : ListInstancesResponse.create().toBuilder().build());
+ assertThatThrownBy(() -> service.listCloudInstances(CREDENTIAL_ID, REGION, null))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(502);
+ when(clientFactory.call(eq(CREDENTIAL_ID), eq(REGION), any())).thenReturn(nullResponse ? null
+ : GetInstanceResponse.create().toBuilder().build());
+ assertThatThrownBy(() -> service.getCloudInstance(CREDENTIAL_ID, REGION, "rmq-a"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(502);
+ }
+
}
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 70a0a42fa..865aa31f9 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
@@ -28,6 +28,10 @@ import com.aliyun.sdk.service.rocketmq20220801.models.GetTraceResponseBody;
import com.aliyun.sdk.service.rocketmq20220801.models.ListConsumerGroupsRequest;
import com.aliyun.sdk.service.rocketmq20220801.models.ListConsumerGroupsResponse;
import com.aliyun.sdk.service.rocketmq20220801.models.ListConsumerGroupsResponseBody;
+import com.aliyun.sdk.service.rocketmq20220801.models.ListConsumerGroupSubscriptionsResponse;
+import com.aliyun.sdk.service.rocketmq20220801.models.ListConsumerGroupSubscriptionsResponseBody;
+import com.aliyun.sdk.service.rocketmq20220801.models.ListTopicSubscriptionsResponse;
+import com.aliyun.sdk.service.rocketmq20220801.models.ListTopicSubscriptionsResponseBody;
import com.aliyun.sdk.service.rocketmq20220801.models.ListMessagesRequest;
import com.aliyun.sdk.service.rocketmq20220801.models.ListMessagesResponse;
import com.aliyun.sdk.service.rocketmq20220801.models.ListMessagesResponseBody;
@@ -169,6 +173,30 @@ class AliyunInstanceProviderTest {
.hasMessageContaining("send denied");
}
+ @Test
+ void topicReadsShouldTolerateOmittedSuccessFlagTest() {
+ stubInstance();
+ stubCallThrough();
+ ListTopicsResponse fixture = topicsResponse(topicRow("orders", "NORMAL"));
+ ListTopicsResponse response = ListTopicsResponse.create().toBuilder()
+ .body(ListTopicsResponseBody.builder().data(fixture.getBody().getData()).build()).build();
+ when(asyncClient.listTopics(any())).thenReturn(CompletableFuture.completedFuture(response));
+ assertThat(provider.listTopics(STUDIO_INSTANCE_ID, null, null)).singleElement()
+ .extracting(TopicVO::getName).isEqualTo("orders");
+ assertThat(provider.countTopics(STUDIO_INSTANCE_ID)).isEqualTo(1);
+ }
+
+ @org.junit.jupiter.params.ParameterizedTest
+ @org.junit.jupiter.params.provider.ValueSource(booleans = {false, true})
+ void topicReadsShouldRejectMissingResponseBodyTest(boolean nullResponse) {
+ stubInstance();
+ stubCallThrough();
+ ListTopicsResponse response = nullResponse ? null : ListTopicsResponse.create().toBuilder().build();
+ when(asyncClient.listTopics(any())).thenReturn(CompletableFuture.completedFuture(response));
+ assertThatThrownBy(() -> provider.countTopics(STUDIO_INSTANCE_ID))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(502);
+ }
+
@Test
void consumeMessageDirectlyShouldCallAliyunVerifyApiTest() {
stubInstance();
@@ -348,7 +376,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
ListConsumerGroupsResponse response = ListConsumerGroupsResponse.create().toBuilder()
.statusCode(200)
- .body(ListConsumerGroupsResponseBody.builder()
+ .body(ListConsumerGroupsResponseBody.builder().success(true)
.data(ListConsumerGroupsResponseBody.Data.builder()
.list(java.util.Arrays.asList(null,
ListConsumerGroupsResponseBody.List.builder()
@@ -413,7 +441,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
ListConsumerGroupsResponse response = ListConsumerGroupsResponse.create().toBuilder()
.statusCode(200)
- .body(ListConsumerGroupsResponseBody.builder()
+ .body(ListConsumerGroupsResponseBody.builder().success(true)
.data(ListConsumerGroupsResponseBody.Data.builder()
.list(java.util.Arrays.asList(ListConsumerGroupsResponseBody.List.builder()
.consumerGroupId("GID_plain")
@@ -510,7 +538,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
GetConsumerGroupLagResponse response = GetConsumerGroupLagResponse.create().toBuilder()
.statusCode(200)
- .body(GetConsumerGroupLagResponseBody.builder()
+ .body(GetConsumerGroupLagResponseBody.builder().success(true)
.data(GetConsumerGroupLagResponseBody.Data.builder()
.consumerGroupId("GID_test")
.topicLagMap(Map.of("topic-a",
@@ -543,7 +571,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
GetConsumerGroupLagResponse response = GetConsumerGroupLagResponse.create().toBuilder()
.statusCode(200)
- .body(GetConsumerGroupLagResponseBody.builder()
+ .body(GetConsumerGroupLagResponseBody.builder().success(true)
.data(GetConsumerGroupLagResponseBody.Data.builder()
.consumerGroupId("GID_test")
.totalLag(GetConsumerGroupLagResponseBody.TotalLag.builder()
@@ -569,7 +597,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
GetConsumerGroupLagResponse response = GetConsumerGroupLagResponse.create().toBuilder()
.statusCode(200)
- .body(GetConsumerGroupLagResponseBody.builder()
+ .body(GetConsumerGroupLagResponseBody.builder().success(true)
.data(GetConsumerGroupLagResponseBody.Data.builder()
.consumerGroupId("GID_test")
.topicLagMap(Map.of("topic-a",
@@ -601,7 +629,7 @@ class AliyunInstanceProviderTest {
String encodedBody = Base64.getEncoder().encodeToString("hello".getBytes(StandardCharsets.UTF_8));
ListMessagesResponse response = ListMessagesResponse.create().toBuilder()
.statusCode(200)
- .body(ListMessagesResponseBody.builder()
+ .body(ListMessagesResponseBody.builder().success(true)
.data(ListMessagesResponseBody.Data.builder()
.list(java.util.Arrays.asList(
null,
@@ -816,7 +844,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
GetTraceResponse response = GetTraceResponse.create().toBuilder()
.statusCode(200)
- .body(GetTraceResponseBody.builder()
+ .body(GetTraceResponseBody.builder().success(true)
.data(GetTraceResponseBody.Data.builder()
.producerInfo(GetTraceResponseBody.ProducerInfo.builder()
.records(List.of(GetTraceResponseBody.ProducerInfoRecords.builder()
@@ -880,7 +908,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
GetTraceResponse response = GetTraceResponse.create().toBuilder()
.statusCode(200)
- .body(GetTraceResponseBody.builder()
+ .body(GetTraceResponseBody.builder().success(true)
.data(GetTraceResponseBody.Data.builder()
.consumerInfos(List.of(GetTraceResponseBody.ConsumerInfos.builder()
.consumerGroupId("GID_test")
@@ -915,7 +943,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
GetTraceResponse response = GetTraceResponse.create().toBuilder()
.statusCode(200)
- .body(GetTraceResponseBody.builder().data(null).build())
+ .body(GetTraceResponseBody.builder().success(true).data(null).build())
.build();
when(asyncClient.getTrace(any())).thenReturn(CompletableFuture.completedFuture(response));
@@ -926,7 +954,7 @@ class AliyunInstanceProviderTest {
}
@Test
- void getMessageTraceShouldReturnEmptyTraceWhenAliyunBodyIsNullTest() {
+ void getMessageTraceShouldRejectMissingAliyunBodyTest() {
stubInstance();
stubCallThrough();
GetTraceResponse response = GetTraceResponse.create().toBuilder()
@@ -935,22 +963,18 @@ class AliyunInstanceProviderTest {
.build();
when(asyncClient.getTrace(any())).thenReturn(CompletableFuture.completedFuture(response));
- TraceRecordVO trace = provider.getMessageTrace(STUDIO_INSTANCE_ID, "msg-without-body", "orders");
-
- assertThat(trace.getNodes()).isEmpty();
- assertThat(trace.getConsumerStatus()).isEmpty();
+ assertThatThrownBy(() -> provider.getMessageTrace(STUDIO_INSTANCE_ID, "msg-without-body", "orders"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(502);
}
@Test
- void getMessageTraceShouldReturnEmptyTraceWhenAliyunResponseIsNullTest() {
+ void getMessageTraceShouldRejectMissingAliyunResponseTest() {
stubInstance();
stubCallThrough();
when(asyncClient.getTrace(any())).thenReturn(CompletableFuture.completedFuture(null));
- TraceRecordVO trace = provider.getMessageTrace(STUDIO_INSTANCE_ID, "msg-without-response", "orders");
-
- assertThat(trace.getNodes()).isEmpty();
- assertThat(trace.getConsumerStatus()).isEmpty();
+ assertThatThrownBy(() -> provider.getMessageTrace(STUDIO_INSTANCE_ID, "msg-without-response", "orders"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(502);
}
@Test
@@ -981,6 +1005,123 @@ class AliyunInstanceProviderTest {
.isEqualTo(400);
}
+ @Test
+ void countTopicsShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.listTopics(any())).thenReturn(CompletableFuture.completedFuture(
+ ListTopicsResponse.create().toBuilder().statusCode(200)
+ .body(ListTopicsResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.countTopics(STUDIO_INSTANCE_ID))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void listTopicsShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.listTopics(any())).thenReturn(CompletableFuture.completedFuture(
+ ListTopicsResponse.create().toBuilder().statusCode(200)
+ .body(ListTopicsResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.listTopics(STUDIO_INSTANCE_ID, null, null))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void countGroupsShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.listConsumerGroups(any())).thenReturn(CompletableFuture.completedFuture(
+ ListConsumerGroupsResponse.create().toBuilder().statusCode(200)
+ .body(ListConsumerGroupsResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.countGroups(STUDIO_INSTANCE_ID))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void listConsumerGroupsShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.listConsumerGroups(any())).thenReturn(CompletableFuture.completedFuture(
+ ListConsumerGroupsResponse.create().toBuilder().statusCode(200)
+ .body(ListConsumerGroupsResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.listConsumerGroups(STUDIO_INSTANCE_ID, null))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void getTopicConsumersShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.listTopicSubscriptions(any())).thenReturn(CompletableFuture.completedFuture(
+ ListTopicSubscriptionsResponse.create().toBuilder().statusCode(200)
+ .body(ListTopicSubscriptionsResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.getTopicConsumers(STUDIO_INSTANCE_ID, "orders"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void getGroupProgressShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.getConsumerGroupLag(any())).thenReturn(CompletableFuture.completedFuture(
+ GetConsumerGroupLagResponse.create().toBuilder().statusCode(200)
+ .body(GetConsumerGroupLagResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.getGroupProgress(STUDIO_INSTANCE_ID, "GID_orders"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void getGroupSubscriptionsShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.listConsumerGroupSubscriptions(any())).thenReturn(CompletableFuture.completedFuture(
+ ListConsumerGroupSubscriptionsResponse.create().toBuilder().statusCode(200)
+ .body(ListConsumerGroupSubscriptionsResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.getGroupSubscriptions(STUDIO_INSTANCE_ID, "GID_orders"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void queryMessagesShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.listMessages(any())).thenReturn(CompletableFuture.completedFuture(
+ ListMessagesResponse.create().toBuilder().statusCode(200)
+ .body(ListMessagesResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.queryMessages(STUDIO_INSTANCE_ID, "orders", null, null, null, null, null))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
+ @Test
+ void getMessageTraceShouldRejectUnsuccessfulBusinessResponseTest() {
+ stubInstance();
+ stubCallThrough();
+ when(asyncClient.getTrace(any())).thenReturn(CompletableFuture.completedFuture(
+ GetTraceResponse.create().toBuilder().statusCode(200)
+ .body(GetTraceResponseBody.builder().success(false)
+ .code("OperationDenied").message("denied").build()).build()));
+
+ assertThatThrownBy(() -> provider.getMessageTrace(STUDIO_INSTANCE_ID, "msg-1", "orders"))
+ .isInstanceOf(BusinessException.class).extracting("code").isEqualTo(422);
+ }
+
private void stubInstance() {
InstanceVO instance = InstanceVO.builder()
.name("aliyun-prod")
@@ -1017,7 +1158,7 @@ class AliyunInstanceProviderTest {
ListTopicsResponseBody.List... rows) {
return ListTopicsResponse.create().toBuilder()
.statusCode(200)
- .body(ListTopicsResponseBody.builder()
+ .body(ListTopicsResponseBody.builder().success(true)
.data(ListTopicsResponseBody.Data.builder()
.list(java.util.Arrays.asList(rows))
.pageNumber(pageNumber)
@@ -1046,7 +1187,7 @@ class AliyunInstanceProviderTest {
.toList();
return ListMessagesResponse.create().toBuilder()
.statusCode(200)
- .body(ListMessagesResponseBody.builder()
+ .body(ListMessagesResponseBody.builder().success(true)
.data(ListMessagesResponseBody.Data.builder()
.list(rows)
.pageNumber((long) pageNumber)
@@ -1063,7 +1204,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
ListTopicsResponse response = ListTopicsResponse.create().toBuilder()
.statusCode(200)
- .body(ListTopicsResponseBody.builder()
+ .body(ListTopicsResponseBody.builder().success(true)
.data(ListTopicsResponseBody.Data.builder()
.list(List.of(topicRow("topic-a", "NORMAL")))
.pageNumber(1L)
@@ -1088,7 +1229,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
ListConsumerGroupsResponse response = ListConsumerGroupsResponse.create().toBuilder()
.statusCode(200)
- .body(ListConsumerGroupsResponseBody.builder()
+ .body(ListConsumerGroupsResponseBody.builder().success(true)
.data(ListConsumerGroupsResponseBody.Data.builder()
.list(List.of(ListConsumerGroupsResponseBody.List.builder()
.consumerGroupId("GID_one")
@@ -1116,7 +1257,7 @@ class AliyunInstanceProviderTest {
stubCallThrough();
ListTopicsResponse missingTotal = ListTopicsResponse.create().toBuilder()
.statusCode(200)
- .body(ListTopicsResponseBody.builder()
+ .body(ListTopicsResponseBody.builder().success(true)
.data(ListTopicsResponseBody.Data.builder()
.list(List.of(topicRow("topic-a", "NORMAL")))
.pageNumber(1L)
@@ -1158,7 +1299,7 @@ class AliyunInstanceProviderTest {
private static ListConsumerGroupsResponse groupsResponse(Long totalCount, String... groupIds) {
return ListConsumerGroupsResponse.create().toBuilder()
.statusCode(200)
- .body(ListConsumerGroupsResponseBody.builder()
+ .body(ListConsumerGroupsResponseBody.builder().success(true)
.data(ListConsumerGroupsResponseBody.Data.builder()
.list(java.util.Arrays.stream(groupIds)
.map(groupId -> ListConsumerGroupsResponseBody.List.builder()