chromium/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowMediatorTest.java

// 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.bookmarks;

import android.content.Context;
import android.content.res.Resources;

import org.junit.Assert;
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.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.commerce.PriceTrackingUtils;
import org.chromium.chrome.browser.commerce.PriceTrackingUtilsJni;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.price_tracking.PriceDropNotificationManager;
import org.chromium.chrome.browser.price_tracking.PriceDropNotificationManagerFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkItem;
import org.chromium.components.commerce.core.CommerceSubscription;
import org.chromium.components.commerce.core.IdentifierType;
import org.chromium.components.commerce.core.ManagementType;
import org.chromium.components.commerce.core.ShoppingService;
import org.chromium.components.commerce.core.SubscriptionType;
import org.chromium.components.commerce.core.SubscriptionsObserver;
import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
import org.chromium.components.power_bookmarks.ProductPrice;
import org.chromium.components.power_bookmarks.ShoppingSpecifics;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.shadows.ShadowAppCompatResources;
import org.chromium.url.GURL;

/** Unit tests for {@link BookmarkSaveFlowMediator}. */
@Batch(Batch.UNIT_TESTS)
@RunWith(BaseRobolectricTestRunner.class)
@Config(
        manifest = Config.NONE,
        shadows = {ShadowAppCompatResources.class})
@DisableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
public class BookmarkSaveFlowMediatorTest {
    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public JniMocker mJniMocker = new JniMocker();

    private BookmarkSaveFlowMediator mMediator;
    private PropertyModel mPropertyModel =
            new PropertyModel(ImprovedBookmarkSaveFlowProperties.ALL_KEYS);

