chromium/ash/webui/os_feedback_ui/backend/feedback_service_provider_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 "ash/webui/os_feedback_ui/backend/feedback_service_provider.h"

#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/webui/os_feedback_ui/backend/histogram_util.h"
#include "ash/webui/os_feedback_ui/backend/os_feedback_delegate.h"
#include "ash/webui/os_feedback_ui/mojom/os_feedback_ui.mojom.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace ash {
namespace feedback {

namespace {

constexpr char kPageUrl[] = "https://www.google.com/";
constexpr char kSignedInUserEmail[] = "[email protected]";
constexpr char kSignedInInternalUserEmail[] = "[email protected]";
constexpr char kFeedbackAppPostSubmitAction[] =
    "Feedback.ChromeOSApp.PostSubmitAction";
constexpr char kTestMacAddress[] = "12:34:56:78:AB";
// Set this flag true to use kSignedInInternalUserEmail as signed in email,
// set false to use kSignedInUserEmail as signed in email.
bool kUseInternalUserEmail = false;
bool kHasLinkedCrossDevicePhone = false;
constexpr bool kIsInternalEmail = true;
constexpr bool kIsNotInternalEmail = false;
constexpr int kPerformanceTraceId = 1;
const std::vector<uint8_t> kFakePngData = {42, 22, 26, 13, 7, 16, 8, 2};

using FeedbackAppPostSubmitAction =
    ash::os_feedback_ui::mojom::FeedbackAppPostSubmitAction;

}  // namespace

using ::ash::os_feedback_ui::mojom::FeedbackContext;
using ::ash::os_feedback_ui::mojom::FeedbackContextPtr;
using ::ash::os_feedback_ui::mojom::Report;
using ::ash::os_feedback_ui::mojom::ReportPtr;
using ::ash::os_feedback_ui::mojom::SendReportStatus;

class TestOsFeedbackDelegate : public OsFeedbackDelegate {
 public:
  TestOsFeedbackDelegate() = default;
  ~TestOsFeedbackDelegate() override = default;

  std::string GetApplicationLocale() override { return "zh"; }

  bool IsChildAccount() override { return false; }

  std::optional<GURL> GetLastActivePageUrl() override { return GURL(kPageUrl); }

  std::optional<std::string> GetLinkedPhoneMacAddress() override {
    return kHasLinkedCrossDevicePhone ? std::make_optional(kTestMacAddress)
                                      : std::nullopt;
  }

  std::optional<std::string> GetSignedInUserEmail() const override {
    return kUseInternalUserEmail ? kSignedInInternalUserEmail
                                 : kSignedInUserEmail;
  }

  bool IsWifiDebugLogsAllowed() const override { return false; }

  int GetPerformanceTraceId() override { return kPerformanceTraceId; }

  void GetScreenshotPng(GetScreenshotPngCallback callback) override {
    std::move(callback).Run(kFakePngData);
  }

  void SendReport(os_feedback_ui::mojom::ReportPtr report,
                  SendReportCallback callback) override {
    std::move(callback).Run(SendReportStatus::kSuccess);
  }

  void OpenDiagnosticsApp() override {}

  void OpenExploreApp() override {}

  void OpenMetricsDialog() override {}

  void OpenSystemInfoDialog() override {}

  void OpenAutofillMetadataDialog(
      const std::string& autofill_metadata) override {}
};

class FeedbackServiceProviderTest : public testing::Test {
 public:
  FeedbackServiceProviderTest()
      : provider_(FeedbackServiceProvider(
            std::make_unique<TestOsFeedbackDelegate>())) {}

  ~FeedbackServiceProviderTest() override = default;

  void SetUp() override {
    provider_.BindInterface(provider_remote_.BindNewPipeAndPassReceiver());
  }

  // Call the GetFeedbackContext of the remote provider async and return the
  // response.
  FeedbackContextPtr GetFeedbackContextAndWait() {
    base::test::TestFuture<FeedbackContextPtr> future;
    provider_remote_->GetFeedbackContext(future.GetCallback());
    return future.Take();
  }

