chromium/components/saved_tab_groups/android/tab_group_sync_service_android_unittest.cc

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

#include "components/saved_tab_groups/android/tab_group_sync_service_android.h"

#include <memory>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "components/saved_tab_groups/android/tab_group_sync_conversions_bridge.h"
#include "components/saved_tab_groups/android/tab_group_sync_conversions_utils.h"
#include "components/saved_tab_groups/mock_tab_group_sync_service.h"
#include "components/saved_tab_groups/saved_tab_group_test_utils.h"
#include "components/sync/test/test_matchers.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/saved_tab_groups/native_j_unittests_jni_headers/TabGroupSyncServiceAndroidUnitTest_jni.h"

using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
using testing::_;
using testing::Eq;
using testing::Return;
using testing::SaveArg;

namespace tab_groups {
namespace {

const char kTestUuid[] = "abcdefgh";
const char16_t kTestGroupTitle[] = u"Test Group";
const char kTestUrl[] = "https://google.com";
const char16_t kTestTabTitle[] = u"Test Tab";
const int kTabId1 = 2;
const int kTabId2 = 4;
const int kPosition = 3;

MATCHER_P(UuidEq, uuid, "") {
  return arg.saved_guid() == uuid;
}

MATCHER_P3(TabBuilderEq, title, url, position, "") {
  return arg.title() == title && arg.url() == url &&
         static_cast<int>(arg.position()) == position;
}

}  // namespace

class TabGroupSyncServiceAndroidTest : public testing::Test {
 public:
  TabGroupSyncServiceAndroidTest() = default;
  ~TabGroupSyncServiceAndroidTest() override = default;

  void SetUp() override {
    j_test_ = Java_TabGroupSyncServiceAndroidUnitTest_Constructor(
        AttachCurrentThread());
    CreateBridge();
    SetUpJavaTestObserver();
  }

  void CreateBridge() {
    EXPECT_CALL(tab_group_sync_service_, AddObserver(_));
    bridge_ =
        std::make_unique<TabGroupSyncServiceAndroid>(&tab_group_sync_service_);
    j_service_ = bridge_->GetJavaObject();
  }

  void SetUpJavaTestObserver() {
    auto* env = AttachCurrentThread();
    Java_TabGroupSyncServiceAndroidUnitTest_setUpTestObserver(env, j_test_,
                                                              j_service_);
  }

  void TearDown() override {
    EXPECT_CALL(tab_group_sync_service_, RemoveObserver(_));
  }

