chromium/chrome/browser/lacros/remote_apps/remote_apps_proxy_lacros_unittest.cc

// 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.

#include "chrome/browser/lacros/remote_apps/remote_apps_proxy_lacros.h"

#include <optional>
#include <utility>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/test/test_future.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/components/remote_apps/mojom/remote_apps.mojom.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/test_event_router.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace chromeos {

using chromeos::remote_apps::mojom::AddAppResultPtr;
using chromeos::remote_apps::mojom::AddFolderResultPtr;
using chromeos::remote_apps::mojom::RemoteAppLaunchObserver;
using chromeos::remote_apps::mojom::RemoteApps;
using chromeos::remote_apps::mojom::RemoteAppsLacrosBridge;

using AddAppCallback = base::OnceCallback<void(AddAppResultPtr)>;
using AddFolderCallback = base::OnceCallback<void(AddFolderResultPtr)>;
using DeleteAppCallback =
    base::OnceCallback<void(const std::optional<std::string>&)>;
using SortLauncherWithRemoteAppsFirstCallback =
    base::OnceCallback<void(const std::optional<std::string>&)>;
using SetPinnedAppsCallback =
    base::OnceCallback<void(const std::optional<std::string>&)>;

using testing::_;

namespace {

class TestRemoteAppsLacrosBridge : public RemoteAppsLacrosBridge,
                                   public RemoteApps {
 public:
  // RemoteAppsLacrosBridge:
  void BindRemoteAppsAndAppLaunchObserverForLacros(
      mojo::PendingReceiver<RemoteApps> remote_apps,
      mojo::PendingRemote<RemoteAppLaunchObserver> observer) override {
    remote_apps_receiver_.Bind(std::move(remote_apps));
    observer_remote_.Bind(std::move(observer));
  }

  void LaunchRemoteApp(const std::string& app_id,
                       const std::string& source_id) {
    observer_remote_->OnRemoteAppLaunched(app_id, source_id);
  }

  // RemoteApps:
  MOCK_METHOD(void,
              AddFolder,
              (const std::string&, bool, AddFolderCallback),
              (override));
  MOCK_METHOD(void,
              AddApp,
              (const std::string&,
               const std::string&,
               const std::string&,
               const GURL&,
               bool,
               AddAppCallback),
              (override));
  MOCK_METHOD(void,
              DeleteApp,
              (const std::string&, DeleteAppCallback),
              (override));
  MOCK_METHOD(void,
              SortLauncherWithRemoteAppsFirst,
              (SortLauncherWithRemoteAppsFirstCallback),
              (override));
  MOCK_METHOD(void,
              SetPinnedApps,
              (const std::vector<std::string>&, SetPinnedAppsCallback),
              (override));

 private:
  mojo::Receiver<RemoteApps> remote_apps_receiver_{this};
  mojo::Remote<RemoteAppLaunchObserver> observer_remote_;
};

class MockRemoteAppLaunchObserver : public RemoteAppLaunchObserver {
 public:
  MOCK_METHOD(void,
              OnRemoteAppLaunched,
              (const std::string&, const std::string&),
              (override));
};

constexpr char kSource[] = "source";
constexpr char kId[] = "id";
constexpr char kName[] = "name";
constexpr char kFolderId[] = "folder_id";
constexpr char kIconUrl[] = "icon_url";

}  // namespace

class RemoteAppsProxyLacrosUnittest : public testing::Test {
 public:
  RemoteAppsProxyLacrosUnittest()
      : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {
    CHECK(testing_profile_manager_.SetUp());
  }

  ~RemoteAppsProxyLacrosUnittest() override {}

