/* * Copyright 2017, Yahoo Inc. * Licensed under the terms of the Apache License, Version 2.0. * See the LICENSE file associated with the project for terms. */ package com.yahoo.bullet.storm.drpc; import com.yahoo.bullet.common.BulletConfig; import com.yahoo.bullet.pubsub.PubSubMessage; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.BoundRequestBuilder; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.Response; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; import java.util.Collections; import java.util.concurrent.CompletableFuture; import static com.yahoo.bullet.storm.drpc.utils.DRPCError.CANNOT_REACH_DRPC; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; public class DRPCQueryResultPubscriberTest { private DRPCQueryResultPubscriber pubscriber; private Response getResponse(int status, String statusText, String body) { Response mock = mock(Response.class); doReturn(status).when(mock).getStatusCode(); doReturn(statusText).when(mock).getStatusText(); doReturn(body).when(mock).getResponseBody(); return mock; } private Response getNotOkResponse(int status) { return getResponse(status, "Error", null); } private Response getOkResponse(String data) { return getResponse(DRPCQueryResultPubscriber.OK_200, "Ok", data); } private CompletableFuture<Response> getOkFuture(Response response) { CompletableFuture<Response> finished = CompletableFuture.completedFuture(response); CompletableFuture<Response> mock = mock(CompletableFuture.class); // This is the weird bit. We mock the call to exceptionally to return the finished response so that chaining // a thenAcceptAsync on it will call the consumer of it with the finished response. This is why it looks // weird: mocking the exceptionally to take the "good" path. doReturn(finished).when(mock).exceptionally(any()); // So if we do get to thenAccept on our mock, we should throw an exception because we shouldn't get there. doThrow(new RuntimeException("Good futures don't throw")).when(mock).thenAcceptAsync(any()); return mock; } private AsyncHttpClient mockClientWith(CompletableFuture<Response> future) { ListenableFuture<Response> mockListenable = (ListenableFuture<Response>) mock(ListenableFuture.class); doReturn(future).when(mockListenable).toCompletableFuture(); BoundRequestBuilder mockBuilder = mock(BoundRequestBuilder.class); doReturn(mockListenable).when(mockBuilder).execute(); // Return itself doReturn(mockBuilder).when(mockBuilder).setBody(anyString()); AsyncHttpClient mockClient = mock(AsyncHttpClient.class); doReturn(mockBuilder).when(mockClient).preparePost(anyString()); return mockClient; } private PubSubMessage fetch() { try { PubSubMessage message; do { message = pubscriber.receive(); Thread.sleep(1); } while (message == null); return message; } catch (Exception e) { throw new RuntimeException(e); } } private CompletableFuture<PubSubMessage> fetchAsync() { return CompletableFuture.supplyAsync(this::fetch); } @BeforeMethod public void setup() { BulletConfig config = new DRPCConfig("src/test/resources/test_drpc_config.yaml"); config.set(DRPCConfig.DRPC_SERVERS, Collections.singletonList("foo.bar.bullet.drpc.com")); pubscriber = new DRPCQueryResultPubscriber(config); } @Test(timeOut = 5000L) public void testReadingOkResponse() throws Exception { PubSubMessage expected = new PubSubMessage("foo", "response"); CompletableFuture<Response> response = getOkFuture(getOkResponse(expected.asJSON())); AsyncHttpClient mockClient = mockClientWith(response); pubscriber.setClient(mockClient); pubscriber.send(new PubSubMessage("foo", "bar")); // This is async (but practically still very fast since we mocked the response), so need a timeout. PubSubMessage actual = fetchAsync().get(); Assert.assertNotNull(actual); Assert.assertEquals(actual.getId(), expected.getId()); Assert.assertEquals(actual.getContent(), expected.getContent()); } @Test(timeOut = 5000L) public void testReadingNotOkResponse() throws Exception { CompletableFuture<Response> response = getOkFuture(getNotOkResponse(500)); AsyncHttpClient mockClient = mockClientWith(response); pubscriber.setClient(mockClient); pubscriber.send(new PubSubMessage("foo", "bar")); // This is async (but practically still very fast since we mocked the response), so need a timeout. PubSubMessage actual = fetchAsync().get(); Assert.assertNotNull(actual); Assert.assertEquals(actual.getId(), "foo"); Assert.assertEquals(actual.getContent(), CANNOT_REACH_DRPC.asJSONClip()); } @Test(timeOut = 5000L) public void testReadingNullResponse() throws Exception { CompletableFuture<Response> response = getOkFuture(null); AsyncHttpClient mockClient = mockClientWith(response); pubscriber.setClient(mockClient); pubscriber.send(new PubSubMessage("foo", "bar")); // This is async (but practically still very fast since we mocked the response), so need a timeout. PubSubMessage actual = fetchAsync().get(); Assert.assertNotNull(actual); Assert.assertEquals(actual.getId(), "foo"); Assert.assertEquals(actual.getContent(), CANNOT_REACH_DRPC.asJSONClip()); } @Test public void testClosing() throws Exception { AsyncHttpClient mockClient = mock(AsyncHttpClient.class); pubscriber.setClient(mockClient); pubscriber.close(); verify(mockClient, times(1)).close(); } @Test public void testClosingWithException() throws Exception { AsyncHttpClient mockClient = mock(AsyncHttpClient.class); doThrow(new IOException()).when(mockClient).close(); pubscriber.setClient(mockClient); pubscriber.close(); verify(mockClient, times(1)).close(); } @Test public void testCommiting() { AsyncHttpClient mockClient = mock(AsyncHttpClient.class); pubscriber.commit("foo"); verifyZeroInteractions(mockClient); } @Test public void testFailing() { AsyncHttpClient mockClient = mock(AsyncHttpClient.class); pubscriber.fail("foo"); verifyZeroInteractions(mockClient); } @Test(timeOut = 5000L) public void testException() throws Exception { // This will hit a non-existent url and fail, testing our exceptions. Our connect and retry is low so even if // block the full amount, it's still fast. pubscriber.send(new PubSubMessage("foo", "bar")); PubSubMessage actual = fetchAsync().get(); Assert.assertNotNull(actual); Assert.assertEquals(actual.getId(), "foo"); Assert.assertEquals(actual.getContent(), CANNOT_REACH_DRPC.asJSONClip()); } }