use of com.nike.backstopper.model.DefaultErrorDTO in project riposte by Nike-Inc.
the class ErrorResponseBodyImplTest method verifyAdapter.
private void verifyAdapter(ErrorResponseBodyImpl adapter, String expectedErrorId, List<ApiError> expectedErrors) {
assertThat(adapter.error_id, is(expectedErrorId));
assertThat(adapter.errorId(), is(expectedErrorId));
assertThat(adapter.errors.size(), is(expectedErrors.size()));
for (int i = 0; i < expectedErrors.size(); i++) {
ApiError apiError = expectedErrors.get(i);
DefaultErrorDTO errorView = adapter.errors.get(i);
assertThat(errorView.code, is(apiError.getErrorCode()));
assertThat(errorView.message, is(apiError.getMessage()));
}
}
use of com.nike.backstopper.model.DefaultErrorDTO in project riposte by Nike-Inc.
the class ResponseSenderHandlerTest method sendResponse_should_setup_a_last_ditch_error_response_if_an_exception_occurs_in_doSendResponse.
@DataProvider(value = { "true", "false" })
@Test
public void sendResponse_should_setup_a_last_ditch_error_response_if_an_exception_occurs_in_doSendResponse(boolean sendLastDitchResponseInline) throws JsonProcessingException {
// given
Object msg = new Object();
AtomicInteger numTimesDoSendResponseCalled = new AtomicInteger(0);
RuntimeException expectedExceptionFromDoSendResponse = new RuntimeException("intentional test exception");
List<ResponseInfo<?>> responseInfosPassedToDoSendResponse = new ArrayList<>();
doAnswer(invocation -> {
int numTimesCalled = numTimesDoSendResponseCalled.incrementAndGet();
// Capture the response data for this call so it can be asserted on later.
ChannelHandlerContext ctx = invocation.getArgument(0);
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
responseInfosPassedToDoSendResponse.add(state.getResponseInfo());
// Only throw the exception the first time.
if (numTimesCalled == 1) {
throw expectedExceptionFromDoSendResponse;
}
return null;
}).when(handlerSpy).doSendResponse(any(), any());
// Use a real HttpProcessingState so that we can verify the last-ditch response gets set on it properly.
HttpProcessingState state = new HttpProcessingState();
state.setResponseInfo(responseInfo, null);
doReturn(state).when(stateAttrMock).get();
// when
Throwable propagatedEx = catchThrowable(() -> handlerSpy.sendResponse(ctxMock, msg, sendLastDitchResponseInline));
// then
// The initial call should have been made to doSendResponse, using the original responseInfo.
assertThat(responseInfosPassedToDoSendResponse.get(0)).isSameAs(responseInfo);
// But now, after the exception handling, the state should contain a new ResponseInfo
// representing the last ditch error response.
ResponseInfo<?> lastDitchResponseInfo = state.getResponseInfo();
assertThat(lastDitchResponseInfo).isNotEqualTo(responseInfo);
assertThat(lastDitchResponseInfo.getContentForFullResponse()).isInstanceOf(ErrorResponseBodyImpl.class);
ErrorResponseBodyImpl lastDitchContent = (ErrorResponseBodyImpl) lastDitchResponseInfo.getContentForFullResponse();
assertThat(lastDitchContent).isNotNull();
assertThat(lastDitchContent.errors).hasSize(1);
DefaultErrorDTO errorDto = lastDitchContent.errors.get(0);
assertThat(errorDto.code).isEqualTo(SampleCoreApiError.GENERIC_SERVICE_ERROR.getErrorCode());
assertThat(errorDto.message).isEqualTo(SampleCoreApiError.GENERIC_SERVICE_ERROR.getMessage());
assertThat(errorDto.metadata).isEqualTo(SampleCoreApiError.GENERIC_SERVICE_ERROR.getMetadata());
String errorId = lastDitchContent.errorId();
assertThat(errorId).isNotBlank();
assertThat(lastDitchResponseInfo.getHeaders().get("error_uid")).isEqualTo(errorId);
assertThat(lastDitchResponseInfo.getHttpStatusCode()).isEqualTo(500);
// The request should have been marked as setting the last ditch response, so we only ever try this once.
verify(handlerSpy).markTriedSendingLastDitchResponse(state);
if (sendLastDitchResponseInline) {
// Verify that the last ditch response was sent inline, and matches expected properties.
assertThat(numTimesDoSendResponseCalled.get()).isEqualTo(2);
assertThat(responseInfosPassedToDoSendResponse.get(1)).isSameAs(lastDitchResponseInfo);
// Since the last ditch response was sent inline, and we made sure it would complete successfully,
// then no exception should have been propagated.
assertThat(propagatedEx).isNull();
} else {
// Verify that the last ditch response was not sent inline.
assertThat(numTimesDoSendResponseCalled.get()).isEqualTo(1);
// Since the last ditch response was not sent inline, the doSendResponse exception should have been
// propagated.
assertThat(propagatedEx).isSameAs(expectedExceptionFromDoSendResponse);
}
// Proxy streaming should be canceled.
verify(proxyStateMock).cancelRequestStreaming(expectedExceptionFromDoSendResponse, ctxMock);
verify(proxyStateMock).cancelDownstreamRequest(expectedExceptionFromDoSendResponse);
// But since the last-ditch response was sent successfully, the channel shouldn't have been closed.
verify(channelMock, never()).close();
}
use of com.nike.backstopper.model.DefaultErrorDTO in project riposte by Nike-Inc.
the class VerifyTimeoutsAndProxyConnectionPoolingWorksComponentTest method verify_incomplete_call_is_timed_out.
@Test
public void verify_incomplete_call_is_timed_out() throws InterruptedException, TimeoutException, ExecutionException, IOException {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
CompletableFuture<Pair<String, String>> responseFromServer = new CompletableFuture<>();
// Create a raw netty HTTP client so we can fiddle with headers and intentionally create a bad request
// that should trigger the bad call timeout.
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new HttpClientCodec());
p.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
p.addLast(new SimpleChannelInboundHandler<HttpObject>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof FullHttpResponse) {
// Store the server response for asserting on later.
FullHttpResponse responseMsg = (FullHttpResponse) msg;
responseFromServer.complete(Pair.of(responseMsg.content().toString(CharsetUtil.UTF_8), responseMsg.headers().get(HttpHeaders.Names.CONNECTION)));
} else {
// Should never happen.
throw new RuntimeException("Received unexpected message type: " + msg.getClass());
}
}
});
}
});
// Connect to the server.
Channel ch = bootstrap.connect("localhost", downstreamServerConfig.endpointsPort()).sync().channel();
// Create a bad HTTP request. This one will be bad because it has a non-zero content-length header,
// but we're sending no payload. The server should (correctly) sit and wait for payload bytes to
// arrive until it hits the timeout, at which point it should return the correct error response.
HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, LongDelayTestEndpoint.MATCHING_PATH);
request.headers().set(HttpHeaders.Names.HOST, "localhost");
request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, "100");
long beforeCallTimeNanos = System.nanoTime();
// Send the bad request.
ch.writeAndFlush(request);
// Wait for the response to be received and the connection to be closed.
try {
ch.closeFuture().get(incompleteCallTimeoutMillis * 10, TimeUnit.MILLISECONDS);
responseFromServer.get(incompleteCallTimeoutMillis * 10, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
fail("The call took much longer than expected without receiving a response. " + "Cancelling this test - it's not working properly", ex);
}
// If we reach here then the call should be complete.
long totalCallTimeMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beforeCallTimeNanos);
// Verify that we got back the correct error response.
// It should be a MALFORMED_REQUEST with extra metadata explaining that the call was bad.
Pair<String, String> responseInfo = responseFromServer.get();
DefaultErrorContractDTO errorContract = objectMapper.readValue(responseInfo.getLeft(), DefaultErrorContractDTO.class);
assertThat(errorContract).isNotNull();
assertThat(errorContract.errors.size()).isEqualTo(1);
DefaultErrorDTO error = errorContract.errors.get(0);
ApiError expectedApiError = SampleCoreApiError.MALFORMED_REQUEST;
Map<String, Object> expectedMetadata = MapBuilder.builder("cause", (Object) "Unfinished/invalid HTTP request").build();
assertThat(error.code).isEqualTo(expectedApiError.getErrorCode());
assertThat(error.message).isEqualTo(expectedApiError.getMessage());
assertThat(error.metadata).isEqualTo(expectedMetadata);
// The server should have closed the connection even though we asked for keep-alive.
assertThat(responseInfo.getRight()).isEqualTo(HttpHeaders.Values.CLOSE);
// Total call time should be pretty close to incompleteCallTimeoutMillis give or take a few
// milliseconds, but due to the inability to account for slow machines running the unit tests,
// a server that isn't warmed up, etc, we can't put a ceiling on the wiggle room we'd need, so
// we'll just verify it took at least the minimum necessary amount of time.
assertThat(totalCallTimeMillis).isGreaterThanOrEqualTo(incompleteCallTimeoutMillis);
} finally {
eventLoopGroup.shutdownGracefully();
}
}
Aggregations