blob: 7f87e302cc68a1fd2aa728320f1b1298310acf7a [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/reporting/reporting_delivery_agent.h"
#include <vector>
#include "base/json/json_reader.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/timer/mock_timer.h"
#include "base/values.h"
#include "net/base/backoff_entry.h"
#include "net/reporting/reporting_cache.h"
#include "net/reporting/reporting_report.h"
#include "net/reporting/reporting_test_util.h"
#include "net/reporting/reporting_uploader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace net {
namespace {
class ReportingDeliveryAgentTest : public ReportingTestBase {
protected:
ReportingDeliveryAgentTest() {
ReportingPolicy policy;
policy.endpoint_backoff_policy.num_errors_to_ignore = 0;
policy.endpoint_backoff_policy.initial_delay_ms = 60000;
policy.endpoint_backoff_policy.multiply_factor = 2.0;
policy.endpoint_backoff_policy.jitter_factor = 0.0;
policy.endpoint_backoff_policy.maximum_backoff_ms = -1;
policy.endpoint_backoff_policy.entry_lifetime_ms = 0;
policy.endpoint_backoff_policy.always_use_initial_delay = false;
UsePolicy(policy);
}
base::TimeTicks tomorrow() {
return tick_clock()->NowTicks() + base::TimeDelta::FromDays(1);
}
void SetClient(const url::Origin& origin,
const GURL& endpoint,
const std::string& group,
ReportingClient::Subdomains subdomains =
ReportingClient::Subdomains::EXCLUDE) {
cache()->SetClient(origin, endpoint, subdomains, group, tomorrow(),
ReportingClient::kDefaultPriority,
ReportingClient::kDefaultWeight);
}
const GURL kUrl_ = GURL("https://origin/path");
const GURL kSubdomainUrl_ = GURL("https://sub.origin/path");
const url::Origin kOrigin_ = url::Origin::Create(GURL("https://origin/"));
const GURL kEndpoint_ = GURL("https://endpoint/");
const std::string kUserAgent_ = "Mozilla/1.0";
const std::string kGroup_ = "group";
const std::string kType_ = "type";
};
TEST_F(ReportingDeliveryAgentTest, SuccessfulImmediateUpload) {
base::DictionaryValue body;
body.SetString("key", "value");
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_, body.CreateDeepCopy(),
0, tick_clock()->NowTicks(), 0);
// Upload is automatically started when cache is modified.
ASSERT_EQ(1u, pending_uploads().size());
EXPECT_EQ(kEndpoint_, pending_uploads()[0]->url());
{
auto value = pending_uploads()[0]->GetValue();
base::ListValue* list;
ASSERT_TRUE(value->GetAsList(&list));
EXPECT_EQ(1u, list->GetSize());
base::DictionaryValue* report;
ASSERT_TRUE(list->GetDictionary(0, &report));
EXPECT_EQ(5u, report->size());
ExpectDictIntegerValue(0, *report, "age");
ExpectDictStringValue(kType_, *report, "type");
ExpectDictStringValue(kUrl_.spec(), *report, "url");
ExpectDictStringValue(kUserAgent_, *report, "user_agent");
ExpectDictDictionaryValue(body, *report, "body");
}
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
// Successful upload should remove delivered reports.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
{
ReportingCache::ClientStatistics stats =
cache()->GetStatisticsForOriginAndEndpoint(kOrigin_, kEndpoint_);
EXPECT_EQ(1, stats.attempted_uploads);
EXPECT_EQ(1, stats.successful_uploads);
EXPECT_EQ(1, stats.attempted_reports);
EXPECT_EQ(1, stats.successful_reports);
}
// TODO(dcreager): Check that BackoffEntry was informed of success.
}
TEST_F(ReportingDeliveryAgentTest, SuccessfulImmediateSubdomainUpload) {
base::DictionaryValue body;
body.SetString("key", "value");
SetClient(kOrigin_, kEndpoint_, kGroup_,
ReportingClient::Subdomains::INCLUDE);
cache()->AddReport(kSubdomainUrl_, kUserAgent_, kGroup_, kType_,
body.CreateDeepCopy(), 0, tick_clock()->NowTicks(), 0);
// Upload is automatically started when cache is modified.
ASSERT_EQ(1u, pending_uploads().size());
EXPECT_EQ(kEndpoint_, pending_uploads()[0]->url());
{
auto value = pending_uploads()[0]->GetValue();
base::ListValue* list;
ASSERT_TRUE(value->GetAsList(&list));
EXPECT_EQ(1u, list->GetSize());
base::DictionaryValue* report;
ASSERT_TRUE(list->GetDictionary(0, &report));
EXPECT_EQ(5u, report->size());
ExpectDictIntegerValue(0, *report, "age");
ExpectDictStringValue(kType_, *report, "type");
ExpectDictStringValue(kSubdomainUrl_.spec(), *report, "url");
ExpectDictStringValue(kUserAgent_, *report, "user_agent");
ExpectDictDictionaryValue(body, *report, "body");
}
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
// Successful upload should remove delivered reports.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
{
ReportingCache::ClientStatistics stats =
cache()->GetStatisticsForOriginAndEndpoint(kOrigin_, kEndpoint_);
EXPECT_EQ(1, stats.attempted_uploads);
EXPECT_EQ(1, stats.successful_uploads);
EXPECT_EQ(1, stats.attempted_reports);
EXPECT_EQ(1, stats.successful_reports);
}
// TODO(dcreager): Check that BackoffEntry was informed of success.
}
TEST_F(ReportingDeliveryAgentTest,
SuccessfulImmediateSubdomainUploadWithEvictedClient) {
base::DictionaryValue body;
body.SetString("key", "value");
SetClient(kOrigin_, kEndpoint_, kGroup_,
ReportingClient::Subdomains::INCLUDE);
cache()->AddReport(kSubdomainUrl_, kUserAgent_, kGroup_, kType_,
body.CreateDeepCopy(), 0, tick_clock()->NowTicks(), 0);
// Upload is automatically started when cache is modified.
ASSERT_EQ(1u, pending_uploads().size());
// Evict the client
SetClient(kOrigin_, kEndpoint_, kGroup_,
ReportingClient::Subdomains::EXCLUDE);
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
{
ReportingCache::ClientStatistics stats =
cache()->GetStatisticsForOriginAndEndpoint(kOrigin_, kEndpoint_);
EXPECT_EQ(1, stats.attempted_uploads);
EXPECT_EQ(1, stats.successful_uploads);
EXPECT_EQ(1, stats.attempted_reports);
EXPECT_EQ(1, stats.successful_reports);
}
// Successful upload should remove delivered reports.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
}
TEST_F(ReportingDeliveryAgentTest, SuccessfulDelayedUpload) {
base::DictionaryValue body;
body.SetString("key", "value");
// Trigger and complete an upload to start the delivery timer.
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_, body.CreateDeepCopy(),
0, tick_clock()->NowTicks(), 0);
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_, body.CreateDeepCopy(),
0, tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
EXPECT_EQ(kEndpoint_, pending_uploads()[0]->url());
{
auto value = pending_uploads()[0]->GetValue();
base::ListValue* list;
ASSERT_TRUE(value->GetAsList(&list));
EXPECT_EQ(1u, list->GetSize());
base::DictionaryValue* report;
ASSERT_TRUE(list->GetDictionary(0, &report));
EXPECT_EQ(5u, report->size());
ExpectDictIntegerValue(0, *report, "age");
ExpectDictStringValue(kType_, *report, "type");
ExpectDictStringValue(kUrl_.spec(), *report, "url");
ExpectDictStringValue(kUserAgent_, *report, "user_agent");
ExpectDictDictionaryValue(body, *report, "body");
}
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
{
ReportingCache::ClientStatistics stats =
cache()->GetStatisticsForOriginAndEndpoint(kOrigin_, kEndpoint_);
EXPECT_EQ(1, stats.attempted_uploads);
EXPECT_EQ(1, stats.successful_uploads);
EXPECT_EQ(1, stats.attempted_reports);
EXPECT_EQ(1, stats.successful_reports);
}
// Successful upload should remove delivered reports.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
// TODO(juliatuttle): Check that BackoffEntry was informed of success.
}
TEST_F(ReportingDeliveryAgentTest, FailedUpload) {
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::FAILURE);
{
ReportingCache::ClientStatistics stats =
cache()->GetStatisticsForOriginAndEndpoint(kOrigin_, kEndpoint_);
EXPECT_EQ(1, stats.attempted_uploads);
EXPECT_EQ(0, stats.successful_uploads);
EXPECT_EQ(1, stats.attempted_reports);
EXPECT_EQ(0, stats.successful_reports);
}
// Failed upload should increment reports' attempts.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
ASSERT_EQ(1u, reports.size());
EXPECT_EQ(1, reports[0]->attempts);
// Since endpoint is now failing, an upload won't be started despite a pending
// report.
ASSERT_TRUE(pending_uploads().empty());
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
EXPECT_TRUE(pending_uploads().empty());
{
ReportingCache::ClientStatistics stats =
cache()->GetStatisticsForOriginAndEndpoint(kOrigin_, kEndpoint_);
EXPECT_EQ(1, stats.attempted_uploads);
EXPECT_EQ(0, stats.successful_uploads);
EXPECT_EQ(1, stats.attempted_reports);
EXPECT_EQ(0, stats.successful_reports);
}
}
TEST_F(ReportingDeliveryAgentTest, DisallowedUpload) {
// This mimics the check that is controlled by the BACKGROUND_SYNC permission
// in a real browser profile.
context()->test_delegate()->set_disallow_report_uploads(true);
static const int kAgeMillis = 12345;
base::DictionaryValue body;
body.SetString("key", "value");
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_, body.CreateDeepCopy(),
0, tick_clock()->NowTicks(), 0);
tick_clock()->Advance(base::TimeDelta::FromMilliseconds(kAgeMillis));
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
// We should not try to upload the report, since we weren't given permission
// for this origin.
EXPECT_TRUE(pending_uploads().empty());
{
ReportingCache::ClientStatistics stats =
cache()->GetStatisticsForOriginAndEndpoint(kOrigin_, kEndpoint_);
EXPECT_EQ(0, stats.attempted_uploads);
EXPECT_EQ(0, stats.successful_uploads);
EXPECT_EQ(0, stats.attempted_reports);
EXPECT_EQ(0, stats.successful_reports);
}
// Disallowed reports should NOT have been removed from the cache.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
EXPECT_EQ(1u, reports.size());
}
TEST_F(ReportingDeliveryAgentTest, RemoveEndpointUpload) {
static const url::Origin kDifferentOrigin =
url::Origin::Create(GURL("https://origin2/"));
SetClient(kOrigin_, kEndpoint_, kGroup_);
SetClient(kDifferentOrigin, kEndpoint_, kGroup_);
ASSERT_TRUE(FindClientInCache(cache(), kOrigin_, kEndpoint_));
ASSERT_TRUE(FindClientInCache(cache(), kDifferentOrigin, kEndpoint_));
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::REMOVE_ENDPOINT);
// "Remove endpoint" upload should remove endpoint from *all* origins and
// increment reports' attempts.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
ASSERT_EQ(1u, reports.size());
EXPECT_EQ(1, reports[0]->attempts);
EXPECT_FALSE(FindClientInCache(cache(), kOrigin_, kEndpoint_));
EXPECT_FALSE(FindClientInCache(cache(), kDifferentOrigin, kEndpoint_));
// Since endpoint is now failing, an upload won't be started despite a pending
// report.
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
EXPECT_TRUE(pending_uploads().empty());
}
TEST_F(ReportingDeliveryAgentTest, ConcurrentRemove) {
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
// Remove the report while the upload is running.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
EXPECT_EQ(1u, reports.size());
const ReportingReport* report = reports[0];
EXPECT_FALSE(cache()->IsReportDoomedForTesting(report));
// Report should appear removed, even though the cache has doomed it.
cache()->RemoveReports(reports, ReportingReport::Outcome::UNKNOWN);
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
EXPECT_TRUE(cache()->IsReportDoomedForTesting(report));
// Completing upload shouldn't crash, and report should still be gone.
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
// This is slightly sketchy since |report| has been freed, but it nonetheless
// should not be in the set of doomed reports.
EXPECT_FALSE(cache()->IsReportDoomedForTesting(report));
}
TEST_F(ReportingDeliveryAgentTest, ConcurrentRemoveDuringPermissionsCheck) {
// Pause the permissions check, so that we can try to remove some reports
// while we're in the middle of verifying that we can upload them. (This is
// similar to the previous test, but removes the reports during a different
// part of the upload process.)
context()->test_delegate()->set_pause_permissions_check(true);
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
ASSERT_TRUE(context()->test_delegate()->PermissionsCheckPaused());
// Remove the report while the upload is running.
std::vector<const ReportingReport*> reports;
cache()->GetReports(&reports);
EXPECT_EQ(1u, reports.size());
const ReportingReport* report = reports[0];
EXPECT_FALSE(cache()->IsReportDoomedForTesting(report));
// Report should appear removed, even though the cache has doomed it.
cache()->RemoveReports(reports, ReportingReport::Outcome::UNKNOWN);
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
EXPECT_TRUE(cache()->IsReportDoomedForTesting(report));
// Completing upload shouldn't crash, and report should still be gone.
context()->test_delegate()->ResumePermissionsCheck();
ASSERT_EQ(1u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
cache()->GetReports(&reports);
EXPECT_TRUE(reports.empty());
// This is slightly sketchy since |report| has been freed, but it nonetheless
// should not be in the set of doomed reports.
EXPECT_FALSE(cache()->IsReportDoomedForTesting(report));
}
// Test that the agent will combine reports destined for the same endpoint, even
// if the reports are from different origins.
TEST_F(ReportingDeliveryAgentTest,
BatchReportsFromDifferentOriginsToSameEndpoint) {
static const GURL kDifferentUrl("https://origin2/path");
static const url::Origin kDifferentOrigin =
url::Origin::Create(kDifferentUrl);
SetClient(kOrigin_, kEndpoint_, kGroup_);
SetClient(kDifferentOrigin, kEndpoint_, kGroup_);
// Trigger and complete an upload to start the delivery timer.
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
// Now that the delivery timer is running, these reports won't be immediately
// uploaded.
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
cache()->AddReport(kDifferentUrl, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_EQ(0u, pending_uploads().size());
// When we fire the delivery timer, we should NOT batch these two reports into
// a single upload, since each upload must only contain reports about a single
// origin.
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(2u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
EXPECT_EQ(0u, pending_uploads().size());
}
// Test that the agent won't start a second upload to the same endpoint for a
// particular origin while one is pending, but will once it is no longer
// pending.
TEST_F(ReportingDeliveryAgentTest, SerializeUploadsToEndpoint) {
SetClient(kOrigin_, kEndpoint_, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
EXPECT_EQ(1u, pending_uploads().size());
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
EXPECT_EQ(0u, pending_uploads().size());
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
EXPECT_EQ(0u, pending_uploads().size());
}
// Test that the agent won't start a second upload for an (origin, group) while
// one is pending, even if a different endpoint is available, but will once the
// original delivery is complete and the (origin, group) is no longer pending.
TEST_F(ReportingDeliveryAgentTest, SerializeUploadsToGroup) {
static const GURL kDifferentEndpoint("https://endpoint2/");
SetClient(kOrigin_, kEndpoint_, kGroup_);
SetClient(kOrigin_, kDifferentEndpoint, kGroup_);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
EXPECT_EQ(1u, pending_uploads().size());
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
EXPECT_EQ(0u, pending_uploads().size());
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(1u, pending_uploads().size());
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
EXPECT_EQ(0u, pending_uploads().size());
}
// Tests that the agent will start parallel uploads to different groups within
// the same origin.
TEST_F(ReportingDeliveryAgentTest, ParallelizeUploadsAcrossGroups) {
static const GURL kDifferentEndpoint("https://endpoint2/");
static const std::string kDifferentGroup("group2");
SetClient(kOrigin_, kEndpoint_, kGroup_);
SetClient(kOrigin_, kDifferentEndpoint, kDifferentGroup);
cache()->AddReport(kUrl_, kUserAgent_, kGroup_, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
cache()->AddReport(kUrl_, kUserAgent_, kDifferentGroup, kType_,
std::make_unique<base::DictionaryValue>(), 0,
tick_clock()->NowTicks(), 0);
EXPECT_TRUE(delivery_timer()->IsRunning());
delivery_timer()->Fire();
ASSERT_EQ(2u, pending_uploads().size());
pending_uploads()[1]->Complete(ReportingUploader::Outcome::SUCCESS);
pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
EXPECT_EQ(0u, pending_uploads().size());
}
} // namespace
} // namespace net