  void SetUp() override {
    testing::Test::SetUp();

    testing_profile_ = testing_profile_manager_.CreateTestingProfile("test");
    event_router_ = extensions::CreateAndUseTestEventRouter(testing_profile_);

    bridge_remote_.Bind(bridge_receiver_.BindNewPipeAndPassRemote());
    proxy_ = RemoteAppsProxyLacros::CreateForTesting(testing_profile_,
                                                     bridge_remote_);
    // Wait for Mojo endpoints to connect.
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override {
    event_router_ = nullptr;
    testing_profile_ = nullptr;
    proxy_.reset();
    testing_profile_manager_.DeleteAllTestingProfiles();
    testing::Test::TearDown();
  }

  void SetExpectationForAddAppSuccess(const std::string& app_id,
                                      const std::string& source_id,
                                      const std::string& app_name,
                                      const std::string& folder_id,
                                      const GURL& icon_url,
                                      bool add_to_front) {
    EXPECT_CALL(remote_apps_bridge_, AddApp(source_id, app_name, folder_id,
                                            icon_url, add_to_front, _))
        .WillOnce([app_id](const std::string&, const std::string&,
                           const std::string&, GURL, bool,
                           AddAppCallback callback) {
          std::move(callback).Run(
              chromeos::remote_apps::mojom::AddAppResult::NewAppId(app_id));
        });
  }

  void SetExpectationForAddAppError(const std::string& error,
                                    const std::string& source_id,
                                    const std::string& app_name,
                                    const std::string& folder_id,
                                    const GURL& icon_url,
                                    bool add_to_front) {
    EXPECT_CALL(remote_apps_bridge_, AddApp(source_id, app_name, folder_id,
                                            icon_url, add_to_front, _))
        .WillOnce([error](const std::string&, const std::string&,
                          const std::string&, GURL, bool,
                          AddAppCallback callback) {
          std::move(callback).Run(
              chromeos::remote_apps::mojom::AddAppResult::NewError(error));
        });
  }

  void SetExpectationForAddFolderSuccess(const std::string& folder_id,
                                         const std::string& folder_name,
                                         bool add_to_front) {
    EXPECT_CALL(remote_apps_bridge_, AddFolder(folder_name, add_to_front, _))
        .WillOnce(
            [folder_id](const std::string&, bool, AddFolderCallback callback) {
              std::move(callback).Run(
                  chromeos::remote_apps::mojom::AddFolderResult::NewFolderId(
                      folder_id));
            });
  }

  void SetExpectationForAddFolderError(const std::string& error,
                                       const std::string& folder_name,
                                       bool add_to_front) {
    EXPECT_CALL(remote_apps_bridge_, AddFolder(folder_name, add_to_front, _))
        .WillOnce([error](const std::string&, bool,
                          AddFolderCallback callback) {
          std::move(callback).Run(
              chromeos::remote_apps::mojom::AddFolderResult::NewError(error));
        });
  }

  void SetExpectationForDeleteAppSuccess(const std::string& app_id) {
    EXPECT_CALL(remote_apps_bridge_, DeleteApp(app_id, _))
        .WillOnce([](const std::string&, DeleteAppCallback callback) {
          std::move(callback).Run(std::nullopt);
        });
  }

  void SetExpectationForDeleteAppError(const std::string& error,
                                       const std::string& app_id) {
    EXPECT_CALL(remote_apps_bridge_, DeleteApp(app_id, _))
        .WillOnce([error](const std::string&, DeleteAppCallback callback) {
          std::move(callback).Run(error);
        });
  }

  void SetExpectationForSortLauncherSuccess() {
    EXPECT_CALL(remote_apps_bridge_, SortLauncherWithRemoteAppsFirst(_))
        .WillOnce([](SortLauncherWithRemoteAppsFirstCallback callback) {
          std::move(callback).Run(std::nullopt);
        });
  }

  void SetExpectationForSortLauncherError(const std::string& error) {
    EXPECT_CALL(remote_apps_bridge_, SortLauncherWithRemoteAppsFirst(_))
        .WillOnce([error](SortLauncherWithRemoteAppsFirstCallback callback) {
          std::move(callback).Run(error);
        });
  }

  void SetExpectationForSetPinnedAppsSuccess(
      const std::vector<std::string>& app_ids) {
    EXPECT_CALL(remote_apps_bridge_, SetPinnedApps(app_ids, _))
        .WillOnce([](const std::vector<std::string>&,
                     SetPinnedAppsCallback callback) {
          std::move(callback).Run(std::nullopt);
        });
  }

  void SetExpectationForSetPinnedAppsError(
      const std::string& error,
      const std::vector<std::string>& app_ids) {
    EXPECT_CALL(remote_apps_bridge_, SetPinnedApps(app_ids, _))
        .WillOnce([error](const std::vector<std::string>&,
                          SetPinnedAppsCallback callback) {
          std::move(callback).Run(error);
        });
  }

 protected:
  TestingProfileManager testing_profile_manager_;
  raw_ptr<TestingProfile> testing_profile_ = nullptr;
  raw_ptr<extensions::TestEventRouter> event_router_ =
      nullptr;  // Created in SetUp().
  testing::StrictMock<TestRemoteAppsLacrosBridge> remote_apps_bridge_;
  mojo::Receiver<RemoteAppsLacrosBridge> bridge_receiver_{&remote_apps_bridge_};
  mojo::Remote<RemoteAppsLacrosBridge> bridge_remote_;
  std::unique_ptr<RemoteAppsProxyLacros> proxy_;

 private:
  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(RemoteAppsProxyLacrosUnittest, AddApp) {
  std::string app_id = "app_id";

  base::test::TestFuture<AddAppResultPtr> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForAddAppSuccess(app_id, kSource, kName, kFolderId,
                                 GURL(kIconUrl), true);
  remote->AddApp(kSource, kName, kFolderId, GURL(kIconUrl), true,
                 future.GetCallback());

  AddAppResultPtr result = future.Take();
  ASSERT_TRUE(result->is_app_id());
  ASSERT_EQ(app_id, result->get_app_id());
}

TEST_F(RemoteAppsProxyLacrosUnittest, AddAppError) {
  std::string error = "error";

  base::test::TestFuture<AddAppResultPtr> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForAddAppError(error, kSource, kName, kFolderId, GURL(kIconUrl),
                               true);
  remote->AddApp(kSource, kName, kFolderId, GURL(kIconUrl), true,
                 future.GetCallback());

  AddAppResultPtr result = future.Take();
  ASSERT_TRUE(result->is_error());
  ASSERT_EQ(error, result->get_error());
}

TEST_F(RemoteAppsProxyLacrosUnittest, AddFolder) {
  std::string folder_id = "folder_id";

  base::test::TestFuture<AddFolderResultPtr> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForAddFolderSuccess(folder_id, kName, true);
  remote->AddFolder(kName, true, future.GetCallback());

  AddFolderResultPtr result = future.Take();
  ASSERT_TRUE(result->is_folder_id());
  ASSERT_EQ(folder_id, result->get_folder_id());
}

TEST_F(RemoteAppsProxyLacrosUnittest, AddFolderError) {
  std::string error = "error";

  base::test::TestFuture<AddFolderResultPtr> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForAddFolderError(error, kName, true);
  remote->AddFolder(kName, true, future.GetCallback());

  AddFolderResultPtr result = future.Take();
  ASSERT_TRUE(result->is_error());
  ASSERT_EQ(error, result->get_error());
}

TEST_F(RemoteAppsProxyLacrosUnittest, DeleteApp) {
  base::test::TestFuture<std::optional<std::string>> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForDeleteAppSuccess(kId);
  remote->DeleteApp(kId,
                    future.GetCallback<const std::optional<std::string>&>());

  ASSERT_FALSE(future.Get());
}

TEST_F(RemoteAppsProxyLacrosUnittest, DeleteAppError) {
  std::string error = "error";

  base::test::TestFuture<std::optional<std::string>> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForDeleteAppError(error, kId);
  remote->DeleteApp(kId,
                    future.GetCallback<const std::optional<std::string>&>());

  std::optional<const std::string> result = future.Get();
  ASSERT_TRUE(result);
  ASSERT_EQ(error, *result);
}

TEST_F(RemoteAppsProxyLacrosUnittest, SortLauncherWithRemoteAppsFirst) {
  base::test::TestFuture<std::optional<std::string>> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForSortLauncherSuccess();
  remote->SortLauncherWithRemoteAppsFirst(
      future.GetCallback<const std::optional<std::string>&>());

  ASSERT_FALSE(future.Get());
}

TEST_F(RemoteAppsProxyLacrosUnittest, SortLauncherWithRemoteAppsFirstError) {
  std::string error = "error";

  base::test::TestFuture<std::optional<std::string>> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  SetExpectationForSortLauncherError(error);
  remote->SortLauncherWithRemoteAppsFirst(
      future.GetCallback<const std::optional<std::string>&>());

  std::optional<const std::string> result = future.Get();
  ASSERT_TRUE(result);
  ASSERT_EQ(error, *result);
}

TEST_F(RemoteAppsProxyLacrosUnittest, SetPinnedApps) {
  base::test::TestFuture<std::optional<std::string>> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  std::vector<std::string> ids = {kId};
  SetExpectationForSetPinnedAppsSuccess(ids);
  remote->SetPinnedApps(
      ids, future.GetCallback<const std::optional<std::string>&>());

  ASSERT_FALSE(future.Get());
}

TEST_F(RemoteAppsProxyLacrosUnittest, SetPinnedAppsError) {
  std::string error = "error";

  base::test::TestFuture<std::optional<std::string>> future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer{&mockObserver};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      kSource, remote.BindNewPipeAndPassReceiver(),
      observer.BindNewPipeAndPassRemote());

  std::vector<std::string> ids = {kId};
  SetExpectationForSetPinnedAppsError(error, ids);
  remote->SetPinnedApps(
      ids, future.GetCallback<const std::optional<std::string>&>());

  std::optional<const std::string> result = future.Get();
  ASSERT_TRUE(result);
  ASSERT_EQ(error, *result);
}

// Tests that the `OnRemoteAppLaunched` event is only dispatched to the source
// which added the app by adding multiple apps from different sources.
TEST_F(RemoteAppsProxyLacrosUnittest, OnRemoteAppLaunched) {
  std::string source1 = "source1";
  std::string source2 = "source2";
  std::string app_id1 = "app_id1";
  std::string app_id2 = "app_id2";

  base::test::TestFuture<AddAppResultPtr> add_app_with_source1_future;
  base::test::TestFuture<AddAppResultPtr> add_app_with_source2_future;
  base::test::TestFuture<std::string>
      on_remote_app_launched_with_app_id1_future;
  base::test::TestFuture<std::string>
      on_remote_app_launched_with_app_id2_future;

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver1;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote1;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer1{&mockObserver1};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      source1, remote1.BindNewPipeAndPassReceiver(),
      observer1.BindNewPipeAndPassRemote());

