Skip to content

Commit

Permalink
test: Update JsonRpcExecutorHandler and test for timeout handling and…
Browse files Browse the repository at this point in the history
… 408 status

- Implement 408 (Request Timeout) status code on timeout in JsonRpcExecutorHandler
- Update JsonRpcExecutorHandlerTest to verify 408 status and timer cancellation
- Improve error handling and test coverage for timeout scenarios
- Apply spotless formatting changes

This change ensures that a 408 status code is returned on timeout and
that timer cancellation is properly implemented and tested, improving
error handling for the JsonRpcExecutorHandler. Spotless formatting
has been applied to maintain code style consistency.

Issue: hyperledger#5589
PR: hyperledger#7469
Signed-off-by: Ade Lucas <ade.lucas@consensys.net>
  • Loading branch information
cloudspores committed Aug 22, 2024
1 parent 1a5b030 commit 1fdad76
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ private static void cancelTimer(final RoutingContext ctx) {
}
}

private static void handleErrorAndEndResponse(final RoutingContext ctx, final Object id, final RpcErrorType errorType) {
private static void handleErrorAndEndResponse(
final RoutingContext ctx, final Object id, final RpcErrorType errorType) {
if (!ctx.response().ended()) {
handleJsonRpcError(ctx, id, errorType);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.handlers;

import static org.mockito.ArgumentMatchers.any;
Expand All @@ -10,85 +24,87 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.context.ContextKey;
import org.hyperledger.besu.ethereum.api.jsonrpc.execution.JsonRpcExecutor;

import io.netty.handler.codec.http.HttpResponseStatus;
import io.opentelemetry.api.trace.Tracer;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.execution.JsonRpcExecutor;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.context.ContextKey;
import io.opentelemetry.api.trace.Tracer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.io.IOException;

class JsonRpcExecutorHandlerTest {

private JsonRpcExecutor mockExecutor;
private Tracer mockTracer;
private JsonRpcConfiguration mockConfig;
private RoutingContext mockContext;
private Vertx mockVertx;
private HttpServerResponse mockResponse;

@BeforeEach
void setUp() {
mockExecutor = mock(JsonRpcExecutor.class);
mockTracer = mock(Tracer.class);
mockConfig = mock(JsonRpcConfiguration.class);
mockContext = mock(RoutingContext.class);
mockVertx = mock(Vertx.class);
mockResponse = mock(HttpServerResponse.class);

when(mockContext.vertx()).thenReturn(mockVertx);
when(mockContext.response()).thenReturn(mockResponse);
when(mockResponse.ended()).thenReturn(false);
when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse);
}

@Test
void testTimeoutHandling() {
// Arrange
Handler<RoutingContext> handler = JsonRpcExecutorHandler.handler(mockExecutor, mockTracer, mockConfig);
ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
@SuppressWarnings("unchecked")
ArgumentCaptor<Handler<Long>> timerHandlerCaptor = ArgumentCaptor.forClass(Handler.class);

when(mockContext.get(eq(ContextKey.REQUEST_BODY_AS_JSON_OBJECT.name()))).thenReturn("{}");
when(mockVertx.setTimer(delayCaptor.capture(), timerHandlerCaptor.capture())).thenReturn(1L);
when(mockContext.get("timerId")).thenReturn(1L);

// Act
handler.handle(mockContext);

// Assert
verify(mockVertx).setTimer(eq(30000L), any());

// Simulate timeout
timerHandlerCaptor.getValue().handle(1L);

// Verify timeout handling
verify(mockResponse, times(1)).setStatusCode(eq(HttpResponseStatus.REQUEST_TIMEOUT.code())); // Expect 408 Request Timeout
verify(mockResponse, times(1)).end(contains("Timeout expired"));
verify(mockVertx, times(1)).cancelTimer(1L);
}

@Test
void testCancelTimerOnSuccessfulExecution() throws IOException {
// Arrange
Handler<RoutingContext> handler = JsonRpcExecutorHandler.handler(mockExecutor, mockTracer, mockConfig);
when(mockContext.get(eq(ContextKey.REQUEST_BODY_AS_JSON_OBJECT.name()))).thenReturn("{}");
when(mockVertx.setTimer(anyLong(), any())).thenReturn(1L);
when(mockContext.get("timerId")).thenReturn(1L);

// Act
handler.handle(mockContext);

// Assert
verify(mockVertx).setTimer(anyLong(), any());
verify(mockVertx).cancelTimer(1L);
}
}
private JsonRpcExecutor mockExecutor;
private Tracer mockTracer;
private JsonRpcConfiguration mockConfig;
private RoutingContext mockContext;
private Vertx mockVertx;
private HttpServerResponse mockResponse;

@BeforeEach
void setUp() {
mockExecutor = mock(JsonRpcExecutor.class);
mockTracer = mock(Tracer.class);
mockConfig = mock(JsonRpcConfiguration.class);
mockContext = mock(RoutingContext.class);
mockVertx = mock(Vertx.class);
mockResponse = mock(HttpServerResponse.class);

when(mockContext.vertx()).thenReturn(mockVertx);
when(mockContext.response()).thenReturn(mockResponse);
when(mockResponse.ended()).thenReturn(false);
when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse);
}

@Test
void testTimeoutHandling() {
// Arrange
Handler<RoutingContext> handler =
JsonRpcExecutorHandler.handler(mockExecutor, mockTracer, mockConfig);
ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
@SuppressWarnings("unchecked")
ArgumentCaptor<Handler<Long>> timerHandlerCaptor = ArgumentCaptor.forClass(Handler.class);

when(mockContext.get(eq(ContextKey.REQUEST_BODY_AS_JSON_OBJECT.name()))).thenReturn("{}");
when(mockVertx.setTimer(delayCaptor.capture(), timerHandlerCaptor.capture())).thenReturn(1L);
when(mockContext.get("timerId")).thenReturn(1L);

// Act
handler.handle(mockContext);

// Assert
verify(mockVertx).setTimer(eq(30000L), any());

// Simulate timeout
timerHandlerCaptor.getValue().handle(1L);

// Verify timeout handling
verify(mockResponse, times(1))
.setStatusCode(eq(HttpResponseStatus.REQUEST_TIMEOUT.code())); // Expect 408 Request Timeout
verify(mockResponse, times(1)).end(contains("Timeout expired"));
verify(mockVertx, times(1)).cancelTimer(1L);
}

@Test
void testCancelTimerOnSuccessfulExecution() {
// Arrange
Handler<RoutingContext> handler =
JsonRpcExecutorHandler.handler(mockExecutor, mockTracer, mockConfig);
when(mockContext.get(eq(ContextKey.REQUEST_BODY_AS_JSON_OBJECT.name()))).thenReturn("{}");
when(mockVertx.setTimer(anyLong(), any())).thenReturn(1L);
when(mockContext.get("timerId")).thenReturn(1L);

// Act
handler.handle(mockContext);

// Assert
verify(mockVertx).setTimer(anyLong(), any());
verify(mockVertx).cancelTimer(1L);
}
}

0 comments on commit 1fdad76

Please sign in to comment.