chromium/chrome/browser/feedback/android/family_info_feedback_source_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/feedback/android/family_info_feedback_source.h"

#include <memory>
#include <string>

#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/memory/weak_ptr.h"
#include "base/notreached.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/signin/test_signin_client_builder.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/supervised_user/core/browser/proto/families_common.pb.h"
#include "components/supervised_user/core/browser/proto/kidsmanagement_messages.pb.h"
#include "components/supervised_user/core/browser/proto_fetcher_status.h"
#include "components/supervised_user/core/browser/supervised_user_service.h"
#include "components/supervised_user/core/browser/supervised_user_url_filter.h"
#include "components/supervised_user/core/browser/supervised_user_utils.h"
#include "components/supervised_user/core/common/supervised_user_constants.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "testing/gtest/include/gtest/gtest.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/test/test_support_jni_headers/FamilyInfoFeedbackSourceTestBridge_jni.h"

using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaLocalRef;

namespace chrome::android {
namespace {

const char kTestEmail[] = "[email protected]";
const char kFeedbackTagParentalControlSitesChild[] =
    "Parental_Control_Sites_Child";

kidsmanagement::ListMembersResponse CreateFamilyWithOneMember(
    const std::string& gaia_id,
    kidsmanagement::FamilyRole role) {
  kidsmanagement::ListMembersResponse response;
  kidsmanagement::FamilyMember* member = response.add_members();

  member->set_user_id(gaia_id);
  member->set_role(role);
  member->mutable_profile()->set_display_name("Name");
  member->mutable_profile()->set_email(kTestEmail);
  return response;
}
}  // namespace

// TODO(b/280772872): Integrate AsyncURLChecker to test
// supervised_user::SupervisedUserURLFilter::WebFilterType::kTryToBlockMatureSites.
class FamilyInfoFeedbackSourceForChildFilterBehaviorTest
    : public testing::TestWithParam<supervised_user::FilteringBehavior> {
 public:
  FamilyInfoFeedbackSourceForChildFilterBehaviorTest()
      : env_(base::android::AttachCurrentThread()) {}

  void SetUp() override {
    TestingProfile::Builder builder;
    builder.AddTestingFactory(
        ChromeSigninClientFactory::GetInstance(),
        base::BindRepeating(&signin::BuildTestSigninClient));
    builder.SetIsSupervisedProfile();

    role_ = kidsmanagement::CHILD;
    profile_ = IdentityTestEnvironmentProfileAdaptor::
        CreateProfileForIdentityTestEnvironment(builder);
    identity_test_env_profile_adaptor_ =
        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_.get());
    j_feedback_source_ = CreateJavaObjectForTesting();
    supervised_user_service_ =
        SupervisedUserServiceFactory::GetForProfile(profile_.get());
  }

 protected:
  signin::IdentityTestEnvironment* identity_test_env() {
    return identity_test_env_profile_adaptor_->identity_test_env();
  }

  // Methods to access Java counterpart FamilyInfoFeedbackSource.
  std::string GetFeedbackValue(std::string feedback_tag) {
    const base::android::JavaRef<jstring>& j_value =
        Java_FamilyInfoFeedbackSourceTestBridge_getValue(
            env_,
            base::android::JavaParamRef<jobject>(env_,
                                                 j_feedback_source_.obj()),
            base::android::ConvertUTF8ToJavaString(env_, feedback_tag));
    return base::android::ConvertJavaStringToUTF8(env_, j_value);
  }

  void OnListFamilyMembersSuccess(
      base::WeakPtr<FamilyInfoFeedbackSource> feedback_source,
      const kidsmanagement::ListMembersResponse& members) {
    feedback_source->OnSuccess(members);
  }

  // Creates a new instance of FamilyInfoFeedbackSource that is destroyed on
  // completion of OnGetFamilyMembers* methods.
  base::WeakPtr<FamilyInfoFeedbackSource> CreateFamilyInfoFeedbackSource() {
    FamilyInfoFeedbackSource* source = new FamilyInfoFeedbackSource(
        base::android::JavaParamRef<jobject>(env_, j_feedback_source_.obj()),
        profile_.get());
    return source->weak_factory_.GetWeakPtr();
  }

  kidsmanagement::FamilyRole role_;
  raw_ptr<supervised_user::SupervisedUserService> supervised_user_service_;

 private:
  // Creates a Java instance of FamilyInfoFeedbackSource.
  base::android::ScopedJavaLocalRef<jobject> CreateJavaObjectForTesting() {
    return Java_FamilyInfoFeedbackSourceTestBridge_createFamilyInfoFeedbackSource(
        env_, profile_.get()->GetJavaObject());
  }

