chromium/components/data_sharing/internal/android/data_sharing_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/data_sharing/internal/android/data_sharing_service_android.h"

#include "base/android/jni_android.h"
#include "base/functional/callback_forward.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "components/data_sharing/internal/data_sharing_service_impl.h"
#include "components/data_sharing/internal/fake_data_sharing_sdk_delegate.h"
#include "components/data_sharing/public/data_sharing_service.h"
#include "components/data_sharing/public/group_data.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/test/data_type_store_test_util.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/status/status.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/data_sharing/internal/test_jni_headers/TestServiceObserver_jni.h"

namespace data_sharing {

using ::base::android::AttachCurrentThread;
using ::base::android::ScopedJavaLocalRef;

// Java observer for testing, counter part of TestServiceObserver.
// On each observation increments a counter and runs the callback.
class TestJavaObserver {
 public:
  TestJavaObserver(DataSharingService* service, base::OnceClosure callback)
      : service_(DataSharingService::GetJavaObject(service)),
        java_obj_(Java_TestServiceObserver_createAndAdd(
            AttachCurrentThread(),
            service_,
            reinterpret_cast<long>(this))),
        callback_(std::move(callback)) {}
  ~TestJavaObserver() {
    Java_TestServiceObserver_destroy(AttachCurrentThread(), java_obj_,
                                     service_);
  }

  int GetGroupChangeCount() {
    return Java_TestServiceObserver_getOnGroupChangeCount(AttachCurrentThread(),
                                                          java_obj_);
  }
  int GetGroupAddedCount() {
    return Java_TestServiceObserver_getOnGroupAddedCount(AttachCurrentThread(),
                                                         java_obj_);
  }
  int GetGroupRemovedCount() {
    return Java_TestServiceObserver_getOnGroupRemovedCount(
        AttachCurrentThread(), java_obj_);
  }

  void ResetCallback(base::OnceClosure callback) {
    callback_ = std::move(callback);
  }

  void OnObserverNotify() { std::move(callback_).Run(); }

 private:
  ScopedJavaLocalRef<jobject> service_;
  ScopedJavaLocalRef<jobject> java_obj_;

  base::OnceClosure callback_;
};

// Implements TestServiceObserver.onObserverNotify static method.
void JNI_TestServiceObserver_OnObserverNotify(JNIEnv* env, jlong observer_ptr) {
  reinterpret_cast<TestJavaObserver*>(observer_ptr)->OnObserverNotify();
}

namespace {

sync_pb::CollaborationGroupSpecifics MakeCollaborationGroupSpecifics(
    const GroupId& id) {
  sync_pb::CollaborationGroupSpecifics result;
  result.set_collaboration_id(id.value());
  result.set_changed_at_timestamp_millis_since_unix_epoch(
      base::Time::Now().InMillisecondsSinceUnixEpoch());
  return result;
}

syncer::EntityData EntityDataFromSpecifics(
    const sync_pb::CollaborationGroupSpecifics& specifics) {
  syncer::EntityData entity_data;
  *entity_data.specifics.mutable_collaboration_group() = specifics;
  entity_data.name = specifics.collaboration_id();
  return entity_data;
}

std::unique_ptr<syncer::EntityChange> EntityChangeAddFromSpecifics(
    const sync_pb::CollaborationGroupSpecifics& specifics) {
  return syncer::EntityChange::CreateAdd(specifics.collaboration_id(),
                                         EntityDataFromSpecifics(specifics));
}

std::unique_ptr<syncer::EntityChange> EntityChangeUpdateFromSpecifics(
    const sync_pb::CollaborationGroupSpecifics& specifics) {
  return syncer::EntityChange::CreateUpdate(specifics.collaboration_id(),
                                            EntityDataFromSpecifics(specifics));
}

std::unique_ptr<syncer::EntityChange> EntityChangeDeleteFromSpecifics(
    const sync_pb::CollaborationGroupSpecifics& specifics) {
  return syncer::EntityChange::CreateDelete(specifics.collaboration_id());
}

}  // namespace

class DataSharingServiceAndroidTest : public testing::Test {
 public:
  DataSharingServiceAndroidTest() = default;

  ~DataSharingServiceAndroidTest() override = default;

  void SetUp() override {
    Test::SetUp();
    scoped_refptr<network::SharedURLLoaderFactory> test_url_loader_factory =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            &test_url_loader_factory_);

    std::unique_ptr<FakeDataSharingSDKDelegate> sdk_delegate =
        std::make_unique<FakeDataSharingSDKDelegate>();
    not_owned_sdk_delegate_ = sdk_delegate.get();

