chromium/chrome/android/javatests/src/org/chromium/chrome/browser/profiles/ProfileResolverTest.java

// Copyright 2021 The Chromium Authors
// 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.profiles;

import static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.hamcrest.Matchers.isEmptyString;

import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.PayloadCallbackHelper;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.ReducedModeNativeTestRule;
import org.chromium.components.embedder_support.simple_factory_key.SimpleFactoryKeyHandle;
import org.chromium.content_public.browser.BrowserContextHandle;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
 * Tests for ProfileResolver. Profile resolution must run on the UI thread and may invoke the given
 * callback asynchronously. This means test cases cannot block the UI thread while waiting for this
 * callback. This complicates writing each test, as care must be taken to avoid staying on the UI
 * thread even though most operations should be performed on it.
 */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class ProfileResolverTest {
    @Rule
    public final ChromeTabbedActivityTestRule mActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public ReducedModeNativeTestRule mReducedModeNativeTestRule =
            new ReducedModeNativeTestRule(/* autoLoadNative= */ false);

    private ProfileResolver mProfileResolver;

    @Before
    public void setUp() {
        mProfileResolver = new ProfileResolver();
    }

    private void initToFullMode() {
        mActivityTestRule.startMainActivityOnBlankPage();
        mActivityTestRule.waitForActivityNativeInitializationComplete();
    }

    private void initToReducedMode() {
        mReducedModeNativeTestRule.loadNative();
    }

    private Profile getLastUsedRegularProfileOnUiThread() throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(() -> ProfileManager.getLastUsedRegularProfile());
    }

    private Profile getPrimaryOtrProfileOnUiThread() throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(
                () ->
                        ProfileManager.getLastUsedRegularProfile()
                                .getPrimaryOTRProfile(/* createIfNeeded= */ true));
    }

    private Profile newOtrProfileOnUiThread(String profileIdPrefix) throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Profile regularProfile = ProfileManager.getLastUsedRegularProfile();
                    OTRProfileID otrProfileId = OTRProfileID.createUnique(profileIdPrefix);
                    return regularProfile.getOffTheRecordProfile(
                            otrProfileId, /* createIfNeeded= */ true);
                });
    }

    private ProfileKey getPrimaryProfileKeyOnUiThread() throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> ProfileManager.getLastUsedRegularProfile().getProfileKey());
    }

    private String tokenizeOnUiThread(Profile profile) throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(() -> mProfileResolver.tokenize(profile));
    }

    private String tokenizeOnUiThread(ProfileKey profileKey) throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(() -> mProfileResolver.tokenize(profileKey));
    }

    private String tokenizeOnUiThread(BrowserContextHandle browserContext)
            throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(() -> mProfileResolver.tokenize(browserContext));
    }

    private String tokenizeOnUiThread(SimpleFactoryKeyHandle simpleFactoryKey)
            throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(() -> mProfileResolver.tokenize(simpleFactoryKey));
    }

    private Profile resolveProfileSync(String token) {
        PayloadCallbackHelper<Profile> callbackHelper = new PayloadCallbackHelper<>();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mProfileResolver.resolveProfile(
                            token, (Profile p) -> callbackHelper.notifyCalled(p));
                });
        return callbackHelper.getOnlyPayloadBlocking();
    }

    private ProfileKey resolveProfileKeySync(String token) {
        PayloadCallbackHelper<ProfileKey> callbackHelper = new PayloadCallbackHelper<>();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mProfileResolver.resolveProfileKey(
                            token, (ProfileKey p) -> callbackHelper.notifyCalled(p));
                });
        return callbackHelper.getOnlyPayloadBlocking();
    }

    private BrowserContextHandle resolveBrowserContextSync(String token) {
        PayloadCallbackHelper<BrowserContextHandle> callbackHelper = new PayloadCallbackHelper<>();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mProfileResolver.resolveBrowserContext(
                            token, (BrowserContextHandle p) -> callbackHelper.notifyCalled(p));
                });
        return callbackHelper.getOnlyPayloadBlocking();
    }

    private SimpleFactoryKeyHandle resolveSimpleFactoryKeySync(String token) {
        PayloadCallbackHelper<SimpleFactoryKeyHandle> callbackHelper =
                new PayloadCallbackHelper<>();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mProfileResolver.resolveSimpleFactoryKey(
                            token, (SimpleFactoryKeyHandle p) -> callbackHelper.notifyCalled(p));
                });
        return callbackHelper.getOnlyPayloadBlocking();
    }

    @Test
    @SmallTest
    public void testResolveProfile() throws ExecutionException {
        initToFullMode();
        Profile profile = getLastUsedRegularProfileOnUiThread();

        String firstToken = tokenizeOnUiThread(profile);
        Assert.assertEquals(
                "Round tripping should result in the same Profile object",
                profile,
                resolveProfileSync(firstToken));

        String secondToken = tokenizeOnUiThread(profile);
        Assert.assertEquals(
                "Round tripping should result in the same Profile object",
                profile,
                resolveProfileSync(secondToken));
        Assert.assertEquals("Tokens should be identical", firstToken, secondToken);
    }

    @Test
    @SmallTest
    public void testResolveOtrProfile() throws ExecutionException {
        initToFullMode();
        Profile profile = getLastUsedRegularProfileOnUiThread();
        Profile primaryOtrProfile = getPrimaryOtrProfileOnUiThread();
        Profile newOtrProfile = newOtrProfileOnUiThread("foo");

        String primaryOtrToken = tokenizeOnUiThread(primaryOtrProfile);
        Profile resolvedPrimaryOtrProfile = resolveProfileSync(primaryOtrToken);
        Assert.assertEquals(
                "Round tripped primary otr profile should match",
                primaryOtrProfile,
                resolvedPrimaryOtrProfile);
        Assert.assertNotEquals(
                "Round tripped primary OTR profile should be different from original Profile",
                profile,
                resolvedPrimaryOtrProfile);

        String newOtrToken = tokenizeOnUiThread(newOtrProfile);
        Profile resolvedNewOtrProfile = resolveProfileSync(newOtrToken);
        Assert.assertEquals(
                "Round tripped new otr profile should match", newOtrProfile, resolvedNewOtrProfile);
        Assert.assertNotEquals(
                "Round tripped new OTR profile should be different from original Profile",
                profile,
                resolvedNewOtrProfile);
        Assert.assertNotEquals(
                "Round tripped new OTR profile should be different from original OTR Profile",
                primaryOtrProfile,
                resolvedNewOtrProfile);
    }

    @Test
    @SmallTest
    public void testResolveProfileKey() throws ExecutionException {
        initToFullMode();
        ProfileKey key = getPrimaryProfileKeyOnUiThread();

        String token = tokenizeOnUiThread(key);
        ProfileKey resolvedKey = resolveProfileKeySync(token);

        Assert.assertEquals("Round tripped profile key should match", key, resolvedKey);
    }

    @Test
    @SmallTest
    public void testResolveProfileKeyBeforeProfileInit() throws ExecutionException {
        initToReducedMode();
        ProfileKey key =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> ProfileKeyUtil.getLastUsedRegularProfileKey());

        String token = tokenizeOnUiThread(key);
        ProfileKey resolvedKey = resolveProfileKeySync(token);

        Assert.assertEquals("Round tripped profile key should match", key, resolvedKey);
    }

    @Test
    @SmallTest
    public void testMixProfileAndProfileKey() throws ExecutionException {
        initToFullMode();
        Profile profile = getLastUsedRegularProfileOnUiThread();
        ProfileKey key = getPrimaryProfileKeyOnUiThread();

        String keyToken = tokenizeOnUiThread(key);
        Profile resolvedProfile = resolveProfileSync(keyToken);
        Assert.assertEquals("Round tripped profile should match", profile, resolvedProfile);
        Assert.assertEquals(
                "Round tripped profile key should match", key, resolvedProfile.getProfileKey());

        String profileToken = tokenizeOnUiThread(profile);
        ProfileKey resolvedKey = resolveProfileKeySync(profileToken);
        Assert.assertEquals("Round tripped profile key should match", key, resolvedKey);
        Assert.assertEquals("Tokens should be identical", keyToken, profileToken);
    }

    @Test
    @SmallTest
    public void testResolveBrowserContext() throws ExecutionException {
        initToFullMode();
        BrowserContextHandle handle = getLastUsedRegularProfileOnUiThread();

        String token = tokenizeOnUiThread(handle);
        BrowserContextHandle resolvedHandle = resolveBrowserContextSync(token);

        Assert.assertEquals(
                "Round tripping should result in the same BrowserContextHandle object",
                handle,
                resolvedHandle);
    }

    @Test
    @SmallTest
    public void testResolveSimpleFactoryKey() throws ExecutionException {
        initToFullMode();
        SimpleFactoryKeyHandle handle = getPrimaryProfileKeyOnUiThread();

        String token = tokenizeOnUiThread(handle);
        SimpleFactoryKeyHandle resolvedHandle = resolveSimpleFactoryKeySync(token);

        Assert.assertEquals(
                "Round tripping should result in the same SimpleFactoryKeyHandle object",
                handle,
                resolvedHandle);
    }

    @Test
    @SmallTest
    public void testTokenizeNulls() throws ExecutionException {
        initToFullMode();

        // Put nulls into variables first to get correct overloaded methods.
        Profile profile = null;
        assertThat(
                "Tokenizing a null profile should not work",
                tokenizeOnUiThread(profile),
                isEmptyString());

        ProfileKey profileKey = null;
        assertThat(
                "Tokenizing a null profile key should not work",
                tokenizeOnUiThread(profileKey),
                isEmptyString());
    }

    @Test
    @SmallTest
    public void testResolveBadTokens() throws ExecutionException {
        initToFullMode();
        List<String> badTokens = Arrays.asList(null, "", "abcdef");

        for (String token : badTokens) {
            Assert.assertNull(
                    "#resolveProfile() did not resolve null for " + token,
                    resolveProfileSync(token));
            Assert.assertNull(
                    "#resolveProfileKey() did not resolve null for " + token,
                    resolveProfileKeySync(token));
        }
    }
}