Index: net/reporting/reporting_service_unittest.cc |
diff --git a/net/reporting/reporting_service_unittest.cc b/net/reporting/reporting_service_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..05885d3f5f5264932fdd4016b21fe4864c277d22 |
--- /dev/null |
+++ b/net/reporting/reporting_service_unittest.cc |
@@ -0,0 +1,488 @@ |
+// Copyright 2016 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_service.h" |
+ |
+#include <string> |
+#include <vector> |
+ |
+#include "base/bind.h" |
+#include "base/json/json_reader.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/run_loop.h" |
+#include "base/test/simple_test_tick_clock.h" |
+#include "base/values.h" |
+#include "net/base/network_change_notifier.h" |
+#include "net/reporting/reporting_uploader.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "url/gurl.h" |
+ |
+namespace net { |
+namespace { |
+ |
+class MockUploader : public ReportingUploader { |
+ public: |
+ class PendingDelivery { |
+ public: |
+ PendingDelivery(MockUploader* uploader, |
+ const GURL& url, |
+ const std::string& json, |
+ const Callback& callback) |
+ : uploader_(uploader), url_(url), json_(json), callback_(callback) { |
+ DCHECK(uploader_); |
+ } |
+ |
+ ~PendingDelivery() {} |
+ |
+ void Complete(Outcome outcome) { |
+ callback_.Run(outcome); |
+ // Deletes |this|. |
+ uploader_->OnDeliveryComplete(this); |
+ } |
+ |
+ const GURL& url() const { return url_; } |
+ const std::string& json() const { return json_; } |
+ |
+ private: |
+ MockUploader* uploader_; |
+ GURL url_; |
+ std::string json_; |
+ Callback callback_; |
+ }; |
+ |
+ MockUploader() {} |
+ ~MockUploader() override {} |
+ |
+ void AttemptDelivery(const GURL& url, |
+ const std::string& json, |
+ const Callback& callback) override { |
+ deliveries_.push_back( |
+ base::MakeUnique<PendingDelivery>(this, url, json, callback)); |
+ } |
+ |
+ const std::vector<std::unique_ptr<PendingDelivery>>& GetPendingDeliveries() |
+ const { |
+ return deliveries_; |
+ } |
+ |
+ void OnDeliveryComplete(PendingDelivery* delivery) { |
+ for (auto it = deliveries_.begin(); it != deliveries_.end(); ++it) { |
+ if (it->get() == delivery) { |
+ deliveries_.erase(it); |
+ return; |
+ } |
+ } |
+ NOTREACHED(); |
+ } |
+ |
+ private: |
+ std::vector<std::unique_ptr<PendingDelivery>> deliveries_; |
+}; |
+ |
+class ReportingServiceTest : public ::testing::Test { |
+ protected: |
+ ReportingServiceTest() |
+ : clock_(new base::SimpleTestTickClock()), |
+ uploader_(new MockUploader()) {} |
+ |
+ ~ReportingServiceTest() override { |
+ // These are owned by the Service, so clear them to avoid accidentally |
+ // accessing them after the Service is destroyed. |
+ clock_ = nullptr; |
+ uploader_ = nullptr; |
+ } |
+ |
+ void CreateServiceWithDefaultPolicy() { |
+ CreateServiceWithPolicy(ReportingService::Policy()); |
+ } |
+ |
+ void CreateServiceWithPolicy(const ReportingService::Policy& policy) { |
+ DCHECK(!service_); |
+ policy_ = policy; |
+ service_ = base::MakeUnique<ReportingService>(policy); |
+ service_->set_clock_for_testing(base::WrapUnique(clock_)); |
+ service_->set_uploader(base::WrapUnique(uploader_)); |
+ } |
+ |
+ void QueueReport(const GURL& url) { |
+ service_->QueueReport(base::MakeUnique<base::DictionaryValue>(), url, |
+ url.GetOrigin(), "default", "test"); |
+ } |
+ |
+ const std::vector<std::unique_ptr<MockUploader::PendingDelivery>>& |
+ GetPendingDeliveries() { |
+ return uploader_->GetPendingDeliveries(); |
+ } |
+ |
+ size_t GetReportCount() { return service_->GetReportCountForTesting(); } |
+ |
+ bool HasEndpoint(const std::string& endpoint_url) { |
+ return service_->HasEndpointForTesting(GURL(endpoint_url)); |
+ } |
+ |
+ bool HasClient(const std::string& endpoint_url, |
+ const std::string& origin_url) { |
+ return service_->HasClientForTesting(GURL(endpoint_url), GURL(origin_url)); |
+ } |
+ |
+ int GetEndpointFailures(const std::string& endpoint_url) { |
+ return service_->GetEndpointFailuresForTesting(GURL(endpoint_url)); |
+ } |
+ |
+ void MockNetworkChange() { |
+ // TODO: Need to SetTestNotificationsOnly(true) to keep things from flaking, |
+ // but have to figure out how to do that before NCN is created or how to |
+ // recreate NCN. |
+ NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests( |
+ NetworkChangeNotifier::CONNECTION_NONE); |
+ base::RunLoop().RunUntilIdle(); |
+ NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests( |
+ NetworkChangeNotifier::CONNECTION_WIFI); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ // |policy_| is a copy of the policy used to configure |service_|, for easier |
+ // reference. |
+ ReportingService::Policy policy_; |
+ // |clock_| and |uploader_| are owned by |service_|, not the test. |
+ base::SimpleTestTickClock* clock_; |
+ MockUploader* uploader_; |
+ std::unique_ptr<ReportingService> service_; |
+}; |
+ |
+TEST_F(ReportingServiceTest, ProcessHeaderFromInsecureOrigin) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("http://insecure/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ EXPECT_FALSE(HasClient("https://endpoint/", "http://insecure")); |
+} |
+ |
+TEST_F(ReportingServiceTest, ProcessInvalidHeaders) { |
+ static const struct { |
+ const char* header_value; |
+ const char* description; |
+ } kInvalidHeaderTestCases[] = { |
+ {"{\"max-age\":1}", "missing url"}, |
+ {"{\"url\":0,\"max-age\":1}", "non-string url"}, |
+ {"{\"url\":\"http://insecure/\",\"max-age\":1}", "insecure url"}, |
+ |
+ {"{\"url\":\"https://endpoint/\"}", "missing max-age"}, |
+ {"{\"url\":\"https://endpoint/\",\"max-age\":\"\"}", |
+ "non-integer max-age"}, |
+ {"{\"url\":\"https://endpoint/\",\"max-age\":-1}", "negative max-age"}, |
+ |
+ {"{\"url\":\"https://endpoint/\",\"max-age\":1,\"group\":0}", |
+ "non-string group"}, |
+ |
+ {"{\"url\":\"https://endpoint/\",\"max-age\":1,\"includeSubdomains\":0}", |
+ "non-boolean includeSubdomains"}, |
+ }; |
+ |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ for (size_t i = 0; i < arraysize(kInvalidHeaderTestCases); ++i) { |
+ auto& test_case = kInvalidHeaderTestCases[i]; |
+ service_->ProcessHeader(GURL("https://origin/"), test_case.header_value); |
+ EXPECT_FALSE(HasClient("https://endpoint/", "https://origin/")); |
+ } |
+} |
+ |
+TEST_F(ReportingServiceTest, ProcessValidHeader) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ EXPECT_TRUE(HasClient("https://endpoint/", "https://origin/")); |
+ |
+#if 0 |
+ EXPECT_EQ(1u, GetCache().GetEndpoints().size()); |
+ const std::unique_ptr<ReportingEndpoint>* endpoint_ptr = |
+ GetCache().GetEndpoint(GURL("https://endpoint/")); |
+ ASSERT_TRUE(endpoint_ptr); |
+ const std::unique_ptr<ReportingEndpoint>& endpoint = *endpoint_ptr; |
+ EXPECT_EQ(GURL("https://endpoint/"), endpoint->url); |
+ |
+ EXPECT_EQ(1u, endpoint->clients.count(GURL("https://origin/"))); |
+ auto client_it = endpoint->clients.find(GURL("https://origin/")); |
+ ASSERT_TRUE(client_it != endpoint->clients.end()); |
+ const ReportingClient& client = client_it->second; |
+ EXPECT_EQ(GURL("https://origin"), client.origin); |
+ EXPECT_FALSE(client.subdomains); |
+ EXPECT_EQ("default", client.group); |
+ EXPECT_EQ(base::TimeDelta::FromSeconds(1), client.ttl); |
+#endif |
+} |
+ |
+TEST_F(ReportingServiceTest, ProcessZeroMaxAgeHeader) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ EXPECT_TRUE(HasClient("https://endpoint/", "https://origin/")); |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":0}"); |
+ EXPECT_FALSE(HasClient("https://endpoint/", "https://origin/")); |
+} |
+ |
+TEST_F(ReportingServiceTest, DeliverySuccess) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ QueueReport(GURL("https://origin/path")); |
+ service_->SendReports(); |
+ |
+ ASSERT_EQ(1u, GetPendingDeliveries().size()); |
+ const std::unique_ptr<MockUploader::PendingDelivery>& delivery = |
+ GetPendingDeliveries()[0]; |
+ EXPECT_EQ(GURL("https://endpoint/"), delivery->url()); |
+ { |
+ auto reports_value = base::JSONReader::Read(delivery->json()); |
+ const base::ListValue* reports; |
+ ASSERT_TRUE(reports_value->GetAsList(&reports)); |
+ ASSERT_EQ(1u, reports->GetSize()); |
+ const base::DictionaryValue* report; |
+ ASSERT_TRUE(reports->GetDictionary(0u, &report)); |
+ std::string type; |
+ ASSERT_TRUE(report->GetString("type", &type)); |
+ EXPECT_EQ("test", type); |
+ std::string url; |
+ ASSERT_TRUE(report->GetString("url", &url)); |
+ EXPECT_EQ("https://origin/path", url); |
+ } |
+ |
+ delivery->Complete(ReportingUploader::SUCCESS); |
+ |
+ EXPECT_EQ(0, GetEndpointFailures("https://endpoint/")); |
+} |
+ |
+TEST_F(ReportingServiceTest, DeliveryFailure) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ QueueReport(GURL("https://origin/path")); |
+ service_->SendReports(); |
+ |
+ GetPendingDeliveries()[0]->Complete(ReportingUploader::FAILURE); |
+ |
+ EXPECT_EQ(1, GetEndpointFailures("https://endpoint/")); |
+} |
+ |
+TEST_F(ReportingServiceTest, DeliveryRemoveEndpoint) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ QueueReport(GURL("https://origin/path")); |
+ service_->SendReports(); |
+ |
+ GetPendingDeliveries()[0]->Complete(ReportingUploader::REMOVE_ENDPOINT); |
+ |
+ EXPECT_FALSE(HasClient("https://endpoint/", "https://origin/")); |
+} |
+ |
+TEST_F(ReportingServiceTest, NoDeliveryToPendingEndpoint) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ QueueReport(GURL("https://origin/")); |
+ service_->SendReports(); |
+ |
+ GetPendingDeliveries()[0]->Complete(ReportingUploader::FAILURE); |
+ |
+ // Without advancing the clock, the endpoint should still be pending, so the |
+ // Manager should not try to deliver reports to it. |
+ service_->SendReports(); |
+ |
+ EXPECT_TRUE(GetPendingDeliveries().empty()); |
+} |
+ |
+TEST_F(ReportingServiceTest, NoDeliveryToMismatchedEndpoint) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin1/"), |
+ "{\"url\":\"https://endpoint1/\",\"max-age\":1}"); |
+ QueueReport(GURL("https://origin2/")); |
+ service_->SendReports(); |
+ |
+ EXPECT_TRUE(GetPendingDeliveries().empty()); |
+} |
+ |
+TEST_F(ReportingServiceTest, BatchReportsForSameOrigin) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ QueueReport(GURL("https://origin/")); |
+ QueueReport(GURL("https://origin/")); |
+ service_->SendReports(); |
+ |
+ ASSERT_EQ(1u, GetPendingDeliveries().size()); |
+ GetPendingDeliveries()[0]->Complete(ReportingUploader::FAILURE); |
+} |
+ |
+TEST_F(ReportingServiceTest, BatchReportsForSameEndpoint) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin1/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ service_->ProcessHeader(GURL("https://origin2/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ QueueReport(GURL("https://origin1/")); |
+ QueueReport(GURL("https://origin2/")); |
+ service_->SendReports(); |
+ |
+ ASSERT_EQ(1u, GetPendingDeliveries().size()); |
+ GetPendingDeliveries()[0]->Complete(ReportingUploader::FAILURE); |
+} |
+ |
+TEST_F(ReportingServiceTest, ExpiredEndpoint) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader(GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":1}"); |
+ EXPECT_TRUE(HasEndpoint("https://endpoint/")); |
+ |
+ clock_->Advance(base::TimeDelta::FromSeconds(2)); |
+ service_->CollectGarbageForTesting(); |
+ EXPECT_FALSE(HasEndpoint("https://endpoint/")); |
+} |
+ |
+TEST_F(ReportingServiceTest, UnusedEndpoint) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ service_->ProcessHeader( |
+ GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":999999999}"); |
+ EXPECT_TRUE(HasEndpoint("https://endpoint/")); |
+ |
+ clock_->Advance(2 * policy_.endpoint_lifetime); |
+ service_->CollectGarbageForTesting(); |
+ EXPECT_FALSE(HasEndpoint("https://endpoint/")); |
+} |
+ |
+TEST_F(ReportingServiceTest, FailedEndpoint) { |
+ ReportingService::Policy policy; |
+ policy.endpoint_backoff.initial_delay_ms = 0; |
+ policy.endpoint_backoff.maximum_backoff_ms = 0; |
+ policy.endpoint_backoff.entry_lifetime_ms = 1000; |
+ policy.max_endpoint_failures = 3; |
+ policy.max_report_failures = -1; |
+ CreateServiceWithPolicy(policy); |
+ |
+ service_->ProcessHeader( |
+ GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":999999999}"); |
+ QueueReport(GURL("https://origin/")); |
+ |
+ for (int i = 0; i < policy_.max_endpoint_failures + 1; i++) { |
+ EXPECT_TRUE(HasEndpoint("https://endpoint/")); |
+ service_->SendReports(); |
+ ASSERT_EQ(1u, GetPendingDeliveries().size()); |
+ GetPendingDeliveries()[0]->Complete(ReportingUploader::FAILURE); |
+ } |
+ EXPECT_TRUE(HasEndpoint("https://endpoint/")); |
+ |
+ clock_->Advance(base::TimeDelta::FromMilliseconds( |
+ 2 * policy_.endpoint_backoff.entry_lifetime_ms)); |
+ |
+ service_->CollectGarbageForTesting(); |
+ EXPECT_FALSE(HasEndpoint("https://endpoint/")); |
+} |
+ |
+TEST_F(ReportingServiceTest, EvictedEndpoint) { |
+ ReportingService::Policy policy; |
+ policy.max_endpoint_count = 1; |
+ CreateServiceWithPolicy(policy); |
+ |
+ service_->ProcessHeader(GURL("https://origin1/"), |
+ "{\"url\":\"https://endpoint1/\",\"max-age\":1}"); |
+ EXPECT_TRUE(HasEndpoint("https://endpoint1")); |
+ |
+ service_->ProcessHeader(GURL("https://origin2/"), |
+ "{\"url\":\"https://endpoint2/\",\"max-age\":1}"); |
+ EXPECT_FALSE(HasEndpoint("https://endpoint1")); |
+ EXPECT_TRUE(HasEndpoint("https://endpoint2")); |
+} |
+ |
+TEST_F(ReportingServiceTest, ExpiredReport) { |
+ CreateServiceWithDefaultPolicy(); |
+ |
+ QueueReport(GURL("https://origin/")); |
+ ASSERT_EQ(1u, GetReportCount()); |
+ |
+ clock_->Advance(policy_.report_lifetime / 2); |
+ service_->CollectGarbageForTesting(); |
+ ASSERT_EQ(1u, GetReportCount()); |
+ |
+ clock_->Advance(policy_.report_lifetime); |
+ service_->CollectGarbageForTesting(); |
+ EXPECT_EQ(0u, GetReportCount()); |
+} |
+ |
+TEST_F(ReportingServiceTest, FailedReport) { |
+ ReportingService::Policy policy; |
+ policy.endpoint_backoff.initial_delay_ms = 0; |
+ policy.endpoint_backoff.maximum_backoff_ms = 0; |
+ policy.endpoint_backoff.entry_lifetime_ms = 0; |
+ policy.max_endpoint_failures = 1000; |
+ policy.max_report_failures = 3; |
+ CreateServiceWithPolicy(policy); |
+ |
+ service_->ProcessHeader( |
+ GURL("https://origin/"), |
+ "{\"url\":\"https://endpoint/\",\"max-age\":999999999}"); |
+ QueueReport(GURL("https://origin/")); |
+ |
+ for (size_t i = 0; i < policy_.max_report_failures + 1; i++) { |
+ EXPECT_EQ(1u, GetReportCount()); |
+ service_->SendReports(); |
+ ASSERT_EQ(1u, GetPendingDeliveries().size()); |
+ GetPendingDeliveries()[0]->Complete(ReportingUploader::FAILURE); |
+ } |
+ EXPECT_EQ(0u, GetReportCount()); |
+} |
+ |
+TEST_F(ReportingServiceTest, EvictedReport) { |
+ ReportingService::Policy policy; |
+ policy.max_report_count = 3; |
+ CreateServiceWithPolicy(policy); |
+ |
+ for (size_t i = 0; i < policy_.max_report_count; i++) { |
+ QueueReport(GURL("https://origin/")); |
+ EXPECT_EQ(static_cast<size_t>(i + 1), GetReportCount()); |
+ } |
+ QueueReport(GURL("https://origin/")); |
+ EXPECT_EQ(static_cast<size_t>(policy_.max_report_count), GetReportCount()); |
+} |
+ |
+TEST_F(ReportingServiceTest, NetworkChangePersist) { |
+ ReportingService::Policy policy; |
+ policy.persist_reports_across_network_changes = true; |
+ CreateServiceWithPolicy(policy); |
+ |
+ QueueReport(GURL("https://origin/")); |
+ EXPECT_EQ(1u, GetReportCount()); |
+ |
+ MockNetworkChange(); |
+ EXPECT_EQ(1u, GetReportCount()); |
+} |
+ |
+TEST_F(ReportingServiceTest, NetworkChangeClear) { |
+ ReportingService::Policy policy; |
+ policy.persist_reports_across_network_changes = false; |
+ CreateServiceWithPolicy(policy); |
+ |
+ QueueReport(GURL("https://origin/")); |
+ EXPECT_EQ(1u, GetReportCount()); |
+ |
+ MockNetworkChange(); |
+ EXPECT_EQ(0u, GetReportCount()); |
+} |
+ |
+} // namespace |
+} // namespace net |