package com.ctrip.framework.apollo.internals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.springframework.test.util.ReflectionTestUtils; import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification; import com.ctrip.framework.apollo.core.dto.ServiceDTO; import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.http.HttpRequest; import com.ctrip.framework.apollo.util.http.HttpResponse; import com.ctrip.framework.apollo.util.http.HttpUtil; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.util.concurrent.SettableFuture; /** * @author Jason Song(song_s@ctrip.com) */ @RunWith(MockitoJUnitRunner.class) public class RemoteConfigLongPollServiceTest { private RemoteConfigLongPollService remoteConfigLongPollService; @Mock private HttpResponse<List<ApolloConfigNotification>> pollResponse; @Mock private HttpUtil httpUtil; @Mock private ConfigServiceLocator configServiceLocator; private Type responseType; private static String someServerUrl; private static String someAppId; private static String someCluster; @Before public void setUp() throws Exception { MockInjector.reset(); MockInjector.setInstance(HttpUtil.class, httpUtil); ServiceDTO serviceDTO = mock(ServiceDTO.class); when(serviceDTO.getHomepageUrl()).thenReturn(someServerUrl); when(configServiceLocator.getConfigServices()).thenReturn(Lists.newArrayList(serviceDTO)); MockInjector.setInstance(ConfigServiceLocator.class, configServiceLocator); MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); remoteConfigLongPollService = new RemoteConfigLongPollService(); responseType = (Type) ReflectionTestUtils.getField(remoteConfigLongPollService, "m_responseType"); someServerUrl = "http://someServer"; someAppId = "someAppId"; someCluster = "someCluster"; } @Test public void testSubmitLongPollNamespaceWith304Response() throws Exception { RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class); final String someNamespace = "someNamespace"; when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_MODIFIED); final SettableFuture<Boolean> longPollFinished = SettableFuture.create(); doAnswer(new Answer<HttpResponse<List<ApolloConfigNotification>>>() { @Override public HttpResponse<List<ApolloConfigNotification>> answer(InvocationOnMock invocation) throws Throwable { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { } HttpRequest request = invocation.getArgumentAt(0, HttpRequest.class); assertTrue(request.getUrl().contains(someServerUrl + "/notifications/v2?")); assertTrue(request.getUrl().contains("appId=" + someAppId)); assertTrue(request.getUrl().contains("cluster=" + someCluster)); assertTrue(request.getUrl().contains("notifications=")); assertTrue(request.getUrl().contains(someNamespace)); longPollFinished.set(true); return pollResponse; } }).when(httpUtil).doGet(any(HttpRequest.class), eq(responseType)); remoteConfigLongPollService.submit(someNamespace, someRepository); longPollFinished.get(5000, TimeUnit.MILLISECONDS); remoteConfigLongPollService.stopLongPollingRefresh(); verify(someRepository, never()).onLongPollNotified(any(ServiceDTO.class)); } @Test public void testSubmitLongPollNamespaceWith200Response() throws Exception { RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class); final String someNamespace = "someNamespace"; ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class); when(someNotification.getNamespaceName()).thenReturn(someNamespace); when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK); when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification)); doAnswer(new Answer<HttpResponse<List<ApolloConfigNotification>>>() { @Override public HttpResponse<List<ApolloConfigNotification>> answer(InvocationOnMock invocation) throws Throwable { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { } return pollResponse; } }).when(httpUtil).doGet(any(HttpRequest.class), eq(responseType)); final SettableFuture<Boolean> onNotified = SettableFuture.create(); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { onNotified.set(true); return null; } }).when(someRepository).onLongPollNotified(any(ServiceDTO.class)); remoteConfigLongPollService.submit(someNamespace, someRepository); onNotified.get(5000, TimeUnit.MILLISECONDS); remoteConfigLongPollService.stopLongPollingRefresh(); verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class)); } @Test public void testSubmitLongPollMultipleNamespaces() throws Exception { RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class); RemoteConfigRepository anotherRepository = mock(RemoteConfigRepository.class); final String someNamespace = "someNamespace"; final String anotherNamespace = "anotherNamespace"; final ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class); when(someNotification.getNamespaceName()).thenReturn(someNamespace); final ApolloConfigNotification anotherNotification = mock(ApolloConfigNotification.class); when(anotherNotification.getNamespaceName()).thenReturn(anotherNamespace); final SettableFuture<Boolean> submitAnotherNamespaceStart = SettableFuture.create(); final SettableFuture<Boolean> submitAnotherNamespaceFinish = SettableFuture.create(); doAnswer(new Answer<HttpResponse<List<ApolloConfigNotification>>>() { final AtomicInteger counter = new AtomicInteger(); @Override public HttpResponse<List<ApolloConfigNotification>> answer(InvocationOnMock invocation) throws Throwable { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { } //the first time if (counter.incrementAndGet() == 1) { HttpRequest request = invocation.getArgumentAt(0, HttpRequest.class); assertTrue(request.getUrl().contains("notifications=")); assertTrue(request.getUrl().contains(someNamespace)); submitAnotherNamespaceStart.set(true); when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK); when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification)); } else if (submitAnotherNamespaceFinish.get()) { HttpRequest request = invocation.getArgumentAt(0, HttpRequest.class); assertTrue(request.getUrl().contains("notifications=")); assertTrue(request.getUrl().contains(someNamespace)); assertTrue(request.getUrl().contains(anotherNamespace)); when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK); when(pollResponse.getBody()).thenReturn(Lists.newArrayList(anotherNotification)); } else { when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_MODIFIED); when(pollResponse.getBody()).thenReturn(null); } return pollResponse; } }).when(httpUtil).doGet(any(HttpRequest.class), eq(responseType)); final SettableFuture<Boolean> onAnotherRepositoryNotified = SettableFuture.create(); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { onAnotherRepositoryNotified.set(true); return null; } }).when(anotherRepository).onLongPollNotified(any(ServiceDTO.class)); remoteConfigLongPollService.submit(someNamespace, someRepository); submitAnotherNamespaceStart.get(5000, TimeUnit.MILLISECONDS); remoteConfigLongPollService.submit(anotherNamespace, anotherRepository); submitAnotherNamespaceFinish.set(true); onAnotherRepositoryNotified.get(5000, TimeUnit.MILLISECONDS); remoteConfigLongPollService.stopLongPollingRefresh(); verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class)); verify(anotherRepository, times(1)).onLongPollNotified(any(ServiceDTO.class)); } @Test public void testSubmitLongPollMultipleNamespacesWithMultipleNotificationsReturned() throws Exception { RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class); RemoteConfigRepository anotherRepository = mock(RemoteConfigRepository.class); final String someNamespace = "someNamespace"; final String anotherNamespace = "anotherNamespace"; final ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class); when(someNotification.getNamespaceName()).thenReturn(someNamespace); final ApolloConfigNotification anotherNotification = mock(ApolloConfigNotification.class); when(anotherNotification.getNamespaceName()).thenReturn(anotherNamespace); when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK); when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification, anotherNotification)); doAnswer(new Answer<HttpResponse<List<ApolloConfigNotification>>>() { @Override public HttpResponse<List<ApolloConfigNotification>> answer(InvocationOnMock invocation) throws Throwable { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { } return pollResponse; } }).when(httpUtil).doGet(any(HttpRequest.class), eq(responseType)); final SettableFuture<Boolean> someRepositoryNotified = SettableFuture.create(); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { someRepositoryNotified.set(true); return null; } }).when(someRepository).onLongPollNotified(any(ServiceDTO.class)); final SettableFuture<Boolean> anotherRepositoryNotified = SettableFuture.create(); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { anotherRepositoryNotified.set(true); return null; } }).when(anotherRepository).onLongPollNotified(any(ServiceDTO.class)); remoteConfigLongPollService.submit(someNamespace, someRepository); remoteConfigLongPollService.submit(anotherNamespace, anotherRepository); someRepositoryNotified.get(5000, TimeUnit.MILLISECONDS); anotherRepositoryNotified.get(5000, TimeUnit.MILLISECONDS); remoteConfigLongPollService.stopLongPollingRefresh(); verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class)); verify(anotherRepository, times(1)).onLongPollNotified(any(ServiceDTO.class)); } @Test public void testAssembleLongPollRefreshUrl() throws Exception { String someUri = someServerUrl; String someAppId = "someAppId"; String someCluster = "someCluster+ &.-_someSign"; String someNamespace = "someName"; long someNotificationId = 1; Map<String, Long> notificationsMap = ImmutableMap.of(someNamespace, someNotificationId); String longPollRefreshUrl = remoteConfigLongPollService .assembleLongPollRefreshUrl(someUri, someAppId, someCluster, null, notificationsMap); assertTrue(longPollRefreshUrl.contains(someServerUrl + "/notifications/v2?")); assertTrue(longPollRefreshUrl.contains("appId=" + someAppId)); assertTrue(longPollRefreshUrl.contains("cluster=someCluster%2B+%26.-_someSign")); assertTrue(longPollRefreshUrl.contains( "notifications=%5B%7B%22namespaceName%22%3A%22" + someNamespace + "%22%2C%22notificationId%22%3A" + 1 + "%7D%5D")); } @Test public void testAssembleLongPollRefreshUrlWithMultipleNamespaces() throws Exception { String someUri = someServerUrl; String someAppId = "someAppId"; String someCluster = "someCluster+ &.-_someSign"; String someNamespace = "someName"; String anotherNamespace = "anotherName"; long someNotificationId = 1; long anotherNotificationId = 2; Map<String, Long> notificationsMap = ImmutableMap.of(someNamespace, someNotificationId, anotherNamespace, anotherNotificationId); String longPollRefreshUrl = remoteConfigLongPollService .assembleLongPollRefreshUrl(someUri, someAppId, someCluster, null, notificationsMap); assertTrue(longPollRefreshUrl.contains(someServerUrl + "/notifications/v2?")); assertTrue(longPollRefreshUrl.contains("appId=" + someAppId)); assertTrue(longPollRefreshUrl.contains("cluster=someCluster%2B+%26.-_someSign")); assertTrue( longPollRefreshUrl.contains("notifications=%5B%7B%22namespaceName%22%3A%22" + someNamespace + "%22%2C%22notificationId%22%3A" + someNotificationId + "%7D%2C%7B%22namespaceName%22%3A%22" + anotherNamespace + "%22%2C%22notificationId%22%3A" + anotherNotificationId + "%7D%5D")); } public static class MockConfigUtil extends ConfigUtil { @Override public String getAppId() { return someAppId; } @Override public String getCluster() { return someCluster; } @Override public String getDataCenter() { return null; } @Override public int getLoadConfigQPS() { return 200; } @Override public int getLongPollQPS() { return 200; } } }