// 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.subscriptions;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
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.url.GURL;
import java.util.concurrent.TimeUnit;
/** Tests for {@link ImplicitPriceDropSubscriptionsManager}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ImplicitPriceDropSubscriptionsManagerUnitTest {
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final String URL1 = "http://www.foo.com";
private static final String URL2 = "http://www.bar.com";
private static final int POSITION1 = 0;
private static final int POSITION2 = 1;
private static final String OFFER1_ID = "offer_foo";
private static final String OFFER2_ID = "offer_bar";
private static final String TAB_ELIGIBLE_HISTOGRAM = "Commerce.Subscriptions.TabEligible";
static class TestImplicitPriceDropSubscriptionsManager
extends ImplicitPriceDropSubscriptionsManager {
private Tab mTab1;
private Tab mTab2;
private String mMockTab1OfferId;
private String mMockTab2OfferId;
TestImplicitPriceDropSubscriptionsManager(
TabModelSelector tabModelSelector, ShoppingService shoppingService) {
super(tabModelSelector, shoppingService);
}
@Override
protected void fetchOfferId(Tab tab, Callback<String> callback) {
if (mTab1.equals(tab)) {
callback.onResult(mMockTab1OfferId);
} else if (mTab2.equals(tab)) {
callback.onResult(mMockTab2OfferId);
} else {
assert false : "Unsupported tab";
}
}
void setupForFetchOfferId(
Tab tab1, Tab tab2, String mockTab1OfferId, String mockTab2OfferId) {
mTab1 = tab1;
mTab2 = tab2;
mMockTab1OfferId = mockTab1OfferId;
mMockTab2OfferId = mockTab2OfferId;
}
}
@Mock TabModel mTabModel;
@Mock TabModelSelector mTabModelSelector;
@Mock ShoppingService mShoppingService;
@Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
@Captor ArgumentCaptor<CommerceSubscription> mSubscriptionCaptor;
private Tab mTab1;
private Tab mTab2;
private CommerceSubscription mSubscription1;
private CommerceSubscription mSubscription2;
private TestImplicitPriceDropSubscriptionsManager mImplicitSubscriptionsManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTab1 = prepareTab(TAB1_ID, URL1, POSITION1);
mTab2 = prepareTab(TAB2_ID, URL2, POSITION2);
// Mock that tab1 and tab2 are both stale tabs.
long fakeTimestamp =
System.currentTimeMillis()
- TimeUnit.SECONDS.toMillis(
ShoppingPersistedTabData.getStaleTabThresholdSeconds())
+ TimeUnit.DAYS.toMillis(7);
doReturn(fakeTimestamp).when(mTab1).getTimestampMillis();
doReturn(fakeTimestamp).when(mTab2).getTimestampMillis();
mSubscription1 =
new CommerceSubscription(
SubscriptionType.PRICE_TRACK,
IdentifierType.OFFER_ID,
OFFER1_ID,
ManagementType.CHROME_MANAGED,
null);
mSubscription2 =
new CommerceSubscription(
SubscriptionType.PRICE_TRACK,
IdentifierType.OFFER_ID,
OFFER2_ID,
ManagementType.CHROME_MANAGED,
null);
doReturn(2).when(mTabModel).getCount();
doReturn(mTabModel).when(mTabModelSelector).getModel(false);
doNothing().when(mTabModel).addObserver(mTabModelObserverCaptor.capture());
mImplicitSubscriptionsManager =
new TestImplicitPriceDropSubscriptionsManager(mTabModelSelector, mShoppingService);
mImplicitSubscriptionsManager.setupForFetchOfferId(mTab1, mTab2, OFFER1_ID, OFFER2_ID);
}
@Test
public void testInitialSetup() {
verify(mTabModel).addObserver(any(TabModelObserver.class));
}
@Test
public void testInitialSubscription_AllUnique() {
initializeSubscriptionsAndVerify(true, true);
verifyEligibleSubscriptionMetrics(2, 2);
}
@Test
public void testInitialSubscription_WithDuplicateURL() {
mTab1 = prepareTab(TAB1_ID, URL1, POSITION1);
mTab2 = prepareTab(TAB2_ID, URL1, POSITION2);
long fakeTimestamp =
System.currentTimeMillis()
- TimeUnit.SECONDS.toMillis(
ShoppingPersistedTabData.getStaleTabThresholdSeconds())
+ TimeUnit.DAYS.toMillis(7);
doReturn(fakeTimestamp).when(mTab1).getTimestampMillis();
doReturn(fakeTimestamp).when(mTab2).getTimestampMillis();
mImplicitSubscriptionsManager.setupForFetchOfferId(mTab1, mTab2, OFFER1_ID, OFFER2_ID);
initializeSubscriptionsAndVerify(true, false);
verifyEligibleSubscriptionMetrics(2, 2);
}
@Test
public void testInitialSubscription_NoOfferID() {
mImplicitSubscriptionsManager.setupForFetchOfferId(mTab1, mTab2, OFFER1_ID, null);
initializeSubscriptionsAndVerify(true, false);
verifyEligibleSubscriptionMetrics(1, 2);
}
@Test
public void testInitialSubscription_TabTooOld() {
doReturn(
System.currentTimeMillis()
- TimeUnit.SECONDS.toMillis(
ShoppingPersistedTabData.getStaleTabThresholdSeconds())
- TimeUnit.DAYS.toMillis(7))
.when(mTab1)
.getTimestampMillis();
initializeSubscriptionsAndVerify(false, true);
verifyEligibleSubscriptionMetrics(1, 2);
}
@Test
public void testInitialSubscription_TabTooNew() {
doReturn(System.currentTimeMillis()).when(mTab1).getTimestampMillis();
initializeSubscriptionsAndVerify(false, true);
verifyEligibleSubscriptionMetrics(1, 2);
}
@Test
public void testTabClosure() {
mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
verify(mShoppingService, times(1))
.unsubscribe(mSubscriptionCaptor.capture(), any(Callback.class));
assertEquals(OFFER1_ID, mSubscriptionCaptor.getValue().id);
}
@Test
public void testTabRemove() {
mTabModelObserverCaptor.getValue().tabRemoved(mTab1);
verify(mShoppingService, times(1))
.unsubscribe(mSubscriptionCaptor.capture(), any(Callback.class));
assertEquals(OFFER1_ID, mSubscriptionCaptor.getValue().id);
}
@Test
public void testTabSelected() {
mTabModelObserverCaptor.getValue().didSelectTab(mTab1, TabSelectionType.FROM_USER, TAB2_ID);
verify(mShoppingService, times(1))
.unsubscribe(mSubscriptionCaptor.capture(), any(Callback.class));
assertEquals(OFFER1_ID, mSubscriptionCaptor.getValue().id);
}
@Test
public void testUnsubscribe_NotUnique() {
mTab1 = prepareTab(TAB1_ID, URL1, POSITION1);
mTab2 = prepareTab(TAB2_ID, URL1, POSITION2);
mImplicitSubscriptionsManager.setupForFetchOfferId(mTab1, mTab2, OFFER1_ID, OFFER2_ID);
mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
verify(mShoppingService, times(0)).unsubscribe(any(), any(Callback.class));
}
@Test
public void testUnsubscribe_NoOfferId() {
mImplicitSubscriptionsManager.setupForFetchOfferId(mTab1, mTab2, null, OFFER2_ID);
mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
verify(mShoppingService, times(0)).unsubscribe(any(), any(Callback.class));
}
@Test
public void testDestroy() {
mImplicitSubscriptionsManager.destroy();
verify(mTabModel).removeObserver(any(TabModelObserver.class));
}
private Tab prepareTab(int id, String urlString, int position) {
Tab tab = mock(Tab.class);
doReturn(id).when(tab).getId();
GURL gurl = new GURL(urlString);
doReturn(gurl).when(tab).getUrl();
doReturn(gurl).when(tab).getOriginalUrl();
doReturn(tab).when(mTabModel).getTabAt(position);
return tab;
}
private void initializeSubscriptionsAndVerify(
boolean shouldSubscribeTab1, boolean shouldSubscribeTab2) {
mImplicitSubscriptionsManager.initializeSubscriptions();
if (shouldSubscribeTab1 && shouldSubscribeTab2) {
verify(mShoppingService, times(2))
.subscribe(mSubscriptionCaptor.capture(), any(Callback.class));
assertEquals(OFFER1_ID, mSubscriptionCaptor.getAllValues().get(0).id);
assertEquals(OFFER2_ID, mSubscriptionCaptor.getAllValues().get(1).id);
} else if (shouldSubscribeTab1 || shouldSubscribeTab2) {
verify(mShoppingService, times(1))
.subscribe(mSubscriptionCaptor.capture(), any(Callback.class));
assertEquals(
shouldSubscribeTab1 ? OFFER1_ID : OFFER2_ID, mSubscriptionCaptor.getValue().id);
} else {
verify(mShoppingService, times(0)).subscribe(any(), any(Callback.class));
}
}
private void verifyEligibleSubscriptionMetrics(int eligibleCount, int totalCount) {
assertThat(
RecordHistogram.getHistogramValueCountForTesting(TAB_ELIGIBLE_HISTOGRAM, 1),
equalTo(eligibleCount));
assertThat(
RecordHistogram.getHistogramTotalCountForTesting(TAB_ELIGIBLE_HISTOGRAM),
equalTo(totalCount));
}
}