910e62b5创建于 1月15日历史提交
// 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 "ash/scanner/scanner_session.h"

#include <memory>
#include <string_view>
#include <utility>
#include <vector>

#include "ash/scanner/fake_scanner_profile_scoped_delegate.h"
#include "ash/scanner/scanner_action_view_model.h"
#include "ash/scanner/scanner_metrics.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/manta/manta_status.h"
#include "components/manta/proto/scanner.pb.h"
#include "components/manta/scanner_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"

namespace ash {
namespace {

using ::base::test::InvokeFuture;
using ::base::test::RunOnceCallback;
using ::base::test::ValueIs;
using ::testing::IsEmpty;
using ::testing::SizeIs;

constexpr std::string_view kScannerFeatureUserStateHistogram =
    "Ash.ScannerFeature.UserState";

using FetchActionsForImageFuture = base::test::TestFuture<
    scoped_refptr<base::RefCountedMemory>,
    manta::ScannerProvider::ScannerProtoResponseCallback>;

scoped_refptr<base::RefCountedMemory> MakeJpegBytes(int width = 100,
                                                    int height = 100) {
  gfx::ImageSkia img = gfx::test::CreateImageSkia(width, height);
  std::optional<std::vector<uint8_t>> data =
      gfx::JPEGCodec::Encode(*img.bitmap(), /*quality=*/90);
  CHECK(data.has_value());
  return base::MakeRefCounted<base::RefCountedBytes>(std::move(*data));
}

TEST(ScannerSessionTest, FetchActionsForImageReturnsErrorWhenDelegateErrors) {
  FakeScannerProfileScopedDelegate delegate;
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          nullptr, manta::MantaStatus{
                       .status_code = manta::MantaStatusCode::kInvalidInput}));
  ScannerSession session(&delegate);

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session.FetchActionsForImage(nullptr, future.GetCallback());

  ScannerSession::FetchActionsResponse response = future.Take();
  EXPECT_EQ(response.error().error_message,
            l10n_util::GetStringUTF16(IDS_ASH_SCANNER_ERROR_GENERIC));
  EXPECT_TRUE(response.error().can_try_again);
}

TEST(ScannerSessionTest,
     FetchActionsForImageReturnsErrorForUnsupportedLanguage) {
  FakeScannerProfileScopedDelegate delegate;
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          nullptr,
          manta::MantaStatus{
              .status_code = manta::MantaStatusCode::kUnsupportedLanguage}));
  ScannerSession session(&delegate);

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session.FetchActionsForImage(nullptr, future.GetCallback());

  ScannerSession::FetchActionsResponse response = future.Take();
  EXPECT_EQ(
      response.error().error_message,
      l10n_util::GetStringUTF16(IDS_ASH_SCANNER_ERROR_UNSUPPORTED_LANGUAGE));
  EXPECT_FALSE(response.error().can_try_again);
}

TEST(ScannerSessionTest,
     FetchActionsForImageReturnsEmptyWhenDelegateHasNoObjects) {
  FakeScannerProfileScopedDelegate delegate;
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  ScannerSession session(&delegate);

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session.FetchActionsForImage(nullptr, future.GetCallback());

  EXPECT_THAT(future.Take(), ValueIs(IsEmpty()));
}

TEST(ScannerSessionTest, FetchActionsForImageRecordsNumberOfActionsMetrics) {
  base::HistogramTester histogram_tester;
  FakeScannerProfileScopedDelegate delegate;
  auto output = std::make_unique<manta::proto::ScannerOutput>();
  manta::proto::ScannerObject& object_one = *output->add_objects();
  object_one.add_actions()->mutable_new_event();
  object_one.add_actions()->mutable_new_contact();
  manta::proto::ScannerObject& object_two = *output->add_objects();
  object_two.add_actions()->mutable_new_event();
  object_two.add_actions()->mutable_new_google_doc();

  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  ScannerSession session(&delegate);
  session.FetchActionsForImage(nullptr, base::DoNothing());

  histogram_tester.ExpectBucketCount(
      kScannerFeatureUserStateHistogram,
      ScannerFeatureUserState::kNewCalendarEventActionDetected, 2);
  histogram_tester.ExpectBucketCount(
      kScannerFeatureUserStateHistogram,
      ScannerFeatureUserState::kNewContactActionDetected, 1);
  histogram_tester.ExpectBucketCount(
      kScannerFeatureUserStateHistogram,
      ScannerFeatureUserState::kNewGoogleDocActionDetected, 1);
  histogram_tester.ExpectBucketCount(
      kScannerFeatureUserStateHistogram,
      ScannerFeatureUserState::kNoActionsDetected, 0);
}

TEST(ScannerSessionTest, FetchActionsForImageNoActionRecordsMetrics) {
  base::HistogramTester histogram_tester;
  FakeScannerProfileScopedDelegate delegate;
  auto output = std::make_unique<manta::proto::ScannerOutput>();
  output->add_objects();
  output->add_objects();

  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  ScannerSession session(&delegate);
  session.FetchActionsForImage(nullptr, base::DoNothing());

  histogram_tester.ExpectBucketCount(
      kScannerFeatureUserStateHistogram,
      ScannerFeatureUserState::kNoActionsDetected, 1);
}

