chromium/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_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/ui/webui/ash/parent_access/parent_access_ui_handler_impl.h"

#include <map>
#include <memory>
#include <optional>
#include <string>

#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/system/sys_info.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h"
#include "chrome/browser/ui/webui/ash/parent_access/parent_access_metrics_utils.h"
#include "chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom.h"
#include "chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_delegate.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/google/core/common/google_util.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;

namespace ash {

class FakeParentAccessUiHandlerDelegate : public ParentAccessUiHandlerDelegate {
 public:
  FakeParentAccessUiHandlerDelegate() = default;
  ~FakeParentAccessUiHandlerDelegate() = default;

  parent_access_ui::mojom::ParentAccessParamsPtr CloneParentAccessParams()
      override {
    switch (flow_type_) {
      case parent_access_ui::mojom::ParentAccessParams::FlowType::
          kWebsiteAccess:
        return parent_access_ui::mojom::ParentAccessParams::New(
            flow_type_,
            parent_access_ui::mojom::FlowTypeParams::NewWebApprovalsParams(
                parent_access_ui::mojom::WebApprovalsParams::New()),
            is_disabled_);
      case parent_access_ui::mojom::ParentAccessParams::FlowType::
          kExtensionAccess:
        return parent_access_ui::mojom::ParentAccessParams::New(
            flow_type_,
            parent_access_ui::mojom::FlowTypeParams::
                NewExtensionApprovalsParams(
                    parent_access_ui::mojom::ExtensionApprovalsParams::New()),
            is_disabled_);
    }
  }

  MOCK_METHOD(void, SetApproved, (const std::string&, const base::Time&));
  MOCK_METHOD(void, SetDeclined, ());
  MOCK_METHOD(void, SetCanceled, ());
  MOCK_METHOD(void, SetDisabled, ());
  MOCK_METHOD(void, SetError, ());

  void set_flow_type(
      parent_access_ui::mojom::ParentAccessParams::FlowType flow_type) {
    flow_type_ = flow_type;
  }

  void set_is_disabled(bool is_disabled) { is_disabled_ = is_disabled; }

 private:
  parent_access_ui::mojom::ParentAccessParams::FlowType flow_type_;
  bool is_disabled_ = false;
};

class ParentAccessUiHandlerImplBaseTest : public testing::Test {
 public:
  ParentAccessUiHandlerImplBaseTest() {
    identity_test_env_ = std::make_unique<signin::IdentityTestEnvironment>();
    identity_test_env_->MakePrimaryAccountAvailable(
        "[email protected]", signin::ConsentLevel::kSync);
  }

  ~ParentAccessUiHandlerImplBaseTest() override = default;

  void TearDown() override { parent_access_ui_handler_.reset(); }

