@Override public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // Exceptions should always execute filters. executeResponseFilters(ctx); return PipelineContinuationBehavior.CONTINUE; }
@Override public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // Exceptions should always execute filters. executeResponseFilters(ctx); return PipelineContinuationBehavior.CONTINUE; }
@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) { if (shouldHandleDoChannelReadMessage(msg)) { executeResponseFilters(ctx); } return PipelineContinuationBehavior.CONTINUE; }
@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) { if (shouldHandleDoChannelReadMessage(msg)) { executeResponseFilters(ctx); } return PipelineContinuationBehavior.CONTINUE; }
@Test public void doExceptionCaught_delegates_to_executeResponseFilters_and_returns_CONTINUE() throws Exception { // given Throwable ex = mock(Throwable.class); doNothing().when(handlerSpy).executeResponseFilters(any()); // when PipelineContinuationBehavior result = handlerSpy.doExceptionCaught(ctxMock, ex); // then assertThat(result).isEqualTo(CONTINUE); verify(handlerSpy).executeResponseFilters(ctxMock); }
@Test public void doChannelRead_does_nothing_and_returns_CONTINUE_if_msg_is_not_a_first_chunk() throws Exception { // given Object msg = new OutboundMessage() {}; // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then assertThat(result).isEqualTo(CONTINUE); verify(handlerSpy, never()).executeResponseFilters(any()); }
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void doChannelRead_delegates_to_executeResponseFilters_and_returns_CONTINUE_if_msg_is_first_chunk_of_response(boolean chunkedResponse) throws Exception { // given OutboundMessage msg = (chunkedResponse) ? mock(OutboundMessageSendHeadersChunkFromResponseInfo.class) : mock(LastOutboundMessageSendFullResponseInfo.class); doNothing().when(handlerSpy).executeResponseFilters(any()); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then assertThat(result).isEqualTo(CONTINUE); verify(handlerSpy).executeResponseFilters(ctxMock); }
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void executeResponseFilters_does_nothing_if_something_blows_up_before_filters_are_executed(boolean useChunkedResponse) throws Exception { // given ResponseInfo<?> responseInfoToUse = (useChunkedResponse) ? chunkedResponseInfoMock : fullResponseInfoMock; state.setResponseInfo(responseInfoToUse, origErrorForOrigResponseInfoMock); doThrow(new RuntimeException("kaboom")).when(ctxMock).channel(); // when handlerSpy.executeResponseFilters(ctxMock); // then reversedFiltersList.forEach(filter -> verify(filter, never()).filterResponse(any(), any(), any())); }
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void executeResponseFilters_does_nothing_if_the_first_chunk_of_the_response_is_already_sent(boolean useChunkedResponse) throws Exception { // given ResponseInfo<?> responseInfoToUse = (useChunkedResponse) ? chunkedResponseInfoMock : fullResponseInfoMock; state.setResponseInfo(responseInfoToUse, origErrorForOrigResponseInfoMock); doReturn(true).when(responseInfoToUse).isResponseSendingStarted(); // when handlerSpy.executeResponseFilters(ctxMock); // then reversedFiltersList.forEach(filter -> verify(filter, never()).filterResponse(any(), any(), any())); }
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void executeResponseFilters_executes_all_filters_in_reverse_order_and_honors_result_and_orig_error_if_filters_return_non_null_responseInfo(boolean useChunkedResponse) throws Exception { // given ResponseInfo<?> origResponseInfo = (useChunkedResponse) ? chunkedResponseInfoMock : fullResponseInfoMock; state.setResponseInfo(origResponseInfo, origErrorForOrigResponseInfoMock); Class<? extends ResponseInfo> responseInfoClassToUse = (useChunkedResponse) ? ChunkedResponseInfo.class : FullResponseInfo.class; ResponseInfo<?> secondFilterResult = mock(responseInfoClassToUse); ResponseInfo<?> firstFilterResult = mock(responseInfoClassToUse); doReturn(secondFilterResult).when(filter2Mock).filterResponse(any(), any(), any()); doReturn(firstFilterResult).when(filter1Mock).filterResponse(any(), any(), any()); // when handlerSpy.executeResponseFilters(ctxMock); // then // Verify reverse order execution - filter 2 should get the original responseInfo, and filter 1 should get filter 2's result. verify(filter2Mock).filterResponse(origResponseInfo, requestInfoMock, ctxMock); verify(filter1Mock).filterResponse(secondFilterResult, requestInfoMock, ctxMock); // The final state should have filter 1's responseInfo since it executed last, and the original error should // still be there. assertThat(state.getResponseInfo()).isSameAs(firstFilterResult); assertThat(state.getErrorThatTriggeredThisResponse()).isSameAs(origErrorForOrigResponseInfoMock); }
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void executeResponseFilters_ignores_filter_responseInfo_if_its_class_is_not_the_same_as_the_previously_used_responseInfo(boolean useChunkedResponse) throws Exception { // given ResponseInfo<?> origResponseInfo = (useChunkedResponse) ? chunkedResponseInfoMock : fullResponseInfoMock; state.setResponseInfo(origResponseInfo, origErrorForOrigResponseInfoMock); Class<? extends ResponseInfo> badResponseInfoClassToUse = (useChunkedResponse) ? FullResponseInfo.class : ChunkedResponseInfo.class; ResponseInfo<?> secondFilterResult = mock(badResponseInfoClassToUse); ResponseInfo<?> firstFilterResult = mock(badResponseInfoClassToUse); doReturn(secondFilterResult).when(filter2Mock).filterResponse(any(), any(), any()); doReturn(firstFilterResult).when(filter1Mock).filterResponse(any(), any(), any()); // when handlerSpy.executeResponseFilters(ctxMock); // then // Verify that both filters were called, but since they return an incompatible ResponseInfo class compared to the original state, // the original response info should be used each time. verify(filter2Mock).filterResponse(origResponseInfo, requestInfoMock, ctxMock); verify(filter1Mock).filterResponse(origResponseInfo, requestInfoMock, ctxMock); // The final state should have the original response info since none of the filters returned a compatible // ResponseInfo class, and the original error should still be there. assertThat(state.getResponseInfo()).isSameAs(origResponseInfo); assertThat(state.getErrorThatTriggeredThisResponse()).isSameAs(origErrorForOrigResponseInfoMock); }
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void executeResponseFilters_gracefully_handles_a_filter_throwing_an_exception_and_continues_processing_other_filters(boolean useChunkedResponse) throws Exception { // given ResponseInfo<?> origResponseInfo = (useChunkedResponse) ? chunkedResponseInfoMock : fullResponseInfoMock; state.setResponseInfo(origResponseInfo, origErrorForOrigResponseInfoMock); Class<? extends ResponseInfo> responseInfoClassToUse = (useChunkedResponse) ? ChunkedResponseInfo.class : FullResponseInfo.class; ResponseInfo<?> firstFilterResult = mock(responseInfoClassToUse); doThrow(new RuntimeException("kaboom")).when(filter2Mock).filterResponse(any(), any(), any()); doReturn(firstFilterResult).when(filter1Mock).filterResponse(any(), any(), any()); // when handlerSpy.executeResponseFilters(ctxMock); // then // Verify reverse order execution with the first filter executed (filter2Mock) blowing up - both filters should be executed, // and both should be called with the orig response info (since filter2Mock didn't return a valid responseInfo). verify(filter2Mock).filterResponse(origResponseInfo, requestInfoMock, ctxMock); verify(filter1Mock).filterResponse(origResponseInfo, requestInfoMock, ctxMock); // The final state should have filter 1's responseInfo since it executed last and executed successfully, and // the original error should still be there. assertThat(state.getResponseInfo()).isSameAs(firstFilterResult); assertThat(state.getErrorThatTriggeredThisResponse()).isSameAs(origErrorForOrigResponseInfoMock); }
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void executeResponseFilters_executes_all_filters_and_uses_original_response_and_orig_error_if_filters_return_null(boolean useChunkedResponse) throws Exception { // given ResponseInfo<?> responseInfoToUse = (useChunkedResponse) ? chunkedResponseInfoMock : fullResponseInfoMock; state.setResponseInfo(responseInfoToUse, origErrorForOrigResponseInfoMock); // when handlerSpy.executeResponseFilters(ctxMock); // then reversedFiltersList.forEach(filter -> verify(filter).filterResponse(responseInfoToUse, requestInfoMock, ctxMock)); assertThat(state.getResponseInfo()).isSameAs(responseInfoToUse); assertThat(state.getErrorThatTriggeredThisResponse()).isSameAs(origErrorForOrigResponseInfoMock); }