chromium/chrome/browser/ash/sync/synced_session_client_ash_unittest.cc

// Copyright 2023 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/ash/sync/synced_session_client_ash.h"

#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chromeos/crosapi/mojom/synced_session_client.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "url/gurl.h"

namespace ash {

namespace {

constexpr char kTestSessionName[] = "testing";
constexpr char kTestUrl[] = "www.google.com";
constexpr char16_t kTestTitle[] = u"helloworld";
constexpr base::Time kTestModifiedTime = base::Time::FromTimeT(100);
constexpr base::Time kTestLastModifiedTimestamp = base::Time::FromTimeT(200);

std::vector<crosapi::mojom::SyncedSessionPtr> CreateTestSyncedSessions() {
  std::vector<crosapi::mojom::SyncedSessionPtr> test_sessions;
  crosapi::mojom::SyncedSessionPtr test_session =
      crosapi::mojom::SyncedSession::New();
  test_session->session_name = kTestSessionName;
  test_session->modified_time = kTestModifiedTime;
  crosapi::mojom::SyncedSessionWindowPtr test_window =
      crosapi::mojom::SyncedSessionWindow::New();
  crosapi::mojom::SyncedSessionTabPtr test_tab =
      crosapi::mojom::SyncedSessionTab::New();
  test_tab->current_navigation_url = GURL(kTestUrl);
  test_tab->last_modified_timestamp = kTestLastModifiedTimestamp;
  test_tab->current_navigation_title = kTestTitle;
  test_window->tabs.push_back(std::move(test_tab));
  test_session->windows.push_back(std::move(test_window));
  test_sessions.push_back(std::move(test_session));
  return test_sessions;
}

class TestSyncedSessionClientObserver
    : public SyncedSessionClientAsh::Observer {
 public:
  TestSyncedSessionClientObserver() = default;
  TestSyncedSessionClientObserver(const TestSyncedSessionClientObserver&) =
      delete;
  TestSyncedSessionClientObserver& operator=(
      const TestSyncedSessionClientObserver&) = delete;
  ~TestSyncedSessionClientObserver() override = default;

  void OnForeignSyncedPhoneSessionsUpdated(
      const std::vector<ForeignSyncedSessionAsh>& sessions) override {
    read_sessions_ = sessions;
  }

  void OnSessionSyncEnabledChanged(bool enabled) override {
    is_session_sync_enabled_ = enabled;
  }

  const std::vector<ForeignSyncedSessionAsh>& GetLastReadSessions() const {
    return read_sessions_;
  }

  bool IsSessionSyncEnabled() const { return is_session_sync_enabled_; }

 private:
  std::vector<ForeignSyncedSessionAsh> read_sessions_;
  bool is_session_sync_enabled_ = false;
};

class FakeCrosapiSessionSyncFaviconDelegate
    : public crosapi::mojom::SyncedSessionClientFaviconDelegate {
 public:
  FakeCrosapiSessionSyncFaviconDelegate() = default;
  FakeCrosapiSessionSyncFaviconDelegate(
      const FakeCrosapiSessionSyncFaviconDelegate&) = delete;
  FakeCrosapiSessionSyncFaviconDelegate& operator=(
      const FakeCrosapiSessionSyncFaviconDelegate&) = delete;
  ~FakeCrosapiSessionSyncFaviconDelegate() override = default;

  // crosapi::mojom::SyncedSessionClientFaviconDelegate:
  void GetFaviconImageForPageURL(
      const GURL& url,
      GetFaviconImageForPageURLCallback callback) override {
    std::move(callback).Run(*result_image_);
  }

  mojo::PendingRemote<crosapi::mojom::SyncedSessionClientFaviconDelegate>
  CreateRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void SetResultImage(gfx::ImageSkia* image) { result_image_ = image; }

 private:
  mojo::Receiver<crosapi::mojom::SyncedSessionClientFaviconDelegate> receiver_{
      this};
  raw_ptr<gfx::ImageSkia> result_image_ = nullptr;
};

gfx::ImageSkia GetTestImage() {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(gfx::kFaviconSize, gfx::kFaviconSize);
  bitmap.eraseColor(SK_ColorBLUE);
  return gfx::Image::CreateFrom1xBitmap(bitmap).AsImageSkia();
}

}  // namespace

class SyncedSessionClientAshTest : public testing::Test {
 public:
  SyncedSessionClientAshTest() = default;
  SyncedSessionClientAshTest(const SyncedSessionClientAshTest&) = delete;
  SyncedSessionClientAshTest& operator=(const SyncedSessionClientAshTest&) =
      delete;
  ~SyncedSessionClientAshTest() override = default;

