This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 560db583be fix: avoid NPE when mock rule omits httpStatusCode (#6881)
(#6953)
560db583be is described below
commit 560db583beb7c20093c9989a9aa9426db2e04fbd
Author: Southern <[email protected]>
AuthorDate: Sat Sep 5 18:05:55 2026 +0800
fix: avoid NPE when mock rule omits httpStatusCode (#6881) (#6953)
MockHandle.httpStatusCode has no default, so when a mock rule's handle
JSON omits or nulls it, Gson leaves the field null and MockPlugin passes
it to HttpStatus.valueOf(int), whose auto-unboxing throws NPE and makes
every matching request return 500.
Fall back to 200 (OK) via Optional.ofNullable(...).orElse(200) in
MockPlugin.doExecute, so the mock response is returned instead.
Add MockPluginTest covering: null httpStatusCode -> 200 mock response,
an explicitly specified status code being honored, and a missing cached
handle falling through to the plugin chain.
Co-authored-by: aias00 <[email protected]>
---
.../org/apache/shenyu/plugin/mock/MockPlugin.java | 3 +-
.../apache/shenyu/plugin/mock/MockPluginTest.java | 143 +++++++++++++++++++++
2 files changed, 145 insertions(+), 1 deletion(-)
diff --git
a/shenyu-plugin/shenyu-plugin-mock/src/main/java/org/apache/shenyu/plugin/mock/MockPlugin.java
b/shenyu-plugin/shenyu-plugin-mock/src/main/java/org/apache/shenyu/plugin/mock/MockPlugin.java
index ce886acaa8..9a8eada8a8 100644
---
a/shenyu-plugin/shenyu-plugin-mock/src/main/java/org/apache/shenyu/plugin/mock/MockPlugin.java
+++
b/shenyu-plugin/shenyu-plugin-mock/src/main/java/org/apache/shenyu/plugin/mock/MockPlugin.java
@@ -38,6 +38,7 @@ import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
+import java.util.Optional;
/**
* MockPlugin.
@@ -53,7 +54,7 @@ public class MockPlugin extends AbstractShenyuPlugin {
return chain.execute(exchange);
}
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
-
exchange.getResponse().setStatusCode(HttpStatus.valueOf(mockHandle.getHttpStatusCode()));
+
exchange.getResponse().setStatusCode(HttpStatus.valueOf(Optional.ofNullable(mockHandle.getHttpStatusCode()).orElse(200)));
return DataBufferUtils.join(exchange.getRequest().getBody())
.switchIfEmpty(Mono.just(DefaultDataBufferFactory.sharedInstance.allocateBuffer(0)))
diff --git
a/shenyu-plugin/shenyu-plugin-mock/src/test/java/org/apache/shenyu/plugin/mock/MockPluginTest.java
b/shenyu-plugin/shenyu-plugin-mock/src/test/java/org/apache/shenyu/plugin/mock/MockPluginTest.java
new file mode 100644
index 0000000000..06f0e89d8d
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-mock/src/test/java/org/apache/shenyu/plugin/mock/MockPluginTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.shenyu.plugin.mock;
+
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.dto.convert.rule.MockHandle;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.base.utils.CacheKeyUtils;
+import org.apache.shenyu.plugin.mock.handler.MockPluginHandler;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * test case for {@link MockPlugin}.
+ */
+@ExtendWith(MockitoExtension.class)
+public final class MockPluginTest {
+
+ private static final String SELECTOR_ID = "mock-selector";
+
+ private static final String RULE_ID = "mock-rule";
+
+ private final MockPlugin mockPlugin = new MockPlugin();
+
+ @Mock
+ private ShenyuPluginChain chain;
+
+ private MockServerWebExchange exchange;
+
+ @BeforeEach
+ public void setUp() {
+ exchange =
MockServerWebExchange.from(MockServerHttpRequest.get("localhost").build());
+
MockPluginHandler.CACHED_HANDLE.get().removeHandle(CacheKeyUtils.INST.getKey(SELECTOR_ID,
RULE_ID));
+ }
+
+ @AfterEach
+ public void clean() {
+
MockPluginHandler.CACHED_HANDLE.get().removeHandle(CacheKeyUtils.INST.getKey(SELECTOR_ID,
RULE_ID));
+ }
+
+ /**
+ * issue #6881: a mock rule whose handle omits {@code httpStatusCode} must
not NPE
+ * and should fall back to a 200 response with the mock content.
+ */
+ @Test
+ public void testDoExecuteWithNullHttpStatusCode() {
+ RuleData ruleData = buildRuleData();
+ MockHandle mockHandle = new MockHandle();
+ mockHandle.setHttpStatusCode(null);
+ mockHandle.setResponseContent("{\"user\":\"test\"}");
+
MockPluginHandler.CACHED_HANDLE.get().cachedHandle(CacheKeyUtils.INST.getKey(ruleData),
mockHandle);
+
+ StepVerifier.create(mockPlugin.doExecute(exchange, chain,
mock(SelectorData.class), ruleData))
+ .expectSubscription()
+ .verifyComplete();
+
+ assertThat(exchange.getResponse().getStatusCode(), is(HttpStatus.OK));
+ assertThat(exchange.getResponse().getBodyAsString().block(),
is("{\"user\":\"test\"}"));
+ verify(chain, never()).execute(any());
+ }
+
+ @Test
+ public void testDoExecuteWithSpecifiedHttpStatusCode() {
+ RuleData ruleData = buildRuleData();
+ MockHandle mockHandle = new MockHandle();
+ mockHandle.setHttpStatusCode(HttpStatus.NOT_FOUND.value());
+ mockHandle.setResponseContent("not found");
+
MockPluginHandler.CACHED_HANDLE.get().cachedHandle(CacheKeyUtils.INST.getKey(ruleData),
mockHandle);
+
+ StepVerifier.create(mockPlugin.doExecute(exchange, chain,
mock(SelectorData.class), ruleData))
+ .expectSubscription()
+ .verifyComplete();
+
+ assertThat(exchange.getResponse().getStatusCode(),
is(HttpStatus.NOT_FOUND));
+ assertThat(exchange.getResponse().getBodyAsString().block(), is("not
found"));
+ verify(chain, never()).execute(any());
+ }
+
+ @Test
+ public void testDoExecuteWithoutCachedHandle() {
+ RuleData ruleData = buildRuleData();
+ when(chain.execute(any())).thenReturn(Mono.empty());
+
+ StepVerifier.create(mockPlugin.doExecute(exchange, chain,
mock(SelectorData.class), ruleData))
+ .expectSubscription()
+ .verifyComplete();
+
+ verify(chain).execute(any());
+ }
+
+ @Test
+ public void testGetOrder() {
+ final int result = mockPlugin.getOrder();
+ assertThat(PluginEnum.MOCK.getCode(), Matchers.is(result));
+ }
+
+ @Test
+ public void testNamed() {
+ final String result = mockPlugin.named();
+ assertThat(PluginEnum.MOCK.getName(), Matchers.is(result));
+ }
+
+ private RuleData buildRuleData() {
+ return new RuleData()
+ .setSelectorId(SELECTOR_ID)
+ .setId(RULE_ID);
+ }
+}