blob: e04a2f25af49c49d5af8db6e50b3c9d97d2cac39 [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 <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/network_error_logging/network_error_logging_service.h"
#include "net/reporting/reporting_service.h"
#include "net/socket/next_proto.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace net {
namespace {
class TestReportingService : public ReportingService {
public:
struct Report {
Report() = default;
Report(Report&& other)
: url(other.url),
group(other.group),
type(other.type),
body(std::move(other.body)) {}
Report(const GURL& url,
const std::string& group,
const std::string& type,
std::unique_ptr<const base::Value> body)
: url(url), group(group), type(type), body(std::move(body)) {}
~Report() = default;
GURL url;
std::string group;
std::string type;
std::unique_ptr<const base::Value> body;
private:
DISALLOW_COPY(Report);
};
TestReportingService() = default;
const std::vector<Report>& reports() const { return reports_; }
// ReportingService implementation:
~TestReportingService() override = default;
void QueueReport(const GURL& url,
const std::string& group,
const std::string& type,
std::unique_ptr<const base::Value> body) override {
reports_.push_back(Report(url, group, type, std::move(body)));
}
void ProcessHeader(const GURL& url,
const std::string& header_value) override {
NOTREACHED();
}
void RemoveBrowsingData(int data_type_mask,
const base::RepeatingCallback<bool(const GURL&)>&
origin_filter) override {
NOTREACHED();
}
bool RequestIsUpload(const URLRequest& request) override {
NOTREACHED();
return true;
}
private:
std::vector<Report> reports_;
DISALLOW_COPY_AND_ASSIGN(TestReportingService);
};
class NetworkErrorLoggingServiceTest : public ::testing::Test {
protected:
NetworkErrorLoggingServiceTest() {
scoped_feature_list_.InitAndEnableFeature(features::kNetworkErrorLogging);
service_ = NetworkErrorLoggingService::Create();
CreateReportingService();
}
void CreateReportingService() {
DCHECK(!reporting_service_);
reporting_service_ = base::MakeUnique<TestReportingService>();
service_->SetReportingService(reporting_service_.get());
}
void DestroyReportingService() {
DCHECK(reporting_service_);
service_->SetReportingService(nullptr);
reporting_service_.reset();
}
NetworkErrorLoggingDelegate::ErrorDetails MakeErrorDetails(GURL url,
Error error_type) {
NetworkErrorLoggingDelegate::ErrorDetails details;
details.uri = url;
details.referrer = kReferrer_;
details.server_ip = IPAddress::IPv4AllZeros();
details.protocol = kProtoUnknown;
details.status_code = 0;
details.elapsed_time = base::TimeDelta::FromSeconds(1);
details.type = error_type;
details.is_reporting_upload = false;
return details;
}
NetworkErrorLoggingService* service() { return service_.get(); }
const std::vector<TestReportingService::Report>& reports() {
return reporting_service_->reports();
}
const GURL kUrl_ = GURL("https://example.com/path");
const GURL kUrlDifferentPort_ = GURL("https://example.com:4433/path");
const GURL kUrlSubdomain_ = GURL("https://subdomain.example.com/path");
const GURL kUrlDifferentHost_ = GURL("https://example2.com/path");
const url::Origin kOrigin_ = url::Origin::Create(kUrl_);
const url::Origin kOriginDifferentPort_ =
url::Origin::Create(kUrlDifferentPort_);
const url::Origin kOriginSubdomain_ = url::Origin::Create(kUrlSubdomain_);
const url::Origin kOriginDifferentHost_ =
url::Origin::Create(kUrlDifferentHost_);
const std::string kHeader_ = "{\"report-to\":\"group\",\"max-age\":86400}";
const std::string kHeaderIncludeSubdomains_ =
"{\"report-to\":\"group\",\"max-age\":86400,\"includeSubdomains\":true}";
const std::string kHeaderMaxAge0_ = "{\"max-age\":0}";
const std::string kGroup_ = "group";
const std::string kType_ = NetworkErrorLoggingService::kReportType;
const GURL kReferrer_ = GURL("https://referrer.com/");
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<NetworkErrorLoggingService> service_;
std::unique_ptr<TestReportingService> reporting_service_;
};
TEST_F(NetworkErrorLoggingServiceTest, FeatureDisabled) {
// N.B. This test does not actually use the test fixture.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(features::kNetworkErrorLogging);
auto service = NetworkErrorLoggingService::Create();
EXPECT_FALSE(service);
}
TEST_F(NetworkErrorLoggingServiceTest, FeatureEnabled) {
EXPECT_TRUE(service());
}
TEST_F(NetworkErrorLoggingServiceTest, NoReportingService) {
DestroyReportingService();
service()->OnHeader(kOrigin_, kHeader_);
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
}
TEST_F(NetworkErrorLoggingServiceTest, OriginInsecure) {
const GURL kInsecureUrl("http://insecure.com/");
const url::Origin kInsecureOrigin = url::Origin::Create(kInsecureUrl);
service()->OnHeader(kInsecureOrigin, kHeader_);
service()->OnNetworkError(
MakeErrorDetails(kInsecureUrl, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, NoPolicyForOrigin) {
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, ReportQueued) {
service()->OnHeader(kOrigin_, kHeader_);
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
EXPECT_EQ(1u, reports().size());
EXPECT_EQ(kUrl_, reports()[0].url);
EXPECT_EQ(kGroup_, reports()[0].group);
EXPECT_EQ(kType_, reports()[0].type);
const base::DictionaryValue* body;
ASSERT_TRUE(reports()[0].body->GetAsDictionary(&body));
base::ExpectDictStringValue(kUrl_.spec(), *body,
NetworkErrorLoggingService::kUriKey);
base::ExpectDictStringValue(kReferrer_.spec(), *body,
NetworkErrorLoggingService::kReferrerKey);
// TODO(juliatuttle): Extract these constants.
base::ExpectDictStringValue("0.0.0.0", *body,
NetworkErrorLoggingService::kServerIpKey);
base::ExpectDictStringValue("", *body,
NetworkErrorLoggingService::kProtocolKey);
base::ExpectDictIntegerValue(0, *body,
NetworkErrorLoggingService::kStatusCodeKey);
base::ExpectDictIntegerValue(1000, *body,
NetworkErrorLoggingService::kElapsedTimeKey);
base::ExpectDictStringValue("tcp.refused", *body,
NetworkErrorLoggingService::kTypeKey);
}
TEST_F(NetworkErrorLoggingServiceTest, MaxAge0) {
service()->OnHeader(kOrigin_, kHeader_);
service()->OnHeader(kOrigin_, kHeaderMaxAge0_);
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest,
ExcludeSubdomainsDoesntMatchDifferentPort) {
service()->OnHeader(kOrigin_, kHeader_);
service()->OnNetworkError(
MakeErrorDetails(kUrlDifferentPort_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, ExcludeSubdomainsDoesntMatchSubdomain) {
service()->OnHeader(kOrigin_, kHeader_);
service()->OnNetworkError(
MakeErrorDetails(kUrlSubdomain_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, IncludeSubdomainsMatchesDifferentPort) {
service()->OnHeader(kOrigin_, kHeaderIncludeSubdomains_);
service()->OnNetworkError(
MakeErrorDetails(kUrlDifferentPort_, ERR_CONNECTION_REFUSED));
EXPECT_EQ(1u, reports().size());
EXPECT_EQ(kUrlDifferentPort_, reports()[0].url);
}
TEST_F(NetworkErrorLoggingServiceTest, IncludeSubdomainsMatchesSubdomain) {
service()->OnHeader(kOrigin_, kHeaderIncludeSubdomains_);
service()->OnNetworkError(
MakeErrorDetails(kUrlSubdomain_, ERR_CONNECTION_REFUSED));
EXPECT_EQ(1u, reports().size());
}
TEST_F(NetworkErrorLoggingServiceTest,
IncludeSubdomainsDoesntMatchSuperdomain) {
service()->OnHeader(kOriginSubdomain_, kHeaderIncludeSubdomains_);
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, RemoveAllBrowsingData) {
service()->OnHeader(kOrigin_, kHeader_);
service()->RemoveBrowsingData(base::RepeatingCallback<bool(const GURL&)>());
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, RemoveSomeBrowsingData) {
service()->OnHeader(kOrigin_, kHeader_);
service()->OnHeader(kOriginDifferentHost_, kHeader_);
service()->RemoveBrowsingData(
base::BindRepeating([](const GURL& origin) -> bool {
return origin.host() == "example.com";
}));
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
service()->OnNetworkError(
MakeErrorDetails(kUrlDifferentHost_, ERR_CONNECTION_REFUSED));
EXPECT_EQ(1u, reports().size());
}
} // namespace
} // namespace net