chromium/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/digitalgoods/MockTrustedWebActivityClient.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.browserservices.digitalgoods;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;

import static org.chromium.chrome.browser.browserservices.digitalgoods.DigitalGoodsConverter.PLAY_BILLING_OK;

import android.os.Bundle;
import android.os.RemoteException;

import androidx.browser.trusted.TrustedWebActivityCallback;

import org.mockito.Mockito;

import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClientWrappers;
import org.chromium.components.embedder_support.util.Origin;

/**
 * Helper class for {@link DigitalGoodsUnitTest} - it pretends to be a TrustedWebActivityClient
 * talking to the Digital Goods handler in the TWA shell, providing hardcoded responses.
 */
public class MockTrustedWebActivityClient {
    private TrustedWebActivityClientWrappers.Connection mConnection;
    private TrustedWebActivityClient mClient;
    private Runnable mCallback;
    private int mVersion = 2;

    private static final Bundle ITEM1 =
            GetDetailsConverter.createItemDetailsBundle(
                    "id1", "title 1", "description 1", "GBP", "20");
    private static final Bundle ITEM2 =
            GetDetailsConverter.createItemDetailsBundle(
                    "id2",
                    "title 2",
                    "description 2",
                    "GBP",
                    "30",
                    "subs",
                    "https://www.example.com/image.png",
                    "2 weeks",
                    "month",
                    "GBP",
                    "20",
                    "week",
                    2);

    private static final Bundle PURCHASE1 =
            ListPurchasesConverter.createPurchaseReferenceBundle("id3", "abc");
    private static final Bundle PURCHASE2 =
            ListPurchasesConverter.createPurchaseReferenceBundle("id4", "def");

    public MockTrustedWebActivityClient() {
        mConnection = Mockito.mock(TrustedWebActivityClientWrappers.Connection.class);
        mClient = Mockito.mock(TrustedWebActivityClient.class);

        doAnswer(
                        invocation -> {
                            TrustedWebActivityClient.ExecutionCallback callback =
                                    invocation.getArgument(1);

                            callback.onConnected(Mockito.mock(Origin.class), mConnection);

                            return null;
                        })
                .when(mClient)
                .connectAndExecute(any(), any());

        try {
            doAnswer(
                            invocation -> {
                                String command = invocation.getArgument(0);
                                Bundle args = invocation.getArgument(1);
                                TrustedWebActivityCallback callback = invocation.getArgument(2);

                                return handleWrapper(command, args, callback);
                            })
                    .when(mConnection)
                    .sendExtraCommand(any(), any(), any());
        } catch (RemoteException e) {
            // This ugliness is because we're mocking a method that could throw an exception.
            throw new RuntimeException(e);
        }
    }

    /** Returns a mocked {@link TrustedWebActivityClient} to be used in tests. */
    public TrustedWebActivityClient getClient() {
        return mClient;
    }

    /** Runs the pending callback. */
    public void runCallback() {
        mCallback.run();
        mCallback = null;
    }

    /** Changes the clients behaviour to simulate older TWA shells. */
    public void setVersion(int version) {
        mVersion = version;
    }

    private boolean handle(String command, Bundle args, TrustedWebActivityCallback callback) {
        // At this point, pretend we're in the TWA shell.
        switch (command) {
            case "getDetails":
                mCallback =
                        () -> {
                            callback.onExtraCallback(
                                    "getDetails.response",
                                    GetDetailsConverter.createResponseBundle(
                                            PLAY_BILLING_OK, ITEM1, ITEM2));
                        };
                return true;
            case "listPurchases":
                mCallback =
                        () -> {
                            callback.onExtraCallback(
                                    "listPurchases.response",
                                    ListPurchasesConverter.createResponseBundle(
                                            PLAY_BILLING_OK, PURCHASE1, PURCHASE2));
                        };
                return true;
            case "listPurchaseHistory":
                // Version 1 clients did not have purchase history.
                if (mVersion == 1) return false;
                mCallback =
                        () -> {
                            // Swap the order of PURCHASE1 and PURCHASE2 as an easy way to make sure
                            // the correct branch of this switch statement is executed.
                            callback.onExtraCallback(
                                    "listPurchaseHistory.response",
                                    ListPurchaseHistoryConverter.createResponseBundle(
                                            PLAY_BILLING_OK, PURCHASE2, PURCHASE1));
                        };
                return true;
            case "consume":
                // Version 1 clients did not have consume.
                if (mVersion == 1) return false;
                mCallback =
                        () -> {
                            callback.onExtraCallback(
                                    "consume.response",
                                    ConsumeConverter.createResponseBundle(PLAY_BILLING_OK));
                        };
                return true;
            case "acknowledge":
                // Only version 1 clients have acknowledge.
                if (mVersion != 1) return false;
                mCallback =
                        () -> {
                            callback.onExtraCallback(
                                    "acknowledge.response",
                                    AcknowledgeConverter.createResponseBundle(PLAY_BILLING_OK));
                        };
                return true;
        }

        return false;
    }

    private Bundle handleWrapper(String command, Bundle args, TrustedWebActivityCallback callback) {
        Bundle bundle = new Bundle();
        bundle.putBoolean("success", handle(command, args, callback));
        return bundle;
    }
}