chromium/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsUnitTest.java

// Copyright 2016 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.offlinepages;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.os.Environment;

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.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;

import org.chromium.base.UserDataHost;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileJni;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.test.util.SadTabRule;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkType;
import org.chromium.content_public.browser.WebContents;

import java.io.File;

/** Unit tests for OfflinePageUtils. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(
        manifest = Config.NONE,
        shadows = {OfflinePageUtilsUnitTest.WrappedEnvironment.class})
public class OfflinePageUtilsUnitTest {
    @Rule public JniMocker mocker = new JniMocker();
    @Mock public Profile.Natives mMockProfileNatives;

    @Mock private File mMockDataDirectory;
    @Mock private Tab mTab;
    @Mock private WebContents mWebContents;
    @Mock private OfflinePageBridge mOfflinePageBridge;
    @Mock private OfflinePageUtils.Internal mOfflinePageUtils;

    @Rule public final SadTabRule mSadTabRule = new SadTabRule();

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mocker.mock(ProfileJni.TEST_HOOKS, mMockProfileNatives);
        WrappedEnvironment.setDataDirectoryForTest(mMockDataDirectory);

        // Setting up a mock tab. These are the values common to most tests, but individual
        // tests might easily overwrite them.
        doReturn(false).when(mTab).isShowingErrorPage();
        doReturn(new UserDataHost()).when(mTab).getUserDataHost();
        doReturn(true).when(mTab).isInitialized();
        mSadTabRule.setTab(mTab);
        doNothing().when(mTab).addObserver(any(TabObserver.class));
        doReturn(mWebContents).when(mTab).getWebContents();
        doReturn(false).when(mWebContents).isDestroyed();
        doReturn(false).when(mWebContents).isIncognito();

        doNothing()
                .when(mOfflinePageBridge)
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));

        doReturn(mOfflinePageBridge)
                .when(mOfflinePageUtils)
                .getOfflinePageBridge((Profile) isNull());
        OfflinePageUtils.setInstanceForTesting(mOfflinePageUtils);
    }

    @Test
    @Feature({"OfflinePages"})
    public void testGetFreeSpaceInBytes() {
        when(mMockDataDirectory.getUsableSpace()).thenReturn(1234L);
        assertEquals(1234L, OfflinePageUtils.getFreeSpaceInBytes());
    }

    @Test
    @Feature({"OfflinePages"})
    public void testGetTotalSpaceInBytes() {
        when(mMockDataDirectory.getTotalSpace()).thenReturn(56789L);
        assertEquals(56789L, OfflinePageUtils.getTotalSpaceInBytes());
    }

    @Test
    @Feature({"OfflinePages"})
    public void testSaveBookmarkOffline() {
        OfflinePageUtils.saveBookmarkOffline(new BookmarkId(42, BookmarkType.NORMAL), mTab);
        verify(mOfflinePageBridge, times(1))
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));
    }

    @Test
    @Feature({"OfflinePages"})
    public void testSaveBookmarkOffline_inputValidation() {
        OfflinePageUtils.saveBookmarkOffline(null, mTab);
        // Save page not called because bookmarkId is null.
        verify(mOfflinePageBridge, times(0))
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));

        BookmarkId bookmarkId = new BookmarkId(42, BookmarkType.NORMAL);
        doReturn(true).when(mTab).isShowingErrorPage();
        OfflinePageUtils.saveBookmarkOffline(bookmarkId, mTab);
        // Save page not called because tab is showing an error page.
        verify(mOfflinePageBridge, times(0))
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));

        doReturn(false).when(mTab).isShowingErrorPage();
        mSadTabRule.show(true);
        OfflinePageUtils.saveBookmarkOffline(bookmarkId, mTab);
        // Save page not called because tab is showing a sad tab.
        verify(mOfflinePageBridge, times(0))
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));

        mSadTabRule.show(false);
        doReturn(null).when(mTab).getWebContents();
        OfflinePageUtils.saveBookmarkOffline(bookmarkId, mTab);
        // Save page not called because tab returns null web contents.
        verify(mOfflinePageBridge, times(0))
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));

        doReturn(mWebContents).when(mTab).getWebContents();
        doReturn(true).when(mWebContents).isDestroyed();
        OfflinePageUtils.saveBookmarkOffline(bookmarkId, mTab);
        // Save page not called because web contents is destroyed.
        verify(mOfflinePageBridge, times(0))
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));

        doReturn(false).when(mWebContents).isDestroyed();
        doReturn(true).when(mWebContents).isIncognito();
        OfflinePageUtils.saveBookmarkOffline(bookmarkId, mTab);
        // Save page not called because web contents is incognito.
        verify(mOfflinePageBridge, times(0))
                .savePage(
                        eq(mWebContents),
                        any(ClientId.class),
                        any(OfflinePageBridge.SavePageCallback.class));
    }

    /** A shadow/wrapper of android.os.Environment that allows injecting a test directory. */
    @Implements(Environment.class)
    public static class WrappedEnvironment {
        private static File sDataDirectory;

        public static void setDataDirectoryForTest(File testDirectory) {
            sDataDirectory = testDirectory;
        }

        @Implementation
        public static File getDataDirectory() {
            return sDataDirectory;
        }
    }
}