  // Call the GetScreenshotPng of the remote provider async and return the
  // response.
  std::vector<uint8_t> GetScreenshotPngAndWait() {
    base::test::TestFuture<const std::vector<uint8_t>&> future;
    provider_remote_->GetScreenshotPng(future.GetCallback());
    return future.Take();
  }

  // Call the SendReport of the remote provider async and return the
  // response.
  SendReportStatus SendReportAndWait(ReportPtr report) {
    base::test::TestFuture<SendReportStatus> future;
    provider_remote_->SendReport(std::move(report), future.GetCallback());
    return future.Take();
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  FeedbackServiceProvider provider_;
  mojo::Remote<os_feedback_ui::mojom::FeedbackServiceProvider> provider_remote_;
};

// Test that GetFeedbackContext returns a response with correct feedback
// context.
TEST_F(FeedbackServiceProviderTest, GetFeedbackContext) {
  base::test::ScopedFeatureList feature_list{
      ash::features::kLinkCrossDeviceDogfoodFeedback};

  kUseInternalUserEmail = true;
  kHasLinkedCrossDevicePhone = true;
  auto internal_feedback_context = GetFeedbackContextAndWait();

  EXPECT_EQ(kSignedInInternalUserEmail,
            internal_feedback_context->email.value());
  EXPECT_EQ(kPageUrl, internal_feedback_context->page_url.value().spec());
  EXPECT_EQ(kIsInternalEmail, internal_feedback_context->is_internal_account);
  EXPECT_EQ(kPerformanceTraceId, internal_feedback_context->trace_id);
  EXPECT_EQ(kHasLinkedCrossDevicePhone,
            internal_feedback_context->has_linked_cross_device_phone);

  kUseInternalUserEmail = false;
  kHasLinkedCrossDevicePhone = false;
  auto feedback_context = GetFeedbackContextAndWait();

  EXPECT_EQ(kSignedInUserEmail, feedback_context->email.value());
  EXPECT_EQ(kPageUrl, feedback_context->page_url.value().spec());
  EXPECT_EQ(kIsNotInternalEmail, feedback_context->is_internal_account);
  EXPECT_EQ(kPerformanceTraceId, feedback_context->trace_id);
  EXPECT_EQ(kHasLinkedCrossDevicePhone,
            feedback_context->has_linked_cross_device_phone);
  EXPECT_FALSE(feedback_context->wifi_debug_logs_allowed);
}

// Test that GetScreenshotPng returns a response with correct status.
TEST_F(FeedbackServiceProviderTest, GetScreenshotPng) {
  auto png_data = GetScreenshotPngAndWait();
  EXPECT_EQ(kFakePngData, png_data);
}

// Test that SendReport returns a response with correct status.
TEST_F(FeedbackServiceProviderTest, SendReportSuccess) {
  ReportPtr report = Report::New();
  report->feedback_context = FeedbackContext::New();
  auto status = SendReportAndWait(std::move(report));
  EXPECT_EQ(status, SendReportStatus::kSuccess);
}

// Test that expected metric is triggered when RecordPostSubmitAction
// is called.
TEST_F(FeedbackServiceProviderTest, RecordPostSubmitAction) {
  base::HistogramTester histogram_tester_;
  histogram_tester_.ExpectBucketCount(
      kFeedbackAppPostSubmitAction,
      FeedbackAppPostSubmitAction::kClickDoneButton, 0);
  provider_.RecordPostSubmitAction(
      FeedbackAppPostSubmitAction::kClickDoneButton);
  histogram_tester_.ExpectBucketCount(
      kFeedbackAppPostSubmitAction,
      FeedbackAppPostSubmitAction::kClickDoneButton, 1);
}

TEST_F(FeedbackServiceProviderTest, ResetReceiverOnBindInterface) {
  // This test simulates a user trying to open a second instant. The receiver
  // should be reset before binding the new receiver. Otherwise we would get a
  // DCHECK error from mojo::Receiver
  provider_remote_.reset();  // reset the binding done in Setup.
  provider_.BindInterface(provider_remote_.BindNewPipeAndPassReceiver());
  base::RunLoop().RunUntilIdle();
}

}  // namespace feedback
}  // namespace ash