 protected:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
  mojo::Remote<parent_access_ui::mojom::ParentAccessUiHandler>
      parent_access_ui_handler_remote_;
  std::unique_ptr<ParentAccessUiHandlerImpl> parent_access_ui_handler_;
  FakeParentAccessUiHandlerDelegate delegate_;
};

class ParentAccessUiHandlerImplTestParameterized
    : public ParentAccessUiHandlerImplBaseTest,
      public testing::WithParamInterface<
          parent_access_ui::mojom::ParentAccessParams::FlowType> {
 public:
  ParentAccessUiHandlerImplTestParameterized() {
    delegate_.set_flow_type(GetTestedFlowType());
    parent_access_ui_handler_ = std::make_unique<ParentAccessUiHandlerImpl>(
        parent_access_ui_handler_remote_.BindNewPipeAndPassReceiver(),
        identity_test_env_->identity_manager(), &delegate_);
  }

  ParentAccessStateTracker::FlowResult GetInitialStateForFlow() const {
    switch (GetTestedFlowType()) {
      case parent_access_ui::mojom::ParentAccessParams::FlowType::
          kWebsiteAccess:
        return ParentAccessStateTracker::FlowResult::kParentAuthentication;
      case parent_access_ui::mojom::ParentAccessParams::FlowType::
          kExtensionAccess:
        return ParentAccessStateTracker::FlowResult::kInitial;
    }
  }

  parent_access_ui::mojom::ParentAccessParams::FlowType GetTestedFlowType()
      const {
    return GetParam();
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    ParentAccessUiHandlerImplTestParameterized,
    testing::Values(
        parent_access_ui::mojom::ParentAccessParams::FlowType::kWebsiteAccess,
        parent_access_ui::mojom::ParentAccessParams::FlowType::
            kExtensionAccess));

// Verifies that the webview URL is properly constructed
TEST_P(ParentAccessUiHandlerImplTestParameterized, GetParentAccessUrl) {
  base::RunLoop run_loop;
  parent_access_ui_handler_->GetParentAccessUrl(
      base::BindLambdaForTesting([&](const std::string& url) -> void {
        GURL webview_url(url);
        ASSERT_TRUE(webview_url.has_query());

        // Split the query string into a map of keys to values.
        std::string query_str = webview_url.query();
        url::Component query(0, query_str.length());
        url::Component key;
        url::Component value;
        std::map<std::string, std::string> query_parts;
        while (url::ExtractQueryKeyValue(query_str, &query, &key, &value)) {
          query_parts[query_str.substr(key.begin, key.len)] =
              query_str.substr(value.begin, value.len);
        }

        // Validate the query parameters.
        switch (GetTestedFlowType()) {
          case parent_access_ui::mojom::ParentAccessParams::FlowType::
              kWebsiteAccess:
            EXPECT_EQ(query_parts.at("callerid"), "39454505");
            break;
          case parent_access_ui::mojom::ParentAccessParams::FlowType::
              kExtensionAccess:
            EXPECT_EQ(query_parts.at("callerid"), "12367dff");
            break;
        }
        EXPECT_EQ(query_parts.at("cros-origin"), "chrome://parent-access");
        EXPECT_EQ(query_parts.at("platform_version"),
                  base::SysInfo::OperatingSystemVersion());
        EXPECT_EQ(query_parts.at("hl"), "en");
        run_loop.Quit();
      }));
  run_loop.Run();
}

// Verify that the access token is successfully fetched.
TEST_P(ParentAccessUiHandlerImplTestParameterized, GetOauthTokenSuccess) {
  identity_test_env_->SetAutomaticIssueOfAccessTokens(true);
  base::RunLoop run_loop;
  parent_access_ui_handler_->GetOauthToken(base::BindLambdaForTesting(
      [&](parent_access_ui::mojom::GetOauthTokenStatus status,
          const std::string& token) -> void {
        EXPECT_EQ(parent_access_ui::mojom::GetOauthTokenStatus::kSuccess,
                  status);
        run_loop.Quit();
      }));
  run_loop.Run();
}

// Verifies that access token fetch errors are recorded.
TEST_P(ParentAccessUiHandlerImplTestParameterized, GetOauthTokenError) {
  base::HistogramTester histogram_tester;
  base::RunLoop run_loop;
  parent_access_ui_handler_->GetOauthToken(base::BindLambdaForTesting(
      [&](parent_access_ui::mojom::GetOauthTokenStatus status,
          const std::string& token) -> void {
        EXPECT_EQ(parent_access_ui::mojom::GetOauthTokenStatus::kError, status);
        run_loop.Quit();
      }));

  // Trigger failure to issue access token.
  identity_test_env_->SetAutomaticIssueOfAccessTokens(false);
  identity_test_env_->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::FromServiceError("FAKE SERVICE ERROR"));

  run_loop.Run();

  // Expect metric to be recorded.
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase, std::nullopt),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kOAuthError, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase,
          GetTestedFlowType()),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kOAuthError, 1);
}

// Verifies that only one access token fetch is possible at a time.
TEST_P(ParentAccessUiHandlerImplTestParameterized,
       GetOauthTokenOnlyOneFetchAtATimeError) {
  identity_test_env_->SetAutomaticIssueOfAccessTokens(false);
  parent_access_ui_handler_->GetOauthToken(base::DoNothing());

  base::RunLoop one_fetch_run_loop;
  parent_access_ui_handler_->GetOauthToken(base::BindLambdaForTesting(
      [&](parent_access_ui::mojom::GetOauthTokenStatus status,
          const std::string& token) -> void {
        EXPECT_EQ(
            parent_access_ui::mojom::GetOauthTokenStatus::kOnlyOneFetchAtATime,
            status);
        one_fetch_run_loop.Quit();
      }));
  one_fetch_run_loop.Run();
}

MATCHER_P(EqualsProto,
          message,
          "Match a proto Message equal to the matcher's argument.") {
  std::string expected_serialized = message.SerializeAsString();
  std::string actual_serialized = arg.SerializeAsString();
  return expected_serialized == actual_serialized;
}

