chromium/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingNotificationBridgeUnitTest.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.price_tracking;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.price_tracking.PriceDropNotifier.NotificationData;
import org.chromium.chrome.browser.price_tracking.proto.Notifications.Action;
import org.chromium.chrome.browser.price_tracking.proto.Notifications.ChromeMessage;
import org.chromium.chrome.browser.price_tracking.proto.Notifications.ChromeNotification;
import org.chromium.chrome.browser.price_tracking.proto.Notifications.ChromeNotification.NotificationDataType;
import org.chromium.chrome.browser.price_tracking.proto.Notifications.ExpandedView;
import org.chromium.chrome.browser.price_tracking.proto.Notifications.PriceDropNotificationPayload;
import org.chromium.components.commerce.PriceTracking.ProductPrice;
import org.chromium.components.optimization_guide.proto.CommonTypesProto.Any;
import org.chromium.components.payments.CurrencyFormatter;
import org.chromium.components.payments.CurrencyFormatterJni;

/** Unit test for {@link PriceDropNotifier}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class PriceTrackingNotificationBridgeUnitTest {
    private static final String TITLE = "title";
    private static final String TEXT = "text";
    private static final String ICON_URL = "http://www.example.com/icon";
    private static final String DESTINATION_URL = "http://www.example.com/destination";
    private static final String PRODUCT_NAME = "Awesome product";
    private static final long OFFER_ID = 10L;
    private static final long CURRENT_PRICE_AMOUNT_MICROS = 1000000L;
    private static final long PREVIOUS_PRICE_AMOUNT_MICROS = 2000000L;
    private static final String CURRENCY_CODE = "USD";
    private static final String ACTION_ID_0 = "visit_site";
    private static final String ACTION_ID_1 = "turn_off_alert";
    private static final String ACTION_TEXT_0 = "Visit site";
    private static final String ACTION_TEXT_1 = "Untrack price";

    private PriceTrackingNotificationBridge mPriceTrackingNotificationBridge;

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock PriceDropNotifier mNotifier;
    @Mock PriceDropNotificationManager mPriceDropNotificationManager;

    @Captor ArgumentCaptor<PriceDropNotifier.NotificationData> mNotificationDataCaptor;

    @Before
    public void setUp() {
        ShadowLog.stream = System.out;
        CurrencyFormatter.Natives currencyFormatterJniMock =
                Mockito.mock(CurrencyFormatter.Natives.class);
        mJniMocker.mock(CurrencyFormatterJni.TEST_HOOKS, currencyFormatterJniMock);
        Mockito.doReturn("$1.00")
                .when(currencyFormatterJniMock)
                .format(
                        Mockito.anyLong(),
                        Mockito.any(CurrencyFormatter.class),
                        Mockito.anyString());
        mPriceTrackingNotificationBridge =
                new PriceTrackingNotificationBridge(0, mNotifier, mPriceDropNotificationManager);
        when(mPriceDropNotificationManager.canPostNotification()).thenReturn(true);
    }

    // Creates a ChromeNotification.Builder that sets a valid ChromeNotification proto.
    private ChromeNotification.Builder createValidChromeNotification() {
        return createChromeNotification(
                createValidChromeMessage(), createValidPriceDropNotificationPayload());
    }

    // Create a ChromeNotification.Builder with specific ChromeMessage.
    private ChromeNotification.Builder createChromeNotification(
            ChromeMessage.Builder chromeMessage,
            PriceDropNotificationPayload.Builder priceDropNotificationPayload) {
        ChromeNotification.Builder builder = ChromeNotification.newBuilder();
        builder.setNotificationDataType(NotificationDataType.PRICE_DROP_NOTIFICATION);
        Any.Builder anyBuilder = Any.newBuilder();
        anyBuilder.setValue(priceDropNotificationPayload.build().toByteString());
        builder.setNotificationData(anyBuilder.build().toByteString());
        builder.setChromeMessage(chromeMessage.build());
        return builder;
    }

    private PriceDropNotificationPayload.Builder createValidPriceDropNotificationPayload() {
        PriceDropNotificationPayload.Builder priceDropPayload =
                PriceDropNotificationPayload.newBuilder();
        priceDropPayload.setProductName(PRODUCT_NAME);
        priceDropPayload.setOfferId(OFFER_ID);
        priceDropPayload.setDestinationUrl(DESTINATION_URL);
        ProductPrice.Builder currentPrice = ProductPrice.newBuilder();
        currentPrice.setAmountMicros(CURRENT_PRICE_AMOUNT_MICROS);
        currentPrice.setCurrencyCode(CURRENCY_CODE);
        priceDropPayload.setCurrentPrice(currentPrice);
        ProductPrice.Builder previousPrice = ProductPrice.newBuilder();
        previousPrice.setAmountMicros(PREVIOUS_PRICE_AMOUNT_MICROS);
        previousPrice.setCurrencyCode(CURRENCY_CODE);
        priceDropPayload.setPreviousPrice(previousPrice);
        return priceDropPayload;
    }

    // Create a valid ChromeMessage proto builder.
    private ChromeMessage.Builder createValidChromeMessage() {
        // Create ChromeMessage, some fields are duplicated to PriceDropNotificationPayload,
        ChromeMessage.Builder message = ChromeMessage.newBuilder();
        message.setDisplayTitle(TITLE);
        message.setDisplayText(TEXT);
        message.setIconImageUrl(ICON_URL);
        message.setDestinationUrl(DESTINATION_URL);
        ExpandedView.Builder expandedView = ExpandedView.newBuilder();
        expandedView.addAction(Action.newBuilder().setActionId(ACTION_ID_0).setText(ACTION_TEXT_0));
        expandedView.addAction(Action.newBuilder().setActionId(ACTION_ID_1).setText(ACTION_TEXT_1));
        message.setExpandedView(expandedView.build());
        return message;
    }

    @Test
    public void testShowNotification_ValidProto() {
        mPriceTrackingNotificationBridge.showNotification(
                createValidChromeNotification().build().toByteArray());
        verify(mNotifier).showNotification(mNotificationDataCaptor.capture());
        NotificationData data = mNotificationDataCaptor.getValue();

        // We are currently use client side strings, instead of the title/text in ChromeMessage
        // proto.
        Assert.assertEquals("$1.00 price drop on Awesome product", data.title);
        Assert.assertEquals("Now $1.00 on www.example.com", data.text);

        Assert.assertEquals(ICON_URL, data.iconUrl);
        Assert.assertEquals(DESTINATION_URL, data.destinationUrl);
        Assert.assertEquals(ACTION_ID_0, data.actions.get(0).actionId);
        Assert.assertEquals(ACTION_TEXT_0, data.actions.get(0).text);
        Assert.assertEquals(ACTION_ID_1, data.actions.get(1).actionId);
        Assert.assertEquals(ACTION_TEXT_1, data.actions.get(1).text);
        Assert.assertEquals(String.valueOf(OFFER_ID), data.offerId);
    }

    @Test
    public void testShowNotification_ChannelNotCreated() {
        when(mPriceDropNotificationManager.canPostNotification()).thenReturn(false);
        mPriceTrackingNotificationBridge.showNotification(
                createValidChromeNotification().build().toByteArray());
        verify(mNotifier, times(0)).showNotification(any());
    }

    @Test
    public void testShowNotification_GarbageBytePayload() {
        mPriceTrackingNotificationBridge.showNotification(new byte[2]);
        verify(mNotifier, times(0)).showNotification(any());
    }

    @Test
    public void testShowNotification_NoChromeMessage() {
        ChromeNotification.Builder builder = createValidChromeNotification();
        builder.clearChromeMessage();
        mPriceTrackingNotificationBridge.showNotification(builder.build().toByteArray());
        verify(mNotifier, times(0)).showNotification(any());
    }

    @Test
    public void testShowNotification_NoDestinationURL() {
        PriceDropNotificationPayload.Builder priceDropNotificationPayload =
                createValidPriceDropNotificationPayload();
        priceDropNotificationPayload.clearDestinationUrl();
        ChromeNotification.Builder builder =
                createChromeNotification(createValidChromeMessage(), priceDropNotificationPayload);
        mPriceTrackingNotificationBridge.showNotification(builder.build().toByteArray());
        verify(mNotifier, times(0)).showNotification(any());
    }

    @Test
    public void testShowNotification_InvalidDestinationURL() {
        PriceDropNotificationPayload.Builder priceDropNotificationPayload =
                createValidPriceDropNotificationPayload();
        priceDropNotificationPayload.setDestinationUrl("test");
        ChromeNotification.Builder builder =
                createChromeNotification(createValidChromeMessage(), priceDropNotificationPayload);
        mPriceTrackingNotificationBridge.showNotification(builder.build().toByteArray());
        verify(mNotifier, times(0)).showNotification(any());
    }

    @Test
    public void testShowNotification_NoOfferId() {
        PriceDropNotificationPayload.Builder priceDropNotificationPayload =
                createValidPriceDropNotificationPayload();
        priceDropNotificationPayload.clearOfferId();
        ChromeNotification.Builder builder =
                createChromeNotification(createValidChromeMessage(), priceDropNotificationPayload);
        mPriceTrackingNotificationBridge.showNotification(builder.build().toByteArray());
        verify(mNotifier, times(0)).showNotification(any());
    }

    @Test
    public void testShowNotification_PriceNotDrop() {
        PriceDropNotificationPayload.Builder priceDropNotificationPayload =
                createValidPriceDropNotificationPayload();
        // Make current price and previous price equal.
        priceDropNotificationPayload.setPreviousPrice(
                priceDropNotificationPayload.getCurrentPrice());
        ChromeNotification.Builder builder =
                createChromeNotification(createValidChromeMessage(), priceDropNotificationPayload);
        mPriceTrackingNotificationBridge.showNotification(builder.build().toByteArray());
        verify(mNotifier, times(0)).showNotification(any());
    }
}