blob: fcb03db702794a19f195d03824dd91ed9c10266d [file] [log] [blame]
// Copyright 2015 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.sync;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.test.InstrumentationRegistry;
import androidx.annotation.Nullable;
import androidx.preference.TwoStatePreference;
import androidx.test.espresso.contrib.RecyclerViewActions;
import org.junit.Assert;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.chromium.base.IntentUtils;
import org.chromium.base.Promise;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.UnifiedConsentServiceBridge;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
import org.chromium.chrome.test.util.browser.signin.SigninTestUtil;
import org.chromium.chrome.test.util.browser.sync.SyncTestUtil;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.sync.ModelType;
import org.chromium.components.sync.protocol.AutofillWalletSpecifics;
import org.chromium.components.sync.protocol.EntitySpecifics;
import org.chromium.components.sync.protocol.SyncEntity;
import org.chromium.components.sync.protocol.WalletMaskedCreditCard;
import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
/**
* TestRule for common functionality between sync tests.
*/
public class SyncTestRule extends ChromeTabbedActivityTestRule {
private static final String TAG = "SyncTestBase";
private static final Set<Integer> USER_SELECTABLE_TYPES =
new HashSet<Integer>(Arrays.asList(new Integer[] {
ModelType.AUTOFILL, ModelType.BOOKMARKS, ModelType.PASSWORDS,
ModelType.PREFERENCES, ModelType.PROXY_TABS, ModelType.TYPED_URLS,
}));
/**
* Simple activity that mimics a trusted vault key retrieval flow that succeeds immediately.
*/
public static class DummyKeyRetrievalActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_OK);
FakeTrustedVaultClientBackend.get().startPopulateKeys();
finish();
}
};
/**
* Simple activity that mimics a trusted vault degraded recoverability fix flow that succeeds
* immediately.
*/
public static class DummyRecoverabilityDegradedFixActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_OK);
FakeTrustedVaultClientBackend.get().setRecoverabilityDegraded(false);
finish();
}
};
/**
* A fake implementation of TrustedVaultClient.Backend. Allows to specify keys to be fetched.
* Keys aren't populated through fetchKeys() unless startPopulateKeys() is called.
* startPopulateKeys() is called by DummyKeyRetrievalActivity before its completion to mimic
* real TrustedVaultClient.Backend implementation.
*
* Similarly, recoverability-degraded logic is implemented with a dummy activity. Tests can
* choose to enter this state via invoking setRecoverabilityDegraded(true), and the state can be
* resolved with DummyRecoverabilityDegradedFixActivity.
*/
public static class FakeTrustedVaultClientBackend implements TrustedVaultClient.Backend {
private static FakeTrustedVaultClientBackend sInstance;
private boolean mPopulateKeys;
private boolean mRecoverabilityDegraded;
private @Nullable List<byte[]> mKeys;
public FakeTrustedVaultClientBackend() {
mPopulateKeys = false;
mRecoverabilityDegraded = false;
}
public static FakeTrustedVaultClientBackend get() {
if (sInstance == null) {
sInstance = new FakeTrustedVaultClientBackend();
}
return sInstance;
}
@Override
public Promise<List<byte[]>> fetchKeys(CoreAccountInfo accountInfo) {
if (mKeys == null || !mPopulateKeys) {
return Promise.rejected();
}
return Promise.fulfilled(mKeys);
}
@Override
public Promise<PendingIntent> createKeyRetrievalIntent(CoreAccountInfo accountInfo) {
Context context = InstrumentationRegistry.getContext();
Intent intent = new Intent(context, DummyKeyRetrievalActivity.class);
return Promise.fulfilled(PendingIntent.getActivity(context, 0 /* requestCode */, intent,
IntentUtils.getPendingIntentMutabilityFlag(false)));
}
@Override
public Promise<Boolean> markKeysAsStale(CoreAccountInfo accountInfo) {
return Promise.rejected();
}
@Override
public Promise<Boolean> getIsRecoverabilityDegraded(CoreAccountInfo accountInfo) {
return Promise.fulfilled(mRecoverabilityDegraded);
}
@Override
public Promise<PendingIntent> createRecoverabilityDegradedIntent(
CoreAccountInfo accountInfo) {
Context context = InstrumentationRegistry.getContext();
Intent intent = new Intent(context, DummyRecoverabilityDegradedFixActivity.class);
return Promise.fulfilled(PendingIntent.getActivity(context, 0 /* requestCode */, intent,
IntentUtils.getPendingIntentMutabilityFlag(false)));
}
@Override
public Promise<PendingIntent> createOptInIntent(CoreAccountInfo accountInfo) {
return Promise.rejected();
}
public void setKeys(List<byte[]> keys) {
mKeys = Collections.unmodifiableList(keys);
}
public void startPopulateKeys() {
mPopulateKeys = true;
}
public void setRecoverabilityDegraded(boolean degraded) {
mRecoverabilityDegraded = degraded;
}
}
private Context mContext;
private FakeServerHelper mFakeServerHelper;
private SyncService mSyncService;
private MockSyncContentResolverDelegate mSyncContentResolver;
private final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
private void ruleTearDown() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mSyncService.setSyncRequested(false);
mFakeServerHelper = null;
FakeServerHelper.destroyInstance();
});
SyncService.resetForTests();
}
public SyncTestRule() {}
/**Getters for Test variables */
public Context getTargetContext() {
return mContext;
}
public FakeServerHelper getFakeServerHelper() {
return mFakeServerHelper;
}
public SyncService getSyncService() {
return mSyncService;
}
MockSyncContentResolverDelegate getSyncContentResolver() {
return mSyncContentResolver;
}
public void startMainActivityForSyncTest() throws Exception {
// Start the activity by opening about:blank. This URL is ideal because it is not synced as
// a typed URL. If another URL is used, it could interfere with test data.
startMainActivityOnBlankPage();
}
/**
* Adds an account of default account name to AccountManagerFacade and waits for the seeding.
*/
public CoreAccountInfo addTestAccount() {
return addAccount(AccountManagerTestRule.TEST_ACCOUNT_EMAIL);
}
/**
* Adds an account of given account name to AccountManagerFacade and waits for the seeding.
*/
public CoreAccountInfo addAccount(String accountName) {
CoreAccountInfo coreAccountInfo =
mAccountManagerTestRule.addAccountAndWaitForSeeding(accountName);
Assert.assertFalse(SyncTestUtil.isSyncRequested());
return coreAccountInfo;
}
/**
* Returns the currently signed in account.
*/
public CoreAccountInfo getCurrentSignedInAccount() {
return mAccountManagerTestRule.getCurrentSignedInAccount();
}
/**
* Set up a test account, sign in and enable sync. FirstSetupComplete bit will be set after
* this. For most purposes this function should be used as this emulates the basic sign in flow.
* @return the test account that is signed in.
*/
public CoreAccountInfo setUpAccountAndEnableSyncForTesting() {
CoreAccountInfo accountInfo =
mAccountManagerTestRule.addTestAccountThenSigninAndEnableSync(mSyncService);
// Enable UKM when enabling sync as it is done by the sync confirmation UI.
enableUKM();
SyncTestUtil.waitForSyncFeatureActive();
SyncTestUtil.triggerSyncAndWaitForCompletion();
return accountInfo;
}
/**
* Set up a test account and sign in. Does not setup sync.
* @return the test accountInfo that is signed in.
*/
public CoreAccountInfo setUpAccountAndSignInForTesting() {
return mAccountManagerTestRule.addTestAccountThenSignin();
}
/**
* Set up a test account, sign in but don't mark sync setup complete.
* @return the test account that is signed in.
*/
public CoreAccountInfo setUpTestAccountAndSignInWithSyncSetupAsIncomplete() {
CoreAccountInfo accountInfo = mAccountManagerTestRule.addTestAccountThenSigninAndEnableSync(
/* syncService= */ null);
// Enable UKM when enabling sync as it is done by the sync confirmation UI.
enableUKM();
SyncTestUtil.waitForSyncTransportActive();
return accountInfo;
}
public void startSync() {
TestThreadUtils.runOnUiThreadBlocking(() -> { mSyncService.setSyncRequested(true); });
}
public void startSyncAndWait() {
startSync();
SyncTestUtil.waitForSyncFeatureActive();
}
public void stopSync() {
TestThreadUtils.runOnUiThreadBlocking(() -> { mSyncService.setSyncRequested(false); });
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
public void signinAndEnableSync(final CoreAccountInfo accountInfo) {
SigninTestUtil.signinAndEnableSync(accountInfo, mSyncService);
// Enable UKM when enabling sync as it is done by the sync confirmation UI.
enableUKM();
SyncTestUtil.waitForSyncFeatureActive();
SyncTestUtil.triggerSyncAndWaitForCompletion();
}
public void signOut() {
mAccountManagerTestRule.signOut();
Assert.assertNull(mAccountManagerTestRule.getCurrentSignedInAccount());
Assert.assertFalse(SyncTestUtil.isSyncRequested());
}
public void clearServerData() {
mFakeServerHelper.clearServerData();
SyncTestUtil.triggerSync();
CriteriaHelper.pollUiThread(() -> {
return !SyncService.get().isSyncRequested();
}, SyncTestUtil.TIMEOUT_MS, SyncTestUtil.INTERVAL_MS);
}
/*
* Enables the |modelType| Sync data type, which must be in USER_SELECTABLE_TYPES.
*/
public void enableDataType(final int modelType) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
Set<Integer> chosenTypes = mSyncService.getChosenDataTypes();
chosenTypes.add(modelType);
mSyncService.setChosenDataTypes(false, chosenTypes);
});
}
/*
* Enables the |chosenDataTypes|, which must be in USER_SELECTABLE_TYPES.
*/
public void setChosenDataTypes(boolean syncEverything, Set<Integer> chosenDataTypes) {
TestThreadUtils.runOnUiThreadBlocking(
() -> { mSyncService.setChosenDataTypes(syncEverything, chosenDataTypes); });
}
/*
* Sets payments integration to |enabled|.
*/
public void setPaymentsIntegrationEnabled(final boolean enabled) {
TestThreadUtils.runOnUiThreadBlocking(
() -> PersonalDataManager.setPaymentsIntegrationEnabled(enabled));
}
/*
* Disables the |modelType| Sync data type, which must be in USER_SELECTABLE_TYPES.
*/
public void disableDataType(final int modelType) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
Set<Integer> chosenTypes = mSyncService.getChosenDataTypes();
chosenTypes.remove(modelType);
mSyncService.setChosenDataTypes(false, chosenTypes);
});
}
public void pollInstrumentationThread(Runnable criteria) {
CriteriaHelper.pollInstrumentationThread(
criteria, SyncTestUtil.TIMEOUT_MS, SyncTestUtil.INTERVAL_MS);
}
public void pollInstrumentationThread(Callable<Boolean> criteria, String reason) {
CriteriaHelper.pollInstrumentationThread(
criteria, reason, SyncTestUtil.TIMEOUT_MS, SyncTestUtil.INTERVAL_MS);
}
@Override
public Statement apply(final Statement statement, final Description desc) {
final Statement base = super.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
mSyncContentResolver = new MockSyncContentResolverDelegate();
mSyncContentResolver.setMasterSyncAutomatically(true);
TestThreadUtils.runOnUiThreadBlocking(
() -> SyncContentResolverDelegate.overrideForTests(mSyncContentResolver));
TrustedVaultClient.setInstanceForTesting(
new TrustedVaultClient(FakeTrustedVaultClientBackend.get()));
// Load native since the FakeServer needs it and possibly SyncService as well
// (depends on what fake is provided by |createSyncServiceImpl()|).
NativeLibraryTestUtils.loadNativeLibraryAndInitBrowserProcess();
TestThreadUtils.runOnUiThreadBlocking(() -> {
SyncServiceImpl syncService = createSyncServiceImpl();
if (syncService != null) {
SyncService.overrideForTests(syncService);
}
mSyncService = SyncService.get();
mContext = InstrumentationRegistry.getTargetContext();
mFakeServerHelper = FakeServerHelper.createInstanceAndGet();
});
startMainActivityForSyncTest();
// Ensure SyncController is created.
TestThreadUtils.runOnUiThreadBlocking(() -> SyncController.get());
statement.evaluate();
}
}, desc);
return mAccountManagerTestRule.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
ruleTearDown();
}
}, desc);
}
/*
* Adds a credit card to server for autofill.
*/
public void addServerAutofillCreditCard() {
final String serverId = "025eb937c022489eb8dc78cbaa969218";
WalletMaskedCreditCard card =
WalletMaskedCreditCard.newBuilder()
.setId(serverId)
.setStatus(WalletMaskedCreditCard.WalletCardStatus.VALID)
.setNameOnCard("Jon Doe")
.setType(WalletMaskedCreditCard.WalletCardType.UNKNOWN)
.setLastFour("1111")
.setExpMonth(11)
.setExpYear(2020)
.build();
AutofillWalletSpecifics wallet_specifics =
AutofillWalletSpecifics.newBuilder()
.setType(AutofillWalletSpecifics.WalletInfoType.MASKED_CREDIT_CARD)
.setMaskedCard(card)
.build();
EntitySpecifics specifics =
EntitySpecifics.newBuilder().setAutofillWallet(wallet_specifics).build();
SyncEntity entity = SyncEntity.newBuilder()
.setName(serverId)
.setIdString(serverId)
.setSpecifics(specifics)
.build();
getFakeServerHelper().setWalletData(entity);
SyncTestUtil.triggerSyncAndWaitForCompletion();
}
/*
* Checks if server has any credit card information to autofill.
*/
public boolean hasServerAutofillCreditCards() {
return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
List<CreditCard> cards = PersonalDataManager.getInstance().getCreditCardsForSettings();
for (int i = 0; i < cards.size(); i++) {
if (!cards.get(i).getIsLocal()) return true;
}
return false;
});
}
// UI interaction convenience methods.
public void togglePreference(final TwoStatePreference pref) {
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.actionOnItem(
hasDescendant(withText(pref.getTitle().toString())), click()));
}
/**
* Returns an instance of SyncServiceImpl that can be overridden by subclasses.
*/
protected SyncServiceImpl createSyncServiceImpl() {
return null;
}
private static void enableUKM() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
// Outside of tests, URL-keyed anonymized data collection is enabled by sign-in UI.
UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(
Profile.getLastUsedRegularProfile(), true);
});
}
}