// Verifies that the parent approvals sequence is handled correctly.
TEST_P(ParentAccessUiHandlerImplTestParameterized,
       OnParentVerifiedAndApproved) {
  base::HistogramTester histogram_tester;
  // Construct the ParentAccessCallback
  kids::platform::parentaccess::client::proto::ParentAccessCallback
      parent_access_callback;
  kids::platform::parentaccess::client::proto::OnParentVerified*
      on_parent_verified = parent_access_callback.mutable_on_parent_verified();
  kids::platform::parentaccess::client::proto::ParentAccessToken* pat =
      on_parent_verified->mutable_parent_access_token();
  pat->set_token("TEST_TOKEN");
  kids::platform::parentaccess::client::proto::Timestamp* expire_time =
      pat->mutable_expire_time();
  expire_time->set_seconds(123456);
  // Nanoseconds will be ignored.
  expire_time->set_nanos(567890);

  // Encode the proto in base64.
  std::string encoded_parent_access_callback =
      base::Base64Encode(parent_access_callback.SerializeAsString());

  EXPECT_CALL(delegate_,
              SetApproved(pat->token(), base::Time::FromSecondsSinceUnixEpoch(
                                            expire_time->seconds())))
      .Times(1);

  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      encoded_parent_access_callback,
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void {
            // Verify the Parent Verified callback is parsed.
            EXPECT_EQ(parent_access_ui::mojom::ParentAccessServerMessageType::
                          kParentVerified,
                      message->type);
            run_loop.Quit();
          }));

  run_loop.Run();

  // Verify the Parent Access Token was stored.
  EXPECT_THAT(
      *pat,
      EqualsProto(*(parent_access_ui_handler_->GetParentAccessTokenForTest())));

  // Send the approved result status.
  base::RunLoop parent_approved_run_loop;
  parent_access_ui_handler_->OnParentAccessDone(
      parent_access_ui::mojom::ParentAccessResult::kApproved,
      base::BindLambdaForTesting(
          [&]() -> void { parent_approved_run_loop.Quit(); }));

  parent_approved_run_loop.Run();

  // Reset handler to simulate dialog closing.
  parent_access_ui_handler_.reset();
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase, std::nullopt),
      ParentAccessStateTracker::FlowResult::kAccessApproved, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase,
          GetTestedFlowType()),
      ParentAccessStateTracker::FlowResult::kAccessApproved, 1);
}

// Verifies that an unparsable parent access callback proto is handled
// properly.
TEST_P(ParentAccessUiHandlerImplTestParameterized,
       OnInvalidParentAccessCallback) {
  // Encode the proto in base64.
  std::string encoded_parent_access_callback =
      base::Base64Encode("INVALID_SERIALIZED_CALLBACK");

  EXPECT_CALL(delegate_, SetApproved(_, _)).Times(0);

  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      encoded_parent_access_callback,
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void {
            // Verify the Parent Verified callback is parsed.
            EXPECT_EQ(
                parent_access_ui::mojom::ParentAccessServerMessageType::kError,
                message->type);
            run_loop.Quit();
          }));

  run_loop.Run();
}

// Verifies that non-base64 encoded data passed as a parent access callback is
// handled properly.
TEST_P(ParentAccessUiHandlerImplTestParameterized,
       OnNonBase64ParentAccessCallback) {
  EXPECT_CALL(delegate_, SetApproved(_, _)).Times(0);

  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      "**THIS_STRING_HAS_NON_BASE64_CHARACTERS**",
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void {
            // Verify the Parent Verified callback is parsed.
            EXPECT_EQ(
                parent_access_ui::mojom::ParentAccessServerMessageType::kError,
                message->type);
            run_loop.Quit();
          }));

  run_loop.Run();
}

// Verifies that parent declining is handled correctly.
TEST_P(ParentAccessUiHandlerImplTestParameterized, OnParentDeclined) {
  base::HistogramTester histogram_tester;
  EXPECT_CALL(delegate_, SetDeclined()).Times(1);

  // Send the declined result status.
  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessDone(
      parent_access_ui::mojom::ParentAccessResult::kDeclined,
      base::BindLambdaForTesting([&]() -> void { run_loop.Quit(); }));

  run_loop.Run();

  // Reset handler to simulate dialog closing.
  parent_access_ui_handler_.reset();
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase, std::nullopt),
      ParentAccessStateTracker::FlowResult::kAccessDeclined, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase,
          GetTestedFlowType()),
      ParentAccessStateTracker::FlowResult::kAccessDeclined, 1);
}

