// Copyright 2022 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accounts.Account;
import android.app.Activity;
import android.app.PendingIntent;
import org.junit.After;
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.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.Callback;
import org.chromium.base.FakeTimeTestRule;
import org.chromium.base.Promise;
import org.chromium.base.TimeUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.sync.TrustedVaultClient;
import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.signin.test.util.FakeAccountManagerFacade;
import org.chromium.components.sync.SyncService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.components.user_prefs.UserPrefsJni;
import org.chromium.ui.base.WindowAndroid;
import java.lang.ref.WeakReference;
/** Unit tests for the error message helper bridge. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class PasswordManagerErrorMessageHelperBridgeTest {
private final FakeAccountManagerFacade mFakeAccountManagerFacade =
spy(new FakeAccountManagerFacade());
@Rule
public AccountManagerTestRule mAccountManagerTestRule =
new AccountManagerTestRule(mFakeAccountManagerFacade);
@Rule public JniMocker mJniMocker = new JniMocker();
@Rule public FakeTimeTestRule mFakeTimeTestRule = new FakeTimeTestRule();
@Mock private Profile mProfile;
@Mock private PrefService mPrefService;
@Mock private UserPrefs.Natives mUserPrefsJniMock;
@Mock private IdentityServicesProvider mIdentityServicesProviderMock;
@Mock private WindowAndroid mWindowAndroidMock;
@Mock private IdentityManager mIdentityManagerMock;
@Mock private TrustedVaultClient mTrustedVaultClient;
@Mock private SyncService mSyncService;
@Mock private PendingIntent mPendingIntent;
private SharedPreferencesManager mSharedPrefsManager;
private CoreAccountInfo mCoreAccountInfo;
private static final String TEST_EMAIL = "[email protected]";
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mUserPrefsJniMock);
when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
mSharedPrefsManager = ChromeSharedPreferences.getInstance();
mCoreAccountInfo = mAccountManagerTestRule.addAccount(TEST_EMAIL);
when(mIdentityServicesProviderMock.getIdentityManager(mProfile))
.thenReturn(mIdentityManagerMock);
when(mIdentityManagerMock.getPrimaryAccountInfo(ConsentLevel.SIGNIN))
.thenReturn(mCoreAccountInfo);
IdentityServicesProvider.setInstanceForTests(mIdentityServicesProviderMock);
TrustedVaultClient.setInstanceForTesting(mTrustedVaultClient);
SyncServiceFactory.setInstanceForTesting(mSyncService);
}
@After
public void tearDown() {
mSharedPrefsManager.removeKey(ChromePreferenceKeys.SYNC_ERROR_MESSAGE_SHOWN_AT_TIME);
mFakeTimeTestRule.resetTimes();
}
@Test
public void testNotEnoughTimeSinceLastUI() {
final long timeOfFirstUpmPrompt = TimeUtils.currentTimeMillis();
final long timeOfSyncPrompt =
timeOfFirstUpmPrompt
+ PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_TO_SYNC_ERROR_MS;
when(mPrefService.getString(Pref.UPM_ERROR_UI_SHOWN_TIMESTAMP))
.thenReturn(Long.toString(timeOfFirstUpmPrompt));
mSharedPrefsManager.writeLong(
ChromePreferenceKeys.SYNC_ERROR_MESSAGE_SHOWN_AT_TIME, timeOfSyncPrompt);
mFakeTimeTestRule.advanceMillis(
PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_BETWEEN_PROMPTS_MS);
assertFalse(PasswordManagerErrorMessageHelperBridge.shouldShowSignInErrorUI(mProfile));
}
@Test
public void testNotEnoughTimeSinceLastSyncUI() {
final long timeOfFirstUpmPrompt = TimeUtils.currentTimeMillis();
mFakeTimeTestRule.advanceMillis(
PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_BETWEEN_PROMPTS_MS + 1);
final long timeOfSyncPrompt =
TimeUtils.currentTimeMillis()
- PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_TO_SYNC_ERROR_MS;
when(mPrefService.getString(Pref.UPM_ERROR_UI_SHOWN_TIMESTAMP))
.thenReturn(Long.toString(timeOfFirstUpmPrompt));
mSharedPrefsManager.writeLong(
ChromePreferenceKeys.SYNC_ERROR_MESSAGE_SHOWN_AT_TIME, timeOfSyncPrompt);
assertFalse(PasswordManagerErrorMessageHelperBridge.shouldShowSignInErrorUI(mProfile));
}
@Test
public void testEnoughTimeSinceBothUis() {
final long timeOfFirstUpmPrompt = TimeUtils.currentTimeMillis();
final long timeOfSyncPrompt =
timeOfFirstUpmPrompt
+ PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_TO_SYNC_ERROR_MS;
when(mPrefService.getString(Pref.UPM_ERROR_UI_SHOWN_TIMESTAMP))
.thenReturn(Long.toString(timeOfFirstUpmPrompt));
mSharedPrefsManager.writeLong(
ChromePreferenceKeys.SYNC_ERROR_MESSAGE_SHOWN_AT_TIME, timeOfSyncPrompt);
mFakeTimeTestRule.advanceMillis(
PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_BETWEEN_PROMPTS_MS + 1);
assertTrue(PasswordManagerErrorMessageHelperBridge.shouldShowSignInErrorUI(mProfile));
}
@Test
public void testEnoughTimeSinceLastUpmError() {
final long timeOfFirstUpmPrompt = TimeUtils.currentTimeMillis();
when(mPrefService.getString(Pref.UPM_ERROR_UI_SHOWN_TIMESTAMP))
.thenReturn(Long.toString(timeOfFirstUpmPrompt));
mFakeTimeTestRule.advanceMillis(
PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_BETWEEN_PROMPTS_MS + 1);
assertTrue(
PasswordManagerErrorMessageHelperBridge.shouldShowUpdateGMSCoreErrorUI(mProfile));
}
@Test
public void testNotEnoughTimeSinceLastUpmError() {
final long timeOfFirstUpmPrompt = TimeUtils.currentTimeMillis();
when(mPrefService.getString(Pref.UPM_ERROR_UI_SHOWN_TIMESTAMP))
.thenReturn(Long.toString(timeOfFirstUpmPrompt));
assertFalse(
PasswordManagerErrorMessageHelperBridge.shouldShowUpdateGMSCoreErrorUI(mProfile));
}
@Test
public void testSaveErrorUIShownTimestamp() {
final long currentTimeMs = TimeUtils.currentTimeMillis();
final long timeIncrementMs = 30;
mFakeTimeTestRule.advanceMillis(timeIncrementMs);
PasswordManagerErrorMessageHelperBridge.saveErrorUiShownTimestamp(mProfile);
verify(mPrefService)
.setString(
Pref.UPM_ERROR_UI_SHOWN_TIMESTAMP,
Long.toString(currentTimeMs + timeIncrementMs));
}
@Test
public void testUpdateCredentialsRecordsSuccessWhenSigningInSucceeds() {
final Activity activity = mock(Activity.class);
when(mWindowAndroidMock.getActivity()).thenReturn(new WeakReference<>(activity));
doAnswer(
invocation -> {
Callback<Boolean> callback = invocation.getArgument(2);
callback.onResult(true);
return null;
})
.when(mFakeAccountManagerFacade)
.updateCredentials(
eq(CoreAccountInfo.getAndroidAccountFrom(mCoreAccountInfo)),
eq(activity),
any());
PasswordManagerErrorMessageHelperBridge.startUpdateAccountCredentialsFlow(
mWindowAndroidMock, mProfile);
assertEquals(
1,
RecordHistogram.getHistogramValueCountForTesting(
"PasswordManager.UPMUpdateSignInCredentialsSucces", 1));
assertEquals(
0,
RecordHistogram.getHistogramValueCountForTesting(
"PasswordManager.UPMUpdateSignInCredentialsSucces", 0));
}
@Test
public void testUpdateCredentialsRecordsSuccessWhenSigningInFailed() {
final Activity activity = mock(Activity.class);
when(mWindowAndroidMock.getActivity()).thenReturn(new WeakReference<>(activity));
doAnswer(
invocation -> {
Callback<Boolean> callback = invocation.getArgument(2);
callback.onResult(false);
return null;
})
.when(mFakeAccountManagerFacade)
.updateCredentials(
eq(CoreAccountInfo.getAndroidAccountFrom(mCoreAccountInfo)),
eq(activity),
any());
PasswordManagerErrorMessageHelperBridge.startUpdateAccountCredentialsFlow(
mWindowAndroidMock, mProfile);
assertEquals(
1,
RecordHistogram.getHistogramValueCountForTesting(
"PasswordManager.UPMUpdateSignInCredentialsSucces", 0));
assertEquals(
0,
RecordHistogram.getHistogramValueCountForTesting(
"PasswordManager.UPMUpdateSignInCredentialsSucces", 1));
}
@Test
public void testDontShowMessageOnIncognito() {
when(mIdentityServicesProviderMock.getIdentityManager(mProfile)).thenReturn(null);
final long timeOfFirstUpmPrompt = TimeUtils.currentTimeMillis();
final long timeOfSyncPrompt =
timeOfFirstUpmPrompt
+ PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_TO_SYNC_ERROR_MS;
when(mPrefService.getString(Pref.UPM_ERROR_UI_SHOWN_TIMESTAMP))
.thenReturn(Long.toString(timeOfFirstUpmPrompt));
mSharedPrefsManager.writeLong(
ChromePreferenceKeys.SYNC_ERROR_MESSAGE_SHOWN_AT_TIME, timeOfSyncPrompt);
mFakeTimeTestRule.advanceMillis(
PasswordManagerErrorMessageHelperBridge.MINIMAL_INTERVAL_BETWEEN_PROMPTS_MS + 1);
assertFalse(PasswordManagerErrorMessageHelperBridge.shouldShowSignInErrorUI(mProfile));
}
@Test
public void testDontShowMessageWithtoutAccount() {
when(mIdentityManagerMock.getPrimaryAccountInfo(ConsentLevel.SIGNIN)).thenReturn(null);
assertFalse(PasswordManagerErrorMessageHelperBridge.shouldShowSignInErrorUI(mProfile));
}
@Test
public void testDontTryToUpdateCredentialWithNoAccount() {
when(mIdentityManagerMock.getPrimaryAccountInfo(ConsentLevel.SIGNIN)).thenReturn(null);
PasswordManagerErrorMessageHelperBridge.startUpdateAccountCredentialsFlow(
mWindowAndroidMock, mProfile);
verify(mFakeAccountManagerFacade, never())
.updateCredentials(any(Account.class), any(Activity.class), any(Callback.class));
}
@Test
public void testStartTrustedVaultKeyRetrievalFlow() {
final Activity activity = mock(Activity.class);
when(mWindowAndroidMock.getActivity()).thenReturn(new WeakReference<>(activity));
when(mSyncService.getAccountInfo()).thenReturn(mCoreAccountInfo);
Promise<PendingIntent> intentPromise = new Promise<>();
when(mTrustedVaultClient.createKeyRetrievalIntent(any())).thenReturn(intentPromise);
PasswordManagerErrorMessageHelperBridge.startTrustedVaultKeyRetrievalFlow(
mWindowAndroidMock, mProfile);
intentPromise.fulfill(mPendingIntent);
ShadowLooper.idleMainLooper();
verify(activity).startActivity(any(), any());
}
}