| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.tasks.tab_management.suggestions; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyLong; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.doThrow; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.verify; |
| |
| import org.json.JSONException; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.invocation.InvocationOnMock; |
| import org.mockito.stubbing.Answer; |
| import org.robolectric.annotation.Config; |
| |
| import org.chromium.base.Callback; |
| import org.chromium.base.test.util.JniMocker; |
| import org.chromium.chrome.browser.complex_tasks.endpoint_fetcher.EndpointFetcher; |
| import org.chromium.chrome.browser.complex_tasks.endpoint_fetcher.EndpointFetcherJni; |
| import org.chromium.chrome.browser.complex_tasks.endpoint_fetcher.EndpointResponse; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.testing.local.LocalRobolectricTestRunner; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Tests for {@link TabSuggestionsServerFetcher} |
| */ |
| @RunWith(LocalRobolectricTestRunner.class) |
| @Config(manifest = Config.NONE) |
| public class TabSuggestionsServerFetcherUnitTest { |
| @Rule |
| public JniMocker mMocker = new JniMocker(); |
| |
| private static final TabContext.TabInfo TAB_INFO_YANDEX = new TabContext.TabInfo( |
| 0, "Yandex", "https://www.yandex.com", "", "", 1588817215266L, ""); |
| private static final TabContext.TabInfo TAB_INFO_BING = |
| new TabContext.TabInfo(1, "Bing", "https://www.bing.com", "", "", 1588817244592L, ""); |
| private static final TabContext.TabInfo TAB_INFO_AMAZON = new TabContext.TabInfo( |
| 2, "Amazon.com", "https://www.amazon.com", "", "", 1588817215266L, ""); |
| private static final TabContext.TabInfo TAB_INFO_GOOGLE = new TabContext.TabInfo( |
| 3, "Google", "https://www.google.com", "", "", 1588817256727L, ""); |
| private static final TabContext.TabInfo TAB_INFO_INCOGNITO = new TabContext.TabInfo( |
| 4, "Incognito site", "https://www.incognito.com", "", "", 158881926727L, ""); |
| private static final TabContext TAB_CONTEXT_GROUP_ALL = |
| new TabContext(Arrays.asList(TAB_INFO_YANDEX, TAB_INFO_BING, TAB_INFO_GOOGLE), |
| Collections.emptyList()); |
| private static final TabContext TAB_CONTEXT_GROUP_SOME = new TabContext( |
| Arrays.asList(TAB_INFO_YANDEX, TAB_INFO_BING, TAB_INFO_AMAZON, TAB_INFO_GOOGLE), |
| Collections.emptyList()); |
| private static final TabContext TAB_CONTEXT_GROUP_NONE = |
| new TabContext(Arrays.asList(TAB_INFO_AMAZON), Collections.emptyList()); |
| private static final TabContext TAB_CONTEXT_GROUP_EMPTY = |
| new TabContext(Collections.emptyList(), Collections.emptyList()); |
| private static final TabContext.TabInfo[] EXPECTED_GROUP = |
| new TabContext.TabInfo[] {TAB_INFO_YANDEX, TAB_INFO_BING, TAB_INFO_GOOGLE}; |
| private static final String ENDPOINT_GROUPS_RESPONSE = "{\"suggestions\":[{\"tabs\":[{\"id\":" |
| + "0,\"referrer\":\"\",\"timestamp\":\"1588817215266\",\"title\":\"Yandex\",\"url\":" |
| + "\"https://yandex.com/\"},{\"id\":1,\"referrer\":\"\",\"timestamp\":\"1588817244592\"," |
| + "\"title\":\"Bing\",\"url\":\"https://www.bing.com/\"},{\"id\":3,\"referrer\":\"\"," |
| + "\"timestamp\":\"1588817256727\",\"title\":\"Google\",\"url\":\"https://www.google.com/\"}]" |
| + ",\"providerName\":\"VascoJourneyInference\",\"action\":\"Group\"}]}"; |
| private static final String EMPTY_RESPONSE = "{}"; |
| private static final String UNPARSEABLE_RESPONSE = "asdf"; |
| |
| private static final String EXPECTED_ENDPOINT_URL = |
| "https://memex-pa.googleapis.com/v1/suggestions"; |
| private static final String EXPECTED_CONTENT_TYPE = "application/json; charset=UTF-8"; |
| private static final String EXPECTED_METHOD = "POST"; |
| private static final long EXPECTED_TIMEOUT = 10000L; |
| |
| @Mock |
| EndpointFetcher.Natives mEndpointFetcherJniMock; |
| |
| @Mock |
| private Profile mProfile; |
| |
| private TabSuggestionsServerFetcher mTabSuggestionsServerFetcher; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| mMocker.mock(EndpointFetcherJni.TEST_HOOKS, mEndpointFetcherJniMock); |
| doReturn(false).when(mProfile).isOffTheRecord(); |
| mTabSuggestionsServerFetcher = new TabSuggestionsServerFetcher(mProfile); |
| } |
| |
| private void mockEndpointResponse(String response) { |
| doAnswer(new Answer<Void>() { |
| @Override |
| public Void answer(InvocationOnMock invocation) { |
| Callback callback = (Callback) invocation.getArguments()[6]; |
| callback.onResult(new EndpointResponse(response)); |
| return null; |
| } |
| }) |
| .when(mEndpointFetcherJniMock) |
| .nativeFetchChromeAPIKey(any(Profile.class), anyString(), anyString(), anyString(), |
| anyString(), anyLong(), any(Callback.class)); |
| } |
| |
| private void verifyEndpointArguments() { |
| verify(mEndpointFetcherJniMock) |
| .nativeFetchChromeAPIKey(eq(mProfile), eq(EXPECTED_ENDPOINT_URL), |
| eq(EXPECTED_METHOD), eq(EXPECTED_CONTENT_TYPE), any(String.class), |
| eq(EXPECTED_TIMEOUT), any(Callback.class)); |
| } |
| |
| @Test |
| public void testGroupAll() { |
| mockEndpointResponse(ENDPOINT_GROUPS_RESPONSE); |
| mTabSuggestionsServerFetcher.fetch(TAB_CONTEXT_GROUP_ALL, (res) -> { |
| Assert.assertEquals(1, res.tabSuggestions.size()); |
| Assert.assertEquals(3, res.tabSuggestions.get(0).getTabsInfo().size()); |
| List<Integer> foundIds = getIds(res.tabSuggestions.get(0).getTabsInfo()); |
| for (int expectedId : |
| new int[] {TAB_INFO_YANDEX.id, TAB_INFO_BING.id, TAB_INFO_GOOGLE.id}) { |
| Assert.assertTrue(foundIds.contains(expectedId)); |
| } |
| }); |
| verifyEndpointArguments(); |
| } |
| |
| private static List<Integer> getIds(List<TabContext.TabInfo> tabs) { |
| List<Integer> ids = new ArrayList<>(); |
| for (TabContext.TabInfo tab : tabs) { |
| ids.add(tab.id); |
| } |
| return ids; |
| } |
| |
| @Test |
| public void testGroupSome() { |
| mockEndpointResponse(ENDPOINT_GROUPS_RESPONSE); |
| mTabSuggestionsServerFetcher.fetch(TAB_CONTEXT_GROUP_SOME, (res) -> { |
| // TAB_INFO_AMAZON should not be included in the group because it is not a search engine |
| Assert.assertEquals(1, res.tabSuggestions.size()); |
| Assert.assertEquals(3, res.tabSuggestions.get(0).getTabsInfo().size()); |
| List<Integer> foundIds = getIds(res.tabSuggestions.get(0).getTabsInfo()); |
| for (int expectedId : |
| new int[] {TAB_INFO_YANDEX.id, TAB_INFO_BING.id, TAB_INFO_GOOGLE.id}) { |
| Assert.assertTrue(foundIds.contains(expectedId)); |
| } |
| }); |
| verifyEndpointArguments(); |
| } |
| |
| @Test |
| public void testGroupNullEmpty() { |
| mockEndpointResponse(EMPTY_RESPONSE); |
| mTabSuggestionsServerFetcher.fetch(TAB_CONTEXT_GROUP_NONE, |
| (res) -> { Assert.assertEquals(0, res.tabSuggestions.size()); }); |
| verifyEndpointArguments(); |
| } |
| |
| @Test |
| public void testGroupEmpty() { |
| mockEndpointResponse(EMPTY_RESPONSE); |
| mTabSuggestionsServerFetcher.fetch(TAB_CONTEXT_GROUP_EMPTY, |
| (res) -> { Assert.assertEquals(0, res.tabSuggestions.size()); }); |
| verifyEndpointArguments(); |
| } |
| |
| @Test |
| public void testJsonException() throws JSONException { |
| TabContext tabContext = mock(TabContext.class); |
| doThrow(new JSONException("there was a json exception")) |
| .when(tabContext) |
| .getUngroupedTabsJson(); |
| mTabSuggestionsServerFetcher.fetch(tabContext, (res) -> { |
| Assert.assertNotNull(res.tabSuggestions); |
| Assert.assertEquals(0, res.tabSuggestions.size()); |
| }); |
| } |
| |
| @Test |
| public void testUnparseableResponse() { |
| mockEndpointResponse(UNPARSEABLE_RESPONSE); |
| mTabSuggestionsServerFetcher.fetch(TAB_CONTEXT_GROUP_ALL, |
| (res) -> { Assert.assertEquals(0, res.tabSuggestions.size()); }); |
| verifyEndpointArguments(); |
| } |
| |
| @Test |
| public void testServerFetcherEnabled() { |
| for (boolean isSignedIn : new boolean[] {false, true}) { |
| for (boolean isServerFetcherFlagEnabled : new boolean[] {false, true}) { |
| TabSuggestionsServerFetcher fetcher = spy(new TabSuggestionsServerFetcher()); |
| doReturn(isSignedIn).when(fetcher).isSignedIn(); |
| doReturn(isServerFetcherFlagEnabled).when(fetcher).isServerFetcherFlagEnabled(); |
| if (isSignedIn && isServerFetcherFlagEnabled) { |
| Assert.assertTrue(fetcher.isEnabled()); |
| } else { |
| Assert.assertFalse(fetcher.isEnabled()); |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testServerFetcherDisabledIncognito() throws InterruptedException { |
| TabContext tabContext = |
| new TabContext(Arrays.asList(TAB_INFO_INCOGNITO), Collections.emptyList()); |
| mTabSuggestionsServerFetcher.fetch(tabContext, (fetcherResults) -> { |
| Assert.assertTrue(fetcherResults.tabSuggestions.isEmpty()); |
| }); |
| } |
| } |