// Verifies canceling the UI is handled correctly.
TEST_P(ParentAccessUiHandlerImplTestParameterized, OnCanceled) {
  base::HistogramTester histogram_tester;
  EXPECT_CALL(delegate_, SetCanceled()).Times(1);

  // Send the declined result status.
  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessDone(
      parent_access_ui::mojom::ParentAccessResult::kCanceled,
      base::BindLambdaForTesting([&]() -> void { run_loop.Quit(); }));

  run_loop.Run();

  // Reset handler to simulate dialog closing.
  parent_access_ui_handler_.reset();
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase, std::nullopt),
      GetInitialStateForFlow(), 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase,
          GetTestedFlowType()),
      GetInitialStateForFlow(), 1);
}

// Verifies errors are handled correctly.
TEST_P(ParentAccessUiHandlerImplTestParameterized, OnError) {
  base::HistogramTester histogram_tester;
  EXPECT_CALL(delegate_, SetError()).Times(1);

  // Send the declined result status.
  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessDone(
      parent_access_ui::mojom::ParentAccessResult::kError,
      base::BindLambdaForTesting([&]() -> void { run_loop.Quit(); }));

  run_loop.Run();

  // Reset handler to simulate dialog closing.
  parent_access_ui_handler_.reset();
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase, std::nullopt),
      ParentAccessStateTracker::FlowResult::kError, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase,
          GetTestedFlowType()),
      ParentAccessStateTracker::FlowResult::kError, 1);
}

// Verifies that the ConsentDeclined status is ignored.
TEST_P(ParentAccessUiHandlerImplTestParameterized, ConsentDeclinedParsed) {
  base::HistogramTester histogram_tester;
  // Construct the ParentAccessCallback
  kids::platform::parentaccess::client::proto::ParentAccessCallback
      parent_access_callback;
  parent_access_callback.mutable_on_consent_declined();

  // Encode the proto in base64.
  std::string encoded_parent_access_callback =
      base::Base64Encode(parent_access_callback.SerializeAsString());

  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      encoded_parent_access_callback,
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void {
            // Verify that it is ignored.
            EXPECT_EQ(
                parent_access_ui::mojom::ParentAccessServerMessageType::kIgnore,
                message->type);
            run_loop.Quit();
          }));
  run_loop.Run();

  // Expect metric to be recorded.
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase, std::nullopt),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase,
          GetTestedFlowType()),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
}

// Verifies that the OnPageSizeChanged status is ignored.
TEST_P(ParentAccessUiHandlerImplTestParameterized, OnPageSizeChangedIgnored) {
  base::HistogramTester histogram_tester;
  // Construct the ParentAccessCallback
  kids::platform::parentaccess::client::proto::ParentAccessCallback
      parent_access_callback;
  parent_access_callback.mutable_on_page_size_changed();

  // Encode the proto in base64.
  std::string encoded_parent_access_callback =
      base::Base64Encode(parent_access_callback.SerializeAsString());

  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      encoded_parent_access_callback,
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void {
            // Verify that it is ignored.
            EXPECT_EQ(
                parent_access_ui::mojom::ParentAccessServerMessageType::kIgnore,
                message->type);
            run_loop.Quit();
          }));
  run_loop.Run();

  // Expect metric to be recorded.
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase, std::nullopt),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase,
          GetTestedFlowType()),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
}

// Verifies that the OnCommunicationEstablished status is ignored.
TEST_P(ParentAccessUiHandlerImplTestParameterized,
       OnCommunicationEstablishedIgnored) {
  base::HistogramTester histogram_tester;

  // Construct the ParentAccessCallback
  kids::platform::parentaccess::client::proto::ParentAccessCallback
      parent_access_callback;
  parent_access_callback.mutable_on_communication_established();

  // Encode the proto in base64.
  std::string encoded_parent_access_callback =
      base::Base64Encode(parent_access_callback.SerializeAsString());

  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      encoded_parent_access_callback,
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void {
            // Verify that it is ignored.
            EXPECT_EQ(
                parent_access_ui::mojom::ParentAccessServerMessageType::kIgnore,
                message->type);
            run_loop.Quit();
          }));
  run_loop.Run();

  // Expect metric to be recorded.
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase, std::nullopt),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
}