  MockTabGroupSyncService tab_group_sync_service_;
  std::unique_ptr<TabGroupSyncServiceAndroid> bridge_;
  base::android::ScopedJavaLocalRef<jobject> j_service_;
  base::android::ScopedJavaGlobalRef<jobject> j_test_;
  LocalTabGroupID test_tab_group_id_ = base::Token(4, 5);
};

TEST_F(TabGroupSyncServiceAndroidTest, OnInitialized) {
  bridge_->OnInitialized();
  Java_TabGroupSyncServiceAndroidUnitTest_testOnInitialized(
      AttachCurrentThread(), j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, UuidConversion) {
  auto* env = AttachCurrentThread();
  base::Uuid uuid = base::Uuid::ParseCaseInsensitive(kTestUuid);
  auto j_uuid = UuidToJavaString(env, uuid);
  auto uuid2 =
      JavaStringToUuid(env, JavaParamRef<jstring>(env, j_uuid.Release()));
  EXPECT_EQ(uuid, uuid2);
}

TEST_F(TabGroupSyncServiceAndroidTest, TabGroupIdConversion) {
  auto* env = AttachCurrentThread();
  LocalTabGroupID tab_group_id = test_tab_group_id_;
  auto j_tab_group_id = TabGroupSyncConversionsBridge::ToJavaTabGroupId(
      env, std::make_optional<LocalTabGroupID>(tab_group_id));
  auto retrieved_tab_group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(
          env, JavaParamRef<jobject>(env, j_tab_group_id.Release()));
  EXPECT_EQ(retrieved_tab_group_id, tab_group_id);
}

TEST_F(TabGroupSyncServiceAndroidTest, TabIdConversion) {
  LocalTabID tab_id = 5;
  EXPECT_EQ(FromJavaTabId(ToJavaTabId(std::make_optional<LocalTabID>(tab_id))),
            tab_id);
}

TEST_F(TabGroupSyncServiceAndroidTest, SavedTabGroupConversion) {
  auto* env = AttachCurrentThread();
  SavedTabGroup group = test::CreateTestSavedTabGroup();
  group.SetTitle(kTestGroupTitle);
  group.SetColor(tab_groups::TabGroupColorId::kRed);
  group.SetCreatorCacheGuid("creator_cache_guid");
  group.SetLastUpdaterCacheGuid("last_updater_cache_guid");

  SavedTabGroupTab tab3(GURL(), kTestTabTitle, group.saved_guid(),
                        /*position=*/std::nullopt,
                        /*saved_tab_guid=*/std::nullopt, /*local_tab_id=*/9,
                        "creator_cache_guid", "last_updater_cache_guid");
  group.AddTabLocally(tab3);
  auto j_group = TabGroupSyncConversionsBridge::CreateGroup(env, group);
  Java_TabGroupSyncServiceAndroidUnitTest_testSavedTabGroupConversion(
      AttachCurrentThread(), j_test_, j_group);
}

TEST_F(TabGroupSyncServiceAndroidTest, OnTabGroupAdded) {
  auto* env = AttachCurrentThread();
  SavedTabGroup group = test::CreateTestSavedTabGroup();
  group.SetTitle(kTestGroupTitle);
  group.SetColor(tab_groups::TabGroupColorId::kBlue);
  bridge_->OnTabGroupAdded(group, TriggerSource::REMOTE);
  Java_TabGroupSyncServiceAndroidUnitTest_testOnTabGroupAdded(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, OnTabGroupUpdated) {
  auto* env = AttachCurrentThread();
  SavedTabGroup group = test::CreateTestSavedTabGroup();
  group.SetTitle(kTestGroupTitle);
  group.SetColor(tab_groups::TabGroupColorId::kBlue);
  bridge_->OnTabGroupUpdated(group, TriggerSource::REMOTE);
  Java_TabGroupSyncServiceAndroidUnitTest_testOnTabGroupUpdated(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, OnTabGroupRemoved) {
  auto* env = AttachCurrentThread();
  base::Uuid group_id = base::Uuid::GenerateRandomV4();
  bridge_->OnTabGroupRemoved(test_tab_group_id_, TriggerSource::REMOTE);
  bridge_->OnTabGroupRemoved(group_id, TriggerSource::REMOTE);
  Java_TabGroupSyncServiceAndroidUnitTest_testOnTabGroupRemoved(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, OnTabGroupLocalIdChanged) {
  auto* env = AttachCurrentThread();
  base::Uuid group_id = base::Uuid::GenerateRandomV4();
  bridge_->OnTabGroupLocalIdChanged(group_id, test_tab_group_id_);
  Java_TabGroupSyncServiceAndroidUnitTest_testOnTabGroupLocalIdChanged(env,
                                                                       j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, CreateGroup) {
  auto* env = AttachCurrentThread();
  SavedTabGroup captured_group = test::CreateTestSavedTabGroup();
  EXPECT_CALL(tab_group_sync_service_, AddGroup(_))
      .WillOnce(SaveArg<0>(&captured_group));
  Java_TabGroupSyncServiceAndroidUnitTest_testCreateGroup(env, j_test_);

  EXPECT_TRUE(captured_group.local_group_id().has_value());
  EXPECT_EQ(test_tab_group_id_, captured_group.local_group_id().value());
}

TEST_F(TabGroupSyncServiceAndroidTest, RemoveGroupByLocalId) {
  auto* env = AttachCurrentThread();

  EXPECT_CALL(tab_group_sync_service_, RemoveGroup(test_tab_group_id_))
      .Times(1);
  Java_TabGroupSyncServiceAndroidUnitTest_testRemoveGroupByLocalId(env,
                                                                   j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, RemoveGroupBySyncId) {
  auto* env = AttachCurrentThread();

  base::Uuid uuid = base::Uuid::ParseCaseInsensitive(kTestUuid);
  auto j_uuid = UuidToJavaString(env, uuid);

  EXPECT_CALL(tab_group_sync_service_, RemoveGroup(uuid)).Times(1);
  Java_TabGroupSyncServiceAndroidUnitTest_testRemoveGroupBySyncId(env, j_test_,
                                                                  j_uuid);
}

TEST_F(TabGroupSyncServiceAndroidTest, UpdateVisualData) {
  auto* env = AttachCurrentThread();

  EXPECT_CALL(tab_group_sync_service_,
              UpdateVisualData(Eq(test_tab_group_id_), _));
  Java_TabGroupSyncServiceAndroidUnitTest_testUpdateVisualData(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, MakeTabGroupShared) {
  JNIEnv* env = AttachCurrentThread();
  const std::string collaboration_id = "collaboration";

  EXPECT_CALL(tab_group_sync_service_,
              MakeTabGroupShared(Eq(test_tab_group_id_), Eq(collaboration_id)));
  ScopedJavaLocalRef<jstring> j_collaboration_id =
      base::android::ConvertUTF8ToJavaString(env, collaboration_id);
  Java_TabGroupSyncServiceAndroidUnitTest_testMakeTabGroupShared(
      env, j_test_, j_collaboration_id);
}

TEST_F(TabGroupSyncServiceAndroidTest, AddTab) {
  auto* env = AttachCurrentThread();

  GURL url(kTestUrl);
  EXPECT_CALL(tab_group_sync_service_,
              AddTab(Eq(test_tab_group_id_), Eq(kTabId1), Eq(kTestTabTitle),
                     Eq(url), Eq(kPosition)));

  EXPECT_CALL(tab_group_sync_service_,
              AddTab(Eq(test_tab_group_id_), Eq(kTabId2), Eq(kTestTabTitle),
                     Eq(url), Eq(std::nullopt)));
  Java_TabGroupSyncServiceAndroidUnitTest_testAddTab(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, UpdateTab) {
  auto* env = AttachCurrentThread();

  GURL url(kTestUrl);
  EXPECT_CALL(tab_group_sync_service_,
              UpdateTab(Eq(test_tab_group_id_), Eq(kTabId1),
                        TabBuilderEq(kTestTabTitle, url, kPosition)));
  EXPECT_CALL(tab_group_sync_service_,
              UpdateTab(Eq(test_tab_group_id_), Eq(kTabId2),
                        TabBuilderEq(kTestTabTitle, url, 0)));
  Java_TabGroupSyncServiceAndroidUnitTest_testUpdateTab(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, RemoveTab) {
  auto* env = AttachCurrentThread();

  EXPECT_CALL(tab_group_sync_service_,
              RemoveTab(Eq(test_tab_group_id_), Eq(kTabId1)));
  Java_TabGroupSyncServiceAndroidUnitTest_testRemoveTab(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, MoveTab) {
  auto* env = AttachCurrentThread();

  EXPECT_CALL(tab_group_sync_service_,
              MoveTab(Eq(test_tab_group_id_), Eq(kTabId1), Eq(kPosition)));
  Java_TabGroupSyncServiceAndroidUnitTest_testMoveTab(env, j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, GetAllGroups) {
  auto group = test::CreateTestSavedTabGroup();
  std::vector<SavedTabGroup> expectedGroups = {group};
  EXPECT_CALL(tab_group_sync_service_, GetAllGroups())
      .WillOnce(Return(expectedGroups));
  Java_TabGroupSyncServiceAndroidUnitTest_testGetAllGroups(
      AttachCurrentThread(), j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, GetGroupBySyncId) {
  auto* env = AttachCurrentThread();
  auto group1 = test::CreateTestSavedTabGroup();
  base::Uuid uuid2 = base::Uuid::ParseCaseInsensitive(kTestUuid);

  EXPECT_CALL(tab_group_sync_service_, GetGroup(group1.saved_guid()))
      .WillOnce(Return(std::make_optional<SavedTabGroup>(group1)));
  EXPECT_CALL(tab_group_sync_service_, GetGroup(uuid2))
      .WillOnce(Return(std::nullopt));

  auto j_uuid1 = UuidToJavaString(env, group1.saved_guid());
  auto j_uuid2 = UuidToJavaString(env, uuid2);
  Java_TabGroupSyncServiceAndroidUnitTest_testGetGroupBySyncId(
      env, j_test_, j_uuid1, j_uuid2);
}

TEST_F(TabGroupSyncServiceAndroidTest, GetGroupByLocalId) {
  auto* env = AttachCurrentThread();
  auto group1 = test::CreateTestSavedTabGroup();
  auto local_id_1 = test::GenerateRandomTabGroupID();
  auto local_id_2 = test::GenerateRandomTabGroupID();

  EXPECT_CALL(tab_group_sync_service_, GetGroup(local_id_1))
      .WillOnce(Return(std::make_optional<SavedTabGroup>(group1)));
  EXPECT_CALL(tab_group_sync_service_, GetGroup(local_id_2))
      .WillOnce(Return(std::nullopt));

  auto j_local_id_1 =
      TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, local_id_1);
  auto j_local_id_2 =
      TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, local_id_2);
  Java_TabGroupSyncServiceAndroidUnitTest_testGetGroupByLocalId(
      env, j_test_, j_local_id_1, j_local_id_2);
}

TEST_F(TabGroupSyncServiceAndroidTest, GetDeletedGroupIds) {
  auto local_id_1 = test::GenerateRandomTabGroupID();
  std::vector<LocalTabGroupID> expectedGroupIds = {local_id_1};
  EXPECT_CALL(tab_group_sync_service_, GetDeletedGroupIds())
      .WillOnce(Return(expectedGroupIds));
  Java_TabGroupSyncServiceAndroidUnitTest_testGetDeletedGroupIds(
      AttachCurrentThread(), j_test_);
}

TEST_F(TabGroupSyncServiceAndroidTest, UpdateLocalTabGroupMapping) {
  auto* env = AttachCurrentThread();
  base::Uuid group_id = base::Uuid::GenerateRandomV4();
  auto j_group_id = UuidToJavaString(env, group_id);

  // Update the mapping.
  EXPECT_CALL(tab_group_sync_service_,
              UpdateLocalTabGroupMapping(Eq(group_id), Eq(test_tab_group_id_)));
  Java_TabGroupSyncServiceAndroidUnitTest_testUpdateLocalTabGroupMapping(
      AttachCurrentThread(), j_test_, j_group_id,
      TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_));

  // Remove the mapping.
  EXPECT_CALL(tab_group_sync_service_,
              RemoveLocalTabGroupMapping(Eq(test_tab_group_id_)));
  Java_TabGroupSyncServiceAndroidUnitTest_testRemoveLocalTabGroupMapping(
      AttachCurrentThread(), j_test_,
      TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_));
}

TEST_F(TabGroupSyncServiceAndroidTest, UpdateLocalTabId) {
  auto* env = AttachCurrentThread();
  base::Uuid tab_id = base::Uuid::GenerateRandomV4();
  auto j_tab_id = UuidToJavaString(env, tab_id);

  EXPECT_CALL(tab_group_sync_service_,
              UpdateLocalTabId(Eq(test_tab_group_id_), Eq(tab_id), Eq(4)));
  Java_TabGroupSyncServiceAndroidUnitTest_testUpdateLocalTabId(
      AttachCurrentThread(), j_test_,
      TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_),
      j_tab_id, 4);
}

}  // namespace tab_groups