| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/private_aggregation/private_aggregation_host.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/run_loop.h" |
| #include "base/test/gmock_move_support.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "content/browser/aggregation_service/aggregatable_report.h" |
| #include "content/browser/aggregation_service/aggregation_service_test_utils.h" |
| #include "content/browser/private_aggregation/private_aggregation_budget_key.h" |
| #include "content/browser/private_aggregation/private_aggregation_test_utils.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_utils.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/mojom/private_aggregation/aggregatable_report.mojom.h" |
| #include "third_party/blink/public/mojom/private_aggregation/private_aggregation_host.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using testing::_; |
| using testing::Invoke; |
| using testing::Property; |
| |
| constexpr char kPipeResultHistogram[] = |
| "PrivacySandbox.PrivateAggregation.Host.PipeResult"; |
| |
| class PrivateAggregationHostTest : public testing::Test { |
| public: |
| PrivateAggregationHostTest() = default; |
| |
| void SetUp() override { |
| host_ = std::make_unique<PrivateAggregationHost>( |
| /*on_report_request_received=*/mock_callback_.Get(), |
| /*browser_context=*/&test_browser_context_); |
| } |
| |
| void TearDown() override { host_.reset(); } |
| |
| protected: |
| base::MockRepeatingCallback<void(AggregatableReportRequest, |
| PrivateAggregationBudgetKey)> |
| mock_callback_; |
| std::unique_ptr<PrivateAggregationHost> host_; |
| BrowserTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| private: |
| TestBrowserContext test_browser_context_; |
| }; |
| |
| TEST_F(PrivateAggregationHostTest, |
| ContributeToHistogram_ReportRequestHasCorrectMembers) { |
| base::HistogramTester histogram; |
| |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| absl::optional<AggregatableReportRequest> validated_request; |
| EXPECT_CALL( |
| mock_callback_, |
| Run(_, Property(&PrivateAggregationBudgetKey::api, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience))) |
| .WillOnce(MoveArg<0>(&validated_request)); |
| |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remote->ContributeToHistogram(std::move(contributions)); |
| |
| // Should not get a request until after the remote is disconnected. |
| remote.FlushForTesting(); |
| EXPECT_TRUE(remote.is_connected()); |
| EXPECT_FALSE(validated_request); |
| |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| ASSERT_TRUE(validated_request); |
| |
| // We only do some basic validation for the scheduled report time and report |
| // ID as they are not deterministic and will be copied to `expected_request`. |
| // We're using `MOCK_TIME` so we can be sure no time has advanced. |
| base::Time now = base::Time::Now(); |
| EXPECT_GE(validated_request->shared_info().scheduled_report_time, |
| now + base::Minutes(10)); |
| EXPECT_LE(validated_request->shared_info().scheduled_report_time, |
| now + base::Hours(1)); |
| EXPECT_TRUE(validated_request->shared_info().report_id.is_valid()); |
| |
| absl::optional<AggregatableReportRequest> expected_request = |
| AggregatableReportRequest::Create( |
| AggregationServicePayloadContents( |
| AggregationServicePayloadContents::Operation::kHistogram, |
| {blink::mojom::AggregatableReportHistogramContribution( |
| /*bucket=*/123, /*value=*/456)}, |
| blink::mojom::AggregationServiceMode::kDefault, |
| /*aggregation_coordinator_origin=*/absl::nullopt), |
| AggregatableReportSharedInfo( |
| validated_request->shared_info().scheduled_report_time, |
| validated_request->shared_info().report_id, |
| /*reporting_origin=*/kExampleOrigin, |
| AggregatableReportSharedInfo::DebugMode::kDisabled, |
| /*additional_fields=*/base::Value::Dict(), |
| /*api_version=*/"0.1", |
| /*api_identifier=*/"protected-audience"), |
| /*reporting_path=*/ |
| "/.well-known/private-aggregation/report-protected-audience"); |
| ASSERT_TRUE(expected_request); |
| |
| EXPECT_TRUE(aggregation_service::ReportRequestsEqual( |
| validated_request.value(), expected_request.value())); |
| |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, PrivateAggregationHost::PipeResult::kReportSuccess, |
| 1); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, ApiDiffers_RequestUpdatesCorrectly) { |
| base::HistogramTester histogram; |
| |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| const PrivateAggregationBudgetKey::Api apis[] = { |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| PrivateAggregationBudgetKey::Api::kSharedStorage}; |
| |
| std::vector<mojo::Remote<blink::mojom::PrivateAggregationHost>> remotes{ |
| /*n=*/2}; |
| std::vector<absl::optional<AggregatableReportRequest>> validated_requests{ |
| /*n=*/2}; |
| |
| for (int i = 0; i < 2; i++) { |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, apis[i], /*context_id=*/absl::nullopt, |
| remotes[i].BindNewPipeAndPassReceiver())); |
| EXPECT_CALL(mock_callback_, |
| Run(_, Property(&PrivateAggregationBudgetKey::api, apis[i]))) |
| .WillOnce(MoveArg<0>(&validated_requests[i])); |
| |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remotes[i]->ContributeToHistogram(std::move(contributions)); |
| |
| remotes[i].FlushForTesting(); |
| EXPECT_TRUE(remotes[i].is_connected()); |
| ASSERT_FALSE(validated_requests[i]); |
| |
| remotes[i].reset(); |
| host_->FlushReceiverSetForTesting(); |
| ASSERT_TRUE(validated_requests[i]); |
| } |
| |
| EXPECT_EQ(validated_requests[0]->reporting_path(), |
| "/.well-known/private-aggregation/report-protected-audience"); |
| EXPECT_EQ(validated_requests[1]->reporting_path(), |
| "/.well-known/private-aggregation/report-shared-storage"); |
| |
| EXPECT_EQ(validated_requests[0]->shared_info().api_identifier, |
| "protected-audience"); |
| EXPECT_EQ(validated_requests[1]->shared_info().api_identifier, |
| "shared-storage"); |
| |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, PrivateAggregationHost::PipeResult::kReportSuccess, |
| 2); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, EnableDebugMode_ReflectedInReport) { |
| base::HistogramTester histogram; |
| |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| std::vector<blink::mojom::DebugModeDetailsPtr> debug_mode_details_args; |
| debug_mode_details_args.push_back(blink::mojom::DebugModeDetails::New()); |
| debug_mode_details_args.push_back(blink::mojom::DebugModeDetails::New( |
| /*is_enabled=*/true, /*debug_key=*/nullptr)); |
| debug_mode_details_args.push_back(blink::mojom::DebugModeDetails::New( |
| /*is_enabled=*/true, |
| /*debug_key=*/blink::mojom::DebugKey::New(/*value=*/1234u))); |
| |
| std::vector<mojo::Remote<blink::mojom::PrivateAggregationHost>> remotes{ |
| /*n=*/3}; |
| std::vector<absl::optional<AggregatableReportRequest>> validated_requests{ |
| /*n=*/3}; |
| EXPECT_CALL(mock_callback_, Run) |
| .WillOnce(MoveArg<0>(&validated_requests[0])) |
| .WillOnce(MoveArg<0>(&validated_requests[1])) |
| .WillOnce(MoveArg<0>(&validated_requests[2])); |
| |
| for (int i = 0; i < 3; ++i) { |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remotes[i].BindNewPipeAndPassReceiver())); |
| |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remotes[i]->ContributeToHistogram(std::move(contributions)); |
| if (debug_mode_details_args[i]->is_enabled) { |
| remotes[i]->EnableDebugMode( |
| std::move(debug_mode_details_args[i]->debug_key)); |
| } |
| |
| remotes[i].reset(); |
| } |
| host_->FlushReceiverSetForTesting(); |
| |
| ASSERT_TRUE(validated_requests[0].has_value()); |
| ASSERT_TRUE(validated_requests[1].has_value()); |
| ASSERT_TRUE(validated_requests[2].has_value()); |
| |
| EXPECT_EQ(validated_requests[0]->shared_info().debug_mode, |
| AggregatableReportSharedInfo::DebugMode::kDisabled); |
| EXPECT_EQ(validated_requests[1]->shared_info().debug_mode, |
| AggregatableReportSharedInfo::DebugMode::kEnabled); |
| EXPECT_EQ(validated_requests[2]->shared_info().debug_mode, |
| AggregatableReportSharedInfo::DebugMode::kEnabled); |
| |
| EXPECT_EQ(validated_requests[0]->debug_key(), absl::nullopt); |
| EXPECT_EQ(validated_requests[1]->debug_key(), absl::nullopt); |
| EXPECT_EQ(validated_requests[2]->debug_key(), 1234u); |
| |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, PrivateAggregationHost::PipeResult::kReportSuccess, |
| 3); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, |
| MultipleReceievers_ContributeToHistogramCallsRoutedCorrectly) { |
| base::HistogramTester histogram; |
| |
| const url::Origin kExampleOriginA = |
| url::Origin::Create(GURL("https://a.example")); |
| const url::Origin kExampleOriginB = |
| url::Origin::Create(GURL("https://b.example")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| std::vector<mojo::Remote<blink::mojom::PrivateAggregationHost>> remotes( |
| /*n=*/4); |
| |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOriginA, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remotes[0].BindNewPipeAndPassReceiver())); |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOriginB, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remotes[1].BindNewPipeAndPassReceiver())); |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOriginA, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kSharedStorage, |
| /*context_id=*/absl::nullopt, remotes[2].BindNewPipeAndPassReceiver())); |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOriginB, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kSharedStorage, |
| /*context_id=*/absl::nullopt, remotes[3].BindNewPipeAndPassReceiver())); |
| |
| // Use the bucket as a sentinel to ensure that calls were routed correctly. |
| EXPECT_CALL( |
| mock_callback_, |
| Run(_, Property(&PrivateAggregationBudgetKey::api, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience))) |
| .WillOnce( |
| Invoke([&kExampleOriginB](AggregatableReportRequest request, |
| PrivateAggregationBudgetKey budget_key) { |
| ASSERT_EQ(request.payload_contents().contributions.size(), 1u); |
| EXPECT_EQ(request.payload_contents().contributions[0].bucket, 1); |
| EXPECT_EQ(request.shared_info().reporting_origin, kExampleOriginB); |
| EXPECT_EQ(budget_key.origin(), kExampleOriginB); |
| })); |
| |
| EXPECT_CALL( |
| mock_callback_, |
| Run(_, Property(&PrivateAggregationBudgetKey::api, |
| PrivateAggregationBudgetKey::Api::kSharedStorage))) |
| .WillOnce( |
| Invoke([&kExampleOriginA](AggregatableReportRequest request, |
| PrivateAggregationBudgetKey budget_key) { |
| ASSERT_EQ(request.payload_contents().contributions.size(), 1u); |
| EXPECT_EQ(request.payload_contents().contributions[0].bucket, 2); |
| EXPECT_EQ(request.shared_info().reporting_origin, kExampleOriginA); |
| EXPECT_EQ(budget_key.origin(), kExampleOriginA); |
| })); |
| |
| { |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/1, /*value=*/123)); |
| remotes[1]->ContributeToHistogram(std::move(contributions)); |
| } |
| |
| { |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/2, /*value=*/123)); |
| remotes[2]->ContributeToHistogram(std::move(contributions)); |
| } |
| |
| for (auto& remote : remotes) { |
| remote.reset(); |
| } |
| host_->FlushReceiverSetForTesting(); |
| |
| histogram.ExpectTotalCount(kPipeResultHistogram, 4); |
| histogram.ExpectBucketCount( |
| kPipeResultHistogram, PrivateAggregationHost::PipeResult::kReportSuccess, |
| 2); |
| histogram.ExpectBucketCount( |
| kPipeResultHistogram, |
| PrivateAggregationHost::PipeResult::kNoReportButNoError, 2); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, BindUntrustworthyOriginReceiver_Fails) { |
| base::HistogramTester histogram; |
| |
| const url::Origin kInsecureOrigin = |
| url::Origin::Create(GURL("http://example.com")); |
| const url::Origin kOpaqueOrigin; |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote_1; |
| EXPECT_FALSE(host_->BindNewReceiver( |
| kInsecureOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote_1.BindNewPipeAndPassReceiver())); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote_2; |
| EXPECT_FALSE(host_->BindNewReceiver( |
| kOpaqueOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote_2.BindNewPipeAndPassReceiver())); |
| |
| // Attempt to send a message to an unconnected remote. The request should |
| // not be processed. |
| EXPECT_CALL(mock_callback_, Run(_, _)).Times(0); |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remote_1->ContributeToHistogram(std::move(contributions)); |
| |
| // Reset then flush to ensure disconnection and the ContributeToHistogram call |
| // have had time to be processed. |
| remote_1.reset(); |
| remote_2.reset(); |
| host_->FlushReceiverSetForTesting(); |
| |
| histogram.ExpectTotalCount(kPipeResultHistogram, 0); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, BindReceiverWithTooLongContextid_Fails) { |
| base::HistogramTester histogram; |
| |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| const std::string kTooLongContextId = |
| "this_is_an_example_of_a_context_id_that_is_too_long_to_be_allowed"; |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_FALSE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, kTooLongContextId, |
| remote.BindNewPipeAndPassReceiver())); |
| |
| // Attempt to send a message to an unconnected remote. The request should |
| // not be processed. |
| EXPECT_CALL(mock_callback_, Run).Times(0); |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remote->ContributeToHistogram(std::move(contributions)); |
| |
| // Reset then flush to ensure disconnection and the ContributeToHistogram call |
| // have had time to be processed. |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| |
| histogram.ExpectTotalCount(kPipeResultHistogram, 0); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, InvalidRequest_Rejected) { |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| // Negative values are invalid |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| negative_contributions; |
| negative_contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/-1)); |
| |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| valid_contributions; |
| valid_contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| |
| EXPECT_CALL(mock_callback_, Run(_, _)).Times(0); |
| |
| { |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| base::HistogramTester histogram; |
| remote->ContributeToHistogram(std::move(negative_contributions)); |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, |
| PrivateAggregationHost::PipeResult::kNegativeValue, 1); |
| } |
| { |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| base::HistogramTester histogram; |
| |
| remote->ContributeToHistogram(std::move(valid_contributions)); |
| remote->EnableDebugMode( |
| /*debug_key=*/nullptr); |
| remote->EnableDebugMode( |
| /*debug_key=*/blink::mojom::DebugKey::New(1234u)); |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, |
| PrivateAggregationHost::PipeResult::kEnableDebugModeCalledMultipleTimes, |
| 1); |
| } |
| } |
| |
| TEST_F(PrivateAggregationHostTest, TooManyContributions_Truncated) { |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| too_many_contributions; |
| for (int i = 0; i < PrivateAggregationHost::kMaxNumberOfContributions + 1; |
| ++i) { |
| too_many_contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/1)); |
| } |
| |
| base::HistogramTester histogram; |
| |
| absl::optional<AggregatableReportRequest> validated_request; |
| EXPECT_CALL(mock_callback_, Run).WillOnce(MoveArg<0>(&validated_request)); |
| |
| remote->ContributeToHistogram(std::move(too_many_contributions)); |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, |
| PrivateAggregationHost::PipeResult:: |
| kReportSuccessButTruncatedDueToTooManyContributions, |
| 1); |
| |
| ASSERT_TRUE(validated_request); |
| EXPECT_EQ( |
| validated_request->payload_contents().contributions.size(), |
| static_cast<size_t>(PrivateAggregationHost::kMaxNumberOfContributions)); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, PrivateAggregationAllowed_RequestSucceeds) { |
| base::HistogramTester histogram; |
| |
| MockPrivateAggregationContentBrowserClient browser_client; |
| ScopedContentBrowserClientSetting setting(&browser_client); |
| |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| // If the API is enabled, the call should succeed. |
| EXPECT_CALL(browser_client, |
| IsPrivateAggregationAllowed(_, kMainFrameOrigin, kExampleOrigin)) |
| .Times(2) |
| .WillRepeatedly(testing::Return(true)); |
| EXPECT_CALL(mock_callback_, Run); |
| |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remote->ContributeToHistogram(std::move(contributions)); |
| |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, PrivateAggregationHost::PipeResult::kReportSuccess, |
| 1); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, PrivateAggregationDisallowed_RequestFails) { |
| base::HistogramTester histogram; |
| |
| MockPrivateAggregationContentBrowserClient browser_client; |
| ScopedContentBrowserClientSetting setting(&browser_client); |
| |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| // If the API is enabled, the call should succeed. |
| EXPECT_CALL(browser_client, |
| IsPrivateAggregationAllowed(_, kMainFrameOrigin, kExampleOrigin)) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(mock_callback_, Run).Times(0); |
| |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remote->ContributeToHistogram(std::move(contributions)); |
| |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, |
| PrivateAggregationHost::PipeResult::kApiDisabledInSettings, 1); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, ContextIdSet_ReflectedInSingleReport) { |
| base::HistogramTester histogram; |
| |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| "example_context_id", remote.BindNewPipeAndPassReceiver())); |
| |
| absl::optional<AggregatableReportRequest> validated_request; |
| EXPECT_CALL(mock_callback_, Run).WillOnce(MoveArg<0>(&validated_request)); |
| |
| { |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remote->ContributeToHistogram(std::move(contributions)); |
| } |
| |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| |
| ASSERT_TRUE(validated_request.has_value()); |
| |
| EXPECT_THAT( |
| validated_request->additional_fields(), |
| testing::ElementsAre(testing::Pair("context_id", "example_context_id"))); |
| |
| histogram.ExpectUniqueSample( |
| kPipeResultHistogram, PrivateAggregationHost::PipeResult::kReportSuccess, |
| 1); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, |
| ContextIdSetNoContributions_NullReportSentWithoutDebugModeEnabled) { |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| std::vector<blink::mojom::DebugModeDetailsPtr> debug_mode_details_args; |
| debug_mode_details_args.push_back(blink::mojom::DebugModeDetails::New()); |
| debug_mode_details_args.push_back(blink::mojom::DebugModeDetails::New( |
| /*is_enabled=*/true, /*debug_key=*/nullptr)); |
| debug_mode_details_args.push_back(blink::mojom::DebugModeDetails::New( |
| /*is_enabled=*/true, |
| /*debug_key=*/blink::mojom::DebugKey::New(/*value=*/1234u))); |
| |
| std::vector<absl::optional<AggregatableReportRequest>> validated_requests{ |
| /*n=*/3}; |
| EXPECT_CALL(mock_callback_, Run) |
| .WillOnce(MoveArg<0>(&validated_requests[0])) |
| .WillOnce(MoveArg<0>(&validated_requests[1])) |
| .WillOnce(MoveArg<0>(&validated_requests[2])); |
| for (auto& debug_mode_details_arg : debug_mode_details_args) { |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| "example_context_id", remote.BindNewPipeAndPassReceiver())); |
| |
| if (debug_mode_details_arg->is_enabled) { |
| remote->EnableDebugMode(std::move(debug_mode_details_arg->debug_key)); |
| } |
| |
| EXPECT_TRUE(remote.is_connected()); |
| remote.reset(); |
| } |
| |
| host_->FlushReceiverSetForTesting(); |
| |
| for (absl::optional<AggregatableReportRequest>& validated_request : |
| validated_requests) { |
| ASSERT_TRUE(validated_request.has_value()); |
| EXPECT_THAT(validated_request->additional_fields(), |
| testing::ElementsAre( |
| testing::Pair("context_id", "example_context_id"))); |
| ASSERT_EQ(validated_request->payload_contents().contributions.size(), 1u); |
| EXPECT_EQ(validated_request->payload_contents().contributions[0].bucket, |
| 0u); |
| EXPECT_EQ(validated_request->payload_contents().contributions[0].value, 0); |
| |
| // Null reports never have debug mode set according to the current spec. |
| EXPECT_EQ(validated_request->shared_info().debug_mode, |
| AggregatableReportSharedInfo::DebugMode::kDisabled); |
| EXPECT_EQ(validated_request->debug_key(), absl::nullopt); |
| } |
| } |
| |
| TEST_F(PrivateAggregationHostTest, ContextIdNotSet_NoNullReportSent) { |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| EXPECT_CALL(mock_callback_, Run).Times(0); |
| |
| { |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| EXPECT_TRUE(remote.is_connected()); |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| } |
| |
| { |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| // Enabling the debug mode has no effect. |
| remote->EnableDebugMode( |
| /*debug_key=*/blink::mojom::DebugKey::New(/*value=*/1234u)); |
| |
| EXPECT_TRUE(remote.is_connected()); |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| } |
| } |
| |
| TEST_F(PrivateAggregationHostTest, PipeClosedBeforeShutdown_NoHistogram) { |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| EXPECT_CALL(mock_callback_, Run).Times(0); |
| |
| base::HistogramTester histogram; |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| EXPECT_TRUE(remote.is_connected()); |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| host_.reset(); |
| |
| histogram.ExpectTotalCount( |
| "PrivacySandbox.PrivateAggregation.Host.PipeOpenDurationOnShutdown", 0); |
| } |
| |
| TEST_F(PrivateAggregationHostTest, PipeStillOpenAtShutdown_Histogram) { |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| EXPECT_CALL(mock_callback_, Run).Times(0); |
| |
| base::HistogramTester histogram; |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| task_environment_.FastForwardBy(base::Minutes(10)); |
| |
| EXPECT_TRUE(remote.is_connected()); |
| host_.reset(); |
| |
| histogram.ExpectUniqueTimeSample( |
| "PrivacySandbox.PrivateAggregation.Host.PipeOpenDurationOnShutdown", |
| base::Minutes(10), 1); |
| } |
| |
| class PrivateAggregationHostDeveloperModeTest |
| : public PrivateAggregationHostTest { |
| public: |
| PrivateAggregationHostDeveloperModeTest() { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kPrivateAggregationDeveloperMode); |
| } |
| }; |
| |
| TEST_F(PrivateAggregationHostDeveloperModeTest, |
| ContributeToHistogram_ScheduledReportTimeIsNotDelayed) { |
| const url::Origin kExampleOrigin = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main_frame.com")); |
| |
| mojo::Remote<blink::mojom::PrivateAggregationHost> remote; |
| EXPECT_TRUE(host_->BindNewReceiver( |
| kExampleOrigin, kMainFrameOrigin, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience, |
| /*context_id=*/absl::nullopt, remote.BindNewPipeAndPassReceiver())); |
| |
| absl::optional<AggregatableReportRequest> validated_request; |
| EXPECT_CALL( |
| mock_callback_, |
| Run(_, Property(&PrivateAggregationBudgetKey::api, |
| PrivateAggregationBudgetKey::Api::kProtectedAudience))) |
| .WillOnce(MoveArg<0>(&validated_request)); |
| |
| std::vector<blink::mojom::AggregatableReportHistogramContributionPtr> |
| contributions; |
| contributions.push_back( |
| blink::mojom::AggregatableReportHistogramContribution::New( |
| /*bucket=*/123, /*value=*/456)); |
| remote->ContributeToHistogram(std::move(contributions)); |
| remote.reset(); |
| host_->FlushReceiverSetForTesting(); |
| |
| ASSERT_TRUE(validated_request); |
| |
| // We're using `MOCK_TIME` so we can be sure no time has advanced. |
| EXPECT_EQ(validated_request->shared_info().scheduled_report_time, |
| base::Time::Now()); |
| } |
| |
| } // namespace |
| |
| } // namespace content |