@Override public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { HttpProcessingState state = getStateAndCreateIfNeeded(ctx, cause); finalizeChannelPipeline(ctx, null, state, cause); return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT; }
@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof LastOutboundMessage) { Exception ex = new Exception("Manually created exception to be used for diagnostic stack trace"); HttpProcessingState state = getStateAndCreateIfNeeded(ctx, ex); finalizeChannelPipeline(ctx, msg, state, ex); } return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT; }
@Override public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { HttpProcessingState state = getStateAndCreateIfNeeded(ctx, cause); finalizeChannelPipeline(ctx, null, state, cause); return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT; }
@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof LastOutboundMessage) { Exception ex = new Exception("Manually created exception to be used for diagnostic stack trace"); HttpProcessingState state = getStateAndCreateIfNeeded(ctx, ex); finalizeChannelPipeline(ctx, msg, state, ex); } return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT; }
@Test public void doChannelRead_do_nothing_and_return_DO_NOT_FIRE_CONTINUE_EVENT_if_msg_is_not_LastOutboundMessage() throws Exception { // given ChannelPipelineFinalizerHandler handlerSpy = spy(handler); Object msg = new Object(); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then verify(handlerSpy, times(0)).finalizeChannelPipeline(eq(ctxMock), eq(msg), eq(state), any(Throwable.class)); assertThat(result, is(PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT)); }
@Test public void doChannelRead_gets_state_from_getStateAndCreateIfNeeded_method_and_then_calls_finalizeChannelPipeline_and_then_returns_DO_NOT_FIRE_CONTINUE_EVENT_if_msg_is_LastOutboundMessage() throws Exception { // given ChannelPipelineFinalizerHandler handlerSpy = spy(handler); LastOutboundMessage msg = mock(LastOutboundMessage.class); state.setResponseWriterFinalChunkChannelFuture(mock(ChannelFuture.class)); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then verify(handlerSpy).finalizeChannelPipeline(eq(ctxMock), eq(msg), eq(state), any(Throwable.class)); assertThat(result, is(PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT)); }
@Test public void finalizeChannelPipeline_should_add_idle_channel_timeout_handler_first_in_pipeline_if_workerChannelIdleTimeoutMillis_is_greater_than_0() throws JsonProcessingException { // given LastOutboundMessage msg = mock(LastOutboundMessage.class); // when handler.finalizeChannelPipeline(ctxMock, msg, state, null); // then ArgumentCaptor<ChannelHandler> idleHandlerArgCaptor = ArgumentCaptor.forClass(ChannelHandler.class); verify(pipelineMock).addFirst(eq(IDLE_CHANNEL_TIMEOUT_HANDLER_NAME), idleHandlerArgCaptor.capture()); ChannelHandler handlerRegistered = idleHandlerArgCaptor.getValue(); assertThat(handlerRegistered, instanceOf(IdleChannelTimeoutHandler.class)); IdleChannelTimeoutHandler idleHandler = (IdleChannelTimeoutHandler)handlerRegistered; long idleValue = (long) Whitebox.getInternalState(idleHandler, "idleTimeoutMillis"); assertThat(idleValue, is(workerChannelIdleTimeoutMillis)); }
@Test public void doExceptionCaught_gets_state_from_getStateAndCreateIfNeeded_method_and_then_calls_finalizeChannelPipeline_and_then_returns_DO_NOT_FIRE_CONTINUE_EVENT() throws Exception { // given ChannelPipelineFinalizerHandler handlerSpy = spy(handler); Exception cause = new Exception("intentional test exception"); state.setResponseWriterFinalChunkChannelFuture(mock(ChannelFuture.class)); // when PipelineContinuationBehavior result = handlerSpy.doExceptionCaught(ctxMock, cause); // then verify(handlerSpy).getStateAndCreateIfNeeded(ctxMock, cause); verify(handlerSpy).finalizeChannelPipeline(ctxMock, null, state, cause); assertThat(result, is(PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT)); }
@Test public void finalizeChannelPipeline_should_send_event_to_metricsListener_for_failure_response_and_flush_context() throws Exception { // given ChannelFuture responseWriterChannelFuture = mock(ChannelFuture.class); state.setResponseWriterFinalChunkChannelFuture(responseWriterChannelFuture); HttpProcessingState stateSpy = spy(state); doReturn(stateSpy).when(stateAttributeMock).get(); ChannelFuture responseWriteFutureResult = mock(ChannelFuture.class); doReturn(false).when(responseWriteFutureResult).isSuccess(); Assertions.assertThat(stateSpy.isRequestMetricsRecordedOrScheduled()).isFalse(); // when handler.finalizeChannelPipeline(ctxMock, null, stateSpy, null); // then ArgumentCaptor<GenericFutureListener> channelFutureListenerArgumentCaptor = ArgumentCaptor.forClass(GenericFutureListener.class); verify(responseWriterChannelFuture).addListener(channelFutureListenerArgumentCaptor.capture()); GenericFutureListener futureListener = channelFutureListenerArgumentCaptor.getValue(); assertThat(futureListener, notNullValue()); futureListener.operationComplete(responseWriteFutureResult); verify(metricsListenerMock).onEvent(ServerMetricsEvent.RESPONSE_WRITE_FAILED, null); verify(ctxMock).flush(); Assertions.assertThat(stateSpy.isRequestMetricsRecordedOrScheduled()).isTrue(); }
@Test public void finalizeChannelPipeline_should_send_event_to_metricsListener_for_successful_response_and_flush_context() throws Exception { // given ChannelFuture responseWriterChannelFuture = mock(ChannelFuture.class); state.setResponseWriterFinalChunkChannelFuture(responseWriterChannelFuture); HttpProcessingState stateSpy = spy(state); doReturn(stateSpy).when(stateAttributeMock).get(); ChannelFuture responseWriteFutureResult = mock(ChannelFuture.class); doReturn(true).when(responseWriteFutureResult).isSuccess(); Assertions.assertThat(stateSpy.isRequestMetricsRecordedOrScheduled()).isFalse(); // when handler.finalizeChannelPipeline(ctxMock, null, stateSpy, null); // then ArgumentCaptor<GenericFutureListener> channelFutureListenerArgumentCaptor = ArgumentCaptor.forClass(GenericFutureListener.class); verify(responseWriterChannelFuture).addListener(channelFutureListenerArgumentCaptor.capture()); GenericFutureListener futureListener = channelFutureListenerArgumentCaptor.getValue(); assertThat(futureListener, notNullValue()); futureListener.operationComplete(responseWriteFutureResult); verify(metricsListenerMock).onEvent(eq(ServerMetricsEvent.RESPONSE_SENT), any(HttpProcessingState.class)); verify(ctxMock).flush(); Assertions.assertThat(stateSpy.isRequestMetricsRecordedOrScheduled()).isTrue(); }
@Test public void finalizeChannelPipeline_should_send_error_response_if_state_indicates_no_response_already_sent() throws JsonProcessingException { // given state.setResponseWriterFinalChunkChannelFuture(null); HttpProcessingState stateSpy = spy(state); doReturn(stateSpy).when(stateAttributeMock).get(); doReturn(false).when(responseInfoMock).isResponseSendingStarted(); Object msg = new Object(); Throwable cause = new Exception("intentional test exception"); RequestInfo<?> requestInfoMock = mock(RequestInfo.class); ResponseInfo<ErrorResponseBody> errorResponseMock = mock(ResponseInfo.class); doReturn(requestInfoMock).when(exceptionHandlingHandlerMock).getRequestInfo(stateSpy, msg); doReturn(errorResponseMock).when(exceptionHandlingHandlerMock).processUnhandledError(eq(stateSpy), eq(msg), any(Throwable.class)); // when handler.finalizeChannelPipeline(ctxMock, msg, stateSpy, cause); // then verify(responseSenderMock).sendErrorResponse(ctxMock, requestInfoMock, errorResponseMock); verify(metricsListenerMock).onEvent(ServerMetricsEvent.RESPONSE_WRITE_FAILED, stateSpy); verify(ctxMock).flush(); }
@DataProvider(value = { "0 | false", "-42 | false", "42 | true" }, splitBy = "\\|") @Test public void finalizeChannelPipeline_does_not_add_idle_channel_timeout_handler_to_pipeline_if_workerChannelIdleTimeoutMillis_is_not_greater_than_0_or_idle_timeout_handler_already_exists( long timeoutVal, boolean idleTimeoutHandlerAlreadyExists ) throws JsonProcessingException { // given Whitebox.setInternalState(handler, "workerChannelIdleTimeoutMillis", timeoutVal); LastOutboundMessage msg = mock(LastOutboundMessage.class); if (idleTimeoutHandlerAlreadyExists) doReturn(mock(ChannelHandler.class)).when(pipelineMock).get(IDLE_CHANNEL_TIMEOUT_HANDLER_NAME); // when handler.finalizeChannelPipeline(ctxMock, msg, state, null); // then verify(pipelineMock, never()).addFirst(anyString(), anyObject()); }
@DataProvider(value = { "false | true | false | true", "true | true | false | false", "false | false | false | false", "false | true | true | false", }, splitBy = "\\|") @Test public void finalizeChannelPipeline_should_close_channel_or_not_depending_on_error_and_state_of_request( boolean errorIsNull, boolean responseSendingStarted, boolean responseSendingCompleted, boolean channelCloseIsExpected ) throws JsonProcessingException { // given Throwable error = (errorIsNull) ? null : new RuntimeException("kaboom"); doReturn(responseSendingStarted).when(responseInfoMock).isResponseSendingStarted(); doReturn(responseSendingCompleted).when(responseInfoMock).isResponseSendingLastChunkSent(); if (responseSendingCompleted) { state.setResponseWriterFinalChunkChannelFuture(mock(ChannelFuture.class)); } // when handler.finalizeChannelPipeline(ctxMock, null, state, error); // then if (channelCloseIsExpected) verify(channelMock).close(); else verify(channelMock, never()).close(); }
@DataProvider(value = { "true | false", "false | true" }, splitBy = "\\|") @Test public void finalizeChannelPipeline_should_not_call_metricsListener_if_metricsListener_is_null_or_metrics_already_scheduled( boolean metricsListenerIsNull, boolean metricsAlreadyScheduled ) throws Exception { // given if (metricsListenerIsNull) Whitebox.setInternalState(handler, "metricsListener", null); state.setRequestMetricsRecordedOrScheduled(metricsAlreadyScheduled); // when handler.finalizeChannelPipeline(ctxMock, null, state, null); // then verifyZeroInteractions(metricsListenerMock); Assertions.assertThat(state.isRequestMetricsRecordedOrScheduled()).isEqualTo(metricsAlreadyScheduled); }