1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
10 #include "base/callback.h"
11 #include "base/macros.h"
12 #include "base/test/values_test_util.h"
13 #include "base/time/time.h"
14 #include "base/values.h"
15 #include "net/base/ip_address.h"
16 #include "net/base/net_errors.h"
17 #include "net/network_error_logging/network_error_logging_delegate.h"
18 #include "net/network_error_logging/network_error_logging_service.h"
19 #include "net/reporting/reporting_policy.h"
20 #include "net/reporting/reporting_service.h"
21 #include "net/socket/next_proto.h"
22 #include "testing/gtest/include/gtest/gtest.h"
24 #include "url/origin.h"
29 class TestReportingService : public ReportingService {
34 Report(Report&& other)
38 body(std::move(other.body)),
41 Report(const GURL& url,
42 const std::string& group,
43 const std::string& type,
44 std::unique_ptr<const base::Value> body,
49 body(std::move(body)),
57 std::unique_ptr<const base::Value> body;
61 DISALLOW_COPY(Report);
64 TestReportingService() = default;
66 const std::vector<Report>& reports() const { return reports_; }
68 // ReportingService implementation:
70 ~TestReportingService() override = default;
72 void QueueReport(const GURL& url,
73 const std::string& group,
74 const std::string& type,
75 std::unique_ptr<const base::Value> body,
77 reports_.push_back(Report(url, group, type, std::move(body), depth));
80 void ProcessHeader(const GURL& url,
81 const std::string& header_value) override {
85 void RemoveBrowsingData(int data_type_mask,
86 const base::RepeatingCallback<bool(const GURL&)>&
87 origin_filter) override {
91 int GetUploadDepth(const URLRequest& request) override {
96 const ReportingPolicy& GetPolicy() const override {
102 std::vector<Report> reports_;
103 ReportingPolicy dummy_policy_;
105 DISALLOW_COPY_AND_ASSIGN(TestReportingService);
108 class NetworkErrorLoggingServiceTest : public ::testing::Test {
110 NetworkErrorLoggingServiceTest() {
111 service_ = NetworkErrorLoggingService::Create(
112 NetworkErrorLoggingDelegate::Create());
113 CreateReportingService();
116 void CreateReportingService() {
117 DCHECK(!reporting_service_);
119 reporting_service_ = std::make_unique<TestReportingService>();
120 service_->SetReportingService(reporting_service_.get());
123 void DestroyReportingService() {
124 DCHECK(reporting_service_);
126 service_->SetReportingService(nullptr);
127 reporting_service_.reset();
130 NetworkErrorLoggingService::RequestDetails
131 MakeRequestDetails(GURL url, Error error_type, int status_code = 0) {
132 NetworkErrorLoggingService::RequestDetails details;
135 details.referrer = kReferrer_;
136 details.server_ip = IPAddress::IPv4AllZeros();
137 details.protocol = kProtoUnknown;
138 details.status_code = status_code;
139 details.elapsed_time = base::TimeDelta::FromSeconds(1);
140 details.type = error_type;
141 details.reporting_upload_depth = 0;
146 NetworkErrorLoggingService* service() { return service_.get(); }
147 const std::vector<TestReportingService::Report>& reports() {
148 return reporting_service_->reports();
151 const GURL kUrl_ = GURL("https://example.com/path");
152 const GURL kUrlDifferentPort_ = GURL("https://example.com:4433/path");
153 const GURL kUrlSubdomain_ = GURL("https://subdomain.example.com/path");
154 const GURL kUrlDifferentHost_ = GURL("https://example2.com/path");
156 const url::Origin kOrigin_ = url::Origin::Create(kUrl_);
157 const url::Origin kOriginDifferentPort_ =
158 url::Origin::Create(kUrlDifferentPort_);
159 const url::Origin kOriginSubdomain_ = url::Origin::Create(kUrlSubdomain_);
160 const url::Origin kOriginDifferentHost_ =
161 url::Origin::Create(kUrlDifferentHost_);
163 const std::string kHeader_ = "{\"report-to\":\"group\",\"max-age\":86400}";
164 const std::string kHeaderIncludeSubdomains_ =
165 "{\"report-to\":\"group\",\"max-age\":86400,\"includeSubdomains\":true}";
166 const std::string kHeaderMaxAge0_ = "{\"max-age\":0}";
167 const std::string kHeaderTooLong_ =
168 "{\"report-to\":\"group\",\"max-age\":86400,\"junk\":\"" +
169 std::string(32 * 1024, 'a') + "\"}";
170 const std::string kHeaderTooDeep_ =
171 "{\"report-to\":\"group\",\"max-age\":86400,\"junk\":[[[[[[[[[[]]]]]]]]]]"
174 const std::string kGroup_ = "group";
176 const std::string kType_ = NetworkErrorLoggingService::kReportType;
178 const GURL kReferrer_ = GURL("https://referrer.com/");
181 std::unique_ptr<NetworkErrorLoggingService> service_;
182 std::unique_ptr<TestReportingService> reporting_service_;
185 void ExpectDictDoubleValue(double expected_value,
186 const base::DictionaryValue& value,
187 const std::string& key) {
188 double double_value = 0.0;
189 EXPECT_TRUE(value.GetDouble(key, &double_value)) << key;
190 EXPECT_DOUBLE_EQ(expected_value, double_value) << key;
193 TEST_F(NetworkErrorLoggingServiceTest, CreateService) {
194 // Service is created by default in the test fixture..
195 EXPECT_TRUE(service());
198 TEST_F(NetworkErrorLoggingServiceTest, NoReportingService) {
199 DestroyReportingService();
201 service()->OnHeader(kOrigin_, kHeader_);
203 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
206 TEST_F(NetworkErrorLoggingServiceTest, OriginInsecure) {
207 const GURL kInsecureUrl("http://insecure.com/");
208 const url::Origin kInsecureOrigin = url::Origin::Create(kInsecureUrl);
210 service()->OnHeader(kInsecureOrigin, kHeader_);
212 service()->OnRequest(
213 MakeRequestDetails(kInsecureUrl, ERR_CONNECTION_REFUSED));
215 EXPECT_TRUE(reports().empty());
218 TEST_F(NetworkErrorLoggingServiceTest, NoPolicyForOrigin) {
219 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
221 EXPECT_TRUE(reports().empty());
224 TEST_F(NetworkErrorLoggingServiceTest, JsonTooLong) {
225 service()->OnHeader(kOrigin_, kHeaderTooLong_);
227 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
229 EXPECT_TRUE(reports().empty());
232 TEST_F(NetworkErrorLoggingServiceTest, JsonTooDeep) {
233 service()->OnHeader(kOrigin_, kHeaderTooDeep_);
235 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
237 EXPECT_TRUE(reports().empty());
240 TEST_F(NetworkErrorLoggingServiceTest, SuccessReportQueued) {
241 static const std::string kHeaderSuccessFraction1 =
242 "{\"report-to\":\"group\",\"max-age\":86400,\"success-fraction\":1.0}";
243 service()->OnHeader(kOrigin_, kHeaderSuccessFraction1);
245 service()->OnRequest(MakeRequestDetails(kUrl_, OK));
247 ASSERT_EQ(1u, reports().size());
248 EXPECT_EQ(kUrl_, reports()[0].url);
249 EXPECT_EQ(kGroup_, reports()[0].group);
250 EXPECT_EQ(kType_, reports()[0].type);
251 EXPECT_EQ(0, reports()[0].depth);
253 const base::DictionaryValue* body;
254 ASSERT_TRUE(reports()[0].body->GetAsDictionary(&body));
255 base::ExpectDictStringValue(kUrl_.spec(), *body,
256 NetworkErrorLoggingService::kUriKey);
257 base::ExpectDictStringValue(kReferrer_.spec(), *body,
258 NetworkErrorLoggingService::kReferrerKey);
259 // TODO(juliatuttle): Extract these constants.
260 ExpectDictDoubleValue(1.0, *body,
261 NetworkErrorLoggingService::kSamplingFractionKey);
262 base::ExpectDictStringValue("0.0.0.0", *body,
263 NetworkErrorLoggingService::kServerIpKey);
264 base::ExpectDictStringValue("", *body,
265 NetworkErrorLoggingService::kProtocolKey);
266 base::ExpectDictIntegerValue(0, *body,
267 NetworkErrorLoggingService::kStatusCodeKey);
268 base::ExpectDictIntegerValue(1000, *body,
269 NetworkErrorLoggingService::kElapsedTimeKey);
270 base::ExpectDictStringValue("ok", *body,
271 NetworkErrorLoggingService::kTypeKey);
274 TEST_F(NetworkErrorLoggingServiceTest, FailureReportQueued) {
275 static const std::string kHeaderFailureFraction1 =
276 "{\"report-to\":\"group\",\"max-age\":86400,\"failure-fraction\":1.0}";
277 service()->OnHeader(kOrigin_, kHeaderFailureFraction1);
279 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
281 ASSERT_EQ(1u, reports().size());
282 EXPECT_EQ(kUrl_, reports()[0].url);
283 EXPECT_EQ(kGroup_, reports()[0].group);
284 EXPECT_EQ(kType_, reports()[0].type);
285 EXPECT_EQ(0, reports()[0].depth);
287 const base::DictionaryValue* body;
288 ASSERT_TRUE(reports()[0].body->GetAsDictionary(&body));
289 base::ExpectDictStringValue(kUrl_.spec(), *body,
290 NetworkErrorLoggingService::kUriKey);
291 base::ExpectDictStringValue(kReferrer_.spec(), *body,
292 NetworkErrorLoggingService::kReferrerKey);
293 // TODO(juliatuttle): Extract these constants.
294 ExpectDictDoubleValue(1.0, *body,
295 NetworkErrorLoggingService::kSamplingFractionKey);
296 base::ExpectDictStringValue("0.0.0.0", *body,
297 NetworkErrorLoggingService::kServerIpKey);
298 base::ExpectDictStringValue("", *body,
299 NetworkErrorLoggingService::kProtocolKey);
300 base::ExpectDictIntegerValue(0, *body,
301 NetworkErrorLoggingService::kStatusCodeKey);
302 base::ExpectDictIntegerValue(1000, *body,
303 NetworkErrorLoggingService::kElapsedTimeKey);
304 base::ExpectDictStringValue("tcp.refused", *body,
305 NetworkErrorLoggingService::kTypeKey);
308 TEST_F(NetworkErrorLoggingServiceTest, HttpErrorReportQueued) {
309 static const std::string kHeaderFailureFraction1 =
310 "{\"report-to\":\"group\",\"max-age\":86400,\"failure-fraction\":1.0}";
311 service()->OnHeader(kOrigin_, kHeaderFailureFraction1);
313 service()->OnRequest(MakeRequestDetails(kUrl_, OK, 504));
315 ASSERT_EQ(1u, reports().size());
316 EXPECT_EQ(kUrl_, reports()[0].url);
317 EXPECT_EQ(kGroup_, reports()[0].group);
318 EXPECT_EQ(kType_, reports()[0].type);
319 EXPECT_EQ(0, reports()[0].depth);
321 const base::DictionaryValue* body;
322 ASSERT_TRUE(reports()[0].body->GetAsDictionary(&body));
323 base::ExpectDictStringValue(kUrl_.spec(), *body,
324 NetworkErrorLoggingService::kUriKey);
325 base::ExpectDictStringValue(kReferrer_.spec(), *body,
326 NetworkErrorLoggingService::kReferrerKey);
327 // TODO(juliatuttle): Extract these constants.
328 ExpectDictDoubleValue(1.0, *body,
329 NetworkErrorLoggingService::kSamplingFractionKey);
330 base::ExpectDictStringValue("0.0.0.0", *body,
331 NetworkErrorLoggingService::kServerIpKey);
332 base::ExpectDictStringValue("", *body,
333 NetworkErrorLoggingService::kProtocolKey);
334 base::ExpectDictIntegerValue(504, *body,
335 NetworkErrorLoggingService::kStatusCodeKey);
336 base::ExpectDictIntegerValue(1000, *body,
337 NetworkErrorLoggingService::kElapsedTimeKey);
338 base::ExpectDictStringValue("http.error", *body,
339 NetworkErrorLoggingService::kTypeKey);
342 TEST_F(NetworkErrorLoggingServiceTest, MaxAge0) {
343 service()->OnHeader(kOrigin_, kHeader_);
345 service()->OnHeader(kOrigin_, kHeaderMaxAge0_);
347 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
349 EXPECT_TRUE(reports().empty());
352 TEST_F(NetworkErrorLoggingServiceTest, SuccessFraction0) {
353 static const std::string kHeaderSuccessFraction0 =
354 "{\"report-to\":\"group\",\"max-age\":86400,\"success-fraction\":0.0}";
355 service()->OnHeader(kOrigin_, kHeaderSuccessFraction0);
357 // Each network error has a 0% chance of being reported. Fire off several and
358 // verify that no reports are produced.
359 constexpr size_t kReportCount = 100;
360 for (size_t i = 0; i < kReportCount; ++i)
361 service()->OnRequest(MakeRequestDetails(kUrl_, OK));
363 EXPECT_TRUE(reports().empty());
366 TEST_F(NetworkErrorLoggingServiceTest, SuccessFractionHalf) {
367 // Include a different value for failure-fraction to ensure that we copy the
368 // right value into sampling-fraction.
369 static const std::string kHeaderSuccessFractionHalf =
370 "{\"report-to\":\"group\",\"max-age\":86400,\"success-fraction\":0.5,"
371 "\"failure-fraction\":0.25}";
372 service()->OnHeader(kOrigin_, kHeaderSuccessFractionHalf);
374 // Each network error has a 50% chance of being reported. Fire off several
375 // and verify that some requests were reported and some weren't. (We can't
376 // verify exact counts because each decision is made randomly.)
377 constexpr size_t kReportCount = 100;
378 for (size_t i = 0; i < kReportCount; ++i)
379 service()->OnRequest(MakeRequestDetails(kUrl_, OK));
381 // If our random selection logic is correct, there is a 2^-100 chance that
382 // every single report above was skipped. If this check fails, it's much more
383 // likely that our code is wrong.
384 EXPECT_FALSE(reports().empty());
386 // There's also a 2^-100 chance that every single report was logged. Same as
387 // above, that's much more likely to be a code error.
388 EXPECT_GT(kReportCount, reports().size());
390 for (const auto& report : reports()) {
391 const base::DictionaryValue* body;
392 ASSERT_TRUE(report.body->GetAsDictionary(&body));
393 // Our header includes a different value for failure-fraction, so that this
394 // check verifies that we copy the correct fraction into sampling-fraction.
395 ExpectDictDoubleValue(0.5, *body,
396 NetworkErrorLoggingService::kSamplingFractionKey);
400 TEST_F(NetworkErrorLoggingServiceTest, FailureFraction0) {
401 static const std::string kHeaderFailureFraction0 =
402 "{\"report-to\":\"group\",\"max-age\":86400,\"failure-fraction\":0.0}";
403 service()->OnHeader(kOrigin_, kHeaderFailureFraction0);
405 // Each network error has a 0% chance of being reported. Fire off several and
406 // verify that no reports are produced.
407 constexpr size_t kReportCount = 100;
408 for (size_t i = 0; i < kReportCount; ++i)
409 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
411 EXPECT_TRUE(reports().empty());
414 TEST_F(NetworkErrorLoggingServiceTest, FailureFractionHalf) {
415 // Include a different value for success-fraction to ensure that we copy the
416 // right value into sampling-fraction.
417 static const std::string kHeaderFailureFractionHalf =
418 "{\"report-to\":\"group\",\"max-age\":86400,\"failure-fraction\":0.5,"
419 "\"success-fraction\":0.25}";
420 service()->OnHeader(kOrigin_, kHeaderFailureFractionHalf);
422 // Each network error has a 50% chance of being reported. Fire off several
423 // and verify that some requests were reported and some weren't. (We can't
424 // verify exact counts because each decision is made randomly.)
425 constexpr size_t kReportCount = 100;
426 for (size_t i = 0; i < kReportCount; ++i)
427 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
429 // If our random selection logic is correct, there is a 2^-100 chance that
430 // every single report above was skipped. If this check fails, it's much more
431 // likely that our code is wrong.
432 EXPECT_FALSE(reports().empty());
434 // There's also a 2^-100 chance that every single report was logged. Same as
435 // above, that's much more likely to be a code error.
436 EXPECT_GT(kReportCount, reports().size());
438 for (const auto& report : reports()) {
439 const base::DictionaryValue* body;
440 ASSERT_TRUE(report.body->GetAsDictionary(&body));
441 ExpectDictDoubleValue(0.5, *body,
442 NetworkErrorLoggingService::kSamplingFractionKey);
446 TEST_F(NetworkErrorLoggingServiceTest,
447 ExcludeSubdomainsDoesntMatchDifferentPort) {
448 service()->OnHeader(kOrigin_, kHeader_);
450 service()->OnRequest(
451 MakeRequestDetails(kUrlDifferentPort_, ERR_CONNECTION_REFUSED));
453 EXPECT_TRUE(reports().empty());
456 TEST_F(NetworkErrorLoggingServiceTest, ExcludeSubdomainsDoesntMatchSubdomain) {
457 service()->OnHeader(kOrigin_, kHeader_);
459 service()->OnRequest(
460 MakeRequestDetails(kUrlSubdomain_, ERR_CONNECTION_REFUSED));
462 EXPECT_TRUE(reports().empty());
465 TEST_F(NetworkErrorLoggingServiceTest, IncludeSubdomainsMatchesDifferentPort) {
466 service()->OnHeader(kOrigin_, kHeaderIncludeSubdomains_);
468 service()->OnRequest(
469 MakeRequestDetails(kUrlDifferentPort_, ERR_CONNECTION_REFUSED));
471 ASSERT_EQ(1u, reports().size());
472 EXPECT_EQ(kUrlDifferentPort_, reports()[0].url);
475 TEST_F(NetworkErrorLoggingServiceTest, IncludeSubdomainsMatchesSubdomain) {
476 service()->OnHeader(kOrigin_, kHeaderIncludeSubdomains_);
478 service()->OnRequest(
479 MakeRequestDetails(kUrlSubdomain_, ERR_CONNECTION_REFUSED));
481 ASSERT_EQ(1u, reports().size());
484 TEST_F(NetworkErrorLoggingServiceTest,
485 IncludeSubdomainsDoesntMatchSuperdomain) {
486 service()->OnHeader(kOriginSubdomain_, kHeaderIncludeSubdomains_);
488 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
490 EXPECT_TRUE(reports().empty());
493 TEST_F(NetworkErrorLoggingServiceTest, RemoveAllBrowsingData) {
494 service()->OnHeader(kOrigin_, kHeader_);
496 service()->RemoveBrowsingData(base::RepeatingCallback<bool(const GURL&)>());
498 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
500 EXPECT_TRUE(reports().empty());
503 TEST_F(NetworkErrorLoggingServiceTest, RemoveSomeBrowsingData) {
504 service()->OnHeader(kOrigin_, kHeader_);
505 service()->OnHeader(kOriginDifferentHost_, kHeader_);
507 service()->RemoveBrowsingData(
508 base::BindRepeating([](const GURL& origin) -> bool {
509 return origin.host() == "example.com";
512 service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
514 EXPECT_TRUE(reports().empty());
516 service()->OnRequest(
517 MakeRequestDetails(kUrlDifferentHost_, ERR_CONNECTION_REFUSED));
519 ASSERT_EQ(1u, reports().size());
522 TEST_F(NetworkErrorLoggingServiceTest, Nested) {
523 service()->OnHeader(kOrigin_, kHeader_);
525 NetworkErrorLoggingService::RequestDetails details =
526 MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED);
527 details.reporting_upload_depth =
528 NetworkErrorLoggingService::kMaxNestedReportDepth;
529 service()->OnRequest(details);
531 ASSERT_EQ(1u, reports().size());
532 EXPECT_EQ(NetworkErrorLoggingService::kMaxNestedReportDepth,
536 TEST_F(NetworkErrorLoggingServiceTest, NestedTooDeep) {
537 service()->OnHeader(kOrigin_, kHeader_);
539 NetworkErrorLoggingService::RequestDetails details =
540 MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED);
541 details.reporting_upload_depth =
542 NetworkErrorLoggingService::kMaxNestedReportDepth + 1;
543 service()->OnRequest(details);
545 EXPECT_TRUE(reports().empty());