chromium/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/RelatedSearchesStampTest.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.contextualsearch;

import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.net.Uri;
import android.text.TextUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.chromium.base.FeatureList;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;

/** Tests the {@link RelatedSearchesStamp} class. */
@RunWith(BaseRobolectricTestRunner.class)
public class RelatedSearchesStampTest {
    /** The "stamp" encodes the experiment and its processing history, and is built from these. */
    private static final String RELATED_SEARCHES_LANGUAGE_RESTRICTION = "l";

    /**
     * The stamps to use for various experiment configurations. Note that users still may need
     * the ability to send everything in order to keep the experiment populations balanced.
     */
    private static final String EXPECTED_DEFAULT_STAMP = "1Rs";
    private static final String EXPECTED_DEFAULT_STAMP_LANGUAGE_RESTRICTED = "1Rsl";
    private static final String EXPECTED_DEFAULT_STAMP_ALL_LANGUAGE = "1Rsa";

    /** The stamp CGI parameter. */
    private static final String RELATED_SEARCHES_STAMP_PARAM = "ctxsl_rs";

    private static final String EXPECTED_POSITION_ENDING = "Up";
    private static final Uri SAMPLE_URI =
            Uri.parse("https://www.google.com/search?q=query&ctxsl_rs=" + EXPECTED_DEFAULT_STAMP);

    private static final String ENGLISH = "en";
    private static final String SPANISH = "es";
    private static final String GERMAN = "de";

    @Mock private Profile mProfile;

    private ContextualSearchPolicy mPolicy;
    private FeatureList.TestValues mFeatureListValues;

