chromium/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AutofillTest.java

// Copyright 2015 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.sync;

import android.util.Pair;

import androidx.test.filters.LargeTest;

import org.hamcrest.Matchers;
import org.json.JSONException;
import org.json.JSONObject;
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.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.CriteriaNotSatisfiedException;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.sync.SyncTestUtil;
import org.chromium.components.sync.DataType;
import org.chromium.components.sync.UserSelectableType;
import org.chromium.components.sync.protocol.AutofillProfileSpecifics;
import org.chromium.components.sync.protocol.EntitySpecifics;

import java.util.ArrayList;
import java.util.List;

/** Test suite for the autofill profile sync data type. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AutofillTest {
    @Rule public SyncTestRule mSyncTestRule = new SyncTestRule();

    private static final String AUTOFILL_TYPE = "Autofill Profiles";

    private static final String GUID = "EDC609ED-7EEE-4F27-B00C-423242A9C44B";

    private static final String STREET = "1600 Amphitheatre Pkwy";
    private static final String CITY = "Mountain View";
    private static final String MODIFIED_CITY = "Sunnyvale";
    private static final String STATE = "CA";
    private static final String ZIP = "94043";

    // A container to store autofill profile information for data verification.
    private static class Autofill {
        public final String id;
        public final String clientTagHash;
        public final String street;
        public final String city;
        public final String state;
        public final String zip;

        public Autofill(
                String id,
                String clientTagHash,
                String street,
                String city,
                String state,
                String zip) {
            this.id = id;
            this.clientTagHash = clientTagHash;
            this.street = street;
            this.city = city;
            this.state = state;
            this.zip = zip;
        }
    }

    @Before
    public void setUp() throws Exception {
        mSyncTestRule.setUpAccountAndEnableSyncForTesting();
        // Make sure the initial state is clean.
        assertClientAutofillProfileCount(0);
        assertServerAutofillProfileCountWithName(0, STREET);
    }

    // Test syncing an autofill profile from server to client.
    @Test
    @LargeTest
    @Feature({"Sync"})
    public void testDownloadAutofill() throws Exception {
        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
        SyncTestUtil.triggerSync();
        waitForClientAutofillProfileCount(1);

        // Verify data synced to client.
        List<Autofill> autofills = getClientAutofillProfiles();
        Assert.assertEquals(
                "Only the injected autofill should exist on the client.", 1, autofills.size());
        Autofill autofill = autofills.get(0);
        Assert.assertEquals("The wrong street was found for the address.", STREET, autofill.street);
        Assert.assertEquals("The wrong city was found for the autofill.", CITY, autofill.city);
        Assert.assertEquals("The wrong state was found for the autofill.", STATE, autofill.state);
        Assert.assertEquals("The wrong zip was found for the autofill.", ZIP, autofill.zip);
    }

    // Test syncing an autofill profile modification from server to client.
    @Test
    @LargeTest
    @Feature({"Sync"})
    public void testDownloadAutofillModification() throws Exception {
        // Add the entity to test modifying.
        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
        SyncTestUtil.triggerSync();
        waitForClientAutofillProfileCount(1);

        // Modify on server, sync, and verify modification locally.
        Autofill autofill = getClientAutofillProfiles().get(0);
        mSyncTestRule
                .getFakeServerHelper()
                .modifyEntitySpecifics(
                        autofill.id, getServerAutofillProfile(STREET, MODIFIED_CITY, STATE, ZIP));
        SyncTestUtil.triggerSync();
        mSyncTestRule.pollInstrumentationThread(
                () -> {
                    try {
                        Autofill modifiedAutofill = getClientAutofillProfiles().get(0);
                        Criteria.checkThat(modifiedAutofill.city, Matchers.is(MODIFIED_CITY));
                    } catch (JSONException ex) {
                        throw new RuntimeException(ex);
                    }
                });
    }

    // Test syncing an autofill profile deletion from server to client.
    @Test
    @LargeTest
    @Feature({"Sync"})
    public void testDownloadDeletedAutofill() throws Exception {
        // Add the entity to test deleting.
        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
        SyncTestUtil.triggerSync();
        waitForClientAutofillProfileCount(1);

        // Delete on server, sync, and verify deleted locally.
        Autofill autofill = getClientAutofillProfiles().get(0);
        mSyncTestRule.getFakeServerHelper().deleteEntity(autofill.id, autofill.clientTagHash);
        SyncTestUtil.triggerSync();
        waitForClientAutofillProfileCount(0);
    }

    // Test that autofill profiles don't get synced if the data type is disabled.
    @Test
    @LargeTest
    @Feature({"Sync"})
    public void testDisabledNoDownloadAutofill() throws Exception {
        // The AUTOFILL type here controls both AUTOFILL and AUTOFILL_PROFILE.
        mSyncTestRule.disableDataType(UserSelectableType.AUTOFILL);
        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
        SyncTestUtil.triggerSyncAndWaitForCompletion();
        assertClientAutofillProfileCount(0);
    }

    private EntitySpecifics getServerAutofillProfile(
            String street, String city, String state, String zip) {
        AutofillProfileSpecifics profile =
                AutofillProfileSpecifics.newBuilder()
                        .setGuid(GUID)
                        .setAddressHomeLine1(street)
                        .setAddressHomeCity(city)
                        .setAddressHomeState(state)
                        .setAddressHomeZip(zip)
                        .build();
        return EntitySpecifics.newBuilder().setAutofillProfile(profile).build();
    }

    private void addServerAutofillProfile(EntitySpecifics specifics) {
        mSyncTestRule
                .getFakeServerHelper()
                .injectUniqueClientEntity(
                        specifics.getAutofillProfile().getGuid()
                        /* nonUniqueName= */ ,
                        specifics.getAutofillProfile().getGuid()
                        /* clientTag= */ ,
                        specifics);
    }

    private List<Autofill> getClientAutofillProfiles() throws JSONException {
        List<Pair<String, JSONObject>> entities =
                SyncTestUtil.getLocalData(mSyncTestRule.getTargetContext(), AUTOFILL_TYPE);
        List<Autofill> autofills = new ArrayList<Autofill>(entities.size());
        for (Pair<String, JSONObject> entity : entities) {
            String id = entity.first;
            JSONObject profile = entity.second;
            String clientTagHash = "";
            if (profile.has("metadata")) {
                JSONObject metadata = profile.getJSONObject("metadata");
                if (metadata.has("client_tag_hash")) {
                    clientTagHash = metadata.getString("client_tag_hash");
                }
            }
            String street = profile.getString("address_home_line1");
            String city = profile.getString("address_home_city");
            String state = profile.getString("address_home_state");
            String zip = profile.getString("address_home_zip");
            autofills.add(new Autofill(id, clientTagHash, street, city, state, zip));
        }
        return autofills;
    }

    private void assertClientAutofillProfileCount(int count) throws JSONException {
        Assert.assertEquals(
                "There should be " + count + " local autofill profiles.",
                count,
                SyncTestUtil.getLocalData(mSyncTestRule.getTargetContext(), AUTOFILL_TYPE).size());
    }

    private void assertServerAutofillProfileCountWithName(int count, String name) {
        Assert.assertTrue(
                "Expected " + count + " server autofill profiles with name " + name + ".",
                mSyncTestRule
                        .getFakeServerHelper()
                        .verifyEntityCountByTypeAndName(count, DataType.AUTOFILL_PROFILE, name));
    }

    private void waitForClientAutofillProfileCount(int count) {
        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    try {
                        Criteria.checkThat(
                                SyncTestUtil.getLocalData(
                                                mSyncTestRule.getTargetContext(), AUTOFILL_TYPE)
                                        .size(),
                                Matchers.is(count));
                    } catch (JSONException ex) {
                        throw new CriteriaNotSatisfiedException(ex);
                    }
                },
                SyncTestUtil.TIMEOUT_MS,
                SyncTestUtil.INTERVAL_MS);
    }
}