    data_sharing_service_ = std::make_unique<DataSharingServiceImpl>(
        std::move(test_url_loader_factory),
        identity_test_env_.identity_manager(),
        syncer::DataTypeStoreTestUtil::FactoryForInMemoryStoreForTest(),
        version_info::Channel::UNKNOWN, std::move(sdk_delegate),
        /*ui_delegate=*/nullptr);
    data_sharing_service_android_ = std::make_unique<DataSharingServiceAndroid>(
        data_sharing_service_.get());
  }

  void TearDown() override {
    data_sharing_service_android_.reset();
    not_owned_sdk_delegate_ = nullptr;
    data_sharing_service_.reset();
  }

  // Creates group and returns ID.
  // Mimics initial sync for collaboration group datatype, this should trigger
  // OnGroupAdded() notification.
  GroupId CreateGroup() {
    const std::string display_name = "display_name";
    const GroupId group_id =
        not_owned_sdk_delegate_->AddGroupAndReturnId(display_name);

    auto* collaboration_group_bridge =
        data_sharing_service_->GetCollaborationGroupSyncBridgeForTesting();

    syncer::EntityChangeList entity_changes;
    entity_changes.push_back(EntityChangeAddFromSpecifics(
        MakeCollaborationGroupSpecifics(group_id)));

    collaboration_group_bridge->MergeFullSyncData(
        collaboration_group_bridge->CreateMetadataChangeList(),
        std::move(entity_changes));
    return group_id;
  }

  // Removes the group with `group_id`, which would trigger the OnGroupRemoved()
  // notification.
  void RemoveGroup(const GroupId& group_id) {
    auto* collaboration_group_bridge =
        data_sharing_service_->GetCollaborationGroupSyncBridgeForTesting();
    not_owned_sdk_delegate_->RemoveGroup(group_id);
    syncer::EntityChangeList entity_changes;
    entity_changes.push_back(EntityChangeDeleteFromSpecifics(
        MakeCollaborationGroupSpecifics(group_id)));

    collaboration_group_bridge->ApplyIncrementalSyncChanges(
        collaboration_group_bridge->CreateMetadataChangeList(),
        std::move(entity_changes));
  }

  // Updates the group with `group_id` with a different name, which wuld trigger
  // the OnGroupUpdated() notification.
  void UpdateGroup(const GroupId& group_id) {
    const std::string new_display_name = "new_display_name";
    auto* collaboration_group_bridge =
        data_sharing_service_->GetCollaborationGroupSyncBridgeForTesting();
    not_owned_sdk_delegate_->UpdateGroup(group_id, new_display_name);
    syncer::EntityChangeList entity_changes;
    entity_changes.push_back(EntityChangeUpdateFromSpecifics(
        MakeCollaborationGroupSpecifics(group_id)));

    collaboration_group_bridge->ApplyIncrementalSyncChanges(
        collaboration_group_bridge->CreateMetadataChangeList(),
        std::move(entity_changes));
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  signin::IdentityTestEnvironment identity_test_env_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  std::unique_ptr<DataSharingServiceImpl> data_sharing_service_;
  std::unique_ptr<DataSharingServiceAndroid> data_sharing_service_android_;
  raw_ptr<FakeDataSharingSDKDelegate> not_owned_sdk_delegate_;
};

TEST_F(DataSharingServiceAndroidTest, GroupAddedObservation) {
  base::RunLoop run_loop;
  TestJavaObserver observer(data_sharing_service_.get(),
                            run_loop.QuitClosure());

  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
  EXPECT_EQ(observer.GetGroupAddedCount(), 0);
  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);

  CreateGroup();

  run_loop.Run();

  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);
}

TEST_F(DataSharingServiceAndroidTest, GroupRemovedObservation) {
  base::RunLoop run_loop;
  TestJavaObserver observer(data_sharing_service_.get(),
                            run_loop.QuitClosure());
  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
  EXPECT_EQ(observer.GetGroupAddedCount(), 0);
  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);

  GroupId group_id = CreateGroup();

  run_loop.Run();
  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);

  base::RunLoop wait_for_remove;
  observer.ResetCallback(wait_for_remove.QuitClosure());

  RemoveGroup(group_id);

  wait_for_remove.Run();
  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
  EXPECT_EQ(observer.GetGroupRemovedCount(), 1);
}

TEST_F(DataSharingServiceAndroidTest, GroupChangeObservation) {
  base::RunLoop run_loop;
  TestJavaObserver observer(data_sharing_service_.get(),
                            run_loop.QuitClosure());
  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
  EXPECT_EQ(observer.GetGroupAddedCount(), 0);
  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);

  GroupId group_id = CreateGroup();

  run_loop.Run();
  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);

  base::RunLoop wait_for_update;
  observer.ResetCallback(wait_for_update.QuitClosure());

  UpdateGroup(group_id);

  wait_for_update.Run();
}

}  // namespace data_sharing