    /** Our instance under test. */
    private RelatedSearchesStamp mStamp;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mFeatureListValues = new FeatureList.TestValues();
        FeatureList.setTestValues(mFeatureListValues);
        mPolicy = new ContextualSearchPolicy(mProfile, null, null);
        mStamp = new RelatedSearchesStamp(mPolicy);
    }

    // ====================================================================================
    // Helper methods
    // ====================================================================================

    /** Sets whether the user has allowed sending content (has done the opt-in). */
    private void setCanSendContent(boolean canSend) {
        mPolicy.overrideDecidedStateForTesting(canSend);
    }

    /**
     * Sets whether the user has allowed sending the URL (has enabled "Make search and browsing
     * better").
     */
    private void setCanSendUrl(boolean canSend) {
        mPolicy.overrideAllowSendingPageUrlForTesting(canSend);
    }

    /**
     * Sets whether the config specifies if the content can be any language to get any Related
     * Searches.
     */
    private void setSupportAllLanguage(boolean support) {
        mFeatureListValues.addFeatureFlagOverride(
                ChromeFeatureList.RELATED_SEARCHES_ALL_LANGUAGE, support);
    }

    /** Sets whether the Related Searches switch is enabled. */
    private void setRelatedSearchesSwitch(boolean enable) {
        mFeatureListValues.addFeatureFlagOverride(
                ChromeFeatureList.RELATED_SEARCHES_SWITCH, enable);
    }

    /** Sets the standard config setup that we're using for Related Searches experiments. */
    private void setStandardExperimentRequirements() {
        // For experimentation we currently require all users have all the permissions
        // for all experiment arms, and we restrict the language to English-only.
        setSupportAllLanguage(false);
    }

    /** Sets a standard config setup for the default Related Searches launch configuration. */
    private void setStandardDefaultLaunchConfiguration() {
        setStandardExperimentRequirements();
        setCanSendUrl(true);
        setCanSendContent(true);
        setRelatedSearchesSwitch(true);
    }

    // ====================================================================================
    // TESTS
    // ====================================================================================

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testGetRelatedSearchesStampForUnspecifiedExperiments() {
        setStandardDefaultLaunchConfiguration();
        // When there's no stamp in the config we expect to build a version-1 stamp.
        // This can happen when flags are flipped manually.
        assertThat(
                "A config without any stamp should default to language restricted, but German is "
                        + "still generating suggestions!",
                mStamp.getRelatedSearchesStamp(GERMAN),
                is(""));
        assertThat(
                "A config without any stamp should default to language restricted with both kinds "
                        + "of suggestions, but is not!",
                mStamp.getRelatedSearchesStamp(ENGLISH),
                is(EXPECTED_DEFAULT_STAMP + RELATED_SEARCHES_LANGUAGE_RESTRICTION));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testGetStampNotUrlQualified() {
        setStandardDefaultLaunchConfiguration();
        setCanSendUrl(false);
        assertTrue(
                "Users that have not enabled sending a URL are still generating Related Searches "
                        + "on a URL experiment!",
                TextUtils.isEmpty(mStamp.getRelatedSearchesStamp(ENGLISH)));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testGetStampNotContentQualified() {
        setStandardDefaultLaunchConfiguration();
        setCanSendContent(false);
        assertTrue(
                "Users that have not enabled sending page content are still generating Related "
                        + "Searches on a content-only experiment!",
                TextUtils.isEmpty(mStamp.getRelatedSearchesStamp(ENGLISH)));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testGetStampNotLanguageQualified() {
        setStandardDefaultLaunchConfiguration();
        assertFalse(
                "A standard experiment with both inputs is not generating Related Searches for "
                        + "English, but should!",
                TextUtils.isEmpty(mStamp.getRelatedSearchesStamp(ENGLISH)));
        assertTrue(
                "A standard experiment with both inputs is generating Related Searches for German, "
                        + "but should not!",
                TextUtils.isEmpty(mStamp.getRelatedSearchesStamp(GERMAN)));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testGetStampLanguageRestricted() {
        setStandardDefaultLaunchConfiguration();
        assertThat(
                "A launch configuration with multiple languages is not generating the expected "
                        + "processing stamp for English!",
                mStamp.getRelatedSearchesStamp(ENGLISH),
                is(EXPECTED_DEFAULT_STAMP_LANGUAGE_RESTRICTED));
        assertThat(
                "A launch configuration with multiple languages is generating Related Searches "
                        + "when it should be language restricted for German!",
                mStamp.getRelatedSearchesStamp(GERMAN),
                is(""));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testGetStampLanguageRestrictedForAllLanguages() {
        setStandardDefaultLaunchConfiguration();
        setSupportAllLanguage(true);
        assertThat(
                "A launch configuration with all languages support is not generating the expected "
                        + "processing stamp for English!",
                mStamp.getRelatedSearchesStamp(ENGLISH),
                is(EXPECTED_DEFAULT_STAMP_ALL_LANGUAGE));
        assertThat(
                "A launch configuration with all languages support is not generating the expected "
                        + "processing stamp for Spanish!",
                mStamp.getRelatedSearchesStamp(SPANISH),
                is(EXPECTED_DEFAULT_STAMP_ALL_LANGUAGE));
        assertThat(
                "A launch configuration with all languages support is not generating the expected "
                        + "processing stamp for German!",
                mStamp.getRelatedSearchesStamp(GERMAN),
                is(EXPECTED_DEFAULT_STAMP_ALL_LANGUAGE));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testUpdateUriForSelectedPosition() {
        setStandardDefaultLaunchConfiguration();
        Uri updatedUri = RelatedSearchesStamp.updateUriForSuggestionPosition(SAMPLE_URI, 3);
        String stampParam = updatedUri.getQueryParameter(RELATED_SEARCHES_STAMP_PARAM);
        assertThat(
                "Appending the UI position of a chosen Related Searches suggestion doesn't have "
                        + "the expected prefix!",
                stampParam,
                startsWith(EXPECTED_DEFAULT_STAMP));
        assertThat(
                "Appending the UI position of a chosen Related Searches suggestion doesn't have "
                        + "the expected psotion index!",
                stampParam,
                endsWith(EXPECTED_POSITION_ENDING + "3"));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testReplaceQueryParam() {
        Uri updatedQuery = RelatedSearchesStamp.replaceQueryParam(SAMPLE_URI, "q", "newQuery");
        assertTrue(
                "Replacing a query parameter is not producing the expected CGI param!",
                updatedQuery.toString().contains("?q=newQuery"));
        String doubleUpdatedQuery =
                RelatedSearchesStamp.replaceQueryParam(updatedQuery, "q", "newerQuery").toString();
        assertTrue(
                "Replacing a query parameter with a newer one is not producing the expected CGI "
                        + "param!",
                doubleUpdatedQuery.contains("?q=newerQuery"));
        assertFalse(
                "Replacing a query parameter appears to fail to remove the original param!",
                doubleUpdatedQuery.contains("newQuery"));
        // Test removal
        String uriWithNoQ =
                RelatedSearchesStamp.replaceQueryParam(SAMPLE_URI, "q", null).toString();
        assertFalse("Removing a query parameter isn't working!", uriWithNoQ.contains("q"));
        // Test replacement of a param that doesn't exist
        String shouldBeUnchanged =
                RelatedSearchesStamp.replaceQueryParam(SAMPLE_URI, "qqq", "newQuery").toString();
        assertTrue(
                "Replacing a non-existing parameter is removing an existing one!",
                shouldBeUnchanged.contains("?q=query"));
        assertFalse(
                "Replacing a non-existing parameter is adding the new parameter anyway!",
                shouldBeUnchanged.contains("qqq"));
    }

    @Test
    @Feature({"RelatedSearches", "RelatedSearchesStamp"})
    public void testRelatedSearchSwitchIsDisabled() {
        setStandardDefaultLaunchConfiguration();
        setRelatedSearchesSwitch(false);
        assertThat(
                "related searches should be disabled!",
                mStamp.getRelatedSearchesStamp(GERMAN),
                is(""));
        assertThat(
                "related searches should be disabled!",
                mStamp.getRelatedSearchesStamp(ENGLISH),
                is(""));
    }
}