  testing::StrictMock<MockRemoteAppLaunchObserver> mockObserver2;
  mojo::Remote<chromeos::remote_apps::mojom::RemoteApps> remote2;
  mojo::Receiver<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
      observer2{&mockObserver2};
  proxy_->BindRemoteAppsAndAppLaunchObserver(
      source2, remote2.BindNewPipeAndPassReceiver(),
      observer2.BindNewPipeAndPassRemote());

  SetExpectationForAddAppSuccess(app_id1, source1, kName, kFolderId,
                                 GURL(kIconUrl), true);
  remote1->AddApp(source1, kName, kFolderId, GURL(kIconUrl), true,
                  add_app_with_source1_future.GetCallback());

  AddAppResultPtr result1 = add_app_with_source1_future.Take();
  ASSERT_TRUE(result1->is_app_id());
  ASSERT_EQ(app_id1, result1->get_app_id());

  SetExpectationForAddAppSuccess(app_id2, source2, kName, kFolderId,
                                 GURL(kIconUrl), true);
  remote1->AddApp(source2, kName, kFolderId, GURL(kIconUrl), true,
                  add_app_with_source2_future.GetCallback());

  AddAppResultPtr result2 = add_app_with_source2_future.Take();
  ASSERT_TRUE(result2->is_app_id());
  ASSERT_EQ(app_id2, result2->get_app_id());

  EXPECT_CALL(mockObserver1, OnRemoteAppLaunched(app_id1, source1))
      .WillOnce([&on_remote_app_launched_with_app_id1_future](
                    const std::string& app_id, const std::string& source_id) {
        on_remote_app_launched_with_app_id1_future.SetValue(app_id);
      });

  remote_apps_bridge_.LaunchRemoteApp(app_id1, source1);
  ASSERT_EQ(app_id1, on_remote_app_launched_with_app_id1_future.Get());

  EXPECT_CALL(mockObserver2, OnRemoteAppLaunched(app_id2, source2))
      .WillOnce([&on_remote_app_launched_with_app_id2_future](
                    const std::string& app_id, const std::string& source_id) {
        on_remote_app_launched_with_app_id2_future.SetValue(app_id);
      });

  remote_apps_bridge_.LaunchRemoteApp(app_id2, source2);
  ASSERT_EQ(app_id2, on_remote_app_launched_with_app_id2_future.Get());
}

}  // namespace chromeos