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

import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.PendingIntent;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.common.api.Status;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.JniMocker;

import java.util.Optional;

/** Tests that settings updater callbacks invoke the right native callbacks. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@Batch(Batch.PER_CLASS)
public class PasswordSettingsUpdaterReceiverBridgeTest {

    private static final long sFakeNativePointer = 7;

    @Rule public JniMocker mJniMocker = new JniMocker();
    @Mock private PasswordSettingsUpdaterReceiverBridge.Natives mReceiverBridgeJniMock;
    @Mock private PasswordSettingsUpdaterMetricsRecorder mMetricsRecorderMock;

    private PasswordSettingsUpdaterReceiverBridge mReceiverBridge;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(
                PasswordSettingsUpdaterReceiverBridgeJni.TEST_HOOKS, mReceiverBridgeJniMock);
        mReceiverBridge = new PasswordSettingsUpdaterReceiverBridge(sFakeNativePointer);
    }

    @Test
    public void testOnSettingValueFetchedCalled() {
        mReceiverBridge.onSettingValueFetched(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                Optional.of(true),
                mMetricsRecorderMock);
        verify(mReceiverBridgeJniMock)
                .onSettingValueFetched(
                        sFakeNativePointer, PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS, true);
        verify(mMetricsRecorderMock).recordMetrics(isNull());
    }

    @Test
    public void testOnSettingValueAbsentCalled() {
        mReceiverBridge.onSettingValueFetched(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                Optional.empty(),
                mMetricsRecorderMock);
        verify(mReceiverBridgeJniMock)
                .onSettingValueAbsent(
                        sFakeNativePointer, PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS);
        verify(mMetricsRecorderMock).recordMetrics(isNull());
    }

    @Test
    public void testOnSettingFetchingErrorCalled() {
        Exception expectedException = new ApiException(new Status(CommonStatusCodes.NETWORK_ERROR));
        mReceiverBridge.handleFetchingException(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                expectedException,
                mMetricsRecorderMock);

        verify(mReceiverBridgeJniMock)
                .onSettingFetchingError(
                        sFakeNativePointer,
                        PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                        AndroidBackendErrorType.EXTERNAL_ERROR,
                        CommonStatusCodes.NETWORK_ERROR);
        verify(mMetricsRecorderMock).recordMetrics(expectedException);
    }

    @Test
    public void testOnSuccessfulSettingChangeCalled() {
        mReceiverBridge.onSettingValueSet(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS, mMetricsRecorderMock);
        verify(mReceiverBridgeJniMock)
                .onSuccessfulSettingChange(
                        sFakeNativePointer, PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS);
        verify(mMetricsRecorderMock).recordMetrics(isNull());
    }

    @Test
    public void testOnFailedSettingChangeCalled() {
        Exception expectedException = new ApiException(new Status(CommonStatusCodes.NETWORK_ERROR));
        mReceiverBridge.handleSettingException(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                expectedException,
                mMetricsRecorderMock);

        verify(mReceiverBridgeJniMock)
                .onFailedSettingChange(
                        sFakeNativePointer,
                        PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                        AndroidBackendErrorType.EXTERNAL_ERROR,
                        CommonStatusCodes.NETWORK_ERROR);
        verify(mMetricsRecorderMock).recordMetrics(expectedException);
    }

    @Test
    public void testResolutionNotLaunchedOnResolvableFetchingError()
            throws PendingIntent.CanceledException {
        PendingIntent pendingIntentMock = mock(PendingIntent.class);
        Exception expectedException =
                new ResolvableApiException(
                        new Status(CommonStatusCodes.RESOLUTION_REQUIRED, "", pendingIntentMock));

        mReceiverBridge.handleFetchingException(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                expectedException,
                mMetricsRecorderMock);

        verify(pendingIntentMock, never()).send();
        verify(mReceiverBridgeJniMock)
                .onSettingFetchingError(
                        sFakeNativePointer,
                        PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                        AndroidBackendErrorType.EXTERNAL_ERROR,
                        CommonStatusCodes.RESOLUTION_REQUIRED);
        verify(mMetricsRecorderMock).recordMetrics(expectedException);
    }

    @Test
    public void testResolutionNotLaunchedOnResolvableSettingError()
            throws PendingIntent.CanceledException {
        PendingIntent pendingIntentMock = mock(PendingIntent.class);
        Exception expectedException =
                new ResolvableApiException(
                        new Status(CommonStatusCodes.RESOLUTION_REQUIRED, "", pendingIntentMock));

        mReceiverBridge.handleSettingException(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                expectedException,
                mMetricsRecorderMock);

        verify(pendingIntentMock, never()).send();
        verify(mReceiverBridgeJniMock)
                .onFailedSettingChange(
                        sFakeNativePointer,
                        PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                        AndroidBackendErrorType.EXTERNAL_ERROR,
                        CommonStatusCodes.RESOLUTION_REQUIRED);
        verify(mMetricsRecorderMock).recordMetrics(expectedException);
    }

    @Test
    public void testResolutionNotLaunchedOnResolvableFetchingErrorIfDestroyed()
            throws PendingIntent.CanceledException {
        PendingIntent pendingIntentMock = mock(PendingIntent.class);
        Exception expectedException =
                new ResolvableApiException(
                        new Status(CommonStatusCodes.RESOLUTION_REQUIRED, "", pendingIntentMock));

        // Simulate native bridge destruction. No resolution or native bridge interaction should
        // happen after this.
        mReceiverBridge.destroyForTesting();

        mReceiverBridge.handleFetchingException(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                expectedException,
                mMetricsRecorderMock);

        verify(pendingIntentMock, never()).send();
        verify(mReceiverBridgeJniMock, never())
                .onSettingFetchingError(
                        sFakeNativePointer,
                        PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                        AndroidBackendErrorType.EXTERNAL_ERROR,
                        CommonStatusCodes.RESOLUTION_REQUIRED);
        verify(mMetricsRecorderMock).recordMetrics(expectedException);
    }

    @Test
    public void testResolutionNotLaunchedOnResolvableSettingErrorIfDestroyed()
            throws PendingIntent.CanceledException {
        PendingIntent pendingIntentMock = mock(PendingIntent.class);
        Exception expectedException =
                new ResolvableApiException(
                        new Status(CommonStatusCodes.RESOLUTION_REQUIRED, "", pendingIntentMock));

        // Simulate native bridge destruction. No resolution or native bridge interaction should
        // happen after this.
        mReceiverBridge.destroyForTesting();

        mReceiverBridge.handleSettingException(
                PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                expectedException,
                mMetricsRecorderMock);

        verify(pendingIntentMock, never()).send();
        verify(mReceiverBridgeJniMock, never())
                .onFailedSettingChange(
                        sFakeNativePointer,
                        PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS,
                        AndroidBackendErrorType.EXTERNAL_ERROR,
                        CommonStatusCodes.RESOLUTION_REQUIRED);
        verify(mMetricsRecorderMock).recordMetrics(expectedException);
    }
}