    @Mock private Context mContext;
    @Mock private Resources mResources;
    @Mock private Runnable mCloseRunnable;
    @Mock private BookmarkModel mModel;
    @Mock private ShoppingService mShoppingService;
    @Mock private CommerceSubscription mSubscription;
    @Mock private BookmarkImageFetcher mBookmarkImageFetcher;
    @Mock private Profile mProfile;
    @Mock private IdentityManager mIdentityManager;
    @Mock PriceTrackingUtils.Natives mMockPriceTrackingUtilsJni;
    @Mock private PriceDropNotificationManager mMockNotificationManager;
    @Captor private ArgumentCaptor<SubscriptionsObserver> mSubscriptionsObserverCaptor;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(PriceTrackingUtilsJni.TEST_HOOKS, mMockPriceTrackingUtilsJni);
        Mockito.doReturn(mResources).when(mContext).getResources();
        PriceDropNotificationManagerFactory.setInstanceForTesting(mMockNotificationManager);
        mMediator =
                new BookmarkSaveFlowMediator(
                        mModel,
                        mPropertyModel,
                        mContext,
                        mCloseRunnable,
                        mShoppingService,
                        mBookmarkImageFetcher,
                        mProfile,
                        mIdentityManager);
        mMediator.setSubscriptionForTesting(mSubscription);
        Mockito.verify(mShoppingService)
                .addSubscriptionsObserver(mSubscriptionsObserverCaptor.capture());
    }

    @Test
    public void testSubscribedInBackground() {
        mMediator.setPriceTrackingToggleVisualsOnly(false);
        Assert.assertFalse(
                mPropertyModel.get(BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_TOGGLED));
        Assert.assertEquals(
                R.drawable.price_tracking_disabled,
                (int)
                        mPropertyModel.get(
                                BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_START_ICON_RES));

        mMediator.onSubscribe(mSubscription, true);
        Assert.assertTrue(
                mPropertyModel.get(BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_TOGGLED));
        Assert.assertEquals(
                R.drawable.price_tracking_enabled_filled,
                (int)
                        mPropertyModel.get(
                                BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_START_ICON_RES));
        Mockito.verify(mShoppingService, Mockito.never())
                .subscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
        Mockito.verify(mShoppingService, Mockito.never())
                .unsubscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
    }

    @Test
    @EnableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
    public void testSubscribedInBackground_improved() {
        mMediator.setPriceTrackingToggleVisualsOnly(false);
        Assert.assertFalse(
                mPropertyModel.get(
                        ImprovedBookmarkSaveFlowProperties.PRICE_TRACKING_SWITCH_CHECKED));

        mMediator.onSubscribe(mSubscription, true);
        Assert.assertTrue(
                mPropertyModel.get(
                        ImprovedBookmarkSaveFlowProperties.PRICE_TRACKING_SWITCH_CHECKED));
        Mockito.verify(mShoppingService, Mockito.never())
                .subscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
        Mockito.verify(mShoppingService, Mockito.never())
                .unsubscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
    }

    // Ensure the toggle logic still works when the subscription object changes but has identical
    // information.
    @Test
    public void testSubscribed_differentObjects() {
        String clusterId = "1234";
        CommerceSubscription original =
                new CommerceSubscription(
                        SubscriptionType.PRICE_TRACK,
                        IdentifierType.PRODUCT_CLUSTER_ID,
                        clusterId,
                        ManagementType.USER_MANAGED,
                        null);
        CommerceSubscription clone =
                new CommerceSubscription(
                        SubscriptionType.PRICE_TRACK,
                        IdentifierType.PRODUCT_CLUSTER_ID,
                        clusterId,
                        ManagementType.USER_MANAGED,
                        null);

        mMediator.setSubscriptionForTesting(original);

        mMediator.setPriceTrackingToggleVisualsOnly(false);
        Assert.assertFalse(
                mPropertyModel.get(BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_TOGGLED));
        Assert.assertEquals(
                R.drawable.price_tracking_disabled,
                (int)
                        mPropertyModel.get(
                                BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_START_ICON_RES));

        mMediator.onSubscribe(clone, true);
        Assert.assertTrue(
                mPropertyModel.get(BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_TOGGLED));
        Assert.assertEquals(
                R.drawable.price_tracking_enabled_filled,
                (int)
                        mPropertyModel.get(
                                BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_START_ICON_RES));
        Mockito.verify(mShoppingService, Mockito.never())
                .subscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
        Mockito.verify(mShoppingService, Mockito.never())
                .unsubscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
    }

    @Test
    @EnableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
    public void testSubscribed_differentObjects_improved() {
        String clusterId = "1234";
        CommerceSubscription original =
                new CommerceSubscription(
                        SubscriptionType.PRICE_TRACK,
                        IdentifierType.PRODUCT_CLUSTER_ID,
                        clusterId,
                        ManagementType.USER_MANAGED,
                        null);
        CommerceSubscription clone =
                new CommerceSubscription(
                        SubscriptionType.PRICE_TRACK,
                        IdentifierType.PRODUCT_CLUSTER_ID,
                        clusterId,
                        ManagementType.USER_MANAGED,
                        null);

        mMediator.setSubscriptionForTesting(original);

        mMediator.setPriceTrackingToggleVisualsOnly(false);
        Assert.assertFalse(
                mPropertyModel.get(
                        ImprovedBookmarkSaveFlowProperties.PRICE_TRACKING_SWITCH_CHECKED));

        mMediator.onSubscribe(clone, true);
        Assert.assertTrue(
                mPropertyModel.get(
                        ImprovedBookmarkSaveFlowProperties.PRICE_TRACKING_SWITCH_CHECKED));
        Mockito.verify(mShoppingService, Mockito.never())
                .subscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
        Mockito.verify(mShoppingService, Mockito.never())
                .unsubscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
    }

    @Test
    public void testUnsubscribedInBackground() {
        mMediator.setPriceTrackingToggleVisualsOnly(true);
        Assert.assertTrue(
                mPropertyModel.get(BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_TOGGLED));
        Assert.assertEquals(
                R.drawable.price_tracking_enabled_filled,
                (int)
                        mPropertyModel.get(
                                BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_START_ICON_RES));

        mMediator.onUnsubscribe(mSubscription, true);
        Assert.assertFalse(
                mPropertyModel.get(BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_TOGGLED));
        Assert.assertEquals(
                R.drawable.price_tracking_disabled,
                (int)
                        mPropertyModel.get(
                                BookmarkSaveFlowProperties.NOTIFICATION_SWITCH_START_ICON_RES));
        Mockito.verify(mShoppingService, Mockito.never())
                .subscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
        Mockito.verify(mShoppingService, Mockito.never())
                .unsubscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
    }

    @Test
    @EnableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
    public void testUnsubscribedInBackground_improved() {
        mMediator.setPriceTrackingToggleVisualsOnly(true);
        Assert.assertTrue(
                mPropertyModel.get(
                        ImprovedBookmarkSaveFlowProperties.PRICE_TRACKING_SWITCH_CHECKED));

        mMediator.onUnsubscribe(mSubscription, true);
        Assert.assertFalse(
                mPropertyModel.get(
                        ImprovedBookmarkSaveFlowProperties.PRICE_TRACKING_SWITCH_CHECKED));
        Mockito.verify(mShoppingService, Mockito.never())
                .subscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
        Mockito.verify(mShoppingService, Mockito.never())
                .unsubscribe(Mockito.any(CommerceSubscription.class), Mockito.any());
    }

    @Test
    public void testShow_FromExplicitPriceTracking() {
        BookmarkId bookmarkId = new BookmarkId(0, 0);
        PowerBookmarkMeta meta = PowerBookmarkMeta.newBuilder().build();
        BookmarkItem item =
                new BookmarkItem(
                        bookmarkId,
                        "",
                        new GURL("http://example.com"),
                        false,
                        new BookmarkId(1, 0),
                        true,
                        false,
                        0,
                        false,
                        0,
                        false);
        Mockito.doReturn(item).when(mModel).getBookmarkById(Mockito.any());
        Mockito.doReturn("title").when(mModel).getBookmarkTitle(Mockito.any());

        mMediator.show(bookmarkId, meta, /* fromExplicitTrackUi= */ true, false, false);

        Mockito.verify(mMockPriceTrackingUtilsJni)
                .setPriceTrackingStateForBookmark(
                        Mockito.any(),
                        Mockito.anyLong(),
                        Mockito.eq(true),
                        Mockito.any(),
                        Mockito.anyBoolean());
    }

    @Test
    public void testShow_NonPriceTracking() {
        BookmarkId bookmarkId = new BookmarkId(0, 0);
        BookmarkItem item =
                new BookmarkItem(
                        bookmarkId,
                        "",
                        new GURL("http://example.com"),
                        false,
                        new BookmarkId(1, 0),
                        true,
                        false,
                        0,
                        false,
                        0,
                        false);
        Mockito.doReturn(item).when(mModel).getBookmarkById(Mockito.any());
        Mockito.doReturn("title").when(mModel).getBookmarkTitle(Mockito.any());

        mMediator.show(bookmarkId, null, /* fromExplicitTrackUi= */ false, false, false);

        Mockito.verify(mMockPriceTrackingUtilsJni, Mockito.never())
                .setPriceTrackingStateForBookmark(
                        Mockito.any(),
                        Mockito.anyLong(),
                        Mockito.anyBoolean(),
                        Mockito.any(),
                        Mockito.anyBoolean());
    }

    @Test
    public void subscriptionCreatesNotificationChannel() {
        BookmarkId bookmarkId = new BookmarkId(0, 0);
        ShoppingSpecifics specifics =
                ShoppingSpecifics.newBuilder()
                        .setProductClusterId(123)
                        .setOfferId(456)
                        .setCountryCode("us")
                        .setCurrentPrice(ProductPrice.newBuilder().setAmountMicros(100).build())
                        .build();
        PowerBookmarkMeta meta =
                PowerBookmarkMeta.newBuilder().setShoppingSpecifics(specifics).build();
        CommerceSubscription sub =
                new CommerceSubscription(
                        SubscriptionType.PRICE_TRACK,
                        IdentifierType.PRODUCT_CLUSTER_ID,
                        /* id= */ "123",
                        ManagementType.USER_MANAGED,
                        /* userSeenOffer= */ null);
        mMediator.setSubscriptionForTesting(sub);
        BookmarkItem item =
                new BookmarkItem(
                        bookmarkId,
                        /* title= */ "",
                        new GURL("http://example.com"),
                        /* isFolder= */ false,
                        /* parentId= */ new BookmarkId(1, 0),
                        /* isEditable= */ true,
                        /* isManaged= */ false,
                        /* dateAdded= */ 0,
                        /* read= */ false,
                        /* dateLastOpened= */ 0,
                        /* isAccountBookmark= */ false);
        Mockito.doReturn(item).when(mModel).getBookmarkById(Mockito.any());
        Mockito.doReturn("title").when(mModel).getBookmarkTitle(Mockito.any());

        mMediator.show(bookmarkId, meta, /* fromExplicitTrackUi= */ true, false, false);

        // We should see a call to subscribe to the product.
        Mockito.verify(mMockPriceTrackingUtilsJni)
                .setPriceTrackingStateForBookmark(
                        Mockito.any(),
                        Mockito.anyLong(),
                        Mockito.eq(true),
                        Mockito.any(),
                        Mockito.anyBoolean());

        // Simulate a successful subscription.
        mSubscriptionsObserverCaptor.getValue().onSubscribe(sub, true);

        Mockito.verify(mMockNotificationManager).createNotificationChannel();
    }
}