  content::BrowserTaskEnvironment task_environment_;
  base::android::ScopedJavaLocalRef<jobject> j_feedback_source_;
  raw_ptr<JNIEnv> env_;
  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
      identity_test_env_profile_adaptor_;
  std::unique_ptr<TestingProfile> profile_;
};

// Tests that the parental control sites value for a child user is recorded.
TEST_P(FamilyInfoFeedbackSourceForChildFilterBehaviorTest,
       GetChildFilteringBehaviour) {
  CoreAccountInfo primary_account =
      identity_test_env()->MakePrimaryAccountAvailable(
          kTestEmail, signin::ConsentLevel::kSignin);

  supervised_user_service_->GetURLFilter()->SetDefaultFilteringBehavior(
      GetParam());

  kidsmanagement::ListMembersResponse members =
      CreateFamilyWithOneMember(primary_account.gaia, role_);

  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
      CreateFamilyInfoFeedbackSource();
  OnListFamilyMembersSuccess(feedback_source, members);

  std::string expected_feedback_value =
      GetFeedbackValue(kFeedbackTagParentalControlSitesChild);

  // Don't put logic in tests, test explicit values.
  switch (GetParam()) {
    case (supervised_user::FilteringBehavior::kBlock):
      EXPECT_EQ("allow_certain_sites", expected_feedback_value);
      break;
    case (supervised_user::FilteringBehavior::kAllow):
      // Safe sites is enabled by default.
      EXPECT_EQ("block_mature_sites", expected_feedback_value);
      break;
    default:
      // Remaining combinations are not tested.
      NOTREACHED();
  }
}

INSTANTIATE_TEST_SUITE_P(
    FilterBehaviourContainer,
    FamilyInfoFeedbackSourceForChildFilterBehaviorTest,
    ::testing::Values(supervised_user::FilteringBehavior::kBlock,
                      supervised_user::FilteringBehavior::kAllow));

class FamilyInfoFeedbackSourceTest
    : public testing::TestWithParam<kidsmanagement::FamilyRole> {
 public:
  FamilyInfoFeedbackSourceTest() : env_(base::android::AttachCurrentThread()) {}

  void SetUp() override {
    TestingProfile::Builder builder;
    builder.AddTestingFactory(
        ChromeSigninClientFactory::GetInstance(),
        base::BindRepeating(&signin::BuildTestSigninClient));

    is_child_ = false;
    // Check if the test is parametrized and the parameter is a CHILD role.
    if (::testing::UnitTest::GetInstance()
            ->current_test_info()
            ->value_param()) {
      is_child_ = GetParam() == kidsmanagement::CHILD;
      if (is_child_) {
        builder.SetIsSupervisedProfile();
      }
    }

    profile_ = IdentityTestEnvironmentProfileAdaptor::
        CreateProfileForIdentityTestEnvironment(builder);
    identity_test_env_profile_adaptor_ =
        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_.get());
    j_feedback_source_ = CreateJavaObjectForTesting();
  }

 protected:
  signin::IdentityTestEnvironment* identity_test_env() {
    return identity_test_env_profile_adaptor_->identity_test_env();
  }

  // Methods to access Java counterpart FamilyInfoFeedbackSource.
  std::string GetFeedbackValue() {
    const base::android::JavaRef<jstring>& j_value =
        Java_FamilyInfoFeedbackSourceTestBridge_getValue(
            env_,
            base::android::JavaParamRef<jobject>(env_,
                                                 j_feedback_source_.obj()),
            base::android::ConvertUTF8ToJavaString(
                env_, supervised_user::kFamilyMemberRoleFeedbackTag));
    return base::android::ConvertJavaStringToUTF8(env_, j_value);
  }

  void OnListFamilyMembersSuccess(
      base::WeakPtr<FamilyInfoFeedbackSource> feedback_source,
      const kidsmanagement::ListMembersResponse& members) {
    feedback_source->OnSuccess(members);
  }

  void OnGetFamilyMembersFailure(
      base::WeakPtr<FamilyInfoFeedbackSource> feedback_source) {
    feedback_source->OnFailure(
        supervised_user::ProtoFetcherStatus::GoogleServiceAuthError(
            GoogleServiceAuthError(
                GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS)));
  }

  // Creates a new instance of FamilyInfoFeedbackSource that is destroyed on
  // completion of OnGetFamilyMembers* methods.
  base::WeakPtr<FamilyInfoFeedbackSource> CreateFamilyInfoFeedbackSource() {
    FamilyInfoFeedbackSource* source = new FamilyInfoFeedbackSource(
        base::android::JavaParamRef<jobject>(env_, j_feedback_source_.obj()),
        profile_.get());
    return source->weak_factory_.GetWeakPtr();
  }

  Profile* profile() const { return profile_.get(); }

  bool is_child() const { return is_child_; }

 private:
  // Creates a Java instance of FamilyInfoFeedbackSource.
  base::android::ScopedJavaLocalRef<jobject> CreateJavaObjectForTesting() {
    return Java_FamilyInfoFeedbackSourceTestBridge_createFamilyInfoFeedbackSource(
        env_, profile_.get()->GetJavaObject());
  }

  content::BrowserTaskEnvironment task_environment_;

  base::android::ScopedJavaLocalRef<jobject> j_feedback_source_;
  raw_ptr<JNIEnv> env_;
  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
      identity_test_env_profile_adaptor_;
  std::unique_ptr<TestingProfile> profile_;
  bool is_child_;
};

