summaryrefslogtreecommitdiff
path: root/chromium/components/doodle/doodle_service_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/doodle/doodle_service_unittest.cc')
-rw-r--r--chromium/components/doodle/doodle_service_unittest.cc412
1 files changed, 374 insertions, 38 deletions
diff --git a/chromium/components/doodle/doodle_service_unittest.cc b/chromium/components/doodle/doodle_service_unittest.cc
index 48b3a10cade..8f6fc7e1011 100644
--- a/chromium/components/doodle/doodle_service_unittest.cc
+++ b/chromium/components/doodle/doodle_service_unittest.cc
@@ -10,6 +10,13 @@
#include "base/bind.h"
#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -32,9 +39,10 @@ class FakeDoodleFetcher : public DoodleFetcher {
size_t num_pending_callbacks() const { return callbacks_.size(); }
void ServeAllCallbacks(DoodleState state,
+ base::TimeDelta time_to_live,
const base::Optional<DoodleConfig>& config) {
for (auto& callback : callbacks_) {
- std::move(callback).Run(state, config);
+ std::move(callback).Run(state, time_to_live, config);
}
callbacks_.clear();
}
@@ -49,28 +57,66 @@ class MockDoodleObserver : public DoodleService::Observer {
void(const base::Optional<DoodleConfig>&));
};
-} // namespace
-
-// Equality operator for DoodleConfigs, for use by testing::Eq.
-// Note: This must be outside of the anonymous namespace.
-bool operator==(const DoodleConfig& lhs, const DoodleConfig& rhs) {
- return lhs.IsEquivalent(rhs);
+DoodleConfig CreateConfig(DoodleType type) {
+ return DoodleConfig(type, DoodleImage(GURL("https://doodle.com/image.jpg")));
}
+} // namespace
+
class DoodleServiceTest : public testing::Test {
public:
- DoodleServiceTest() : fetcher_(nullptr) {
+ DoodleServiceTest()
+ : task_runner_(new base::TestMockTimeTaskRunner()),
+ task_runner_handle_(task_runner_),
+ tick_clock_(task_runner_->GetMockTickClock()),
+ fetcher_(nullptr),
+ expiry_timer_(nullptr) {
+ DoodleService::RegisterProfilePrefs(pref_service_.registry());
+
+ task_runner_->FastForwardBy(base::TimeDelta::FromHours(12345));
+
+ // Set the minimum refresh interval to 0 by default, so tests don't have to
+ // worry about it. The tests that care set it explicitly.
+ RecreateServiceWithZeroRefreshInterval();
+ }
+
+ void DestroyService() { service_ = nullptr; }
+
+ void RecreateServiceWithZeroRefreshInterval() {
+ RecreateService(/*min_refresh_interval=*/base::TimeDelta());
+ }
+
+ void RecreateService(base::Optional<base::TimeDelta> refresh_interval) {
+ auto expiry_timer = base::MakeUnique<base::OneShotTimer>(tick_clock_.get());
+ expiry_timer->SetTaskRunner(task_runner_);
+ expiry_timer_ = expiry_timer.get();
+
auto fetcher = base::MakeUnique<FakeDoodleFetcher>();
fetcher_ = fetcher.get();
- service_ = base::MakeUnique<DoodleService>(std::move(fetcher));
+
+ service_ = base::MakeUnique<DoodleService>(
+ &pref_service_, std::move(fetcher), std::move(expiry_timer),
+ task_runner_->GetMockClock(), task_runner_->GetMockTickClock(),
+ refresh_interval);
}
DoodleService* service() { return service_.get(); }
FakeDoodleFetcher* fetcher() { return fetcher_; }
+ base::TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); }
+
private:
+ TestingPrefServiceSimple pref_service_;
+
+ scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle task_runner_handle_;
+ std::unique_ptr<base::TickClock> tick_clock_;
+
std::unique_ptr<DoodleService> service_;
+
+ // Weak, owned by the service.
FakeDoodleFetcher* fetcher_;
+ base::OneShotTimer* expiry_timer_;
};
TEST_F(DoodleServiceTest, FetchesConfigOnRefresh) {
@@ -82,9 +128,9 @@ TEST_F(DoodleServiceTest, FetchesConfigOnRefresh) {
EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(1u));
// Serve it (with an arbitrary config).
- DoodleConfig config;
- config.doodle_type = DoodleType::SIMPLE;
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config);
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
// The config should be available.
EXPECT_THAT(service()->config(), Eq(config));
@@ -95,15 +141,103 @@ TEST_F(DoodleServiceTest, FetchesConfigOnRefresh) {
EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(1u));
// Serve it with a different config.
- DoodleConfig other_config;
- other_config.doodle_type = DoodleType::SLIDESHOW;
- DCHECK(!config.IsEquivalent(other_config));
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, other_config);
+ DoodleConfig other_config = CreateConfig(DoodleType::SLIDESHOW);
+ DCHECK(config != other_config);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), other_config);
// The config should have been updated.
EXPECT_THAT(service()->config(), Eq(other_config));
}
+TEST_F(DoodleServiceTest, PersistsConfig) {
+ // Load some doodle config.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ config.large_image.url = GURL("https://doodle.com/doodle.jpg");
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Re-create the service. It should have persisted the config, and load it
+ // again automatically.
+ RecreateServiceWithZeroRefreshInterval();
+ EXPECT_THAT(service()->config(), Eq(config));
+}
+
+TEST_F(DoodleServiceTest, PersistsExpiryDate) {
+ // Load some doodle config.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ config.large_image.url = GURL("https://doodle.com/doodle.jpg");
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Destroy the service, and let some time pass.
+ DestroyService();
+ task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(15));
+
+ // Remove the abandoned expiry task from the task runner.
+ ASSERT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u));
+ task_runner()->ClearPendingTasks();
+
+ // Re-create the service. The persisted config should have been loaded again.
+ RecreateServiceWithZeroRefreshInterval();
+ EXPECT_THAT(service()->config(), Eq(config));
+
+ // Its time-to-live should have been updated.
+ EXPECT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u));
+ EXPECT_THAT(task_runner()->NextPendingTaskDelay(),
+ Eq(base::TimeDelta::FromMinutes(45)));
+}
+
+TEST_F(DoodleServiceTest, PersistedConfigExpires) {
+ // Load some doodle config.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ config.large_image.url = GURL("https://doodle.com/doodle.jpg");
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Destroy the service, and let enough time pass so that the config expires.
+ DestroyService();
+ task_runner()->FastForwardBy(base::TimeDelta::FromHours(1));
+
+ // Re-create the service. The persisted config should have been discarded
+ // because it is expired.
+ RecreateServiceWithZeroRefreshInterval();
+ EXPECT_THAT(service()->config(), Eq(base::nullopt));
+}
+
+TEST_F(DoodleServiceTest, RespectsMinRefreshInterval) {
+ // Create a service with the default refresh interval.
+ RecreateService(/*min_refresh_interval=*/base::nullopt);
+
+ // Load some doodle config.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ config.large_image.url = GURL("https://doodle.com/doodle.jpg");
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Let some time pass (less than the refresh interval).
+ task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(10));
+
+ // Request a refresh. This should get ignored.
+ service()->Refresh();
+ EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(0u));
+
+ // Let more time pass, so we get past the refresh interval.
+ task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(6));
+
+ // Now the refresh request should be honored again.
+ service()->Refresh();
+ EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(1u));
+}
+
TEST_F(DoodleServiceTest, CallsObserverOnConfigReceived) {
StrictMock<MockDoodleObserver> observer;
service()->AddObserver(&observer);
@@ -116,10 +250,10 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigReceived) {
ASSERT_THAT(fetcher()->num_pending_callbacks(), Eq(1u));
// Serve it (with an arbitrary config). The observer should get notified.
- DoodleConfig config;
- config.doodle_type = DoodleType::SIMPLE;
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(config)));
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
// Remove the observer before the service gets destroyed.
service()->RemoveObserver(&observer);
@@ -128,9 +262,9 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigReceived) {
TEST_F(DoodleServiceTest, CallsObserverOnConfigRemoved) {
// Load some doodle config.
service()->Refresh();
- DoodleConfig config;
- config.doodle_type = DoodleType::SIMPLE;
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config);
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
ASSERT_THAT(service()->config(), Eq(config));
// Register an observer and request a refresh.
@@ -142,7 +276,8 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigRemoved) {
// Serve the request with an empty doodle config. The observer should get
// notified.
EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(base::nullopt)));
- fetcher()->ServeAllCallbacks(DoodleState::NO_DOODLE, base::nullopt);
+ fetcher()->ServeAllCallbacks(DoodleState::NO_DOODLE, base::TimeDelta(),
+ base::nullopt);
// Remove the observer before the service gets destroyed.
service()->RemoveObserver(&observer);
@@ -151,9 +286,9 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigRemoved) {
TEST_F(DoodleServiceTest, CallsObserverOnConfigUpdated) {
// Load some doodle config.
service()->Refresh();
- DoodleConfig config;
- config.doodle_type = DoodleType::SIMPLE;
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config);
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
ASSERT_THAT(service()->config(), Eq(config));
// Register an observer and request a refresh.
@@ -164,22 +299,22 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigUpdated) {
// Serve the request with a different doodle config. The observer should get
// notified.
- DoodleConfig other_config;
- other_config.doodle_type = DoodleType::SLIDESHOW;
- DCHECK(!config.IsEquivalent(other_config));
+ DoodleConfig other_config = CreateConfig(DoodleType::SLIDESHOW);
+ DCHECK(config != other_config);
EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(other_config)));
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, other_config);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), other_config);
// Remove the observer before the service gets destroyed.
service()->RemoveObserver(&observer);
}
-TEST_F(DoodleServiceTest, DoesNotCallObserverWhenConfigEquivalent) {
+TEST_F(DoodleServiceTest, DoesNotCallObserverIfConfigEquivalent) {
// Load some doodle config.
service()->Refresh();
- DoodleConfig config;
- config.doodle_type = DoodleType::SIMPLE;
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config);
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
ASSERT_THAT(service()->config(), Eq(config));
// Register an observer and request a refresh.
@@ -190,13 +325,214 @@ TEST_F(DoodleServiceTest, DoesNotCallObserverWhenConfigEquivalent) {
// Serve the request with an equivalent doodle config. The observer should
// *not* get notified.
- DoodleConfig equivalent_config;
- equivalent_config.doodle_type = DoodleType::SIMPLE;
- DCHECK(config.IsEquivalent(equivalent_config));
- fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, equivalent_config);
+ DoodleConfig equivalent_config = CreateConfig(DoodleType::SIMPLE);
+ DCHECK(config == equivalent_config);
+ fetcher()->ServeAllCallbacks(
+ DoodleState::AVAILABLE, base::TimeDelta::FromHours(1), equivalent_config);
// Remove the observer before the service gets destroyed.
service()->RemoveObserver(&observer);
}
+TEST_F(DoodleServiceTest, CallsObserverWhenConfigExpires) {
+ // Load some doodle config.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Make sure the task arrived at the timer's task runner.
+ EXPECT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u));
+ EXPECT_THAT(task_runner()->NextPendingTaskDelay(),
+ Eq(base::TimeDelta::FromHours(1)));
+
+ // Register an observer.
+ StrictMock<MockDoodleObserver> observer;
+ service()->AddObserver(&observer);
+
+ // Fast-forward time so that the expiry task will run. The observer should get
+ // notified that there's no config anymore.
+ EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(base::nullopt)));
+ task_runner()->FastForwardBy(base::TimeDelta::FromHours(1));
+
+ // Remove the observer before the service gets destroyed.
+ service()->RemoveObserver(&observer);
+}
+
+TEST_F(DoodleServiceTest, DisregardsAlreadyExpiredConfigs) {
+ StrictMock<MockDoodleObserver> observer;
+ service()->AddObserver(&observer);
+
+ ASSERT_THAT(service()->config(), Eq(base::nullopt));
+
+ // Load an already-expired config. This should have no effect; in particular
+ // no call to the observer.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromSeconds(0), config);
+ EXPECT_THAT(service()->config(), Eq(base::nullopt));
+
+ // Load a doodle config as usual. Nothing to see here.
+ service()->Refresh();
+ EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(config)));
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Now load an expired config again. The cached one should go away.
+ service()->Refresh();
+ EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(base::nullopt)));
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromSeconds(0), config);
+
+ // Remove the observer before the service gets destroyed.
+ service()->RemoveObserver(&observer);
+}
+
+TEST_F(DoodleServiceTest, ClampsTimeToLive) {
+ // Load a config with an excessive time-to-live.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromDays(100), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // The time-to-live should have been clamped to a reasonable maximum.
+ ASSERT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u));
+ EXPECT_THAT(task_runner()->NextPendingTaskDelay(),
+ Eq(base::TimeDelta::FromDays(30)));
+}
+
+TEST_F(DoodleServiceTest, RecordsMetricsForSuccessfulDownload) {
+ base::HistogramTester histograms;
+
+ // Load a doodle config as usual, but let it take some time.
+ service()->Refresh();
+ task_runner()->FastForwardBy(base::TimeDelta::FromSeconds(5));
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Metrics should have been recorded.
+ histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome",
+ 0, // OUTCOME_NEW_DOODLE
+ 1);
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 1);
+ histograms.ExpectTimeBucketCount("Doodle.ConfigDownloadTime",
+ base::TimeDelta::FromSeconds(5), 1);
+}
+
+TEST_F(DoodleServiceTest, RecordsMetricsForEmptyDownload) {
+ base::HistogramTester histograms;
+
+ // Send a "no doodle" response after some time.
+ service()->Refresh();
+ task_runner()->FastForwardBy(base::TimeDelta::FromSeconds(5));
+ fetcher()->ServeAllCallbacks(DoodleState::NO_DOODLE, base::TimeDelta(),
+ base::nullopt);
+ ASSERT_THAT(service()->config(), Eq(base::nullopt));
+
+ // Metrics should have been recorded.
+ histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome",
+ 3, // OUTCOME_NO_DOODLE
+ 1);
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 1);
+ histograms.ExpectTimeBucketCount("Doodle.ConfigDownloadTime",
+ base::TimeDelta::FromSeconds(5), 1);
+}
+
+TEST_F(DoodleServiceTest, RecordsMetricsForFailedDownload) {
+ base::HistogramTester histograms;
+
+ // Let the download fail after some time.
+ service()->Refresh();
+ task_runner()->FastForwardBy(base::TimeDelta::FromSeconds(5));
+ fetcher()->ServeAllCallbacks(DoodleState::DOWNLOAD_ERROR, base::TimeDelta(),
+ base::nullopt);
+ ASSERT_THAT(service()->config(), Eq(base::nullopt));
+
+ // The outcome should have been recorded, but not the time - we only record
+ // that for successful downloads.
+ histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome",
+ 5, // OUTCOME_DOWNLOAD_ERROR
+ 1);
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0);
+}
+
+TEST_F(DoodleServiceTest, RecordsMetricsForEarlyRefreshRequest) {
+ // Create a service with some refresh interval.
+ RecreateService(/*min_refresh_interval=*/base::TimeDelta::FromMinutes(10));
+
+ // Load a doodle config as usual.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ base::HistogramTester histograms;
+
+ // Request a refresh before the min refresh interval has passed.
+ task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(5));
+ service()->Refresh();
+
+ // This should not have resulted in a request.
+ ASSERT_THAT(fetcher()->num_pending_callbacks(), Eq(0u));
+
+ // The outcome should still have been recorded, but not the time - we only
+ // record that for successful downloads.
+ histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome",
+ 7, // OUTCOME_REFRESH_INTERVAL_NOT_PASSED
+ 1);
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0);
+}
+
+TEST_F(DoodleServiceTest, DoesNotRecordMetricsAtStartup) {
+ // Creating the service should not emit any histogram samples.
+ base::HistogramTester histograms;
+ RecreateServiceWithZeroRefreshInterval();
+ ASSERT_THAT(service()->config(), Eq(base::nullopt));
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadOutcome", 0);
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0);
+}
+
+TEST_F(DoodleServiceTest, DoesNotRecordMetricsAtStartupWithConfig) {
+ // Load a doodle config as usual.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ // Recreating the service should not emit any histogram samples, even though
+ // a config gets loaded.
+ base::HistogramTester histograms;
+ RecreateServiceWithZeroRefreshInterval();
+ ASSERT_THAT(service()->config(), Eq(config));
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadOutcome", 0);
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0);
+}
+
+TEST_F(DoodleServiceTest, DoesNotRecordMetricsWhenConfigExpires) {
+ // Load some doodle config.
+ service()->Refresh();
+ DoodleConfig config = CreateConfig(DoodleType::SIMPLE);
+ fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE,
+ base::TimeDelta::FromHours(1), config);
+ ASSERT_THAT(service()->config(), Eq(config));
+
+ base::HistogramTester histograms;
+
+ // Fast-forward time so that the config expires.
+ task_runner()->FastForwardBy(base::TimeDelta::FromHours(1));
+ ASSERT_THAT(service()->config(), Eq(base::nullopt));
+
+ // This should not have resulted in any metrics being emitted.
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadOutcome", 0);
+ histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0);
+}
+
} // namespace doodle