chromium/android_webview/javatests/src/org/chromium/android_webview/test/devui/NetLogsFragmentTest.java

// Copyright 2024 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.android_webview.test.devui;

import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.greaterThan;

import android.app.Activity;
import android.app.Instrumentation.ActivityResult;
import android.content.Context;
import android.content.Intent;
import android.widget.ListView;

import androidx.test.espresso.DataInteraction;
import androidx.test.espresso.intent.Intents;
import androidx.test.espresso.intent.matcher.IntentMatchers;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.android_webview.devui.MainActivity;
import org.chromium.android_webview.devui.NetLogsFragment;
import org.chromium.android_webview.devui.R;
import org.chromium.android_webview.nonembedded_util.WebViewPackageHelper;
import org.chromium.android_webview.services.AwNetLogService;
import org.chromium.android_webview.test.AwJUnit4ClassRunner;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseActivityTestRule;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.ui.test.util.ViewUtils;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * UI tests for {@link NetLogsFragment}.
 *
 * <p>These tests should not be batched to make sure that the DeveloperUiService is killed after
 * each test, leaving a clean state.
 */
@RunWith(AwJUnit4ClassRunner.class)
@DoNotBatch(reason = "Clean up DeveloperUiService after each test")
public class NetLogsFragmentTest {
    @Rule
    public BaseActivityTestRule<MainActivity> mRule =
            new BaseActivityTestRule<>(MainActivity.class);

    private static final String TAG = "NetLogsFragmentTest";
    private static final String JSON_TAG = ".json";
    private static final String MOCK_PID = "1234_";
    private static final String MOCK_PACKAGE_NAME = "package.name";
    private static List<File> sMockFileList;
    private long mFileTime;

