| // Copyright 2021 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 "content/browser/aggregation_service/aggregatable_report_sender.h" |
| |
| #include <memory> |
| |
| #include "base/callback_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "net/base/isolation_info.h" |
| #include "net/base/load_flags.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "services/network/test/test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| const char kExampleURL[] = "https://a.com/"; |
| |
| base::Value GetExampleContents() { |
| base::Value contents(base::Value::Type::DICTIONARY); |
| contents.SetStringKey("id", "1234"); |
| return contents; |
| } |
| |
| } // namespace |
| |
| class AggregatableReportSenderTest : public testing::Test { |
| public: |
| AggregatableReportSenderTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), |
| sender_(std::make_unique<AggregatableReportSender>( |
| /*storage_partition=*/nullptr)), |
| shared_url_loader_factory_( |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_)) { |
| sender_->SetURLLoaderFactoryForTesting(shared_url_loader_factory_); |
| } |
| |
| protected: |
| content::BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<AggregatableReportSender> sender_; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| |
| private: |
| scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_; |
| }; |
| |
| TEST_F(AggregatableReportSenderTest, ReportSent_RequestAttributesSet) { |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| |
| const network::ResourceRequest* pending_request; |
| EXPECT_TRUE( |
| test_url_loader_factory_.IsPending(kExampleURL, &pending_request)); |
| |
| EXPECT_EQ(pending_request->url, GURL(kExampleURL)); |
| EXPECT_EQ(pending_request->method, net::HttpRequestHeaders::kPostMethod); |
| EXPECT_EQ(pending_request->credentials_mode, |
| network::mojom::CredentialsMode::kOmit); |
| |
| int load_flags = pending_request->load_flags; |
| EXPECT_TRUE(load_flags & net::LOAD_BYPASS_CACHE); |
| EXPECT_TRUE(load_flags & net::LOAD_DISABLE_CACHE); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, ReportSent_IsolationKeyDifferent) { |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 2); |
| |
| const network::ResourceRequest& request1 = |
| test_url_loader_factory_.GetPendingRequest(0)->request; |
| const network::ResourceRequest& request2 = |
| test_url_loader_factory_.GetPendingRequest(1)->request; |
| |
| EXPECT_EQ(request1.trusted_params->isolation_info.request_type(), |
| net::IsolationInfo::RequestType::kOther); |
| EXPECT_EQ(request2.trusted_params->isolation_info.request_type(), |
| net::IsolationInfo::RequestType::kOther); |
| |
| EXPECT_TRUE(request1.trusted_params->isolation_info.network_isolation_key() |
| .IsTransient()); |
| EXPECT_TRUE(request2.trusted_params->isolation_info.network_isolation_key() |
| .IsTransient()); |
| |
| EXPECT_NE(request1.trusted_params->isolation_info.network_isolation_key(), |
| request2.trusted_params->isolation_info.network_isolation_key()); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, ReportSent_UploadDataCorrect) { |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| |
| const network::ResourceRequest* pending_request; |
| EXPECT_TRUE( |
| test_url_loader_factory_.IsPending(kExampleURL, &pending_request)); |
| EXPECT_EQ(network::GetUploadData(*pending_request), R"({"id":"1234"})"); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, ReportSent_StatusOk) { |
| sender_->SendReport( |
| GURL(kExampleURL), GetExampleContents(), |
| base::BindLambdaForTesting( |
| [&](AggregatableReportSender::RequestStatus status) { |
| EXPECT_EQ(status, AggregatableReportSender::RequestStatus::kOk); |
| })); |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 1); |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| kExampleURL, "")); |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 0); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, SenderDeletedDuringRequest_NoCrash) { |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 1); |
| sender_.reset(); |
| EXPECT_FALSE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| kExampleURL, "")); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, ReportRequestHangs_Timeout) { |
| sender_->SendReport( |
| GURL(kExampleURL), GetExampleContents(), |
| base::BindLambdaForTesting( |
| [&](AggregatableReportSender::RequestStatus status) { |
| EXPECT_EQ(status, |
| AggregatableReportSender::RequestStatus::kNetworkError); |
| })); |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 1); |
| |
| // The request should time out after 30 seconds. |
| task_environment_.FastForwardBy(base::Seconds(30)); |
| |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 0); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, |
| ReportRequestFailsDueToNetworkChange_Retries) { |
| // Retry fails |
| { |
| sender_->SendReport( |
| GURL(kExampleURL), GetExampleContents(), |
| base::BindLambdaForTesting( |
| [&](AggregatableReportSender::RequestStatus status) { |
| EXPECT_EQ(status, |
| AggregatableReportSender::RequestStatus::kNetworkError); |
| })); |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 1); |
| |
| // Simulate the request failing due to network change. |
| test_url_loader_factory_.SimulateResponseForPendingRequest( |
| GURL(kExampleURL), |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED), |
| network::mojom::URLResponseHead::New(), std::string()); |
| |
| // The sender should automatically retry. |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 1); |
| |
| // Simulate a second request failure due to network change. |
| test_url_loader_factory_.SimulateResponseForPendingRequest( |
| GURL(kExampleURL), |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED), |
| network::mojom::URLResponseHead::New(), std::string()); |
| |
| // We should not retry again. |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 0); |
| } |
| |
| // Retry succeeds |
| { |
| sender_->SendReport( |
| GURL(kExampleURL), GetExampleContents(), |
| base::BindLambdaForTesting( |
| [&](AggregatableReportSender::RequestStatus status) { |
| EXPECT_EQ(status, AggregatableReportSender::RequestStatus::kOk); |
| })); |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 1); |
| |
| // Simulate the request failing due to network change. |
| test_url_loader_factory_.SimulateResponseForPendingRequest( |
| GURL(kExampleURL), |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED), |
| network::mojom::URLResponseHead::New(), std::string()); |
| |
| // The sender should automatically retry. |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 1); |
| |
| // Simulate a second request with respoonse. |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| kExampleURL, "")); |
| } |
| } |
| |
| TEST_F(AggregatableReportSenderTest, HttpError_CallbackRuns) { |
| bool callback_run = false; |
| sender_->SendReport( |
| GURL(kExampleURL), GetExampleContents(), |
| base::BindLambdaForTesting( |
| [&](AggregatableReportSender::RequestStatus status) { |
| EXPECT_EQ(status, |
| AggregatableReportSender::RequestStatus::kServerError); |
| callback_run = true; |
| })); |
| |
| // We should run the callback even if there is an http error. |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| kExampleURL, "", net::HttpStatusCode::HTTP_BAD_REQUEST)); |
| |
| EXPECT_TRUE(callback_run); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, ManyReports_AllSentSuccessfully) { |
| GURL url = GURL(kExampleURL); |
| int num_callbacks_run = 0; |
| for (int i = 0; i < 10; i++) { |
| sender_->SendReport( |
| url, GetExampleContents(), |
| base::BindLambdaForTesting( |
| [&](AggregatableReportSender::RequestStatus status) { |
| EXPECT_EQ(status, AggregatableReportSender::RequestStatus::kOk); |
| ++num_callbacks_run; |
| })); |
| } |
| |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 10); |
| |
| for (int i = 0; i < 10; i++) { |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| kExampleURL, "")); |
| } |
| |
| EXPECT_EQ(num_callbacks_run, 10); |
| EXPECT_EQ(test_url_loader_factory_.NumPending(), 0); |
| } |
| |
| TEST_F(AggregatableReportSenderTest, StatusHistoram_Expected) { |
| // All OK. |
| { |
| base::HistogramTester histograms; |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| kExampleURL, "")); |
| histograms.ExpectUniqueSample( |
| "PrivacySandbox.AggregationService.ReportStatus", |
| AggregatableReportSender::RequestStatus::kOk, 1); |
| } |
| |
| // Network error. |
| { |
| base::HistogramTester histograms; |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| network::URLLoaderCompletionStatus completion_status(net::ERR_FAILED); |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| GURL(kExampleURL), completion_status, |
| network::mojom::URLResponseHead::New(), std::string())); |
| histograms.ExpectUniqueSample( |
| "PrivacySandbox.AggregationService.ReportStatus", |
| AggregatableReportSender::RequestStatus::kNetworkError, 1); |
| } |
| |
| // Server error. |
| { |
| base::HistogramTester histograms; |
| sender_->SendReport(GURL(kExampleURL), GetExampleContents(), |
| base::DoNothing()); |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| kExampleURL, std::string(), net::HTTP_UNAUTHORIZED)); |
| histograms.ExpectUniqueSample( |
| "PrivacySandbox.AggregationService.ReportStatus", |
| AggregatableReportSender::RequestStatus::kServerError, 1); |
| } |
| } |
| |
| } // namespace content |