blob: 98b28b06341c08210f63a1c107829e34683d94e5 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/page_image_service/image_service_consent_helper.h"
#include <memory>
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/page_image_service/metrics_util.h"
#include "components/page_image_service/mojom/page_image_service.mojom-shared.h"
#include "components/sync/test/test_sync_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace page_image_service {
namespace {
using ::testing::ElementsAre;
class ImageServiceConsentHelperTest : public testing::Test {
public:
ImageServiceConsentHelperTest() = default;
void SetUp() override {
test_sync_service_ = std::make_unique<syncer::TestSyncService>();
consent_helper_ = std::make_unique<ImageServiceConsentHelper>(
test_sync_service_.get(), syncer::DataType::BOOKMARKS);
}
void SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus download_status) {
test_sync_service_->SetDownloadStatusFor({syncer::DataType::BOOKMARKS},
download_status);
test_sync_service_->FireStateChanged();
}
PageImageServiceConsentStatus GetResultSynchronously() {
PageImageServiceConsentStatus out_result;
consent_helper_->EnqueueRequest(
base::BindLambdaForTesting(
[&](PageImageServiceConsentStatus result) { out_result = result; }),
mojom::ClientId::Bookmarks);
return out_result;
}
void FastForwardBy(base::TimeDelta duration) {
task_environment_.FastForwardBy(duration);
}
ImageServiceConsentHelper* consent_helper() { return consent_helper_.get(); }
syncer::TestSyncService* sync_service() { return test_sync_service_.get(); }
private:
base::test::TaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<syncer::TestSyncService> test_sync_service_;
std::unique_ptr<ImageServiceConsentHelper> consent_helper_;
};
TEST_F(ImageServiceConsentHelperTest, EnabledAndDisabledRunSynchronously) {
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kError);
EXPECT_EQ(GetResultSynchronously(), PageImageServiceConsentStatus::kFailure);
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
EXPECT_EQ(GetResultSynchronously(), PageImageServiceConsentStatus::kSuccess);
// Set explicit passphrase for Bookmarks to simulate UploadToGoogleState not
// active.
sync_service()->GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{syncer::UserSelectableType::kBookmarks});
sync_service()->SetIsUsingExplicitPassphrase(true);
EXPECT_EQ(GetResultSynchronously(), PageImageServiceConsentStatus::kFailure);
}
TEST_F(ImageServiceConsentHelperTest, ExpireOldRequests) {
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
std::vector<PageImageServiceConsentStatus> results;
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
}),
mojom::ClientId::Bookmarks);
EXPECT_TRUE(results.empty()) << "Callback should not be run immediately.";
FastForwardBy(base::Seconds(3));
EXPECT_TRUE(results.empty()) << "Callback should not be run after 3 seconds.";
// Add another request while the first request is still pending.
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
}),
mojom::ClientId::Bookmarks);
FastForwardBy(base::Seconds(10));
ASSERT_EQ(results.size(), 2U) << "Both callbacks should expire as false.";
EXPECT_THAT(results, ElementsAre(PageImageServiceConsentStatus::kTimedOut,
PageImageServiceConsentStatus::kTimedOut));
// Enqueuing another one should restart the timer, which should expire after
// a second delay of 10 seconds.
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
}),
mojom::ClientId::Bookmarks);
EXPECT_EQ(results.size(), 2U) << "Callback should not be run immediately.";
FastForwardBy(base::Seconds(3));
EXPECT_EQ(results.size(), 2U);
FastForwardBy(base::Seconds(10));
ASSERT_EQ(results.size(), 3U);
EXPECT_EQ(results[2], PageImageServiceConsentStatus::kTimedOut);
// Enqueuing another one should restart the timer and shut down sync.
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
}),
mojom::ClientId::Bookmarks);
EXPECT_EQ(results.size(), 3U) << "Callback should not be run immediately.";
consent_helper()->OnSyncShutdown(sync_service());
FastForwardBy(base::Seconds(11));
ASSERT_EQ(results.size(), 4U);
// Sync service shutdown. We do not know consent status.
EXPECT_EQ(results[3], PageImageServiceConsentStatus::kFailure);
}
TEST_F(ImageServiceConsentHelperTest, InitializationFulfillsAllQueuedRequests) {
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
// Enqueue two requests, 2 seconds apart.
std::vector<PageImageServiceConsentStatus> results;
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
}),
mojom::ClientId::Bookmarks);
ASSERT_TRUE(results.empty());
FastForwardBy(base::Seconds(2));
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
}),
mojom::ClientId::Bookmarks);
ASSERT_TRUE(results.empty()) << "Still nothing should be run yet.";
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kUpToDate);
ASSERT_EQ(results.size(), 2U)
<< "Requests should have been immediately fulfilled as true.";
EXPECT_THAT(results, ElementsAre(PageImageServiceConsentStatus::kSuccess,
PageImageServiceConsentStatus::kSuccess));
}
TEST_F(ImageServiceConsentHelperTest, InitializationDisabledCase) {
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
std::vector<PageImageServiceConsentStatus> results;
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
}),
mojom::ClientId::Bookmarks);
ASSERT_TRUE(results.empty());
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kError);
EXPECT_THAT(results, ElementsAre(PageImageServiceConsentStatus::kFailure));
}
// In production, sometimes the callback to a request enqueues a new request.
// This tests this case and fixes the crash in https://crbug.com/1472360.
TEST_F(ImageServiceConsentHelperTest, CallbacksMakingNewRequests) {
SetDownloadStatusAndFireNotification(
syncer::SyncService::DataTypeDownloadStatus::kWaitingForUpdates);
std::vector<PageImageServiceConsentStatus> results;
// These two blocks are identical. The crash is reliably triggered when
// adding two of these. Probably having two pushes the vector to reallocate
// while iterating.
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting(
[&](PageImageServiceConsentStatus result2) {
results.push_back(result2);
}),
mojom::ClientId::Bookmarks);
}),
mojom::ClientId::Bookmarks);
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting([&](PageImageServiceConsentStatus result) {
results.push_back(result);
consent_helper()->EnqueueRequest(
base::BindLambdaForTesting(
[&](PageImageServiceConsentStatus result2) {
results.push_back(result2);
}),
mojom::ClientId::Bookmarks);
}),
mojom::ClientId::Bookmarks);
// New requests added during iteration live as long as the NEXT timeout.
FastForwardBy(base::Seconds(12));
EXPECT_EQ(results.size(), 2U);
FastForwardBy(base::Seconds(12));
EXPECT_EQ(results.size(), 4U);
}
} // namespace
} // namespace page_image_service