    @Before
    public void setUp() throws Exception {
        Context context = ContextUtils.getApplicationContext();
        Intent intent = new Intent(context, MainActivity.class);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    sMockFileList = initalizeTestFiles();
                    NetLogsFragment.setFileListForTesting(sMockFileList);
                });
        WebViewPackageHelper.setCurrentWebViewPackageForTesting(
                WebViewPackageHelper.getContextPackageInfo(context));
        intent.putExtra(MainActivity.FRAGMENT_ID_INTENT_EXTRA, MainActivity.FRAGMENT_ID_NETLOGS);
        mRule.launchActivity(intent);
        waitForInflatedNetLogFragment();
    }

    @After
    public void tearDown() {
        ArrayList<File> filesToDelete = new ArrayList<>(sMockFileList);
        for (File file : filesToDelete) {
            file.delete();
        }
    }

    private List<File> initalizeTestFiles() {
        String package_num = "";
        mFileTime = System.currentTimeMillis();
        List<File> files = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            package_num += 'I';
            String fileName =
                    MOCK_PID
                            + Long.toString(mFileTime + i)
                            + "_"
                            + MOCK_PACKAGE_NAME
                            + package_num
                            + JSON_TAG;
            File file = new File(AwNetLogService.getNetLogFileDirectory(), fileName);
            try {
                file.createNewFile();
                files.add(file);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return files;
    }

    private void waitForInflatedNetLogFragment() {
        // Espresso is normally configured to automatically wait for the main thread to go idle, but
        // BaseActivityTestRule turns that behavior off so we must explicitly wait for the View
        // hierarchy to inflate.
        ViewUtils.waitForVisibleView(withId(R.id.navigation_home));
        ViewUtils.waitForVisibleView(withId(R.id.net_log_list));
        ViewUtils.waitForVisibleView(withId(R.id.net_logs_total_capacity));
        ViewUtils.waitForVisibleView(withId(R.id.delete_all_net_logs_button));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testHasPublicNoArgsConstructor() throws Throwable {
        NetLogsFragment fragment = new NetLogsFragment();
        Assert.assertNotNull(fragment);
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    public void testCorrectListDisplay() throws Throwable {
        ListView filesList = mRule.getActivity().findViewById(R.id.net_log_list);
        Assert.assertEquals(5, filesList.getCount());

        String package_num = "";
        DateFormat dateFormat = DateFormat.getDateTimeInstance();

        for (int i = 0; i < filesList.getCount(); i++) {
            package_num += 'I';
            DataInteraction fileInteraction =
                    onData(anything()).inAdapterView(withId(R.id.net_log_list)).atPosition(i);

            fileInteraction
                    .onChildView(withId(R.id.file_name))
                    .check(matches(withText(MOCK_PACKAGE_NAME + package_num)));

            fileInteraction
                    .onChildView(withId(R.id.file_capacity))
                    .check(matches(withText("0.00 MB")));

            String date = dateFormat.format(new Date(mFileTime + i));

            fileInteraction.onChildView(withId(R.id.file_time)).check(matches(withText(date)));
        }
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    public void testDeleteAllNetLogsButton() throws Throwable {
        onView(withId(R.id.delete_all_net_logs_button)).perform(click());

        ListView filesList = mRule.getActivity().findViewById(R.id.net_log_list);
        Assert.assertEquals(0, filesList.getCount());
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    public void testSortedFiles() throws Throwable {
        ListView filesList = mRule.getActivity().findViewById(R.id.net_log_list);

        File prevFile = (File) filesList.getAdapter().getItem(0);
        long prevTime = AwNetLogService.getCreationTimeFromFileName(prevFile.getName());

        for (int i = 1; i < filesList.getCount(); i++) {
            File currFile = (File) filesList.getAdapter().getItem(i);
            long currTime = AwNetLogService.getCreationTimeFromFileName(currFile.getName());
            assertThat(currTime, greaterThan(prevTime));

            prevTime = currTime;
        }
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    public void testMenuDelete() throws Throwable {
        ListView filesList = mRule.getActivity().findViewById(R.id.net_log_list);
        Assert.assertEquals(5, filesList.getCount());

        File firstFile = (File) filesList.getAdapter().getItem(1);
        String firstFileName = NetLogsFragment.getFilePackageName(firstFile);
        onView(withText(firstFileName)).perform(click());
        onView(withText("Delete")).check(matches(isDisplayed()));
        onView(withText("Delete")).perform(click());

        Assert.assertEquals(4, filesList.getCount());

        for (int i = 0; i < filesList.getCount(); i++) {
            File file = (File) filesList.getAdapter().getItem(i);

            // Ensuring that the remaining files are not the first file that was deleted.
            Assert.assertNotEquals(NetLogsFragment.getFilePackageName(file), firstFileName);
        }
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    public void testMenuShare() throws Throwable {
        try {
            Intents.init();

            // Stub out the Intent to the File Provider, to verify the case where the File Provider
            // Intent
            // resolves.
            intending(
                            allOf(
                                    IntentMatchers.hasAction(Intent.ACTION_CHOOSER),
                                    IntentMatchers.hasExtra(
                                            Intent.EXTRA_INTENT,
                                            allOf(
                                                    IntentMatchers.hasAction(Intent.ACTION_SEND),
                                                    IntentMatchers.hasType("application/json")))))
                    .respondWith(new ActivityResult(Activity.RESULT_OK, null));

            String firstFileName = NetLogsFragment.getFilePackageName(sMockFileList.get(1));
            onView(withText(firstFileName)).perform(click());
            onView(withText("Share")).check(matches(isDisplayed()));
            onView(withText("Share")).perform(click());

            intended(
                    allOf(
                            IntentMatchers.hasAction(Intent.ACTION_CHOOSER),
                            IntentMatchers.hasExtra(
                                    Intent.EXTRA_INTENT,
                                    allOf(
                                            IntentMatchers.hasAction(Intent.ACTION_SEND),
                                            IntentMatchers.hasType("application/json")))));
        } finally {
            Intents.release();
        }
    }
}