  void SetUp() override {
    client_ = std::make_unique<SyncedSessionClientAsh>();
    client_remote_.Bind(client_->CreateRemote());
    test_observer_ = std::make_unique<TestSyncedSessionClientObserver>();
    client_->AddObserver(test_observer_.get());
  }

  void RequestFaviconImage(gfx::ImageSkia expected_image) {
    base::test::TestFuture<const gfx::ImageSkia&> future;
    client_->GetFaviconImageForPageURL(GURL(kTestUrl), future.GetCallback());
    EXPECT_TRUE(gfx::test::AreImagesEqual(gfx::Image(expected_image),
                                          gfx::Image(future.Get())));
  }

  SyncedSessionClientAsh* client() {
    DCHECK(client_);
    return client_.get();
  }

  mojo::Remote<crosapi::mojom::SyncedSessionClient>* client_remote() {
    return &client_remote_;
  }

  TestSyncedSessionClientObserver* test_observer() {
    DCHECK(test_observer_);
    return test_observer_.get();
  }

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;

  std::unique_ptr<SyncedSessionClientAsh> client_;
  mojo::Remote<crosapi::mojom::SyncedSessionClient> client_remote_;
  std::unique_ptr<TestSyncedSessionClientObserver> test_observer_;
};

TEST_F(SyncedSessionClientAshTest, OnForeignSyncedPhoneSessionsUpdated) {
  client()->OnForeignSyncedPhoneSessionsUpdated(CreateTestSyncedSessions());
  std::vector<ForeignSyncedSessionAsh> observed_sessions =
      test_observer()->GetLastReadSessions();
  ASSERT_EQ(observed_sessions.size(), 1u);
  EXPECT_EQ(observed_sessions[0].session_name, kTestSessionName);
  EXPECT_EQ(observed_sessions[0].modified_time, kTestModifiedTime);
  ASSERT_EQ(observed_sessions[0].windows.size(), 1u);
  ASSERT_EQ(observed_sessions[0].windows[0].tabs.size(), 1u);
  EXPECT_EQ(observed_sessions[0].windows[0].tabs[0].current_navigation_url,
            GURL(kTestUrl));
  EXPECT_EQ(observed_sessions[0].windows[0].tabs[0].last_modified_timestamp,
            kTestLastModifiedTimestamp);
  EXPECT_EQ(observed_sessions[0].windows[0].tabs[0].current_navigation_title,
            kTestTitle);
}

TEST_F(SyncedSessionClientAshTest, OnSessionSyncEnabledChanged) {
  client()->OnSessionSyncEnabledChanged(/*enabled=*/true);
  EXPECT_TRUE(test_observer()->IsSessionSyncEnabled());
  client()->OnSessionSyncEnabledChanged(/*enabled=*/false);
  EXPECT_FALSE(test_observer()->IsSessionSyncEnabled());
}

TEST_F(SyncedSessionClientAshTest, GetFaviconImage_NoRemote) {
  RequestFaviconImage(gfx::ImageSkia());
}

TEST_F(SyncedSessionClientAshTest, GetFaviconImage_ImagesMatch) {
  FakeCrosapiSessionSyncFaviconDelegate favicon_delegate;
  client()->SetFaviconDelegate(favicon_delegate.CreateRemote());

  gfx::ImageSkia empty_image;
  favicon_delegate.SetResultImage(&empty_image);
  RequestFaviconImage(gfx::ImageSkia());

  gfx::ImageSkia test_image = GetTestImage();
  favicon_delegate.SetResultImage(&test_image);
  RequestFaviconImage(GetTestImage());
}

}  // namespace ash