chromium/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java

// Copyright 2023 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.password_manager;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.password_manager.PasswordCheckReferrer.LEAK_DIALOG;

import android.accounts.Account;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import org.chromium.base.CollectionUtil;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.password_manager.PasswordCheckupClientHelper.PasswordCheckBackendException;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.safety_check.SafetyCheckSettingsFragment;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.AccountUtils;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.test.util.FakeAccountManagerFacade;
import org.chromium.components.sync.SyncService;
import org.chromium.components.sync.UserSelectableType;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.components.user_prefs.UserPrefsJni;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;

import java.lang.ref.WeakReference;

/** Tests for password manager helper methods. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@Batch(Batch.PER_CLASS)
public class PasswordCheckupLauncherTest {
    private static final String TEST_EMAIL_ADDRESS = "[email protected]";
    private static final String TEST_NO_EMAIL_ADDRESS = null;

    @Rule public JniMocker mJniMocker = new JniMocker();

    @Spy private Context mContext = RuntimeEnvironment.application.getApplicationContext();

    @Captor private ArgumentCaptor<Intent> mIntentCaptor;

    @Mock private Profile mProfile;

    @Mock private PrefService mPrefService;

    @Mock private UserPrefs.Natives mMockUserPrefsJni;

    @Mock private PasswordManagerUtilBridge.Natives mMockPasswordManagerUtilBridgeJni;

    @Mock private SyncService mMockSyncService;

    @Mock WindowAndroid mMockWindowAndroid;

    @Mock private PendingIntent mMockPendingIntentForLocalCheckup;

    @Mock private PendingIntent mMockPendingIntentForAccountCheckup;

    private FakePasswordManagerBackendSupportHelper mFakeBackendSupportHelper =
            new FakePasswordManagerBackendSupportHelper();

    private ModalDialogManager mModalDialogManager;

    private FakePasswordCheckupClientHelperFactoryImpl mFakePasswordCheckupClientHelperFactory =
            new FakePasswordCheckupClientHelperFactoryImpl();

    private FakePasswordCheckupClientHelper mFakePasswordCheckupClientHelper;

    private final FakeAccountManagerFacade mFakeAccountManagerFacade =
            new FakeAccountManagerFacade();

    @Before
    public void setUp() throws PasswordCheckBackendException {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mMockUserPrefsJni);
        mJniMocker.mock(PasswordManagerUtilBridgeJni.TEST_HOOKS, mMockPasswordManagerUtilBridgeJni);
        when(mMockPasswordManagerUtilBridgeJni.areMinUpmRequirementsMet()).thenReturn(true);

        when(mProfile.getOriginalProfile()).thenReturn(mProfile);
        when(mMockUserPrefsJni.get(mProfile)).thenReturn(mPrefService);

        SyncServiceFactory.setInstanceForTesting(mMockSyncService);
        when(mMockSyncService.isSyncFeatureEnabled()).thenReturn(true);
        when(mMockSyncService.isEngineInitialized()).thenReturn(true);
        when(mMockSyncService.hasSyncConsent()).thenReturn(true);

        AccountManagerFacadeProvider.setInstanceForTests(mFakeAccountManagerFacade);
        Account newAccount = AccountUtils.createAccountFromName(TEST_EMAIL_ADDRESS);
        mFakeAccountManagerFacade.addAccount(newAccount);
        when(mMockSyncService.getAccountInfo())
                .thenReturn(
                        CoreAccountInfo.createFromEmailAndGaiaId(
                                TEST_EMAIL_ADDRESS,
                                mFakeAccountManagerFacade.getAccountGaiaId(TEST_EMAIL_ADDRESS)));

        PasswordManagerBackendSupportHelper.setInstanceForTesting(mFakeBackendSupportHelper);
        mFakeBackendSupportHelper.setBackendPresent(true);

        PasswordCheckupClientHelperFactory.setFactoryForTesting(
                mFakePasswordCheckupClientHelperFactory);
        mFakePasswordCheckupClientHelper =
                (FakePasswordCheckupClientHelper)
                        mFakePasswordCheckupClientHelperFactory.createHelper();
        mFakePasswordCheckupClientHelper.setIntentForLocalCheckup(
                mMockPendingIntentForLocalCheckup);
        mFakePasswordCheckupClientHelper.setIntentForAccountCheckup(
                mMockPendingIntentForAccountCheckup);

        when(mMockWindowAndroid.getContext()).thenReturn(new WeakReference<>(mContext));
        mModalDialogManager =
                new ModalDialogManager(
                        mock(ModalDialogManager.Presenter.class),
                        ModalDialogManager.ModalDialogType.APP);
        when(mMockWindowAndroid.getModalDialogManager()).thenReturn(mModalDialogManager);
    }

    @Test
    public void testLaunchCheckupOnDeviceShowsPasswordCheckupForAccount()
            throws PendingIntent.CanceledException {
        when(mMockSyncService.getSelectedTypes())
                .thenReturn(CollectionUtil.newHashSet(UserSelectableType.PASSWORDS));
        when(mMockPasswordManagerUtilBridgeJni.shouldUseUpmWiring(mMockSyncService, mPrefService))
                .thenReturn(true);

        PasswordCheckupLauncher.launchCheckupOnDevice(
                mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_EMAIL_ADDRESS);

        verify(mMockPendingIntentForAccountCheckup).send();
    }

    @Test
    public void testLaunchCheckupOnDeviceShowsPasswordCheckupForLocalWhenNotSyncing()
            throws PendingIntent.CanceledException {
        when(mMockPasswordManagerUtilBridgeJni.shouldUseUpmWiring(mMockSyncService, mPrefService))
                .thenReturn(true);

        PasswordCheckupLauncher.launchCheckupOnDevice(
                mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_NO_EMAIL_ADDRESS);

        verify(mMockPendingIntentForLocalCheckup).send();
    }

    @Test
    public void testLaunchCheckupOnDeviceShowsPasswordCheckupForLocalWhenSyncing()
            throws PendingIntent.CanceledException {
        // Local checkup will be launched from the leak detection dialog if the leaked credential is
        // stored only in the local store, even though the user is syncing passwords.
        when(mMockSyncService.getSelectedTypes())
                .thenReturn(CollectionUtil.newHashSet(UserSelectableType.PASSWORDS));
        when(mMockPasswordManagerUtilBridgeJni.shouldUseUpmWiring(mMockSyncService, mPrefService))
                .thenReturn(true);

        PasswordCheckupLauncher.launchCheckupOnDevice(
                mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_NO_EMAIL_ADDRESS);

        verify(mMockPendingIntentForLocalCheckup).send();
    }

    @Test
    public void testLaunchPasswordCheckShowsUpdateGmsDialog()
            throws PendingIntent.CanceledException {
        when(mMockPasswordManagerUtilBridgeJni.shouldUseUpmWiring(mMockSyncService, mPrefService))
                .thenReturn(true);
        when(mMockPasswordManagerUtilBridgeJni.isGmsCoreUpdateRequired(
                        mPrefService, mMockSyncService))
                .thenReturn(true);

        PasswordCheckupLauncher.launchCheckupOnDevice(
                mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_NO_EMAIL_ADDRESS);

        verify(mMockPendingIntentForLocalCheckup, times(0)).send();
        verify(mMockPendingIntentForAccountCheckup, times(0)).send();
        PropertyModel dialogModel = mModalDialogManager.getCurrentDialogForTest();
        assertThat(
                dialogModel.get(ModalDialogProperties.MESSAGE_PARAGRAPH_1),
                is(mContext.getString(R.string.password_manager_outdated_gms_dialog_description)));
    }

    @Test
    public void testLaunchSafetyCheckOpensSafetyCheckInChromeSettings()
            throws PendingIntent.CanceledException {
        when(mMockSyncService.getSelectedTypes())
                .thenReturn(CollectionUtil.newHashSet(UserSelectableType.PASSWORDS));
        when(mMockPasswordManagerUtilBridgeJni.shouldUseUpmWiring(mMockSyncService, mPrefService))
                .thenReturn(true);

        PasswordCheckupLauncher.launchSafetyCheck(mMockWindowAndroid);

        verify(mContext, times(1)).startActivity(mIntentCaptor.capture(), isNull());

        Intent intent = mIntentCaptor.getValue();
        assertThat(
                intent.getExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT),
                is(SafetyCheckSettingsFragment.class.getName()));
    }
}