// Verifies metric is recorded for no delegate error.
TEST_P(ParentAccessUiHandlerImplTestParameterized,
       NoDelegateErrorMetricRecorded) {
  base::HistogramTester histogram_tester;

  // Construct a ParentAccessUiHandler without a delegate.
  mojo::Remote<parent_access_ui::mojom::ParentAccessUiHandler> remote;
  auto parent_access_ui_handler_no_delegate =
      std::make_unique<ParentAccessUiHandlerImpl>(
          remote.BindNewPipeAndPassReceiver(),
          identity_test_env_->identity_manager(), nullptr);

  // Send a result status.
  base::RunLoop run_loop;
  parent_access_ui_handler_no_delegate->OnParentAccessDone(
      parent_access_ui::mojom::ParentAccessResult::kApproved,
      base::BindLambdaForTesting([&]() -> void { run_loop.Quit(); }));

  run_loop.Run();

  // Expect metric to be recorded.
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase, std::nullopt),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kDelegateNotAvailable,
      1);
}

TEST_P(ParentAccessUiHandlerImplTestParameterized,
       DecodingErrorMetricRecorded) {
  base::HistogramTester histogram_tester;
  base::RunLoop run_loop;

  // Receive non-decodable callback.
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      "not_a_callback",
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void { run_loop.Quit(); }));
  run_loop.Run();

  // Expect metric to be recorded.
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase, std::nullopt),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kDecodingError, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase,
          GetTestedFlowType()),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kDecodingError, 1);
}

// Verifies metric is recorded when received callback cannot be parsed to proto.
TEST_P(ParentAccessUiHandlerImplTestParameterized, ParsingErrorMetricRecorded) {
  base::HistogramTester histogram_tester;

  // Receive non-parseable callback.
  base::RunLoop run_loop;
  std::string encoded_not_a_callback = base::Base64Encode("not_a_callback");
  parent_access_ui_handler_->OnParentAccessCallbackReceived(
      encoded_not_a_callback,
      base::BindLambdaForTesting(
          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
              -> void { run_loop.Quit(); }));
  run_loop.Run();

  // Expect metric to be recorded.
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase, std::nullopt),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kParsingError, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessWidgetErrorHistogramBase,
          GetTestedFlowType()),
      ParentAccessUiHandlerImpl::ParentAccessWidgetError::kParsingError, 1);
}

class ExtensionApprovalsDisabledTest
    : public ParentAccessUiHandlerImplBaseTest {
 public:
  ExtensionApprovalsDisabledTest() {
    // Only test extensions flow because disabled state is not allowed for web
    // approvals.
    delegate_.set_flow_type(parent_access_ui::mojom::ParentAccessParams::
                                FlowType::kExtensionAccess);
    delegate_.set_is_disabled(true);
    parent_access_ui_handler_ = std::make_unique<ParentAccessUiHandlerImpl>(
        parent_access_ui_handler_remote_.BindNewPipeAndPassReceiver(),
        identity_test_env_->identity_manager(), &delegate_);
  }
};

// Verifies disabling requests is handled correctly.
TEST_F(ExtensionApprovalsDisabledTest, OnDisabled) {
  base::HistogramTester histogram_tester;
  EXPECT_CALL(delegate_, SetDisabled()).Times(1);

  // Send the disabled result status.
  base::RunLoop run_loop;
  parent_access_ui_handler_->OnParentAccessDone(
      parent_access_ui::mojom::ParentAccessResult::kDisabled,
      base::BindLambdaForTesting([&]() -> void { run_loop.Quit(); }));

  run_loop.Run();

  // Reset handler to simulate dialog closing.
  parent_access_ui_handler_.reset();
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase, std::nullopt),
      ParentAccessStateTracker::FlowResult::kRequestsDisabled, 1);
  histogram_tester.ExpectUniqueSample(
      parent_access::GetHistogramTitleForFlowType(
          parent_access::kParentAccessFlowResultHistogramBase,
          parent_access_ui::mojom::ParentAccessParams::FlowType::
              kExtensionAccess),
      ParentAccessStateTracker::FlowResult::kRequestsDisabled, 1);
}
}  // namespace ash