blob: 8848c7b3c25f5d3ee6c13ea7fc1f531f42e27274 [file] [log] [blame] [edit]
// 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