// Tests that the family role for a user in a Family Group is recorded.
TEST_P(FamilyInfoFeedbackSourceTest, GetFamilyMembersSignedIn) {
  CoreAccountInfo primary_account =
      identity_test_env()->MakePrimaryAccountAvailable(
          kTestEmail, signin::ConsentLevel::kSignin);

  kidsmanagement::FamilyRole role = GetParam();
  kidsmanagement::ListMembersResponse members =
      CreateFamilyWithOneMember(primary_account.gaia, role);

  if (is_child()) {
    supervised_user::SupervisedUserService* supervised_user_service_ =
        SupervisedUserServiceFactory::GetForProfile(profile());
    // Set some filtering behavior for the user, as ListFamilyMembers
    // will try to obtain this along with the family role (and crush otherwise).
    supervised_user_service_->GetURLFilter()->SetDefaultFilteringBehavior(
        supervised_user::FilteringBehavior::kAllow);
  }

  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
      CreateFamilyInfoFeedbackSource();
  OnListFamilyMembersSuccess(feedback_source, members);

  // Don't put logic in tests, test explicit values.
  switch (role) {
    case kidsmanagement::HEAD_OF_HOUSEHOLD:
      EXPECT_EQ("family_manager", GetFeedbackValue());
      break;
    case kidsmanagement::PARENT:
      EXPECT_EQ("parent", GetFeedbackValue());
      break;
    case kidsmanagement::MEMBER:
      EXPECT_EQ("member", GetFeedbackValue());
      break;
    case kidsmanagement::CHILD:
      EXPECT_EQ("child", GetFeedbackValue());
      break;
    default:
      NOTREACHED();
  }
}

// Tests that a user that is not in a Family group is not processed.
TEST_F(FamilyInfoFeedbackSourceTest, GetFamilyMembersSignedInNoFamily) {
  CoreAccountInfo primary_account =
      identity_test_env()->MakePrimaryAccountAvailable(
          kTestEmail, signin::ConsentLevel::kSignin);

  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
      CreateFamilyInfoFeedbackSource();
  kidsmanagement::ListMembersResponse members;
  OnListFamilyMembersSuccess(feedback_source, members);

  EXPECT_EQ("", GetFeedbackValue());
}

// Tests that a signed-in user that fails its request to the server is not
// processed.
TEST_F(FamilyInfoFeedbackSourceTest, GetFamilyMembersOnFailure) {
  CoreAccountInfo primary_account =
      identity_test_env()->MakePrimaryAccountAvailable(
          kTestEmail, signin::ConsentLevel::kSignin);

  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
      CreateFamilyInfoFeedbackSource();
  OnGetFamilyMembersFailure(feedback_source);

  EXPECT_EQ("", GetFeedbackValue());
}

TEST_F(FamilyInfoFeedbackSourceTest, FeedbackSourceDestroyedOnCompletion) {
  kidsmanagement::ListMembersResponse members;
  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
      CreateFamilyInfoFeedbackSource();
  OnListFamilyMembersSuccess(feedback_source, members);

  EXPECT_TRUE(feedback_source.WasInvalidated());
}

TEST_F(FamilyInfoFeedbackSourceTest, FeedbackSourceDestroyedOnFailure) {
  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
      CreateFamilyInfoFeedbackSource();
  OnGetFamilyMembersFailure(feedback_source);

  EXPECT_TRUE(feedback_source.WasInvalidated());
}

INSTANTIATE_TEST_SUITE_P(AllFamilyMemberRoles,
                         FamilyInfoFeedbackSourceTest,
                         ::testing::Values(kidsmanagement::HEAD_OF_HOUSEHOLD,
                                           kidsmanagement::CHILD,
                                           kidsmanagement::MEMBER,
                                           kidsmanagement::PARENT));

}  // namespace chrome::android