TEST(ScannerSessionTest, FetchActionsForImageRecordsTimerMetric) {
  base::test::SingleThreadTaskEnvironment task_environment(
      base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME);
  base::HistogramTester histogram_tester;
  FetchActionsForImageFuture future;
  FakeScannerProfileScopedDelegate delegate;
  EXPECT_CALL(delegate, FetchActionsForImage).WillOnce(InvokeFuture(future));
  ScannerSession session(&delegate);
  session.FetchActionsForImage(nullptr, base::DoNothing());
  task_environment.FastForwardBy(base::Milliseconds(500));
  auto output = std::make_unique<manta::proto::ScannerOutput>();
  output->add_objects();
  auto [ignored, callback] = future.Take();
  std::move(callback).Run(std::move(output), manta::MantaStatus());

  histogram_tester.ExpectBucketCount(kScannerFeatureTimerFetchActionsForImage,
                                     500, 1);
}

TEST(ScannerSessionTest,
     FetchActionsForImageReturnsEqualNumberOfActionsAsProtoResponse) {
  FakeScannerProfileScopedDelegate delegate;
  manta::proto::NewEventAction event_action;
  event_action.set_title("Event");
  auto output = std::make_unique<manta::proto::ScannerOutput>();
  manta::proto::ScannerObject& object = *output->add_objects();
  *object.add_actions()->mutable_new_event() = event_action;
  *object.add_actions()->mutable_new_event() = event_action;
  *object.add_actions()->mutable_new_event() = event_action;
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  ScannerSession session(&delegate);

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session.FetchActionsForImage(nullptr, future.GetCallback());

  EXPECT_THAT(future.Take(), ValueIs(SizeIs(3)));
}

TEST(ScannerSessionTest, ResizesImageHeightToMaxEdge) {
  FakeScannerProfileScopedDelegate delegate;
  FetchActionsForImageFuture future;
  EXPECT_CALL(delegate, FetchActionsForImage).WillOnce(InvokeFuture(future));
  ScannerSession session(&delegate);

  scoped_refptr<base::RefCountedMemory> bytes =
      MakeJpegBytes(/*width=*/2300, /*height=*/23000);
  session.FetchActionsForImage(bytes, base::DoNothing());

  auto processed_bytes = future.Get<scoped_refptr<base::RefCountedMemory>>();

  SkBitmap processed_bitmap = gfx::JPEGCodec::Decode(*processed_bytes);

  EXPECT_EQ(processed_bitmap.width(), 230);
  EXPECT_EQ(processed_bitmap.height(), 2300);
}

TEST(ScannerSessionTest, ResizesImageWidthToMaxEdge) {
  FakeScannerProfileScopedDelegate delegate;
  FetchActionsForImageFuture future;
  EXPECT_CALL(delegate, FetchActionsForImage).WillOnce(InvokeFuture(future));
  ScannerSession session(&delegate);

  scoped_refptr<base::RefCountedMemory> bytes =
      MakeJpegBytes(/*width=*/23000, /*height=*/2300);
  session.FetchActionsForImage(bytes, base::DoNothing());

  auto processed_bytes = future.Get<scoped_refptr<base::RefCountedMemory>>();

  SkBitmap processed_bitmap = gfx::JPEGCodec::Decode(*processed_bytes);

  EXPECT_EQ(processed_bitmap.width(), 2300);
  EXPECT_EQ(processed_bitmap.height(), 230);
}

TEST(ScannerSessionTest, NoResizeIfWithinLimit) {
  FakeScannerProfileScopedDelegate delegate;
  FetchActionsForImageFuture future;
  EXPECT_CALL(delegate, FetchActionsForImage).WillOnce(InvokeFuture(future));
  ScannerSession session(&delegate);

  scoped_refptr<base::RefCountedMemory> bytes =
      MakeJpegBytes(/*width=*/1000, /*height=*/1000);
  session.FetchActionsForImage(bytes, base::DoNothing());

  auto processed_bytes = future.Get<scoped_refptr<base::RefCountedMemory>>();

  EXPECT_EQ(bytes, processed_bytes);
}

TEST(ScannerSessionTest, DoesNotResizeIfTotalPixelSizeLowerThanMax) {
  FakeScannerProfileScopedDelegate delegate;
  FetchActionsForImageFuture future;
  EXPECT_CALL(delegate, FetchActionsForImage).WillOnce(InvokeFuture(future));
  ScannerSession session(&delegate);

  scoped_refptr<base::RefCountedMemory> bytes =
      MakeJpegBytes(/*width=*/4600, /*height=*/1100);
  session.FetchActionsForImage(bytes, base::DoNothing());

  auto processed_bytes = future.Get<scoped_refptr<base::RefCountedMemory>>();

  EXPECT_EQ(bytes, processed_bytes);
}

}  // namespace
}  // namespace ash