| // Copyright 2024 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/interest_group/trusted_signals_cache_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <list> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "base/unguessable_token.h" |
| #include "base/values.h" |
| #include "content/browser/interest_group/bidding_and_auction_server_key_fetcher.h" |
| #include "content/browser/interest_group/data_decoder_manager.h" |
| #include "content/browser/interest_group/trusted_signals_fetcher.h" |
| #include "content/public/browser/frame_tree_node_id.h" |
| #include "content/services/auction_worklet/public/mojom/trusted_signals_cache.mojom.h" |
| #include "mojo/public/cpp/base/big_buffer.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/test_support/test_utils.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_loader_factory.mojom-forward.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| namespace { |
| |
| // Some tests need a small, non-zero time, so tests can simulate time passing |
| // until just before something should happen, and then skip forward to exactly |
| // when it should happen, to make sure the passage of time is handled correctly. |
| // Time is mocked out in these tests, so a small value will never cause flake. |
| const base::TimeDelta kTinyTime = base::Milliseconds(1); |
| |
| // Generic success/error strings used in most tests. |
| const char kSuccessBody[] = "Successful result"; |
| const char kOtherSuccessBody[] = "Other successful result"; |
| const char kSomeOtherSuccessBody[] = "Some other successful result"; |
| const char kErrorMessage[] = "Error message"; |
| |
| // The error message received when a compression group is requested over the |
| // Mojo interface, but no matching CompressionGroupData is found. |
| const char kRequestCancelledError[] = "Request cancelled"; |
| |
| // Creates a consistent fixed-size URL from a given integer from 0 to 999, |
| // inclusive. |
| GURL CreateUrl(std::uint32_t i) { |
| CHECK_LE(i, 999u); |
| // Always return a fixed size string. While the size doesn't currently matter, |
| // the URL length may be included in size computations in the future, at which |
| // point a constant size string may be useful in making the computed size |
| // consistent. |
| return GURL(base::StringPrintf("https://%03u.test", i)); |
| } |
| |
| // Creates a string of size `length`. Each value of `i` creates a distinct but |
| // consistent string. `i` may be in the range 0 to 999. |
| std::string CreateString(std::uint32_t i, size_t length = 3) { |
| CHECK_LE(i, 999u); |
| std::string out = base::StringPrintf("%03u", i); |
| out.append(length - 3, 'a'); |
| CHECK_EQ(out.size(), length); |
| return out; |
| } |
| |
| // Struct with input parameters for RequestTrustedBiddingSignals(). Having a |
| // struct allows for more easily checking changing a single parameter, and |
| // validating all parameters passed to the TrustedSignalsFetcher, without |
| // duplicating a lot of code. |
| struct BiddingParams { |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory; |
| FrameTreeNodeId frame_tree_node_id; |
| // Actual requests may only have a single devtools ID, but this struct is also |
| // be used to represent the result of multiple merged requests. |
| std::set<std::string> devtools_auction_ids{"devtools_auction_id1"}; |
| url::Origin main_frame_origin; |
| network::mojom::IPAddressSpace ip_address_space = |
| network::mojom::IPAddressSpace::kPublic; |
| // The bidder / interest group owner. |
| url::Origin script_origin; |
| |
| // Actual requests may only have a single interest group, so only one name. |
| // This is a set because this struct is also used to validate fetch |
| // parameters, which may include a set of interest groups in the |
| // group-by-origin case. |
| std::set<std::string> interest_group_names; |
| |
| blink::mojom::InterestGroup_ExecutionMode execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kCompatibilityMode; |
| url::Origin joining_origin; |
| GURL trusted_signals_url; |
| url::Origin coordinator; |
| std::optional<std::vector<std::string>> trusted_bidding_signals_keys; |
| base::Value::Dict additional_params; |
| std::optional<std::string> buyer_tkv_signals; |
| }; |
| |
| // Struct with input parameters for RequestTrustedScoringSignals(). |
| struct ScoringParams { |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory; |
| FrameTreeNodeId frame_tree_node_id; |
| // While ScoringParams are never merged, so this will always only have one ID, |
| // use a set here to mirror BiddingParams. |
| std::set<std::string> devtools_auction_ids{"devtools_auction_id1"}; |
| url::Origin main_frame_origin; |
| network::mojom::IPAddressSpace ip_address_space = |
| network::mojom::IPAddressSpace::kPublic; |
| // The seller. |
| url::Origin script_origin; |
| GURL trusted_signals_url; |
| url::Origin coordinator; |
| url::Origin interest_group_owner; |
| url::Origin joining_origin; |
| GURL render_url; |
| std::vector<GURL> component_render_urls; |
| base::Value::Dict additional_params; |
| std::optional<std::string> seller_tkv_signals; |
| }; |
| |
| // Just like TrustedSignalsFetcher::BiddingPartition, but owns its arguments. |
| struct FetcherBiddingPartitionArgs { |
| int partition_id; |
| std::set<std::string> interest_group_names; |
| std::set<std::string> keys; |
| base::Value::Dict additional_params; |
| std::optional<std::string> buyer_tkv_signals; |
| }; |
| |
| // Just like TrustedSignalsFetcher::ScoringPartition, but owns its arguments. |
| struct FetcherScoringPartitionArgs { |
| int partition_id; |
| GURL render_url; |
| std::set<GURL> component_render_urls; |
| base::Value::Dict additional_params; |
| std::optional<std::string> seller_tkv_signals; |
| }; |
| |
| // Creates a BiddingAndAuctionServerKey that embeds the signals and coordinator |
| // origins in it, so the mock fetcher can calidate that its parameters match |
| // those used to get the BiddingAndAuctionServerKey. |
| BiddingAndAuctionServerKey CreateServerKey(const url::Origin& signals_origin, |
| const url::Origin& coordinator) { |
| return BiddingAndAuctionServerKey{ |
| /*key=*/base::StrCat( |
| {signals_origin.Serialize(), " ", coordinator.Serialize()}), |
| /*id=*/"01"}; |
| } |
| |
| // Subclass of TrustedSignalsCacheImpl that mocks out TrustedSignalsFetcher |
| // calls, and lets tests monitor and respond to those fetches. |
| class TestTrustedSignalsCache : public TrustedSignalsCacheImpl { |
| public: |
| static constexpr std::string_view kKeyFetchFailed = "Key fetch failed"; |
| |
| // Controls how coordinator key requests are handled. |
| enum class GetCoordinatorKeyMode { |
| kSync, |
| kAsync, |
| kSyncFail, |
| kAsyncFail, |
| // Grabs the callback and lets the consumer wait for it and invoke it |
| // directly via WaitForCoordinatorKeyCallback(). Checks that all received |
| // callbacks have been consumed on destruction. |
| kStashCallback |
| }; |
| |
| class TestTrustedSignalsFetcher : public TrustedSignalsFetcher { |
| public: |
| struct PendingBiddingSignalsFetch { |
| GURL trusted_signals_url; |
| BiddingAndAuctionServerKey bidding_and_auction_key; |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory; |
| FrameTreeNodeId frame_tree_node_id; |
| base::flat_set<std::string> devtools_auction_ids; |
| url::Origin main_frame_origin; |
| network::mojom::IPAddressSpace ip_address_space; |
| base::UnguessableToken network_partition_nonce; |
| url::Origin script_origin; |
| std::map<int, std::vector<FetcherBiddingPartitionArgs>> |
| compression_groups; |
| TrustedSignalsFetcher::Callback callback; |
| |
| // Weak pointer to Fetcher to allow checking if the Fetcher has been |
| // destroyed. |
| base::WeakPtr<TestTrustedSignalsFetcher> fetcher_alive; |
| }; |
| |
| struct PendingScoringSignalsFetch { |
| GURL trusted_signals_url; |
| BiddingAndAuctionServerKey bidding_and_auction_key; |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory; |
| FrameTreeNodeId frame_tree_node_id; |
| base::flat_set<std::string> devtools_auction_ids; |
| url::Origin main_frame_origin; |
| network::mojom::IPAddressSpace ip_address_space; |
| base::UnguessableToken network_partition_nonce; |
| url::Origin script_origin; |
| std::map<int, std::vector<FetcherScoringPartitionArgs>> |
| compression_groups; |
| TrustedSignalsFetcher::Callback callback; |
| |
| // Weak pointer to Fetcher to allow checking if the Fetcher has been |
| // destroyed. |
| base::WeakPtr<TestTrustedSignalsFetcher> fetcher_alive; |
| }; |
| |
| explicit TestTrustedSignalsFetcher(TestTrustedSignalsCache* cache) |
| : cache_(cache) {} |
| |
| ~TestTrustedSignalsFetcher() override = default; |
| |
| private: |
| void FetchBiddingSignals( |
| DataDecoderManager& data_decoder_manager, |
| network::mojom::URLLoaderFactory* url_loader_factory, |
| FrameTreeNodeId frame_tree_node_id, |
| base::flat_set<std::string> devtools_auction_ids, |
| const url::Origin& main_frame_origin, |
| network::mojom::IPAddressSpace ip_address_space, |
| base::UnguessableToken network_partition_nonce, |
| const url::Origin& script_origin, |
| const GURL& trusted_signals_url, |
| const BiddingAndAuctionServerKey& bidding_and_auction_key, |
| const std::map<int, std::vector<BiddingPartition>>& compression_groups, |
| Callback callback) override { |
| // This class is single use. Make sure a Fetcher isn't used more than |
| // once. |
| EXPECT_FALSE(fetch_started_); |
| fetch_started_ = true; |
| |
| std::map<int, std::vector<FetcherBiddingPartitionArgs>> |
| compression_groups_copy; |
| for (const auto& compression_group : compression_groups) { |
| auto& bidding_partitions_copy = |
| compression_groups_copy.try_emplace(compression_group.first) |
| .first->second; |
| for (const auto& bidding_partition : compression_group.second) { |
| bidding_partitions_copy.emplace_back( |
| bidding_partition.partition_id, |
| *bidding_partition.interest_group_names, *bidding_partition.keys, |
| bidding_partition.additional_params->Clone(), |
| bidding_partition.buyer_tkv_signals |
| ? std::make_optional(*bidding_partition.buyer_tkv_signals) |
| : std::nullopt); |
| } |
| } |
| |
| cache_->OnPendingBiddingSignalsFetch(PendingBiddingSignalsFetch( |
| trusted_signals_url, bidding_and_auction_key, |
| static_cast<network::SharedURLLoaderFactory*>(url_loader_factory), |
| frame_tree_node_id, std::move(devtools_auction_ids), |
| main_frame_origin, ip_address_space, network_partition_nonce, |
| script_origin, std::move(compression_groups_copy), |
| std::move(callback), weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void FetchScoringSignals( |
| DataDecoderManager& data_decoder_manager, |
| network::mojom::URLLoaderFactory* url_loader_factory, |
| FrameTreeNodeId frame_tree_node_id, |
| base::flat_set<std::string> devtools_auction_ids, |
| const url::Origin& main_frame_origin, |
| network::mojom::IPAddressSpace ip_address_space, |
| base::UnguessableToken network_partition_nonce, |
| const url::Origin& script_origin, |
| const GURL& trusted_signals_url, |
| const BiddingAndAuctionServerKey& bidding_and_auction_key, |
| const std::map<int, std::vector<ScoringPartition>>& compression_groups, |
| Callback callback) override { |
| // This class is single use. Make sure a Fetcher isn't used more than |
| // once. |
| EXPECT_FALSE(fetch_started_); |
| fetch_started_ = true; |
| |
| std::map<int, std::vector<FetcherScoringPartitionArgs>> |
| compression_groups_copy; |
| for (const auto& compression_group : compression_groups) { |
| auto& scoring_partitions_copy = |
| compression_groups_copy.try_emplace(compression_group.first) |
| .first->second; |
| for (const auto& scoring_partition : compression_group.second) { |
| scoring_partitions_copy.emplace_back( |
| scoring_partition.partition_id, *scoring_partition.render_url, |
| *scoring_partition.component_render_urls, |
| scoring_partition.additional_params->Clone(), |
| scoring_partition.seller_tkv_signals |
| ? std::make_optional(*scoring_partition.seller_tkv_signals) |
| : std::nullopt); |
| } |
| } |
| |
| cache_->OnPendingScoringSignalsFetch(PendingScoringSignalsFetch( |
| trusted_signals_url, bidding_and_auction_key, |
| reinterpret_cast<network::SharedURLLoaderFactory*>( |
| url_loader_factory), |
| frame_tree_node_id, std::move(devtools_auction_ids), |
| main_frame_origin, ip_address_space, network_partition_nonce, |
| script_origin, std::move(compression_groups_copy), |
| std::move(callback), weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| const raw_ptr<TestTrustedSignalsCache> cache_; |
| bool fetch_started_ = false; |
| base::WeakPtrFactory<TestTrustedSignalsFetcher> weak_ptr_factory_{this}; |
| }; |
| |
| explicit TestTrustedSignalsCache(DataDecoderManager* data_decoder_manager) |
| : TrustedSignalsCacheImpl( |
| data_decoder_manager, |
| // The use of base::Unretained here means that all async calls must |
| // be accounted for before a test completes. The base class always |
| // invokes this |
| // method synchronously, however, and never on destruction. |
| base::BindRepeating(&TestTrustedSignalsCache::GetCoordinatorKey, |
| base::Unretained(this))) {} |
| |
| ~TestTrustedSignalsCache() override { |
| // All pending fetches should have been claimed by calls to |
| // WaitForBiddingSignalsFetch[es](). |
| EXPECT_TRUE(trusted_bidding_signals_fetches_.empty()); |
| EXPECT_TRUE(pending_coordinator_key_callbacks_.empty()); |
| } |
| |
| void set_get_coordinator_key_mode( |
| GetCoordinatorKeyMode get_coordinator_key_mode) { |
| get_coordinator_key_mode_ = get_coordinator_key_mode; |
| } |
| |
| // Callback to handle coordinator key requests by the base |
| // TrustedSignalsCacheImpl class. |
| void GetCoordinatorKey( |
| const url::Origin& scope_origin, |
| const std::optional<url::Origin>& coordinator, |
| base::OnceCallback<void( |
| base::expected<BiddingAndAuctionServerKey, std::string>)> callback) { |
| switch (get_coordinator_key_mode_) { |
| case GetCoordinatorKeyMode::kSync: |
| std::move(callback).Run(CreateServerKey(scope_origin, *coordinator)); |
| break; |
| case GetCoordinatorKeyMode::kAsync: |
| // This should be safe, as the base class guards this callback with a |
| // weak pointer. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), |
| CreateServerKey(scope_origin, *coordinator))); |
| break; |
| case GetCoordinatorKeyMode::kSyncFail: |
| std::move(callback).Run(base::unexpected(std::string(kKeyFetchFailed))); |
| break; |
| case GetCoordinatorKeyMode::kAsyncFail: |
| // This should be safe, as the base class guards this callback with a |
| // weak pointer. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), |
| base::unexpected(std::string(kKeyFetchFailed)))); |
| break; |
| case GetCoordinatorKeyMode::kStashCallback: |
| pending_coordinator_key_callbacks_.emplace_back(base::BindOnce( |
| std::move(callback), CreateServerKey(scope_origin, *coordinator))); |
| if (wait_for_coordinator_key_callback_run_loop_) { |
| wait_for_coordinator_key_callback_run_loop_->Quit(); |
| } |
| } |
| } |
| |
| // Allows invoking a callback asynchronously to be guarded by |
| // `weak_ptr_factory_`. |
| void InvokeCallback(base::OnceClosure callback) { std::move(callback).Run(); } |
| |
| // Waits for the next attempt to retrieve a coordinator key, and returns the |
| // passed in callback with the return value bound to the result of |
| // CreateServerKey(). The GetCoordinatorKeyMode must be kStashCallback. |
| base::OnceClosure WaitForCoordinatorKeyCallback() { |
| CHECK_EQ(get_coordinator_key_mode_, GetCoordinatorKeyMode::kStashCallback); |
| if (pending_coordinator_key_callbacks_.empty()) { |
| wait_for_coordinator_key_callback_run_loop_ = |
| std::make_unique<base::RunLoop>(); |
| wait_for_coordinator_key_callback_run_loop_->Run(); |
| wait_for_coordinator_key_callback_run_loop_.reset(); |
| } |
| |
| auto out = std::move(pending_coordinator_key_callbacks_.front()); |
| pending_coordinator_key_callbacks_.pop_front(); |
| return out; |
| } |
| |
| // Waits until there have been `num_fetches` fetches whose |
| // FetchBiddingSignals() method has been invoked and returns them all, |
| // clearing the list of pending fetches. EXPECTs that number is not exceeded. |
| std::vector<TestTrustedSignalsFetcher::PendingBiddingSignalsFetch> |
| WaitForBiddingSignalsFetches(size_t num_fetches) { |
| DCHECK(!run_loop_); |
| while (trusted_bidding_signals_fetches_.size() < num_fetches) { |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| run_loop_.reset(); |
| } |
| EXPECT_EQ(num_fetches, trusted_bidding_signals_fetches_.size()); |
| std::vector<TestTrustedSignalsFetcher::PendingBiddingSignalsFetch> out; |
| std::swap(out, trusted_bidding_signals_fetches_); |
| return out; |
| } |
| |
| // Wrapper around WaitForBiddingSignalsFetches() that waits for a single fetch |
| // and returns only it. Expects there to be at most one fetch. |
| TestTrustedSignalsFetcher::PendingBiddingSignalsFetch |
| WaitForBiddingSignalsFetch() { |
| return std::move(WaitForBiddingSignalsFetches(1).at(0)); |
| } |
| |
| // Waits until there have been `num_fetches` fetches whose |
| // FetchScoringSignals() method has been invoked and returns them all, |
| // clearing the list of pending fetches. EXPECTs that number is not exceeded. |
| std::vector<TestTrustedSignalsFetcher::PendingScoringSignalsFetch> |
| WaitForScoringSignalsFetches(size_t num_fetches) { |
| DCHECK(!run_loop_); |
| while (trusted_scoring_signals_fetches_.size() < num_fetches) { |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| run_loop_.reset(); |
| } |
| EXPECT_EQ(num_fetches, trusted_scoring_signals_fetches_.size()); |
| std::vector<TestTrustedSignalsFetcher::PendingScoringSignalsFetch> out; |
| std::swap(out, trusted_scoring_signals_fetches_); |
| return out; |
| } |
| |
| // Wrapper around WaitForScoringSignalsFetches() that waits for a single fetch |
| // and returns only it. Expects there to be at most one fetch. |
| TestTrustedSignalsFetcher::PendingScoringSignalsFetch |
| WaitForScoringSignalsFetch() { |
| return std::move(WaitForScoringSignalsFetches(1).at(0)); |
| } |
| |
| size_t num_pending_fetches() const { |
| return trusted_bidding_signals_fetches_.size() + |
| trusted_scoring_signals_fetches_.size(); |
| } |
| |
| private: |
| std::unique_ptr<TrustedSignalsFetcher> CreateFetcher() override { |
| return std::make_unique<TestTrustedSignalsFetcher>(this); |
| } |
| |
| void OnPendingBiddingSignalsFetch( |
| TestTrustedSignalsFetcher::PendingBiddingSignalsFetch fetch) { |
| trusted_bidding_signals_fetches_.emplace_back(std::move(fetch)); |
| if (run_loop_) { |
| run_loop_->Quit(); |
| } |
| } |
| |
| void OnPendingScoringSignalsFetch( |
| TestTrustedSignalsFetcher::PendingScoringSignalsFetch fetch) { |
| trusted_scoring_signals_fetches_.emplace_back(std::move(fetch)); |
| if (run_loop_) { |
| run_loop_->Quit(); |
| } |
| } |
| |
| std::unique_ptr<base::RunLoop> run_loop_; |
| |
| std::list<base::OnceClosure> pending_coordinator_key_callbacks_; |
| std::unique_ptr<base::RunLoop> wait_for_coordinator_key_callback_run_loop_; |
| |
| std::vector<TestTrustedSignalsFetcher::PendingBiddingSignalsFetch> |
| trusted_bidding_signals_fetches_; |
| std::vector<TestTrustedSignalsFetcher::PendingScoringSignalsFetch> |
| trusted_scoring_signals_fetches_; |
| |
| GetCoordinatorKeyMode get_coordinator_key_mode_ = |
| GetCoordinatorKeyMode::kSync; |
| }; |
| |
| // Validates that `partition` has a single partition corresponding to the |
| // BiddingParams in `params`. |
| void ValidateFetchParamsForPartition( |
| const FetcherBiddingPartitionArgs& partition, |
| const BiddingParams& params, |
| int expected_partition_id) { |
| EXPECT_THAT(partition.interest_group_names, |
| testing::ElementsAreArray(params.interest_group_names)); |
| if (!params.trusted_bidding_signals_keys) { |
| EXPECT_THAT(partition.keys, testing::ElementsAre()); |
| } else { |
| EXPECT_THAT(partition.keys, testing::UnorderedElementsAreArray( |
| *params.trusted_bidding_signals_keys)); |
| } |
| EXPECT_EQ(partition.additional_params, params.additional_params); |
| EXPECT_EQ(partition.partition_id, expected_partition_id); |
| EXPECT_EQ(partition.buyer_tkv_signals, params.buyer_tkv_signals); |
| } |
| |
| // Validates that `partition` has a single partition corresponding to the |
| // ScoringParams in `params`. |
| void ValidateFetchParamsForPartition( |
| const FetcherScoringPartitionArgs& partition, |
| const ScoringParams& params, |
| int expected_partition_id) { |
| EXPECT_EQ(partition.render_url, params.render_url); |
| EXPECT_THAT(partition.component_render_urls, |
| testing::ElementsAreArray(params.component_render_urls)); |
| EXPECT_EQ(partition.additional_params, params.additional_params); |
| EXPECT_EQ(partition.partition_id, expected_partition_id); |
| EXPECT_EQ(partition.seller_tkv_signals, params.seller_tkv_signals); |
| } |
| |
| // Validates that `partitions` has a single partition corresponding to the |
| // params in `params`. `ParamType` is expected to be BiddingParams or |
| // ScoringParams, and `FetcherPartitionType` one of |
| // TrustedSignalsFetcher::BiddingPartition or |
| // TrustedSignalsFetcher::ScoringPartition. |
| template <typename ParamType, typename FetcherPartitionType> |
| void ValidateFetchParamsForPartitions( |
| const std::vector<FetcherPartitionType>& partitions, |
| const ParamType& params, |
| int expected_partition_id) { |
| ASSERT_EQ(partitions.size(), 1u); |
| ValidateFetchParamsForPartition(partitions.at(0), params, |
| expected_partition_id); |
| } |
| |
| // Verifies that all fields of `trusted_signals_fetch` exactly match `params` |
| // and the provided IDs. Doesn't handle the case that that multiple fetches were |
| // merged into a single fetch. Note that `compression_group_id` is never exposed |
| // externally by the TrustedSignalsCache API nor passed in, so relies on |
| // information about the internal logic of the cache to provide the expected |
| // value for. |
| // |
| // `ParamType` is expected to be BiddingParams or ScoringParams, and |
| // `FetcherFetchType` one of |
| // TestTrustedSignalsFetcher::PendingBiddingSignalsFetch or |
| // TestTrustedSignalsFetcher::PendingScoringSignalsFetch. |
| template <typename ParamType, typename FetcherFetchType> |
| void ValidateFetchParams(const FetcherFetchType& fetch, |
| const ParamType& params, |
| int expected_compression_group_id, |
| int expected_partition_id) { |
| EXPECT_EQ(fetch.url_loader_factory, params.url_loader_factory); |
| EXPECT_EQ(fetch.frame_tree_node_id, params.frame_tree_node_id); |
| EXPECT_THAT(fetch.devtools_auction_ids, |
| testing::ElementsAreArray(params.devtools_auction_ids)); |
| EXPECT_EQ(fetch.main_frame_origin, params.main_frame_origin); |
| EXPECT_EQ(fetch.ip_address_space, params.ip_address_space); |
| EXPECT_EQ(fetch.trusted_signals_url, params.trusted_signals_url); |
| EXPECT_EQ(fetch.script_origin, params.script_origin); |
| auto expected_key = CreateServerKey( |
| url::Origin::Create(params.trusted_signals_url), params.coordinator); |
| EXPECT_EQ(fetch.bidding_and_auction_key.key, expected_key.key); |
| EXPECT_EQ(fetch.bidding_and_auction_key.id, expected_key.id); |
| ASSERT_EQ(fetch.compression_groups.size(), 1u); |
| EXPECT_EQ(fetch.compression_groups.begin()->first, |
| expected_compression_group_id); |
| |
| ValidateFetchParamsForPartitions(fetch.compression_groups.begin()->second, |
| params, expected_partition_id); |
| } |
| |
| TrustedSignalsFetcher::CompressionGroupResult CreateCompressionGroupResult( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme compression_scheme = |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| std::string_view body = kSuccessBody, |
| base::TimeDelta ttl = base::Days(1)) { |
| TrustedSignalsFetcher::CompressionGroupResult result; |
| result.compression_group_data = |
| base::Value::BlobStorage(body.begin(), body.end()); |
| result.compression_scheme = compression_scheme; |
| result.ttl = ttl; |
| return result; |
| } |
| |
| TrustedSignalsFetcher::CompressionGroupResultMap |
| CreateCompressionGroupResultMap( |
| int compression_group_id, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme compression_scheme = |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| std::string_view body = kSuccessBody, |
| base::TimeDelta ttl = base::Days(1)) { |
| TrustedSignalsFetcher::CompressionGroupResultMap map; |
| map[compression_group_id] = |
| CreateCompressionGroupResult(compression_scheme, body, ttl); |
| return map; |
| } |
| |
| // Respond to the next fetch with a generic successful body. Expects only one |
| // compression group. Uses a template so it can handle both |
| // PendingBiddingSignalsFetches and PendingScoringSignalsFetches. |
| template <class PendingSignalsFetch> |
| void RespondToFetchWithSuccess( |
| PendingSignalsFetch& trusted_signals_fetch, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme compression_scheme = |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| std::string_view body = kSuccessBody, |
| base::TimeDelta ttl = base::Days(1)) { |
| // Shouldn't be calling this after the fetcher was destroyed. |
| ASSERT_TRUE(trusted_signals_fetch.fetcher_alive); |
| |
| // Method only supports a single compression group. |
| ASSERT_EQ(trusted_signals_fetch.compression_groups.size(), 1u); |
| |
| ASSERT_TRUE(trusted_signals_fetch.callback); |
| std::move(trusted_signals_fetch.callback) |
| .Run(CreateCompressionGroupResultMap( |
| trusted_signals_fetch.compression_groups.begin()->first, |
| compression_scheme, body, ttl)); |
| } |
| |
| // Responds to a two-compression group fetch with two successful responses, with |
| // different parameters. The first uses a gzip with kSuccessBody, and the second |
| // uses brotli with kOtherSuccessBody. Uses a template so it can handle both |
| // PendingBiddingSignalsFetches and PendingScoringSignalsFetches. |
| template <class PendingSignalsFetch> |
| void RespondToTwoCompressionGroupFetchWithSuccess( |
| PendingSignalsFetch& trusted_signals_fetch, |
| base::TimeDelta ttl1 = base::Hours(1), |
| base::TimeDelta ttl2 = base::Hours(1)) { |
| ASSERT_EQ(trusted_signals_fetch.compression_groups.size(), 2u); |
| TrustedSignalsFetcher::CompressionGroupResultMap map; |
| map[trusted_signals_fetch.compression_groups.begin()->first] = |
| CreateCompressionGroupResult( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| kSuccessBody, ttl1); |
| map[std::next(trusted_signals_fetch.compression_groups.begin())->first] = |
| CreateCompressionGroupResult( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody, ttl2); |
| std::move(trusted_signals_fetch.callback).Run(std::move(map)); |
| } |
| |
| // Respond to the next fetch with a generic successful body. Does not care about |
| // number of compression groups, as on error, all groups are failed. Uses a |
| // template so it can handle both PendingBiddingSignalsFetches and |
| // PendingScoringSignalsFetches. |
| template <class PendingSignalsFetch> |
| void RespondToFetchWithError(PendingSignalsFetch& trusted_signals_fetch) { |
| CHECK(trusted_signals_fetch.callback); |
| std::move(trusted_signals_fetch.callback) |
| .Run(base::unexpected(kErrorMessage)); |
| } |
| |
| // Single use auction_worklet::mojom::TrustedSignalsCacheClient. Requests |
| // trusted signals on construction. |
| class TestTrustedSignalsCacheClient |
| : public auction_worklet::mojom::TrustedSignalsCacheClient { |
| public: |
| TestTrustedSignalsCacheClient( |
| const base::UnguessableToken& compression_group_id, |
| mojo::Remote<auction_worklet::mojom::TrustedSignalsCache>& |
| cache_mojo_pipe) |
| : receiver_(this) { |
| cache_mojo_pipe->GetTrustedSignals(compression_group_id, |
| receiver_.BindNewPipeAndPassRemote()); |
| receiver_.set_disconnect_handler( |
| base::BindOnce(&TestTrustedSignalsCacheClient::OnReceiverDisconnected, |
| base::Unretained(this))); |
| } |
| |
| // Overload used by almost all callers, to simplify the call a bit. |
| TestTrustedSignalsCacheClient( |
| TestTrustedSignalsCache::Handle* handle, |
| mojo::Remote<auction_worklet::mojom::TrustedSignalsCache>& |
| cache_mojo_pipe) |
| : TestTrustedSignalsCacheClient(handle->compression_group_token(), |
| cache_mojo_pipe) {} |
| |
| ~TestTrustedSignalsCacheClient() override = default; |
| |
| // Waits for OnSuccess() to be called with the provided arguments. Quits loop |
| // and has an assert failure if OnError() is called instead. |
| void WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme |
| expected_compression_scheme = |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| std::string_view expected_compression_group_data = kSuccessBody) { |
| ASSERT_TRUE(WaitForResult()); |
| EXPECT_EQ(compression_scheme_, expected_compression_scheme); |
| EXPECT_EQ(compression_group_data_, expected_compression_group_data); |
| } |
| |
| // Waits for OnError() to be called with the provided arguments. Quits loop |
| // and has an assert failure if OnSuccess() is called instead. |
| void WaitForError(std::string_view expected_error = kErrorMessage) { |
| ASSERT_FALSE(WaitForResult()); |
| EXPECT_EQ(error_message_, expected_error); |
| } |
| |
| bool has_result() { return run_loop_.AnyQuitCalled(); } |
| |
| // auction_worklet::mojom::TrustedSignalsCacheClient implementation: |
| |
| void OnSuccess(auction_worklet::mojom::TrustedSignalsCompressionScheme |
| compression_scheme, |
| mojo_base::BigBuffer compression_group_data) override { |
| EXPECT_FALSE(compression_group_data_); |
| EXPECT_FALSE(error_message_); |
| |
| compression_scheme_ = compression_scheme; |
| compression_group_data_ = std::string(compression_group_data.begin(), |
| compression_group_data.end()); |
| |
| EXPECT_FALSE(run_loop_.AnyQuitCalled()); |
| run_loop_.Quit(); |
| } |
| |
| void OnError(const std::string& error_message) override { |
| EXPECT_FALSE(compression_group_data_); |
| EXPECT_FALSE(error_message_); |
| |
| error_message_ = error_message; |
| |
| EXPECT_FALSE(run_loop_.AnyQuitCalled()); |
| run_loop_.Quit(); |
| } |
| |
| bool IsReceiverDisconnected() { |
| receiver_.FlushForTesting(); |
| return received_disconnected_; |
| } |
| |
| private: |
| // Waits until OnSuccess() or OnError() has been called, and returns true on |
| // success. |
| bool WaitForResult() { |
| run_loop_.Run(); |
| return compression_group_data_.has_value(); |
| } |
| |
| void OnReceiverDisconnected() { received_disconnected_ = true; } |
| |
| base::RunLoop run_loop_; |
| |
| std::optional<auction_worklet::mojom::TrustedSignalsCompressionScheme> |
| compression_scheme_; |
| // Use a string instead of a vector<uint8_t> for more useful error messages on |
| // failure comparisons. |
| std::optional<std::string> compression_group_data_; |
| |
| std::optional<std::string> error_message_; |
| |
| // True if the receiver has been disconnected. Unlike Remotes, Receivers don't |
| // have an is_connected() method, so have to set a disconnect handler to get |
| // this information. |
| bool received_disconnected_ = false; |
| |
| mojo::Receiver<auction_worklet::mojom::TrustedSignalsCacheClient> receiver_; |
| }; |
| |
| // The expected relationship between two sequential signals requests, if the |
| // second request is made without waiting for the first to start its Fetch. |
| enum class RequestRelation { |
| // Requests cannot share a fetch. |
| kDifferentFetches, |
| // Requests can use different compression groups within a fetch. |
| kDifferentCompressionGroups, |
| // Requests can use different partition within a fetch. |
| kDifferentPartitions, |
| // Requests can use the same partition, but the second request needs to |
| // modify the partition (and thus the fetch) to do so. As a result, if the |
| // first request's fetch has already been started, the second request cannot |
| // reuse it. |
| kSamePartitionModified, |
| // Requests can use the same partition, with the second request not |
| // modifying the partition of the first, which means it can use the same |
| // partition even if the first request already as a second request. |
| kSamePartitionUnmodified, |
| }; |
| |
| template <typename ParamsType> |
| class TrustedSignalsCacheTest : public testing::Test { |
| public: |
| // Test case class shared by a number of tests. Each test makes a request |
| // using `params1` before `params2`. |
| struct TestCase { |
| // Used for documentation + useful output on errors. |
| const char* description; |
| RequestRelation request_relation = RequestRelation::kDifferentFetches; |
| // This is primarily relevant for kDifferentFetches. If there's a single |
| // fetch, there's necessarily a single nonce. Default to true since in some |
| // tests with other RequestRelations, multiple network fetches end up being |
| // made, and they should use the same nonce. |
| bool expect_same_network_partition_nonce = true; |
| ParamsType params1; |
| ParamsType params2; |
| }; |
| |
| TrustedSignalsCacheTest() { CreateCache(); } |
| |
| ~TrustedSignalsCacheTest() override = default; |
| |
| void CreateCache() { |
| trusted_signals_cache_ = |
| std::make_unique<TestTrustedSignalsCache>(&data_decoder_manager_); |
| cache_mojo_pipe_.reset(); |
| other_cache_mojo_pipe_.reset(); |
| // This is a little awkward, but works for both bidders and sellers. |
| cache_mojo_pipe_.Bind( |
| CreateMojoPendingRemoteForOrigin(CreateDefaultParams().script_origin)); |
| } |
| |
| // Creates a pending scoring or bidding TrustedSignalsCache pipe for the given |
| // origin, using the SignalsType corresponding to the current test type. |
| mojo::PendingRemote<auction_worklet::mojom::TrustedSignalsCache> |
| CreateMojoPendingRemoteForOrigin(const url::Origin& script_origin) { |
| if constexpr (std::is_same<ParamsType, BiddingParams>::value) { |
| return trusted_signals_cache_->CreateRemote( |
| TrustedSignalsCacheImpl::SignalsType::kBidding, script_origin); |
| } else { |
| return trusted_signals_cache_->CreateRemote( |
| TrustedSignalsCacheImpl::SignalsType::kScoring, script_origin); |
| } |
| } |
| |
| // If `script_origin` matches the default origin, returns `cache_mojo_pipe_`. |
| // Otherwise, creates a new pipe for `script_origin`. Unconditionally destroys |
| // any previous pipe created by this method. Primarily used in the case of a |
| // second set of parameters with RequestRelation::kDifferentFetches, which |
| // sometimes don't share an origin. |
| mojo::Remote<auction_worklet::mojom::TrustedSignalsCache>& |
| CreateOrGetMojoPipeGivenParams(const ParamsType& params) { |
| other_cache_mojo_pipe_.reset(); |
| const url::Origin& origin = params.script_origin; |
| if (origin == CreateDefaultParams().script_origin) { |
| return cache_mojo_pipe_; |
| } |
| other_cache_mojo_pipe_.Bind(CreateMojoPendingRemoteForOrigin(origin)); |
| return other_cache_mojo_pipe_; |
| } |
| |
| // Waits for the next `num_fetches` |
| // TestTrustedSignalsFetcher::PendingBiddingSignalsFetches or |
| // TestTrustedSignalsFetcher::PendingScoringSignalsFetches, depending on |
| // ParamsType. |
| auto WaitForSignalsFetches(int num_fetches) { |
| if constexpr (std::is_same<ParamsType, BiddingParams>::value) { |
| return trusted_signals_cache_->WaitForBiddingSignalsFetches(num_fetches); |
| } |
| if constexpr (std::is_same<ParamsType, ScoringParams>::value) { |
| return trusted_signals_cache_->WaitForScoringSignalsFetches(num_fetches); |
| } |
| } |
| |
| // Waits for the next TestTrustedSignalsFetcher::PendingBiddingSignalsFetch or |
| // TestTrustedSignalsFetcher::PendingScoringSignalsFetch, depending on |
| // ParamsType. |
| auto WaitForSignalsFetch() { |
| if constexpr (std::is_same<ParamsType, BiddingParams>::value) { |
| return trusted_signals_cache_->WaitForBiddingSignalsFetch(); |
| } |
| if constexpr (std::is_same<ParamsType, ScoringParams>::value) { |
| return trusted_signals_cache_->WaitForScoringSignalsFetch(); |
| } |
| } |
| |
| // Separate methods to create default bidding and scoring params, for the few |
| // tests that need both. |
| |
| BiddingParams CreateDefaultBiddingParams() const { |
| BiddingParams out; |
| out.url_loader_factory = url_loader_factory_; |
| out.frame_tree_node_id = kFrameTreeNodeId; |
| out.main_frame_origin = kMainFrameOrigin; |
| out.ip_address_space = network::mojom::IPAddressSpace::kPublic; |
| out.script_origin = kBidder; |
| out.interest_group_names = {kInterestGroupName}; |
| out.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kCompatibilityMode; |
| out.joining_origin = kJoiningOrigin; |
| out.trusted_signals_url = kTrustedBiddingSignalsUrl; |
| out.coordinator = kCoordinator; |
| out.trusted_bidding_signals_keys = {{"key1", "key2"}}; |
| return out; |
| } |
| |
| ScoringParams CreateDefaultScoringParams() const { |
| ScoringParams out; |
| out.url_loader_factory = url_loader_factory_; |
| out.frame_tree_node_id = kFrameTreeNodeId; |
| out.main_frame_origin = kMainFrameOrigin; |
| out.ip_address_space = network::mojom::IPAddressSpace::kPublic; |
| out.script_origin = kSeller; |
| out.trusted_signals_url = kTrustedScoringSignalsUrl; |
| out.coordinator = kCoordinator; |
| out.interest_group_owner = kBidder; |
| out.joining_origin = kJoiningOrigin; |
| out.render_url = kRenderUrl; |
| out.component_render_urls = kComponentRenderUrls; |
| return out; |
| } |
| |
| // Creates the default parameters of ParamsType. |
| ParamsType CreateDefaultParams() const { |
| if constexpr (std::is_same<ParamsType, BiddingParams>::value) { |
| return CreateDefaultBiddingParams(); |
| } |
| if constexpr (std::is_same<ParamsType, ScoringParams>::value) { |
| return CreateDefaultScoringParams(); |
| } |
| } |
| |
| TestCase CreateDefaultTestCase() { |
| TestCase out; |
| out.params1 = CreateDefaultParams(); |
| out.params2 = CreateDefaultParams(); |
| out.params2.devtools_auction_ids = {"devtools_auction_id2"}; |
| |
| return out; |
| } |
| |
| // Returns a shared set of test cases used by a number of different tests. |
| std::vector<TestCase> CreateTestCases() { |
| std::vector<TestCase> out; |
| |
| if constexpr (std::is_same<ParamsType, BiddingParams>::value) { |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different main frame origins"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.main_frame_origin = |
| url::Origin::Create(GURL("https://other.origin.test/")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different bidders"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().expect_same_network_partition_nonce = false; |
| out.back().params2.script_origin = |
| url::Origin::Create(GURL("https://other.bidder.test/")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different interest group names"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.interest_group_names = {"other interest group"}; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different joining origins"; |
| out.back().request_relation = |
| RequestRelation::kDifferentCompressionGroups; |
| out.back().params2.joining_origin = |
| url::Origin::Create(GURL("https://other.joining.origin.test")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different trusted bidding signals URLs"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().expect_same_network_partition_nonce = false; |
| out.back().params2.trusted_signals_url = |
| GURL("https://other.bidder.test/signals"); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "First request has no keys"; |
| out.back().request_relation = RequestRelation::kSamePartitionModified; |
| out.back().params1.trusted_bidding_signals_keys.reset(); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Second request has no keys"; |
| out.back().request_relation = RequestRelation::kSamePartitionUnmodified; |
| out.back().params2.trusted_bidding_signals_keys.reset(); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "First request's keys are a subset of the second request's"; |
| out.back().request_relation = RequestRelation::kSamePartitionModified; |
| out.back().params2.trusted_bidding_signals_keys->emplace_back( |
| "other key"); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Second request's keys are a subset of the first request's"; |
| out.back().request_relation = RequestRelation::kSamePartitionUnmodified; |
| out.back().params2.trusted_bidding_signals_keys->erase( |
| out.back().params2.trusted_bidding_signals_keys->begin()); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have complete distinct keys"; |
| out.back().request_relation = RequestRelation::kSamePartitionModified; |
| out.back().params2.trusted_bidding_signals_keys = {{"other key"}}; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have different `additional_params`"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.additional_params.Set("additional", "param"); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Requests have one null and one valid buyer_tkv_signals"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.buyer_tkv_signals = "signals"; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have two different buyer_tkv_signals"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params1.buyer_tkv_signals = "signals1"; |
| out.back().params2.buyer_tkv_signals = "signals2"; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have same buyer_tkv_signals"; |
| out.back().request_relation = RequestRelation::kSamePartitionUnmodified; |
| out.back().params1.buyer_tkv_signals = "signals"; |
| out.back().params2.buyer_tkv_signals = "signals"; |
| |
| // Group-by-origin tests. |
| |
| // Same interest group name is unlikely when other fields don't match, but |
| // best to test it. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Group-by-origin: First request group-by-origin"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| |
| // Same interest group name is unlikely when other fields don't match, but |
| // best to test it. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Group-by-origin: Second request group-by-origin"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Group-by-origin: Different interest group names"; |
| out.back().request_relation = RequestRelation::kSamePartitionModified; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.interest_group_names = {"other interest group"}; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Group-by-origin: Different keys."; |
| out.back().request_relation = RequestRelation::kSamePartitionModified; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.trusted_bidding_signals_keys = {{"other key"}}; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Group-by-origin: Different keys and interest group names."; |
| out.back().request_relation = RequestRelation::kSamePartitionModified; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.interest_group_names = {"other interest group"}; |
| out.back().params2.trusted_bidding_signals_keys = {{"other key"}}; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Group-by-origin: Different main frame origins"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.main_frame_origin = |
| url::Origin::Create(GURL("https://other.origin.test/")); |
| |
| // It would be unusual to have the same IG with different joining origins, |
| // since one would overwrite the other, but if it does happen, the |
| // requests should use different compression groups. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Group-by-origin: Different joining origin."; |
| out.back().request_relation = |
| RequestRelation::kDifferentCompressionGroups; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.joining_origin = |
| url::Origin::Create(GURL("https://other.joining.origin.test")); |
| |
| // Like above test, but the more common case of different IGs with |
| // different joining origins. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Group-by-origin: Different joining origin, different IGs."; |
| out.back().request_relation = |
| RequestRelation::kDifferentCompressionGroups; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.interest_group_names = {"group2"}; |
| out.back().params2.joining_origin = |
| url::Origin::Create(GURL("https://other.joining.origin.test")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Group-by-origin: Requests have one null and one valid " |
| "buyer_tkv_signals"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.buyer_tkv_signals = "signals"; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Group-by-origin: Requests have two different buyer_tkv_signals"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params1.buyer_tkv_signals = "signals1"; |
| out.back().params2.buyer_tkv_signals = "signals2"; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Group-by-origin: Requests have same buyer_tkv_signals"; |
| out.back().request_relation = RequestRelation::kSamePartitionUnmodified; |
| out.back().params1.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params2.execution_mode = |
| blink::mojom::InterestGroup_ExecutionMode::kGroupedByOriginMode; |
| out.back().params1.buyer_tkv_signals = "signals"; |
| out.back().params2.buyer_tkv_signals = "signals"; |
| |
| // Different coordinators. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have different coordinators."; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.coordinator = |
| url::Origin::Create(GURL("https://other.coordinator.test")); |
| |
| // Different IP address spaces. Nonce is shared, because merging nonces in |
| // the case that a more local and less local frame are running auctions at |
| // the same time would only leak data to hosts on the more-local network, |
| // the leak doesn't include much data, and the situation is very unlikely |
| // to occur in practice. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different IP address spaces"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.ip_address_space = |
| network::mojom::IPAddressSpace::kLoopback; |
| } |
| |
| if constexpr (std::is_same<ParamsType, ScoringParams>::value) { |
| // Note that no ScoringParams case currently results in |
| // RequestRelation::kSamePartitionModified. |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different main frame origins"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.main_frame_origin = |
| url::Origin::Create(GURL("https://other.origin.test/")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different sellers"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().expect_same_network_partition_nonce = false; |
| out.back().params2.script_origin = |
| url::Origin::Create(GURL("https://other.seller.test/")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different trusted scoring signals URLs"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().expect_same_network_partition_nonce = false; |
| out.back().params2.trusted_signals_url = |
| GURL("https://seller.test/signals2"); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different interest group owners"; |
| out.back().request_relation = |
| RequestRelation::kDifferentCompressionGroups; |
| out.back().params2.interest_group_owner = |
| url::Origin::Create(GURL("https://other.bidder.test/")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different joining origins"; |
| out.back().request_relation = |
| RequestRelation::kDifferentCompressionGroups; |
| out.back().params2.joining_origin = |
| url::Origin::Create(GURL("https://other.joining.origin.test")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different render URLs"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.render_url = GURL("https://other.render.test/foo"); |
| |
| // Currently only exact matches of all URLs are results in reuse. Could do |
| // better, but it would require more complicated searching routine, and |
| // either a multimap potentially searching through multiple entries or |
| // std::map and a willingness to throw away entries, like with bidding |
| // signals. It's not clear if either approach is worth doing. |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "First request's component render URLs are a subset of the second " |
| "request's"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.component_render_urls.emplace_back( |
| GURL("https://component3.test/d")); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Second request's component render URLs are a subset of the first " |
| "request's"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.component_render_urls.erase( |
| out.back().params2.component_render_urls.begin()); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have completely distinct keys"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.component_render_urls = { |
| GURL("https://component3.test/d")}; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have different `additional_params`"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.additional_params.Set("additional", "param"); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = |
| "Requests have one null and one valid seller_tkv_signals"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params2.seller_tkv_signals = "signals"; |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have two different seller_tkv_signals"; |
| out.back().request_relation = RequestRelation::kDifferentPartitions; |
| out.back().params1.seller_tkv_signals = "signals1"; |
| out.back().params2.seller_tkv_signals = "signals2"; |
| |
| // Different coordinators. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Requests have different coordinators."; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.coordinator = |
| url::Origin::Create(GURL("https://other.coordinator.test")); |
| |
| // Different IP address spaces. Nonce is shared, because merging nonces in |
| // the case that a more local and less local frame are running auctions at |
| // the same time would only leak data to hosts on the more-local network, |
| // the leak doesn't include much data, and the situation is very unlikely |
| // to occur in practice. |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different IP address spaces"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.ip_address_space = |
| network::mojom::IPAddressSpace::kLoopback; |
| } |
| |
| // Cases shared by bidder and seller tests. |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different SharedURLLoaderFactories"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.url_loader_factory = |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| /*factory_ptr=*/nullptr); |
| |
| out.emplace_back(CreateDefaultTestCase()); |
| out.back().description = "Different FrameTreeNodeIds"; |
| out.back().request_relation = RequestRelation::kDifferentFetches; |
| out.back().params2.frame_tree_node_id = FrameTreeNodeId(2); |
| |
| return out; |
| } |
| |
| // Create set of merged bidding parameters. Useful to use with |
| // ValidateFetchParams() when two requests should be merged into a single |
| // partition. |
| BiddingParams CreateMergedParams(const BiddingParams& bidding_params1, |
| const BiddingParams& bidding_params2) { |
| // In order to merge two sets of params, only `interest_group_names` and |
| // `trusted_bidding_signals_keys` may be different. |
| EXPECT_EQ(bidding_params1.url_loader_factory, |
| bidding_params2.url_loader_factory); |
| EXPECT_EQ(bidding_params1.frame_tree_node_id, |
| bidding_params2.frame_tree_node_id); |
| EXPECT_EQ(bidding_params1.main_frame_origin, |
| bidding_params2.main_frame_origin); |
| EXPECT_EQ(bidding_params1.ip_address_space, |
| bidding_params2.ip_address_space); |
| EXPECT_EQ(bidding_params1.script_origin, bidding_params2.script_origin); |
| EXPECT_EQ(bidding_params1.execution_mode, bidding_params2.execution_mode); |
| EXPECT_EQ(bidding_params1.joining_origin, bidding_params2.joining_origin); |
| EXPECT_EQ(bidding_params1.trusted_signals_url, |
| bidding_params2.trusted_signals_url); |
| EXPECT_EQ(bidding_params1.coordinator, bidding_params2.coordinator); |
| EXPECT_EQ(bidding_params1.additional_params, |
| bidding_params2.additional_params); |
| EXPECT_EQ(bidding_params1.buyer_tkv_signals, |
| bidding_params2.buyer_tkv_signals); |
| |
| BiddingParams merged_bidding_params{ |
| bidding_params1.url_loader_factory, |
| bidding_params1.frame_tree_node_id, |
| bidding_params1.devtools_auction_ids, |
| bidding_params1.main_frame_origin, |
| bidding_params1.ip_address_space, |
| bidding_params1.script_origin, |
| bidding_params1.interest_group_names, |
| bidding_params1.execution_mode, |
| bidding_params1.joining_origin, |
| bidding_params1.trusted_signals_url, |
| bidding_params1.coordinator, |
| bidding_params1.trusted_bidding_signals_keys, |
| bidding_params1.additional_params.Clone(), |
| bidding_params1.buyer_tkv_signals}; |
| |
| merged_bidding_params.devtools_auction_ids.insert( |
| bidding_params2.devtools_auction_ids.begin(), |
| bidding_params2.devtools_auction_ids.end()); |
| merged_bidding_params.interest_group_names.insert( |
| bidding_params2.interest_group_names.begin(), |
| bidding_params2.interest_group_names.end()); |
| if (bidding_params2.trusted_bidding_signals_keys) { |
| if (!merged_bidding_params.trusted_bidding_signals_keys) { |
| merged_bidding_params.trusted_bidding_signals_keys.emplace(); |
| } |
| for (const auto& key : *bidding_params2.trusted_bidding_signals_keys) { |
| if (!base::Contains(*merged_bidding_params.trusted_bidding_signals_keys, |
| key)) { |
| merged_bidding_params.trusted_bidding_signals_keys->push_back(key); |
| } |
| } |
| } |
| return merged_bidding_params; |
| } |
| |
| // Method to create set of merged scoring parameters. Currently this should |
| // never happen, so adds a failure. Needed for the kSamePartitionUnmodified |
| // case, which never happens for scoring, currently. |
| ScoringParams CreateMergedParams(const ScoringParams& scoring_params1, |
| const ScoringParams& params2) { |
| ADD_FAILURE() << "This should not be reached"; |
| return ScoringParams(); |
| } |
| |
| // Returns a pair of a handle and `partition_id`. This pattern reduces |
| // boilerplate a bit, at the cost of making types at callsites a little less |
| // clear. |
| // |
| // If `start_fetch` is true, calls StartFetch() on the handle. |
| std::pair<std::unique_ptr<TestTrustedSignalsCache::Handle>, int> |
| RequestTrustedSignals(const BiddingParams& bidding_params, |
| bool start_fetch = true) { |
| int partition_id = -1; |
| // There should only be a single name for each request. It's a std::set |
| // solely for the ValidateFetchParams family of methods. |
| CHECK_EQ(1u, bidding_params.interest_group_names.size()); |
| auto handle = trusted_signals_cache_->RequestTrustedBiddingSignals( |
| bidding_params.url_loader_factory, bidding_params.frame_tree_node_id, |
| *bidding_params.devtools_auction_ids.begin(), |
| bidding_params.main_frame_origin, bidding_params.ip_address_space, |
| bidding_params.script_origin, |
| *bidding_params.interest_group_names.begin(), |
| bidding_params.execution_mode, bidding_params.joining_origin, |
| bidding_params.trusted_signals_url, bidding_params.coordinator, |
| bidding_params.trusted_bidding_signals_keys, |
| bidding_params.additional_params.Clone(), |
| bidding_params.buyer_tkv_signals, partition_id); |
| |
| // The call should never fail. |
| CHECK(handle); |
| CHECK(!handle->compression_group_token().is_empty()); |
| CHECK_GE(partition_id, 0); |
| if (start_fetch) { |
| handle->StartFetch(); |
| } |
| |
| return std::pair(std::move(handle), partition_id); |
| } |
| |
| // Same as above, but for scoring signals. |
| std::pair<std::unique_ptr<TestTrustedSignalsCache::Handle>, int> |
| RequestTrustedSignals(const ScoringParams& scoring_params, |
| bool start_fetch = true) { |
| int partition_id = -1; |
| auto handle = trusted_signals_cache_->RequestTrustedScoringSignals( |
| scoring_params.url_loader_factory, scoring_params.frame_tree_node_id, |
| *scoring_params.devtools_auction_ids.begin(), |
| scoring_params.main_frame_origin, scoring_params.ip_address_space, |
| scoring_params.script_origin, scoring_params.trusted_signals_url, |
| scoring_params.coordinator, scoring_params.interest_group_owner, |
| scoring_params.joining_origin, scoring_params.render_url, |
| scoring_params.component_render_urls, |
| scoring_params.additional_params.Clone(), |
| scoring_params.seller_tkv_signals, partition_id); |
| |
| // The call should never fail. |
| CHECK(handle); |
| CHECK(!handle->compression_group_token().is_empty()); |
| CHECK_GE(partition_id, 0); |
| if (start_fetch) { |
| handle->StartFetch(); |
| } |
| |
| return std::pair(std::move(handle), partition_id); |
| } |
| |
| protected: |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| // Defaults used by most tests. |
| |
| static constexpr FrameTreeNodeId kFrameTreeNodeId{1}; |
| const url::Origin kMainFrameOrigin = |
| url::Origin::Create(GURL("https://main.frame.test")); |
| const url::Origin kBidder = url::Origin::Create(GURL("https://bidder.test")); |
| const std::string kInterestGroupName = "group1"; |
| const url::Origin kJoiningOrigin = |
| url::Origin::Create(GURL("https://joining.origin.test")); |
| const GURL kTrustedBiddingSignalsUrl{"https://bidder.test/signals"}; |
| |
| const url::Origin kSeller = url::Origin::Create(GURL("https://seller.test")); |
| const GURL kTrustedScoringSignalsUrl{"https://seller.test/signals"}; |
| const GURL kRenderUrl{"https://render.test/foo"}; |
| const std::vector<GURL> kComponentRenderUrls{ |
| GURL("https://component1.test/a"), GURL("https://component2.test/b")}; |
| |
| const url::Origin kCoordinator = |
| url::Origin::Create(GURL("https://coordinator.test")); |
| |
| // Use a SharedURLLoaderFactory that can't be used to make requests. This is |
| // only used in pointer equality tests. |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_ = |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| /*factory_ptr=*/nullptr); |
| |
| DataDecoderManager data_decoder_manager_; |
| std::unique_ptr<TestTrustedSignalsCache> trusted_signals_cache_; |
| mojo::Remote<auction_worklet::mojom::TrustedSignalsCache> cache_mojo_pipe_; |
| // Some test cases need a second Mojo pipe for use with a second script |
| // origin. This holds such a pipe. See CreateOrGetMojoPipeGivenParams(). Tests |
| // that need more than one such pipe manage the extra pipes themselves. |
| mojo::Remote<auction_worklet::mojom::TrustedSignalsCache> |
| other_cache_mojo_pipe_; |
| }; |
| |
| // Used by gtest to provide clearer names for tests. |
| class SignalsCacheTestNames { |
| public: |
| template <typename T> |
| static std::string GetName(int) { |
| if constexpr (std::is_same<T, BiddingParams>::value) { |
| return "BiddingSignals"; |
| } |
| if constexpr (std::is_same<T, ScoringParams>::value) { |
| return "ScoringSignals"; |
| } |
| } |
| }; |
| |
| using ParamTypes = ::testing::Types<BiddingParams, ScoringParams>; |
| TYPED_TEST_SUITE(TrustedSignalsCacheTest, ParamTypes, SignalsCacheTestNames); |
| |
| // Test the case where a GetTrustedSignals() request is received before the |
| // fetch completes. |
| TYPED_TEST(TrustedSignalsCacheTest, GetBeforeFetchCompletes) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(partition_id, 0); |
| |
| // Wait for creation of the Fetcher before requesting over Mojo. Not needed, |
| // but ensures the events in the test run in a consistent order. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| |
| // Wait for the GetTrustedSignals call to make it to the cache. |
| this->task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(client.has_result()); |
| |
| RespondToFetchWithSuccess(fetch); |
| |
| client.WaitForSuccess(); |
| } |
| |
| // Test the case where a GetTrustedSignals() request is received before the |
| // fetch fails. |
| TYPED_TEST(TrustedSignalsCacheTest, GetBeforeFetchFails) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(partition_id, 0); |
| |
| // Wait for creation of the Fetcher before requesting over Mojo. Not needed, |
| // but ensures the events in the test run in a consistent order. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| |
| // Wait for the GetTrustedSignals call to make it to the cache. |
| this->task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(client.has_result()); |
| |
| RespondToFetchWithError(fetch); |
| client.WaitForError(); |
| } |
| |
| // Test the case where a GetTrustedSignals() request is made after the fetch |
| // completes. |
| TYPED_TEST(TrustedSignalsCacheTest, GetAfterFetchCompletes) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(partition_id, 0); |
| |
| // Wait for the fetch to be observed and respond to it. No need to spin the |
| // message loop, since fetch responses at this layer are passed directly to |
| // the cache, and don't go through Mojo, as the TrustedSignalsFetcher is |
| // entirely mocked out. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| } |
| |
| // Test the case where a GetTrustedSignals() request is made after the fetch |
| // fails. |
| TYPED_TEST(TrustedSignalsCacheTest, GetAfterFetchFails) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(partition_id, 0); |
| |
| // Wait for the fetch to be observed and respond to it. No need to spin the |
| // message loop, since fetch responses at this layer are passed directly to |
| // the cache, and don't go through Mojo, as the TrustedSignalsFetcher is |
| // entirely mocked out. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithError(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForError(); |
| } |
| |
| // Check that fetches are automatically started after kAutoStartDelay has |
| // elapsed. |
| TYPED_TEST(TrustedSignalsCacheTest, AutoStart) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = |
| this->RequestTrustedSignals(params, /*start_fetch=*/false); |
| |
| // Request should only start once `kAutoStartDelay` has elapsed |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kAutoStartDelay - kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| this->task_environment_.FastForwardBy(kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 1u); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| } |
| |
| // Check that fetches are automatically started after kAutoStartDelay, even if a |
| // second request is merged into it. |
| TYPED_TEST(TrustedSignalsCacheTest, AutoStartTwoRequests) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = |
| this->RequestTrustedSignals(params, /*start_fetch=*/false); |
| |
| // After a delay less than `kAutoStartDelay`, create a second request that |
| // matches the first one. The old fetch should be reused. |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kAutoStartDelay - kTinyTime); |
| auto [handle2, partition_id2] = |
| this->RequestTrustedSignals(params, /*start_fetch=*/false); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| |
| // No fetches should have been started. |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| |
| // After exactly `kAutoStartDelay`, the fetch should be started. |
| this->task_environment_.FastForwardBy(kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 1u); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| RespondToFetchWithSuccess(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle1.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| } |
| |
| // Check that manually starting a request that has already automatically started |
| // doesn't cause any issues. |
| TYPED_TEST(TrustedSignalsCacheTest, AutoStartThenManuallyStart) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = |
| this->RequestTrustedSignals(params, /*start_fetch=*/false); |
| |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kAutoStartDelay); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 1u); |
| |
| // This should not cause another fetch to be started, nor cause a crash. |
| handle->StartFetch(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 1u); |
| |
| // Can safely call StartFetch() more than once, and no new fetches should be |
| // started. |
| handle->StartFetch(); |
| handle->StartFetch(); |
| handle->StartFetch(); |
| handle->StartFetch(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 1u); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| } |
| |
| // Check that the auto-start delay passing after a request was manually started |
| // doesn't cause issues. Test 3 cases: Auto start duration passes while Fetch is |
| // live, after Fetch has completed but cache entry is still live, and after |
| // cache entry has been destroyed. |
| TYPED_TEST(TrustedSignalsCacheTest, ManuallyStartThenAutoStart) { |
| enum class TestCase { |
| kAutoStartDuringFetch, |
| kAutoStartAfterFetch, |
| kAutoStartAfterHandleDestroyed, |
| }; |
| |
| for (TestCase test_case : |
| {TestCase::kAutoStartDuringFetch, TestCase::kAutoStartAfterFetch, |
| TestCase::kAutoStartAfterHandleDestroyed}) { |
| SCOPED_TRACE(static_cast<int>(test_case)); |
| |
| // Start with a clean slate for each test. |
| this->CreateCache(); |
| |
| auto params = this->CreateDefaultParams(); |
| // Create request and start the fetch. |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| |
| // Wait for fetch creation. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| |
| if (test_case == TestCase::kAutoStartDuringFetch) { |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kAutoStartDelay); |
| } |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| |
| RespondToFetchWithSuccess(fetch); |
| if (test_case == TestCase::kAutoStartAfterFetch) { |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kAutoStartDelay); |
| } |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| |
| handle.reset(); |
| if (test_case == TestCase::kAutoStartAfterHandleDestroyed) { |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kAutoStartDelay); |
| } |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| } |
| } |
| |
| // Test the case where a Handle is destroyed without ever calling StartFetch() |
| // on it. |
| TYPED_TEST(TrustedSignalsCacheTest, HandleDestroyedWithoutStartingFetch) { |
| enum class TestCase { |
| kCancelBeforeCoordinatorKeyCallback, |
| |
| // Two cases where the Handle is cancelled while waiting on the |
| // GetCoordinatorKeyCallback: |
| // 1) The case where the callback is never invoked |
| // 2) The case where it's invoked after cancellation. |
| kCancelDuringCoordinatorKeyCallback, |
| kCancelDuringCoordinatorKeyCallbackAndInvokeCallback, |
| |
| kCancelAfterCoordinatorKeyCallback, |
| }; |
| |
| for (TestCase test_case : |
| {TestCase::kCancelBeforeCoordinatorKeyCallback, |
| TestCase::kCancelDuringCoordinatorKeyCallback, |
| TestCase::kCancelDuringCoordinatorKeyCallbackAndInvokeCallback, |
| TestCase::kCancelAfterCoordinatorKeyCallback}) { |
| SCOPED_TRACE(static_cast<int>(test_case)); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| this->trusted_signals_cache_->set_get_coordinator_key_mode( |
| TestTrustedSignalsCache::GetCoordinatorKeyMode::kStashCallback); |
| |
| auto [handle, partition_id] = this->RequestTrustedSignals( |
| this->CreateDefaultParams(), /*start_fetch=*/false); |
| base::OnceClosure callback; |
| if (test_case != TestCase::kCancelBeforeCoordinatorKeyCallback) { |
| callback = this->trusted_signals_cache_->WaitForCoordinatorKeyCallback(); |
| if (test_case == TestCase::kCancelAfterCoordinatorKeyCallback) { |
| std::move(callback).Run(); |
| } |
| } |
| |
| // Destroy the handle, after getting a copy of the |
| // `compression_group_token`. |
| base::UnguessableToken compression_group_token = |
| handle->compression_group_token(); |
| handle.reset(); |
| |
| // No fetches should have been started. |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kAutoStartDelay - base::Milliseconds(1)); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| |
| TestTrustedSignalsCacheClient client(compression_group_token, |
| this->cache_mojo_pipe_); |
| client.WaitForError(kRequestCancelledError); |
| |
| if (test_case == |
| TestCase::kCancelDuringCoordinatorKeyCallbackAndInvokeCallback) { |
| // Invoking the GetCoordinatorKeyCallback late should not crash. |
| std::move(callback).Run(); |
| } |
| } |
| } |
| |
| // Test the case where a GetTrustedSignals() request waiting on a fetch when the |
| // Handle is destroyed. |
| TYPED_TEST(TrustedSignalsCacheTest, HandleDestroyedAfterGet) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(partition_id, 0); |
| // Wait for the fetch. |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| // Wait fo the request to hit the cache. |
| this->task_environment_.RunUntilIdle(); |
| |
| handle.reset(); |
| client.WaitForError(kRequestCancelledError); |
| } |
| |
| // Test the case where a GetTrustedSignals() request is made after the Handle |
| // has been destroyed. |
| // |
| // This test covers three cases: |
| // 1) The fetch was never started before the handle was destroyed. |
| // 2) The fetch was started but didn't complete before the handle was destroyed. |
| // 3) The fetch completed before the handle was destroyed. |
| // |
| // In the first two cases, since the fetch never completed, the entry is not |
| // cached, and the request should fail. In the third case, the entry is still |
| // cached, and should succeed. |
| TYPED_TEST(TrustedSignalsCacheTest, GetAfterHandleDestroyed) { |
| enum class TestCase { kFetchNotStarted, kFetchNotCompleted, kFetchSucceeded }; |
| |
| for (auto test_case : |
| {TestCase::kFetchNotStarted, TestCase::kFetchNotCompleted, |
| TestCase::kFetchSucceeded}) { |
| SCOPED_TRACE(static_cast<int>(test_case)); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(partition_id, 0); |
| base::UnguessableToken compression_group_token = |
| handle->compression_group_token(); |
| |
| if (test_case != TestCase::kFetchNotStarted) { |
| // Wait for the fetch to be observed. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| if (test_case == TestCase::kFetchSucceeded) { |
| // Respond to fetch if needed. |
| RespondToFetchWithSuccess(fetch); |
| } |
| } |
| |
| handle.reset(); |
| |
| TestTrustedSignalsCacheClient client(compression_group_token, |
| this->cache_mojo_pipe_); |
| if (test_case == TestCase::kFetchSucceeded) { |
| client.WaitForSuccess(); |
| } else { |
| client.WaitForError(kRequestCancelledError); |
| } |
| } |
| } |
| |
| // Test requesting response bodies with novel keys that did not come from a |
| // Handle. Note that there's no need to test empty UnguessableTokens - the Mojo |
| // serialization code DCHECKs when passed them, and the deserialization code |
| // rejects them. |
| TYPED_TEST(TrustedSignalsCacheTest, GetWithNovelId) { |
| // Novel id with no live cache entries. |
| TestTrustedSignalsCacheClient client1(base::UnguessableToken::Create(), |
| this->cache_mojo_pipe_); |
| client1.WaitForError(kRequestCancelledError); |
| |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| |
| // Novel id with a cache entry with a pending fetch. |
| TestTrustedSignalsCacheClient client2(base::UnguessableToken::Create(), |
| this->cache_mojo_pipe_); |
| client2.WaitForError(kRequestCancelledError); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| |
| // Novel id with a loaded cache entry. |
| TestTrustedSignalsCacheClient client3(base::UnguessableToken::Create(), |
| this->cache_mojo_pipe_); |
| client3.WaitForError(kRequestCancelledError); |
| } |
| |
| // Tests multiple GetTrustedSignals calls for a single request, with one live |
| // handle. Requests are made both before and after the response has been |
| // received. |
| TYPED_TEST(TrustedSignalsCacheTest, GetMultipleTimes) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| |
| // Wait for creation of the Fetcher before requesting over Mojo. Not needed, |
| // but ensures the events in the test run in a consistent order. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| |
| TestTrustedSignalsCacheClient client1(handle.get(), this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client2(handle.get(), this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client3(handle.get(), this->cache_mojo_pipe_); |
| |
| // Wait for the GetTrustedSignals call to make it to the cache. |
| this->task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(client1.has_result()); |
| EXPECT_FALSE(client2.has_result()); |
| EXPECT_FALSE(client3.has_result()); |
| |
| RespondToFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient client4(handle.get(), this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client5(handle.get(), this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client6(handle.get(), this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| client2.WaitForSuccess(); |
| client3.WaitForSuccess(); |
| client4.WaitForSuccess(); |
| client5.WaitForSuccess(); |
| client6.WaitForSuccess(); |
| } |
| |
| // Check that re-requesting trusted bidding with the same arguments returns the |
| // same handle and IDs, when any Handle is still alive. |
| TYPED_TEST(TrustedSignalsCacheTest, ReRequestSignalsReusedHandleAlive) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params); |
| |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| |
| // Destroying the first handle should not cancel the request. This should be |
| // implied by `handle1` and `handle2` being references to the same object as |
| // well. |
| handle1.reset(); |
| |
| // Wait for Fetcher. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| |
| // Create yet another handle, which should again be merged, and destroy the |
| // second handle. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id3); |
| handle2.reset(); |
| |
| // Complete the request. |
| RespondToFetchWithSuccess(fetch); |
| |
| // Create yet another handle, which should again be merged, and destroy the |
| // third handle. |
| auto [handle4, partition_id4] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(handle3->compression_group_token(), |
| handle4->compression_group_token()); |
| EXPECT_EQ(partition_id3, partition_id4); |
| handle3.reset(); |
| |
| // Finally request the response body, which should succeed. |
| TestTrustedSignalsCacheClient client(handle4.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| |
| // No pending fetches should have been created after the first. |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| } |
| |
| // Check that re-requesting trusted bidding with the same arguments returns the |
| // same handle and IDs, when there's no live Handle. |
| TYPED_TEST(TrustedSignalsCacheTest, ReRequestSignalsReusedHandleNotAlive) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params); |
| base::UnguessableToken token = handle1->compression_group_token(); |
| |
| // Wait for Fetcher and complete the request, so the result will be kept alive |
| // when the Handle is destroyed. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| RespondToFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| |
| // Destroying the only Handle should not destroy the underlying data. |
| handle1.reset(); |
| |
| // Re-requesting the data should return the same entry. |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(token, handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| TestTrustedSignalsCacheClient(handle2.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| |
| // Destroying the only Handle should not destroy the underlying data, even if |
| // some time passes, if the time is less than the TTL. |
| handle2.reset(); |
| this->task_environment_.FastForwardBy(base::Minutes(1)); |
| |
| // Re-requesting the data yet again should return the same entry. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(token, handle3->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id3); |
| TestTrustedSignalsCacheClient(handle3.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| } |
| |
| // Check that re-requesting trusted bidding with the same arguments returns the |
| // same handle and IDs. Only starts the fetch after the second request. |
| TYPED_TEST(TrustedSignalsCacheTest, ReRequestSignalsReusedLateStartFetch) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = |
| this->RequestTrustedSignals(params, /*start_fetch=*/false); |
| auto [handle2, partition_id2] = |
| this->RequestTrustedSignals(params, /*start_fetch=*/true); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| |
| // Wait for Fetcher. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| |
| // Complete the request. |
| RespondToFetchWithSuccess(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle1.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| |
| // No pending fetches should have been created after the first. |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| } |
| |
| // Check that re-requesting trusted bidding with the same arguments returns a |
| // different ID, when all Handles have been destroyed and the original fetch did |
| // not complete. |
| TYPED_TEST(TrustedSignalsCacheTest, ReRequestSignalsNotReused) { |
| auto params = this->CreateDefaultParams(); |
| |
| // Create a Handle, create a request for it, destroy the Handle. |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params); |
| base::UnguessableToken compression_group_token1 = |
| handle1->compression_group_token(); |
| TestTrustedSignalsCacheClient client1(handle1.get(), this->cache_mojo_pipe_); |
| handle1.reset(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| |
| // A new request with the same parameters should get a new |
| // `compression_group_id`. |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| base::UnguessableToken compression_group_token2 = |
| handle2->compression_group_token(); |
| EXPECT_NE(compression_group_token1, compression_group_token2); |
| TestTrustedSignalsCacheClient client2(handle2.get(), this->cache_mojo_pipe_); |
| |
| // Wait for fetch request, then destroy the second handle. |
| auto fetch2 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2, params, /*expected_compression_group_id=*/0, |
| partition_id2); |
| handle2.reset(); |
| |
| // A new request with the same parameters should get a new |
| // `compression_group_id`. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params); |
| base::UnguessableToken compression_group_token3 = |
| handle3->compression_group_token(); |
| EXPECT_NE(compression_group_token1, compression_group_token3); |
| EXPECT_NE(compression_group_token2, compression_group_token3); |
| TestTrustedSignalsCacheClient client3(handle3.get(), this->cache_mojo_pipe_); |
| // Wait for another fetch request, send a response, and retrieve it over the |
| // Mojo pipe. |
| auto fetch3 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch3, params, /*expected_compression_group_id=*/0, |
| partition_id3); |
| RespondToFetchWithSuccess(fetch3); |
| |
| // All cache clients but the last should receive errors. |
| client1.WaitForError(kRequestCancelledError); |
| client2.WaitForError(kRequestCancelledError); |
| client3.WaitForSuccess(); |
| } |
| |
| // Test the case where a bidding signals request is made while there's still an |
| // outstanding Handle, but the response has expired. |
| TYPED_TEST(TrustedSignalsCacheTest, OutstandingHandleResponseExpired) { |
| const base::TimeDelta kTtl = base::Minutes(10); |
| |
| auto params = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| kSuccessBody, kTtl); |
| |
| // Wait until just before the response has expired. |
| this->task_environment_.FastForwardBy(kTtl - kTinyTime); |
| |
| // A request for `handle1`'s data should succeed. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| |
| // Re-requesting the data before expiration time should return the same Handle |
| // and partition. |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| |
| // Run until the expiration time. When the time exactly equals the expiration |
| // time, the entry should be considered expired. |
| this->task_environment_.FastForwardBy(kTinyTime); |
| |
| // A request for `handle1`'s data should return the same value as before. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| |
| // Re-request the data. A different handle should be returned, since the old |
| // data has expired. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| |
| // Give a different response for the second fetch. |
| fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id3); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody, kTtl); |
| |
| // A request for `handle3`'s data should return the different data. |
| TestTrustedSignalsCacheClient(handle3.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody); |
| |
| // A request for `handle1`'s data should return the same value as before, even |
| // though it has expired. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| } |
| |
| // Check that bidding signals error responses are not cached beyond the end of |
| // the fetch. |
| TYPED_TEST(TrustedSignalsCacheTest, OutstandingHandleError) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| |
| // Re-requesting the data before the response is received should return the |
| // same Handle and partition. |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| |
| RespondToFetchWithError(fetch); |
| |
| // A request for `handle1`'s data should return the error. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForError(); |
| |
| // Re-request the data. A different handle should be returned, since the error |
| // should not be cached. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| |
| // Give a success response for the second fetch. |
| fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id3); |
| RespondToFetchWithSuccess(fetch); |
| |
| // A request for `handle3`'s data should return a success. |
| TestTrustedSignalsCacheClient(handle3.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| |
| // A request for `handle1`'s data should still return the error. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForError(); |
| } |
| |
| // Check that zero (and negative) TTL bidding signals responses are handled |
| // appropriately. Test both the case the original Handles are kept alive and the |
| // case they're not. |
| TYPED_TEST(TrustedSignalsCacheTest, OutstandingHandleSuccessZeroTTL) { |
| for (bool keep_handles_alive : {false, true}) { |
| for (base::TimeDelta ttl : {base::Seconds(-1), base::Seconds(0)}) { |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| |
| auto params = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| |
| // Re-requesting the data before a response is received should return the |
| // same compression group and partition. |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| kSuccessBody, ttl); |
| |
| // A request for `handle1's` data should succeed. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| |
| base::UnguessableToken handle1_token = handle1->compression_group_token(); |
| if (!keep_handles_alive) { |
| handle1.reset(); |
| handle2.reset(); |
| // If not keeping the Handles alive, the underlying data should be |
| // destroyed, since it's not reusable. |
| EXPECT_EQ(this->trusted_signals_cache_->size_for_testing(), 0u); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 0u); |
| } else { |
| // Otherwise, the data should still be available in the cache. |
| EXPECT_GT(this->trusted_signals_cache_->size_for_testing(), 0u); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| } |
| |
| // Re-request the data. A different Handle should be returned, since the |
| // data should not be cached. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params); |
| EXPECT_NE(handle1_token, handle3->compression_group_token()); |
| |
| // Give a different response for the second fetch. |
| fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id3); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody, ttl); |
| |
| // A request for `handle3`'s data should return the different data. |
| TestTrustedSignalsCacheClient(handle3.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody); |
| |
| TestTrustedSignalsCacheClient client(handle1_token, |
| this->cache_mojo_pipe_); |
| if (keep_handles_alive) { |
| // If `handle1` is still alive, a request for `handle1`'s data should |
| // return the same value as before, even though it has expired. |
| client.WaitForSuccess(); |
| } else { |
| // Otherwise, the data should have been destroyed to free up memory, so |
| // requesting it should fail. |
| client.WaitForError(kRequestCancelledError); |
| } |
| } |
| } |
| } |
| |
| // Test that a cache entry is reusable until it expires. Tests both the case |
| // that handles are kept alive and the case that they're not. |
| TYPED_TEST(TrustedSignalsCacheTest, ReusableUntilExpires) { |
| const base::TimeDelta kTtl = base::Seconds(10); |
| // Time to wait before checking if entry is still accessible. |
| const base::TimeDelta kWaitTime = kTtl / 10; |
| |
| for (bool keep_handles_alive : {false, true}) { |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| |
| auto params = this->CreateDefaultParams(); |
| |
| base::UnguessableToken token; |
| std::vector<std::unique_ptr<TestTrustedSignalsCache::Handle>> handles; |
| for (int i = 0; i < 10; ++i) { |
| this->task_environment_.FastForwardBy(kWaitTime); |
| |
| // For all loop iterations but the first, there should be a single |
| // compression group in the cache. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), |
| i == 0 ? 0u : 1u); |
| |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| if (i == 0) { |
| // Only the first request should trigger a fetch. |
| token = handle->compression_group_token(); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess( |
| fetch, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| kSuccessBody, kTtl); |
| } else { |
| // Others should reuse the compression group from the first request. |
| EXPECT_EQ(token, handle->compression_group_token()); |
| } |
| |
| // A request for the data should succeed. |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| |
| if (keep_handles_alive) { |
| handles.emplace_back(std::move(handle)); |
| } |
| } |
| |
| // This should result in the data finally expiring. |
| this->task_environment_.FastForwardBy(kWaitTime); |
| |
| // A new request for the same data should start a new fetch. |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| EXPECT_NE(handle->compression_group_token(), token); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| // Respond with different data, and make sure it can be retrieved. |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody); |
| } |
| } |
| |
| // Test the case of expiration of two requests that share the same compression |
| // group, but are in different partitions. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| OutstandingHandleResponseExpiredSharedCompressionGroup) { |
| const base::TimeDelta kTtl = base::Minutes(10); |
| |
| auto params1 = this->CreateDefaultParams(); |
| auto params2 = this->CreateDefaultParams(); |
| |
| // Modify `params2` so that the requests share the same compression group but |
| // not the same partition. Need separate bidder and seller code. |
| if constexpr (std::is_same<TypeParam, BiddingParams>::value) { |
| params2.interest_group_names = {"other interest group"}; |
| } |
| if constexpr (std::is_same<TypeParam, ScoringParams>::value) { |
| params2.render_url = GURL("https://render.other.test/"); |
| } |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_NE(partition_id1, partition_id2); |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch.compression_groups.size(), 1u); |
| EXPECT_EQ(fetch.compression_groups.begin()->first, 0); |
| |
| const auto& partitions = fetch.compression_groups.begin()->second; |
| ASSERT_EQ(partitions.size(), 2u); |
| ValidateFetchParamsForPartition(partitions[0], params1, partition_id1); |
| ValidateFetchParamsForPartition(partitions[1], params2, partition_id2); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| kSuccessBody, kTtl); |
| |
| // Wait until just before the response has expired. |
| this->task_environment_.FastForwardBy(kTtl - kTinyTime); |
| |
| // Re-requesting either set of parameters should return the same Handle and |
| // partition as the first requests. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id3); |
| auto [handle4, partition_id4] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle4->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id4); |
| |
| // Run until the expiration time. When the time exactly equals the expiration |
| // time, the entry should be considered expired. |
| this->task_environment_.FastForwardBy(kTinyTime); |
| |
| // Re-request the data for both parameters. A different Handle should be |
| // returned from the original, since the old data has expired. As before, both |
| // requests should share a Handle but have distinct partition IDs. |
| auto [handle5, partition_id5] = this->RequestTrustedSignals(params1); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle5->compression_group_token()); |
| auto [handle6, partition_id6] = this->RequestTrustedSignals(params2); |
| EXPECT_NE(handle2->compression_group_token(), |
| handle6->compression_group_token()); |
| EXPECT_EQ(handle5->compression_group_token(), |
| handle6->compression_group_token()); |
| EXPECT_NE(partition_id5, partition_id6); |
| |
| // Give a different response for the second fetch. |
| fetch = this->WaitForSignalsFetch(); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody, kTtl); |
| |
| // A request for `handle5`'s data should return the second fetch's data. No |
| // need to request the data for `handle6`, since it's the same Handle. |
| TestTrustedSignalsCacheClient(handle5.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kOtherSuccessBody); |
| |
| // A request for `handle1`'s data should return the first fetch's data. No |
| // need to request the data for `handle2`, since it's the same Handle. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| } |
| |
| // Test the case of expiration of two requests that are sent in the same fetch, |
| // but in different compression groups. The requests have different expiration |
| // times. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| OutstandingHandleResponseExpiredDifferentCompressionGroup) { |
| const base::TimeDelta kTtl1 = base::Minutes(5); |
| const base::TimeDelta kTtl2 = base::Minutes(10); |
| |
| auto params1 = this->CreateDefaultParams(); |
| auto params2 = this->CreateDefaultParams(); |
| params2.joining_origin = |
| url::Origin::Create(GURL("https://other.joining.origin.test")); |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch.compression_groups.size(), 2u); |
| |
| // Compression groups are appended in FIFO order. |
| ASSERT_EQ(1u, fetch.compression_groups.count(0)); |
| ValidateFetchParamsForPartitions(fetch.compression_groups.at(0), params1, |
| partition_id1); |
| ASSERT_EQ(1u, fetch.compression_groups.count(1)); |
| ValidateFetchParamsForPartitions(fetch.compression_groups.at(1), params2, |
| partition_id2); |
| |
| // Respond with different results for each compression group. |
| RespondToTwoCompressionGroupFetchWithSuccess(fetch, kTtl1, kTtl2); |
| |
| // Wait until just before the first compression group's data has expired. |
| this->task_environment_.FastForwardBy(kTtl1 - kTinyTime); |
| |
| // Re-request both sets of parameters. The same Handles should be returned. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id3); |
| auto [handle4, partition_id4] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle4->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id4); |
| |
| // Wait until the first compression group's data has expired. |
| this->task_environment_.FastForwardBy(kTinyTime); |
| |
| // Re-request both sets of parameters. The first set of parameters should get |
| // a new handle, and trigger a new fetch. The second set of parameters should |
| // get the same Handle, since it has yet to expire. |
| auto [handle5, partition_id5] = this->RequestTrustedSignals(params1); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle5->compression_group_token()); |
| auto [handle6, partition_id6] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle6->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id6); |
| |
| // Validate there is indeed a new fetch for the first set of parameters, and |
| // provide a response. |
| fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params1, /*expected_compression_group_id=*/0, |
| partition_id5); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kSomeOtherSuccessBody, kTtl2); |
| |
| // Wait until just before the first compression group's data has expired. |
| this->task_environment_.FastForwardBy(kTtl1 - kTinyTime); |
| |
| // Re-request both sets of parameters. The same Handles should be returned as |
| // the last time. |
| auto [handle7, partition_id7] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle5->compression_group_token(), |
| handle7->compression_group_token()); |
| EXPECT_EQ(partition_id5, partition_id7); |
| auto [handle8, partition_id8] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle8->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id8); |
| |
| // Wait until the second compression group's data has expired. |
| this->task_environment_.FastForwardBy(kTinyTime); |
| |
| // Re-request both sets of parameters. This time, only the second set of |
| // parameters should get a new Handle. |
| auto [handle9, partition_id9] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle5->compression_group_token(), |
| handle9->compression_group_token()); |
| EXPECT_EQ(partition_id5, partition_id9); |
| auto [handle10, partition_id10] = this->RequestTrustedSignals(params2); |
| EXPECT_NE(handle2->compression_group_token(), |
| handle10->compression_group_token()); |
| |
| // Validate there is indeed a new fetch for the second set of parameters, and |
| // provide a response. |
| fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params2, /*expected_compression_group_id=*/0, |
| partition_id9); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| kSomeOtherSuccessBody, kTtl2); |
| |
| // Validate the responses for each of the distinct Handles. Even the ones |
| // associated with expired data should still receive success responses, since |
| // data lifetime is scoped to that of the associated Handle. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| TestTrustedSignalsCacheClient(handle2.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient(handle5.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kSomeOtherSuccessBody); |
| TestTrustedSignalsCacheClient(handle10.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| kSomeOtherSuccessBody); |
| } |
| |
| // Test the case where the response has no compression groups. |
| TYPED_TEST(TrustedSignalsCacheTest, NoCompressionGroup) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| |
| // Respond with an empty map with no compression groups. |
| std::move(fetch.callback).Run({}); |
| |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForError("Fetched signals missing compression group 0."); |
| } |
| |
| // Test the case where only information for the wrong compression group is |
| // received. |
| TYPED_TEST(TrustedSignalsCacheTest, WrongCompressionGroup) { |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| |
| // Modify index of the only compression group when generating a response. |
| CHECK(base::Contains(fetch.compression_groups, 0)); |
| auto compression_group_node = fetch.compression_groups.extract(0); |
| compression_group_node.key() = 1; |
| fetch.compression_groups.insert(std::move(compression_group_node)); |
| RespondToFetchWithSuccess(fetch); |
| |
| // A request for `handle3`'s data should return the different data. |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForError("Fetched signals missing compression group 0."); |
| } |
| |
| // Test the case where only one of two compression groups is returned by the |
| // server. Both compression groups should fail. Run two test cases, one with the |
| // first compression group missing, one with the second missing. |
| TYPED_TEST(TrustedSignalsCacheTest, OneCompressionGroupMissing) { |
| for (int missing_group : {0, 1}) { |
| auto params1 = this->CreateDefaultParams(); |
| auto params2 = this->CreateDefaultParams(); |
| params2.joining_origin = |
| url::Origin::Create(GURL("https://other.joining.origin.test")); |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ASSERT_EQ(fetch.compression_groups.size(), 2u); |
| |
| // Remove missing compression group from the request, and generate a valid |
| // response for the other group. |
| ASSERT_TRUE(fetch.compression_groups.erase(missing_group)); |
| RespondToFetchWithSuccess(fetch); |
| |
| std::string expected_error = base::StringPrintf( |
| "Fetched signals missing compression group %i.", missing_group); |
| |
| // Even though the data for only one Handle was missing, both should have |
| // the same error. |
| TestTrustedSignalsCacheClient(handle1.get(), this->cache_mojo_pipe_) |
| .WaitForError(expected_error); |
| TestTrustedSignalsCacheClient(handle2.get(), this->cache_mojo_pipe_) |
| .WaitForError(expected_error); |
| } |
| } |
| |
| // Tests the case where request is made, and then a second request with one |
| // different parameter is issued before any fetch is started. The behavior is |
| // expected to vary based on which parameter is modified. The possibilities are: |
| // |
| // * kDifferentFetches: Different fetches. |
| // |
| // * kDifferentCompressionGroups: Different compression groups within a single |
| // fetch. |
| // |
| // * kDifferentPartitions: Different partitions within the same compression |
| // group. |
| // |
| // * kSamePartitionModified, kSamePartitionUnmodified: Same partition is used. |
| TYPED_TEST(TrustedSignalsCacheTest, DifferentParamsBeforeFetchStart) { |
| for (const auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| const auto& params1 = test_case.params1; |
| const auto& params2 = test_case.params2; |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| switch (test_case.request_relation) { |
| case RequestRelation::kDifferentFetches: { |
| ASSERT_NE(handle1, handle2); |
| ASSERT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| auto fetches = this->WaitForSignalsFetches(2); |
| |
| // fetches are made in FIFO order. |
| ValidateFetchParams(fetches[0], params1, |
| /*expected_compression_group_id=*/0, partition_id1); |
| ValidateFetchParams(fetches[1], params2, |
| /*expected_compression_group_id=*/0, partition_id2); |
| EXPECT_EQ(fetches[0].network_partition_nonce == |
| fetches[1].network_partition_nonce, |
| test_case.expect_same_network_partition_nonce); |
| |
| // Make both requests succeed with different bodies, and check that they |
| // can be read. |
| RespondToFetchWithSuccess(fetches[0]); |
| RespondToFetchWithSuccess( |
| fetches[1], |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client2( |
| handle2.get(), this->CreateOrGetMojoPipeGivenParams(params2)); |
| client1.WaitForSuccess(); |
| client2.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentCompressionGroups: { |
| EXPECT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch.compression_groups.size(), 2u); |
| |
| // Compression groups are appended in FIFO order. |
| ASSERT_EQ(1u, fetch.compression_groups.count(0)); |
| ValidateFetchParamsForPartitions(fetch.compression_groups.at(0), |
| params1, partition_id1); |
| ASSERT_EQ(1u, fetch.compression_groups.count(1)); |
| ValidateFetchParamsForPartitions(fetch.compression_groups.at(1), |
| params2, partition_id2); |
| |
| // Respond with different results for each compression group. |
| RespondToTwoCompressionGroupFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client2(handle2.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| client2.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentPartitions: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_NE(partition_id1, partition_id2); |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch.compression_groups.size(), 1u); |
| EXPECT_EQ(fetch.compression_groups.begin()->first, 0); |
| |
| const auto& partitions = fetch.compression_groups.begin()->second; |
| ASSERT_EQ(partitions.size(), 2u); |
| ValidateFetchParamsForPartition(partitions[0], params1, partition_id1); |
| ValidateFetchParamsForPartition(partitions[1], params2, partition_id2); |
| |
| // Respond with a single response for the partition, and read it - no |
| // need for multiple clients, since the handles are the same. |
| RespondToFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient client(handle1.get(), |
| this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| break; |
| } |
| |
| case RequestRelation::kSamePartitionModified: |
| case RequestRelation::kSamePartitionUnmodified: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| auto merged_params = this->CreateMergedParams(params1, params2); |
| // The fetch exactly match the merged parameters. |
| ValidateFetchParams(fetch, merged_params, |
| /*expected_compression_group_id=*/0, partition_id1); |
| |
| // Respond with a single response for the partition, and read it - no |
| // need for multiple clients, since the handles are the same. |
| RespondToFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient client(handle1.get(), |
| this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Tests the case where request is made, and then after a fetch starts, a second |
| // request with one different parameter is issued. The possible behaviors are: |
| // |
| // * kDifferentFetches, kDifferentCompressionGroups, |
| // kDifferentCompressionGroups, kSamePartitionModified: A new fetch is made. |
| // |
| // * kSamePartitionUnmodified: Old response is reused. |
| TYPED_TEST(TrustedSignalsCacheTest, DifferentParamsAfterFetchStart) { |
| for (const auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| const auto& params1 = test_case.params1; |
| const auto& params2 = test_case.params2; |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto fetch1 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch1, params1, /*expected_compression_group_id=*/0, |
| partition_id1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| switch (test_case.request_relation) { |
| case RequestRelation::kDifferentFetches: |
| case RequestRelation::kDifferentCompressionGroups: |
| case RequestRelation::kDifferentPartitions: |
| case RequestRelation::kSamePartitionModified: { |
| ASSERT_NE(handle1, handle2); |
| ASSERT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| |
| auto fetch2 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2, params2, |
| /*expected_compression_group_id=*/0, partition_id2); |
| EXPECT_EQ( |
| fetch1.network_partition_nonce == fetch2.network_partition_nonce, |
| test_case.expect_same_network_partition_nonce); |
| |
| // Make both requests succeed with different bodies, and check that they |
| // can be read. |
| RespondToFetchWithSuccess(fetch1); |
| RespondToFetchWithSuccess( |
| fetch2, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client2( |
| handle2.get(), this->CreateOrGetMojoPipeGivenParams(params2)); |
| client1.WaitForSuccess(); |
| client2.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kSamePartitionUnmodified: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| |
| // Respond with a single response for the partition, and read it - no |
| // need for multiple clients, since the handles are the same. |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client(handle1.get(), |
| this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Tests the case where request is made, a fetch is made and then completes. The |
| // a second request with one different parameter is issued. The possibilities |
| // are: |
| // |
| // * kDifferentFetches, kDifferentCompressionGroups, |
| // kDifferentCompressionGroups, kSamePartitionModified: A new fetch. |
| // |
| // * kSamePartitionUnmodified: Old response is reused. |
| TYPED_TEST(TrustedSignalsCacheTest, DifferentParamsAfterFetchComplete) { |
| for (const auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| const auto& params1 = test_case.params1; |
| const auto& params2 = test_case.params2; |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto fetch1 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch1, params1, /*expected_compression_group_id=*/0, |
| partition_id1); |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| switch (test_case.request_relation) { |
| case RequestRelation::kDifferentFetches: |
| case RequestRelation::kDifferentCompressionGroups: |
| case RequestRelation::kDifferentPartitions: |
| case RequestRelation::kSamePartitionModified: { |
| ASSERT_NE(handle1, handle2); |
| ASSERT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| |
| auto fetch2 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2, params2, |
| /*expected_compression_group_id=*/0, partition_id2); |
| EXPECT_EQ( |
| fetch1.network_partition_nonce == fetch2.network_partition_nonce, |
| test_case.expect_same_network_partition_nonce); |
| |
| RespondToFetchWithSuccess( |
| fetch2, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient client2( |
| handle2.get(), this->CreateOrGetMojoPipeGivenParams(params2)); |
| client2.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kSamePartitionUnmodified: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| TestTrustedSignalsCacheClient client2(handle2.get(), |
| this->cache_mojo_pipe_); |
| client2.WaitForSuccess(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Tests the case where request is made, and then a second request with one |
| // different parameter is created and canceled before any fetch is started. The |
| // fetch completes, and then the second request is made again. The possibilities |
| // are: |
| // |
| // kDifferentFetches: The requests weren't merged in the first place. Second |
| // fetch is cancelled, and then a new one is made. |
| // |
| // kDifferentCompressionGroups: The requests were merged into single compression |
| // groups in a single fetch. The compression group for the second request should |
| // be removed before the fetch is made, and a new one made. This looks just like |
| // the kDifferentFetch case externally. |
| // |
| // * kDifferentPartitions: Different partitions within the same compression |
| // group. Since lifetimes are managed at the compression group layer, the |
| // partition is not removed when the request is cancelled. Only one fetch is |
| // made. |
| // |
| // * kSamePartitionModified / kSamePartitionUnmodified: Same partition is used. |
| // Only one fetch is made. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| DifferentParamsCancelSecondBeforeFetchStart) { |
| for (auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| auto params1 = std::move(test_case.params1); |
| auto params2 = std::move(test_case.params2); |
| |
| // Don't bother to compare handles here - that's covered by another test. |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| // Cancel the second request immediately, before any fetch is made. |
| handle2.reset(); |
| |
| // In all cases, that should result in a single fetch being made. |
| auto fetch1 = this->WaitForSignalsFetch(); |
| |
| switch (test_case.request_relation) { |
| // Despite these two cases being different internally, they look the same |
| // both to the caller and to the created fetches. |
| case RequestRelation::kDifferentFetches: |
| case RequestRelation::kDifferentCompressionGroups: { |
| // Fetch should not be affected by the first (cancelled) fetch, other |
| // than including its devtools auction ID in the different compression |
| // group case. |
| if (test_case.request_relation == |
| RequestRelation::kDifferentCompressionGroups) { |
| params1.devtools_auction_ids.insert( |
| params2.devtools_auction_ids.begin(), |
| params2.devtools_auction_ids.end()); |
| } |
| ValidateFetchParams(fetch1, params1, |
| /*expected_compression_group_id=*/0, partition_id1); |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| |
| // Make a second request using `params2`. It should result in a new |
| // request. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params2); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| auto fetch3 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch3, params2, |
| /*expected_compression_group_id=*/0, partition_id3); |
| EXPECT_EQ( |
| fetch1.network_partition_nonce == fetch3.network_partition_nonce, |
| test_case.expect_same_network_partition_nonce); |
| RespondToFetchWithSuccess( |
| fetch3, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient client3( |
| handle3.get(), this->CreateOrGetMojoPipeGivenParams(params2)); |
| client3.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentPartitions: { |
| EXPECT_EQ(fetch1.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch1.compression_groups.size(), 1u); |
| EXPECT_EQ(fetch1.compression_groups.begin()->first, 0); |
| EXPECT_THAT(fetch1.devtools_auction_ids, |
| testing::UnorderedElementsAre( |
| *params1.devtools_auction_ids.begin(), |
| *params2.devtools_auction_ids.begin())); |
| |
| const auto& partitions = fetch1.compression_groups.begin()->second; |
| ASSERT_EQ(partitions.size(), 2u); |
| ValidateFetchParamsForPartition(partitions[0], params1, partition_id1); |
| ValidateFetchParamsForPartition(partitions[1], params2, partition_id2); |
| |
| // Respond with a single response for the partition, and read it. |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| |
| // Make a second request using `params2`. It should reuse the response |
| // to the initial request. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_NE(partition_id1, partition_id3); |
| EXPECT_EQ(partition_id2, partition_id3); |
| TestTrustedSignalsCacheClient client3(handle3.get(), |
| this->cache_mojo_pipe_); |
| client3.WaitForSuccess(); |
| break; |
| } |
| |
| case RequestRelation::kSamePartitionModified: |
| case RequestRelation::kSamePartitionUnmodified: { |
| auto merged_params = this->CreateMergedParams(params1, params2); |
| ValidateFetchParams(fetch1, merged_params, |
| /*expected_compression_group_id=*/0, partition_id1); |
| |
| // Respond with a single response for the partition, and read it. |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| |
| // Make a second request using `params2`. It should reuse the response |
| // to the initial request, including the same partition ID. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id3); |
| |
| // For the sake of completeness, read the response again. |
| TestTrustedSignalsCacheClient client3(handle3.get(), |
| this->cache_mojo_pipe_); |
| client3.WaitForSuccess(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Just like the above test, but the first request is cancelled rather than the |
| // second one. This is to test that cancelled the compression group 0 or |
| // partition 0 request doesn't cause issues with the compression group 1 or |
| // partition 1 request. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| DifferentParamsCancelFirstBeforeFetchStart) { |
| for (auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| auto params1 = std::move(test_case.params1); |
| auto params2 = std::move(test_case.params2); |
| |
| // Don't bother to compare handles here - that's covered by another test. |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| // Cancel the first request immediately, before any fetch is made. |
| handle1.reset(); |
| |
| // In all cases, that should result in a single fetch being made. |
| auto fetch1 = this->WaitForSignalsFetch(); |
| |
| switch (test_case.request_relation) { |
| // Despite these two cases being different internally, they look the |
| // same both to the caller and to the created fetches. |
| case RequestRelation::kDifferentFetches: |
| case RequestRelation::kDifferentCompressionGroups: { |
| // Fetch should not be affected by the first (cancelled) fetch, other |
| // than including its devtools auction ID in the different compression |
| // group case. |
| if (test_case.request_relation == |
| RequestRelation::kDifferentCompressionGroups) { |
| params2.devtools_auction_ids.insert( |
| params1.devtools_auction_ids.begin(), |
| params1.devtools_auction_ids.end()); |
| } |
| ValidateFetchParams(fetch1, params2, |
| /*expected_compression_group_id=*/0, partition_id2); |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client1( |
| handle2.get(), this->CreateOrGetMojoPipeGivenParams(params2)); |
| client1.WaitForSuccess(); |
| |
| // Make a second request using `params1`. It should result in a new |
| // request. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params1); |
| auto fetch3 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch3, params1, |
| /*expected_compression_group_id=*/0, partition_id3); |
| EXPECT_EQ( |
| fetch1.network_partition_nonce == fetch3.network_partition_nonce, |
| test_case.expect_same_network_partition_nonce); |
| RespondToFetchWithSuccess( |
| fetch3, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient client3(handle3.get(), |
| this->cache_mojo_pipe_); |
| client3.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentPartitions: { |
| EXPECT_EQ(fetch1.trusted_signals_url, params2.trusted_signals_url); |
| ASSERT_EQ(fetch1.compression_groups.size(), 1u); |
| EXPECT_EQ(fetch1.compression_groups.begin()->first, 0); |
| EXPECT_THAT(fetch1.devtools_auction_ids, |
| testing::UnorderedElementsAre( |
| *params1.devtools_auction_ids.begin(), |
| *params2.devtools_auction_ids.begin())); |
| |
| const auto& partitions = fetch1.compression_groups.begin()->second; |
| ASSERT_EQ(partitions.size(), 2u); |
| ValidateFetchParamsForPartition(partitions[0], params1, partition_id1); |
| ValidateFetchParamsForPartition(partitions[1], params2, partition_id2); |
| |
| // Respond with a single response for the partition, and read it. |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client1(handle2.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| |
| // Make a second request using `params1`. It should reuse the response |
| // to the initial request. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id3); |
| EXPECT_NE(partition_id2, partition_id3); |
| TestTrustedSignalsCacheClient client3(handle3.get(), |
| this->cache_mojo_pipe_); |
| client3.WaitForSuccess(); |
| break; |
| } |
| |
| case RequestRelation::kSamePartitionModified: |
| case RequestRelation::kSamePartitionUnmodified: { |
| auto merged_params = this->CreateMergedParams(params1, params2); |
| ValidateFetchParams(fetch1, merged_params, |
| /*expected_compression_group_id=*/0, partition_id1); |
| |
| // Respond with a single response for the partition, and read it. |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client1(handle2.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| |
| // Make a second request using `params1`. It should reuse the response |
| // to the initial request, including the same partition ID. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id3); |
| |
| // For the sake of completeness, read the response again. |
| TestTrustedSignalsCacheClient client3(handle3.get(), |
| this->cache_mojo_pipe_); |
| client3.WaitForSuccess(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Tests the case where request is made, and then a second request with one |
| // different parameter is issued before any fetch is started. After the fetch |
| // starts the second request is cancelled. Once the fetch from the first request |
| // completes, the second request is made again. The possible behaviors are: |
| // |
| // * kDifferentFetches: Two fetches made, one cancelled, and then a new fetch is |
| // created. |
| // |
| // * kDifferentCompressionGroups: A single fetch is made to handle both |
| // requests. Cancelling the second request throws away its compression group |
| // when the fetch response is received. A new fetch is created when the second |
| // request is issued again. Could do better here, but unclear if it's worth the |
| // investment. |
| // |
| // * kDifferentPartitions: Only one fetch is made, as the lifetime of a |
| // partition is scoped to the lifetime of the compression group. |
| // |
| // * kSamePartitionModified / kSamePartitionUnmodified: Only one fetch is made. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| DifferentParamsCancelSecondAfterFetchStart) { |
| for (const auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| const auto& params1 = test_case.params1; |
| const auto& params2 = test_case.params2; |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| switch (test_case.request_relation) { |
| case RequestRelation::kDifferentFetches: { |
| ASSERT_NE(handle1, handle2); |
| ASSERT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| auto fetches = this->WaitForSignalsFetches(2); |
| |
| // fetches are made in FIFO order. |
| ValidateFetchParams(fetches[0], params1, |
| /*expected_compression_group_id=*/0, partition_id1); |
| ValidateFetchParams(fetches[1], params2, |
| /*expected_compression_group_id=*/0, partition_id2); |
| EXPECT_EQ(fetches[0].network_partition_nonce == |
| fetches[1].network_partition_nonce, |
| test_case.expect_same_network_partition_nonce); |
| |
| // Cancel the second request. Its fetcher should be destroyed. |
| handle2.reset(); |
| EXPECT_FALSE(fetches[1].fetcher_alive); |
| |
| // Reissue second request, which should start a new fetch. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params2); |
| auto fetch3 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch3, params2, |
| /*expected_compression_group_id=*/0, partition_id3); |
| EXPECT_EQ(fetches[1].network_partition_nonce, |
| fetch3.network_partition_nonce); |
| |
| // Make both requests succeed with different bodies, and check that they |
| // can be read. |
| RespondToFetchWithSuccess(fetches[0]); |
| RespondToFetchWithSuccess( |
| fetch3, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client3( |
| handle3.get(), this->CreateOrGetMojoPipeGivenParams(params2)); |
| client1.WaitForSuccess(); |
| client3.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentCompressionGroups: { |
| EXPECT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| auto fetch1 = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch1.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch1.compression_groups.size(), 2u); |
| |
| // Compression groups are appended in FIFO order. |
| ASSERT_EQ(1u, fetch1.compression_groups.count(0)); |
| ValidateFetchParamsForPartitions(fetch1.compression_groups.at(0), |
| params1, partition_id1); |
| ASSERT_EQ(1u, fetch1.compression_groups.count(1)); |
| ValidateFetchParamsForPartitions(fetch1.compression_groups.at(1), |
| params2, partition_id2); |
| |
| // Cancel the second request. The shared fetcher should not be |
| // destroyed. |
| base::UnguessableToken compression_group_token2 = |
| handle2->compression_group_token(); |
| handle2.reset(); |
| EXPECT_TRUE(fetch1.fetcher_alive); |
| |
| // Reissue second request, which should start a new fetch. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params2); |
| EXPECT_NE(handle3->compression_group_token(), |
| handle1->compression_group_token()); |
| EXPECT_NE(handle3->compression_group_token(), compression_group_token2); |
| auto fetch3 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch3, params2, |
| /*expected_compression_group_id=*/0, partition_id3); |
| EXPECT_EQ(fetch1.network_partition_nonce, |
| fetch3.network_partition_nonce); |
| |
| // Respond to requests with 3 different results. `fetch[0]` gets |
| // responses of `kSuccessBody` and `kOtherSuccessBody` for its two |
| // compression groups, and `fetch3` gets a response of |
| // `kSomeOtherSuccessBody` for its single group. Using `handle1` should |
| // provide a body of `kSuccessBody`, and `handle3` should provide a |
| // response of `kSomeOtherSuccessBody`. The other success body should be |
| // thrown out. |
| RespondToTwoCompressionGroupFetchWithSuccess(fetch1); |
| RespondToFetchWithSuccess( |
| fetch3, |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kSomeOtherSuccessBody); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client2(compression_group_token2, |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client3(handle3.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| client2.WaitForError(kRequestCancelledError); |
| client3.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kSomeOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentPartitions: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_NE(partition_id1, partition_id2); |
| auto fetch1 = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch1.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch1.compression_groups.size(), 1u); |
| EXPECT_EQ(fetch1.compression_groups.begin()->first, 0); |
| |
| const auto& partitions = fetch1.compression_groups.begin()->second; |
| ASSERT_EQ(partitions.size(), 2u); |
| ValidateFetchParamsForPartition(partitions[0], params1, partition_id1); |
| ValidateFetchParamsForPartition(partitions[1], params2, partition_id2); |
| |
| // Cancel the second request. The shared fetcher should not be |
| // destroyed. |
| handle2.reset(); |
| EXPECT_TRUE(fetch1.fetcher_alive); |
| |
| // Reissue second request, which should result in the same signals |
| // request ID as the other requests, and the same partition ID as the |
| // second request. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id3); |
| |
| // Respond with a single response for the partition, and read it - no |
| // need for multiple clients, since the handles are the same. |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client(handle1.get(), |
| this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| break; |
| } |
| |
| case RequestRelation::kSamePartitionModified: |
| case RequestRelation::kSamePartitionUnmodified: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| auto fetch1 = this->WaitForSignalsFetch(); |
| |
| auto merged_params = this->CreateMergedParams(params1, params2); |
| ValidateFetchParams(fetch1, merged_params, |
| /*expected_compression_group_id=*/0, partition_id1); |
| |
| // Cancel the second request. The shared fetcher should not be |
| // destroyed. |
| handle2.reset(); |
| EXPECT_TRUE(fetch1.fetcher_alive); |
| |
| // Reissue second request, which should result in the same signals |
| // request ID and partition ID as the other requests. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id3); |
| |
| // Respond with a single request for the partition, and read it - no |
| // need for multiple clients, since the handles are the same. |
| RespondToFetchWithSuccess(fetch1); |
| TestTrustedSignalsCacheClient client(handle1.get(), |
| this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Tests the case where two requests are made and both are cancelled before the |
| // requests starts. No fetches should be made, regardless of whether the two |
| // requests would normally share a fetch or not. |
| TYPED_TEST(TrustedSignalsCacheTest, DifferentParamsCancelBothBeforeFetchStart) { |
| for (const auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| const auto& params1 = test_case.params1; |
| const auto& params2 = test_case.params2; |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| handle1.reset(); |
| handle2.reset(); |
| |
| this->task_environment_.RunUntilIdle(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 0u); |
| } |
| } |
| |
| // Tests the case where two requests are made and both are cancelled after the |
| // fetch(es) start. The fetch(es) should be cancelled. |
| TYPED_TEST(TrustedSignalsCacheTest, DifferentParamsCancelBothAfterFetchStart) { |
| for (const auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| const auto& params1 = test_case.params1; |
| const auto& params2 = test_case.params2; |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| switch (test_case.request_relation) { |
| case RequestRelation::kDifferentFetches: { |
| auto fetches = this->WaitForSignalsFetches(2); |
| handle1.reset(); |
| handle2.reset(); |
| EXPECT_FALSE(fetches[0].fetcher_alive); |
| EXPECT_FALSE(fetches[1].fetcher_alive); |
| break; |
| } |
| |
| case RequestRelation::kDifferentCompressionGroups: |
| case RequestRelation::kDifferentPartitions: |
| case RequestRelation::kSamePartitionModified: |
| case RequestRelation::kSamePartitionUnmodified: { |
| // Don't bother to distinguish these cases - other tests cover the |
| // relations between handles and partitions IDs in this case. |
| auto fetch = this->WaitForSignalsFetch(); |
| handle1.reset(); |
| handle2.reset(); |
| EXPECT_FALSE(fetch.fetcher_alive); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Test the case where the attempt to get the coordinator key completes |
| // asynchronously. |
| TYPED_TEST(TrustedSignalsCacheTest, CoordinatorKeyReceivedAsync) { |
| this->trusted_signals_cache_->set_get_coordinator_key_mode( |
| TestTrustedSignalsCache::GetCoordinatorKeyMode::kAsync); |
| |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| // Wait for the fetch to be observed and respond to it. No need to spin the |
| // message loop, since fetch responses at this layer are passed directly to |
| // the cache, and don't go through Mojo, as the TrustedSignalsFetcher is |
| // entirely mocked out. |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| } |
| |
| // Test the case where the attempt to get the coordinator key fails |
| // synchronously. |
| TYPED_TEST(TrustedSignalsCacheTest, CoordinatorKeyFailsSync) { |
| this->trusted_signals_cache_->set_get_coordinator_key_mode( |
| TestTrustedSignalsCache::GetCoordinatorKeyMode::kSyncFail); |
| |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForError(TestTrustedSignalsCache::kKeyFetchFailed); |
| // Teardown will check that the fetcher saw no unaccounted for requests. |
| } |
| |
| // Test the case where the attempt to get the coordinator key fails |
| // asynchronously. |
| TYPED_TEST(TrustedSignalsCacheTest, CoordinatorKeyFailsAsync) { |
| this->trusted_signals_cache_->set_get_coordinator_key_mode( |
| TestTrustedSignalsCache::GetCoordinatorKeyMode::kAsyncFail); |
| |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForError(TestTrustedSignalsCache::kKeyFetchFailed); |
| // Teardown will check that the fetcher saw no unaccounted for requests. |
| } |
| |
| // Test the case where the fetch is cancelled while attempting to get the |
| // coordinator key. |
| TYPED_TEST(TrustedSignalsCacheTest, CancelledDuringGetCoordinatorKey) { |
| for (bool invoke_callback : {false, true}) { |
| // Use a fresh cache each time. |
| this->CreateCache(); |
| this->trusted_signals_cache_->set_get_coordinator_key_mode( |
| TestTrustedSignalsCache::GetCoordinatorKeyMode::kStashCallback); |
| |
| auto params = this->CreateDefaultParams(); |
| |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| |
| auto callback = |
| this->trusted_signals_cache_->WaitForCoordinatorKeyCallback(); |
| handle.reset(); |
| if (invoke_callback) { |
| std::move(callback).Run(); |
| } |
| |
| // Let any pending async callbacks complete. |
| this->task_environment_.RunUntilIdle(); |
| |
| // Teardown will check that the fetcher saw no unaccounted for requests. |
| } |
| } |
| |
| // Test the case where a second request is made while waiting on the |
| // coordinator. The requests should behave just as in the |
| // DifferentParamsBeforeFetchStart test - that is, if the requests can |
| // theoretically use the same fetch (possibly sharing a partition or compression |
| // group), they should still be able to do so if one of the requests is started |
| // while the first request is waiting for the coordinator's key. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| SecondRequestStartedWhileWaitingOnCoordinatorKey) { |
| for (const auto& test_case : this->CreateTestCases()) { |
| SCOPED_TRACE(test_case.description); |
| |
| // Start with a clean slate for each test. Not strictly necessary, but |
| // limits what's under test a bit. |
| this->CreateCache(); |
| this->trusted_signals_cache_->set_get_coordinator_key_mode( |
| TestTrustedSignalsCache::GetCoordinatorKeyMode::kStashCallback); |
| |
| const auto& params1 = test_case.params1; |
| const auto& params2 = test_case.params2; |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto callback1 = |
| this->trusted_signals_cache_->WaitForCoordinatorKeyCallback(); |
| |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| |
| switch (test_case.request_relation) { |
| case RequestRelation::kDifferentFetches: { |
| ASSERT_NE(handle1, handle2); |
| ASSERT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| |
| // There should be separate callbacks in this case. |
| auto callback2 = |
| this->trusted_signals_cache_->WaitForCoordinatorKeyCallback(); |
| |
| // Invoke both callbacks, with the usual key (the serialized |
| // coordinator). |
| std::move(callback1).Run(); |
| std::move(callback2).Run(); |
| |
| auto fetches = this->WaitForSignalsFetches(2); |
| |
| // fetches are made in FIFO order. |
| ValidateFetchParams(fetches[0], params1, |
| /*expected_compression_group_id=*/0, partition_id1); |
| ValidateFetchParams(fetches[1], params2, |
| /*expected_compression_group_id=*/0, partition_id2); |
| EXPECT_EQ(fetches[0].network_partition_nonce == |
| fetches[1].network_partition_nonce, |
| test_case.expect_same_network_partition_nonce); |
| |
| // Make both requests succeed with different bodies, and check that they |
| // can be read. |
| RespondToFetchWithSuccess(fetches[0]); |
| RespondToFetchWithSuccess( |
| fetches[1], |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client2( |
| handle2.get(), this->CreateOrGetMojoPipeGivenParams(params2)); |
| client1.WaitForSuccess(); |
| client2.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentCompressionGroups: { |
| EXPECT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| std::move(callback1).Run(); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch.compression_groups.size(), 2u); |
| |
| // Compression groups are appended in FIFO order. |
| ASSERT_EQ(1u, fetch.compression_groups.count(0)); |
| ValidateFetchParamsForPartitions(fetch.compression_groups.at(0), |
| params1, partition_id1); |
| ASSERT_EQ(1u, fetch.compression_groups.count(1)); |
| ValidateFetchParamsForPartitions(fetch.compression_groups.at(1), |
| params2, partition_id2); |
| |
| // Respond with different results for each compression group. |
| RespondToTwoCompressionGroupFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient client1(handle1.get(), |
| this->cache_mojo_pipe_); |
| TestTrustedSignalsCacheClient client2(handle2.get(), |
| this->cache_mojo_pipe_); |
| client1.WaitForSuccess(); |
| client2.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kBrotli, |
| kOtherSuccessBody); |
| break; |
| } |
| |
| case RequestRelation::kDifferentPartitions: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_NE(partition_id1, partition_id2); |
| std::move(callback1).Run(); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| EXPECT_EQ(fetch.trusted_signals_url, params1.trusted_signals_url); |
| ASSERT_EQ(fetch.compression_groups.size(), 1u); |
| EXPECT_EQ(fetch.compression_groups.begin()->first, 0); |
| |
| const auto& partitions = fetch.compression_groups.begin()->second; |
| ASSERT_EQ(partitions.size(), 2u); |
| ValidateFetchParamsForPartition(partitions[0], params1, partition_id1); |
| ValidateFetchParamsForPartition(partitions[1], params2, partition_id2); |
| |
| // Respond with a single response for the partition, and read it - no |
| // need for multiple clients, since the handles are the same. |
| RespondToFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient client(handle1.get(), |
| this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| break; |
| } |
| |
| case RequestRelation::kSamePartitionModified: |
| case RequestRelation::kSamePartitionUnmodified: { |
| EXPECT_EQ(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| EXPECT_EQ(partition_id1, partition_id2); |
| std::move(callback1).Run(); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| auto merged_params = this->CreateMergedParams(params1, params2); |
| // The fetch exactly match the merged parameters. |
| ValidateFetchParams(fetch, merged_params, |
| /*expected_compression_group_id=*/0, partition_id1); |
| |
| // Respond with a single response for the partition, and read it - no |
| // need for multiple clients, since the handles are the same. |
| RespondToFetchWithSuccess(fetch); |
| TestTrustedSignalsCacheClient client(handle1.get(), |
| this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Test the case where the attempt to get the coordinator key is received before |
| // StartFetch is called on a Handle. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| CoordinatorKeyReceivedBeforeStartFetchCalled) { |
| this->trusted_signals_cache_->set_get_coordinator_key_mode( |
| TestTrustedSignalsCache::GetCoordinatorKeyMode::kStashCallback); |
| |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = |
| this->RequestTrustedSignals(params, /*start_fetch=*/false); |
| |
| auto callback = this->trusted_signals_cache_->WaitForCoordinatorKeyCallback(); |
| std::move(callback).Run(); |
| |
| // No fetch should have been started yet. |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| |
| // Calling StartFetch() on the handle should immediately trigger a fetch. |
| handle->StartFetch(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 1u); |
| |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| |
| TestTrustedSignalsCacheClient client(handle.get(), this->cache_mojo_pipe_); |
| client.WaitForSuccess(); |
| } |
| |
| // Check that requesting signals over a pipe with the wrong `script_origin` |
| // results in the pipe being closed, with nothing sent. |
| TYPED_TEST(TrustedSignalsCacheTest, RequestWithWrongScriptOrigin) { |
| mojo::test::BadMessageObserver bad_message_observer; |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| // Create a remote associated with some other origin. |
| mojo::Remote<auction_worklet::mojom::TrustedSignalsCache> remote( |
| this->CreateMojoPendingRemoteForOrigin( |
| url::Origin::Create(GURL("https://not.seller.or.buyer.test")))); |
| |
| // Trying to use the remote to get data from another origin's request should |
| // result in a bad message and the TrustedSignalsCache pipe being closed. |
| TestTrustedSignalsCacheClient client(handle.get(), remote); |
| EXPECT_EQ(bad_message_observer.WaitForBadMessage(), |
| "Data from wrong compression group requested."); |
| remote.FlushForTesting(); |
| EXPECT_FALSE(remote.is_connected()); |
| |
| // The client pipe should also have been closed without receiving any data. |
| EXPECT_TRUE(client.IsReceiverDisconnected()); |
| EXPECT_FALSE(client.has_result()); |
| } |
| |
| // Check that requesting signals over a pipe with the wrong signals type |
| // results in the pipe being closed, with nothing sent. |
| TYPED_TEST(TrustedSignalsCacheTest, RequestWithWrongSignalsType) { |
| mojo::test::BadMessageObserver bad_message_observer; |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| |
| TrustedSignalsCacheImpl::SignalsType wrong_signals_type; |
| if constexpr (std::is_same<TypeParam, BiddingParams>::value) { |
| wrong_signals_type = TrustedSignalsCacheImpl::SignalsType::kScoring; |
| } else { |
| wrong_signals_type = TrustedSignalsCacheImpl::SignalsType::kBidding; |
| } |
| |
| // Create a remote associated with the right origin, but wrong signals type. |
| mojo::Remote<auction_worklet::mojom::TrustedSignalsCache> remote( |
| this->trusted_signals_cache_->CreateRemote(wrong_signals_type, |
| params.script_origin)); |
| |
| // Trying to use the remote to get data using the wrong type should result in |
| // a bad message and the TrustedSignalsCache pipe being closed. |
| TestTrustedSignalsCacheClient client(handle.get(), remote); |
| EXPECT_EQ(bad_message_observer.WaitForBadMessage(), |
| "Data from wrong compression group requested."); |
| remote.FlushForTesting(); |
| EXPECT_FALSE(remote.is_connected()); |
| |
| // The client pipe should also have been closed without receiving any data. |
| EXPECT_TRUE(client.IsReceiverDisconnected()); |
| EXPECT_FALSE(client.has_result()); |
| } |
| |
| // Tests the case of merging multiple requests with the same FetchKey. This test |
| // serves to make sure that when there are multiple outstanding fetches, the |
| // last fetch can be modified as long as it has not started. |
| using TrustedBiddingSignalsCacheTest = TrustedSignalsCacheTest<BiddingParams>; |
| TEST_F(TrustedBiddingSignalsCacheTest, MultipleRequestsSameCacheKey) { |
| // Start request and wait for its fetch. |
| auto params1 = this->CreateDefaultParams(); |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params1); |
| auto fetch1 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch1, params1, /*expected_compression_group_id=*/0, |
| partition_id1); |
| |
| // Start another request with same CacheKey as the first, but that can't be |
| // merged into the first request, since it has a live fetch. |
| auto params2 = this->CreateDefaultParams(); |
| params2.trusted_bidding_signals_keys = {{"othey_key2"}}; |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params2); |
| EXPECT_NE(handle1->compression_group_token(), |
| handle2->compression_group_token()); |
| |
| // Create another fetch with the default set of parameters. It's merged into |
| // the second request, not the first. This is because the first and second |
| // request have the same cache key, so the second request overwrite the cache |
| // key of the first, though its compression group ID should still be valid. |
| auto [handle3, partition_id3] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle3->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id3); |
| |
| // Wait for the combined fetch. |
| auto fetch2 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2, CreateMergedParams(params2, params1), |
| /*expected_compression_group_id=*/0, partition_id2); |
| |
| // Reissuing a request with either previous set of params should reuse the |
| // partition shared by the second and third fetches. |
| auto [handle4, partition_id4] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle4->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id4); |
| auto [handle5, partition_id5] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle2->compression_group_token(), |
| handle5->compression_group_token()); |
| EXPECT_EQ(partition_id2, partition_id5); |
| |
| // Complete second fetch before first, just to make sure there's no |
| // expectation about completion order here. |
| RespondToFetchWithSuccess(fetch2); |
| TestTrustedSignalsCacheClient client2(handle2.get(), this->cache_mojo_pipe_); |
| client2.WaitForSuccess(); |
| |
| RespondToFetchWithSuccess( |
| fetch1, auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kSomeOtherSuccessBody); |
| TestTrustedSignalsCacheClient client1(handle1.get(), this->cache_mojo_pipe_); |
| client1.WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone, |
| kSomeOtherSuccessBody); |
| } |
| |
| TYPED_TEST(TrustedSignalsCacheTest, NonceCache) { |
| auto params = this->CreateDefaultParams(); |
| std::array<base::UnguessableToken, TrustedSignalsCacheImpl::kNonceCacheSize> |
| nonces; |
| // Fill nonce cache, make sure all nonces are unique. |
| for (uint32_t i = 0; i < TrustedSignalsCacheImpl::kNonceCacheSize; ++i) { |
| params.trusted_signals_url = |
| GURL(base::StringPrintf("https://%u.test/signals", i)); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| nonces[i] = fetch.network_partition_nonce; |
| for (uint32_t j = 0; j < i; ++j) { |
| EXPECT_NE(nonces[j], nonces[i]); |
| } |
| } |
| |
| // Create requests reusing signals URLs. Make sure all nonces are reused, as |
| // expected. |
| for (uint32_t i = 0; i < TrustedSignalsCacheImpl::kNonceCacheSize; ++i) { |
| params.trusted_signals_url = |
| GURL(base::StringPrintf("https://%u.test/signals", i)); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| EXPECT_EQ(fetch.network_partition_nonce, nonces[i]); |
| } |
| |
| // Make request with a unique trusted signals URL. It should not reuse any of |
| // the existing tokens. This should result in the oldest token (for |
| // https://0.test) being evicted. |
| params.trusted_signals_url = GURL("https://unique.test/signals"); |
| auto [handle_unique, partition_id_unique] = |
| this->RequestTrustedSignals(params); |
| auto fetch_unique = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch_unique, params, /*expected_compression_group_id=*/0, |
| partition_id_unique); |
| base::UnguessableToken nonce_unique = fetch_unique.network_partition_nonce; |
| for (auto nonce : nonces) { |
| EXPECT_NE(nonce_unique, nonce); |
| } |
| |
| // Check all tokens but the first are still be in the cache. |
| for (uint32_t i = 1; i < TrustedSignalsCacheImpl::kNonceCacheSize; ++i) { |
| params.trusted_signals_url = |
| GURL(base::StringPrintf("https://%u.test/signals", i)); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| EXPECT_EQ(fetch.network_partition_nonce, nonces[i]); |
| } |
| |
| // Token for "https://0.test" should have been evicted. A new fetch for it |
| // should now use a new nonce. |
| params.trusted_signals_url = GURL("https://0.test/signals"); |
| auto [handle0, partition_id0] = this->RequestTrustedSignals(params); |
| auto fetch0 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch0, params, /*expected_compression_group_id=*/0, |
| partition_id0); |
| EXPECT_NE(fetch0.network_partition_nonce, nonce_unique); |
| for (auto nonce : nonces) { |
| EXPECT_NE(fetch0.network_partition_nonce, nonce); |
| } |
| } |
| |
| // Make sure bidding and scoring requests to the same URL don't share fetches, |
| // and use their own nonces. |
| TYPED_TEST(TrustedSignalsCacheTest, DifferentTypes) { |
| auto bidding_params = this->CreateDefaultBiddingParams(); |
| auto scoring_params = this->CreateDefaultScoringParams(); |
| // Use the same script origin, so the keys end up being largely the same, |
| // other than signals type. |
| scoring_params.script_origin = bidding_params.script_origin; |
| // By default, bidding and scoring parameters use different URLs, but need to |
| // use the same ones for this test. |
| scoring_params.trusted_signals_url = bidding_params.trusted_signals_url; |
| |
| std::unique_ptr<TestTrustedSignalsCache::Handle> bidding_handle; |
| int bidding_partition_id; |
| std::unique_ptr<TestTrustedSignalsCache::Handle> scoring_handle; |
| int scoring_partition_id; |
| // Vary which request is made first depending on the templatization of the |
| // test. |
| if constexpr (std::is_same<TypeParam, BiddingParams>::value) { |
| std::tie(bidding_handle, bidding_partition_id) = |
| this->RequestTrustedSignals(bidding_params); |
| std::tie(scoring_handle, scoring_partition_id) = |
| this->RequestTrustedSignals(scoring_params); |
| } else { |
| std::tie(scoring_handle, scoring_partition_id) = |
| this->RequestTrustedSignals(scoring_params); |
| std::tie(bidding_handle, bidding_partition_id) = |
| this->RequestTrustedSignals(bidding_params); |
| } |
| EXPECT_NE(bidding_handle, scoring_handle); |
| |
| // There should be two fetches. |
| auto bidding_fetch = |
| this->trusted_signals_cache_->WaitForBiddingSignalsFetch(); |
| ValidateFetchParams(bidding_fetch, bidding_params, |
| /*expected_compression_group_id=*/0, |
| bidding_partition_id); |
| auto scoring_fetch = |
| this->trusted_signals_cache_->WaitForScoringSignalsFetch(); |
| ValidateFetchParams(scoring_fetch, scoring_params, |
| /*expected_compression_group_id=*/0, |
| scoring_partition_id); |
| |
| // And the fetches should not share nonces. |
| EXPECT_NE(bidding_fetch.network_partition_nonce, |
| scoring_fetch.network_partition_nonce); |
| } |
| |
| // Test the cache size limit in the case Handles are not kept alive. |
| TYPED_TEST(TrustedSignalsCacheTest, SizeLimitReachedNoLiveHandles) { |
| // A body size large enough so that only 10 entries may be in the cache. This |
| // doesn't take into account overhead due to fields other than the body |
| // contributing to the total size - that's assumed to be less than |
| // TrustedSignalsCacheImpl::kMaxCacheSizeBytes / 11 for each entry. |
| const size_t kEntrySize = |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes / 11 + 1; |
| auto params = this->CreateDefaultParams(); |
| for (size_t i = 0; i < 10u; ++i) { |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(i, kEntrySize)); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1 + i); |
| // The cache size limit should never be exceeded. |
| EXPECT_LT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| } |
| |
| // Start an 11th request, which corresponds to `i = 10`, since numbering |
| // starts at 0. Nothing should be evicted yet. |
| params.trusted_signals_url = CreateUrl(10); |
| auto [handle10, partition_id10] = this->RequestTrustedSignals(params); |
| auto fetch10 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch10, params, /*expected_compression_group_id=*/0, |
| partition_id10); |
| // 11 entries are in the cache, but only 10 have bodies. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 11u); |
| EXPECT_LT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| |
| // Check that all previous entries are still in the cache. |
| for (size_t i = 0; i < 10u; ++i) { |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(i, kEntrySize)); |
| } |
| |
| // Respond to fetch 10. This should evict the entry least recently closed |
| // entry, which is entry 0. |
| RespondToFetchWithSuccess( |
| fetch10, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(10, kEntrySize)); |
| // Close handle, to avoid impacting eviction logic. |
| handle10.reset(); |
| // Entry 0 should have been evicted. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 10u); |
| EXPECT_LT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| |
| // Trying to request entry 0 should trigger a new fetch, without evicting |
| // anything. |
| params.trusted_signals_url = CreateUrl(0); |
| auto [handle0, partition_id0] = this->RequestTrustedSignals(params); |
| auto fetch0 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch0, params, /*expected_compression_group_id=*/0, |
| partition_id0); |
| // 11 entries should be in the cache again, but entry 0 should have no body |
| // yet. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 11u); |
| EXPECT_LT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| |
| // Check that all other entries are still in the cache. Check in reverse |
| // order, so that entry 10 is the least recently accessed, and thus will |
| // be the one that's evicted when a new entry is added. This serves to |
| // double-check that eviction order is based on time last used, rather than on |
| // creation time. |
| for (size_t i = 10u; i >= 1u; --i) { |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(i, kEntrySize)); |
| } |
| |
| // Respond to the second fetch for entry 0. This should evict the entry least |
| // recently closed entry, which is entry 10. |
| RespondToFetchWithSuccess( |
| fetch0, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(0, kEntrySize)); |
| // Close handle for entry 0. |
| handle0.reset(); |
| |
| // Entry 10 should have been evicted. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 10u); |
| EXPECT_LT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| |
| // Trying to request entry 10 again should trigger a new fetch, without |
| // evicting anything. |
| params.trusted_signals_url = CreateUrl(10); |
| auto [handle10_2, partition_id10_2] = this->RequestTrustedSignals(params); |
| auto fetch10_2 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch10_2, params, /*expected_compression_group_id=*/0, |
| partition_id10_2); |
| // 11 entries should be in the cache again. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 11u); |
| |
| // Check that entries 0 through 9 are all in the cache. |
| for (size_t i = 0; i < 10u; ++i) { |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(i, kEntrySize)); |
| } |
| } |
| |
| // Test the cache size limit in the case Handles are kept alive. The size limit |
| // may be exceeded as long as there are live Handles. |
| TYPED_TEST(TrustedSignalsCacheTest, SizeLimitReachedLiveHandles) { |
| // A body size large enough so that only 10 entries may be in the cache. This |
| // doesn't take into account overhead due to fields other than the body |
| // contributing to the total size - that's assumed to be less than |
| // TrustedSignalsCacheImpl::kMaxCacheSizeBytes / 11 for each entry. |
| const size_t kEntrySize = |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes / 11 + 1; |
| auto params = this->CreateDefaultParams(); |
| std::vector<std::unique_ptr<TestTrustedSignalsCache::Handle>> handles; |
| // Make 15 entries. All should remain in the cache as long as they're live, |
| // though the cache limit will be exceeded. |
| for (size_t i = 0; i < 15u; ++i) { |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(i, kEntrySize)); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1 + i); |
| handles.emplace_back(std::move(handle)); |
| } |
| |
| // Delete all the Handles that exceeded the limit. The underlying cache data |
| // should be destroyed as soon as they're released. |
| for (size_t i = 14u; i >= 10; --i) { |
| // There should be `i` entries in the cache, and the size limit should be |
| // exceeded. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), i + 1); |
| EXPECT_GT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| |
| // Destroying a Handle should immediately destroy the entry, as long as the |
| // max size is exceeded. |
| handles.pop_back(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), i); |
| } |
| |
| // Deleting the other Handles shouldn't result in removing anything. |
| while (!handles.empty()) { |
| handles.pop_back(); |
| EXPECT_LT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 10u); |
| } |
| |
| // Check that entries 0 through 9 are all in the cache. |
| for (size_t i = 0; i < 10u; ++i) { |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(i, kEntrySize)); |
| } |
| |
| // Check that entries 10 through 14 are not in the cache, by checking that |
| // requesting them triggers network requests. |
| for (size_t i = 10; i < 15u; ++i) { |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| } |
| } |
| |
| TYPED_TEST(TrustedSignalsCacheTest, SingleEntryExceedsSizeLimit) { |
| // Value above the maximum allowed size. |
| const size_t kLargeEntrySize = |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes + 1; |
| |
| // Add a single entry exceeding the maximum size, and mmake sure it can be |
| // retrieved. |
| auto params = this->CreateDefaultParams(); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess( |
| fetch, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(0, kLargeEntrySize)); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| EXPECT_GT(this->trusted_signals_cache_->size_for_testing(), |
| TrustedSignalsCacheImpl::kMaxCacheSizeBytes); |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(0, kLargeEntrySize)); |
| |
| // Destroying the Handle should result in the entry being immediately evicted, |
| // even though nothing else is in the cache. |
| handle.reset(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 0u); |
| EXPECT_EQ(this->trusted_signals_cache_->size_for_testing(), 0u); |
| |
| // Re-requesting the data should result in a new fetch, since it was evicted. |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| auto fetch2 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2, params, /*expected_compression_group_id=*/0, |
| partition_id2); |
| } |
| |
| // Test that when there are multiple Handles, entries are kept around until all |
| // of them are destroyed. |
| TYPED_TEST(TrustedSignalsCacheTest, MultipleLiveHandles) { |
| // Size large enough that two entries will exceed the limit. |
| const size_t kEntrySize = TrustedSignalsCacheImpl::kMaxCacheSizeBytes / 2 + 1; |
| |
| auto params1 = this->CreateDefaultParams(); |
| auto params2 = this->CreateDefaultParams(); |
| params2.trusted_signals_url = CreateUrl(2); |
| |
| // Make a request using the first set of parameters, and provide a response. |
| auto [handle1_1, partition_id1_1] = this->RequestTrustedSignals(params1); |
| auto fetch1_1 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch1_1, params1, /*expected_compression_group_id=*/0, |
| partition_id1_1); |
| RespondToFetchWithSuccess( |
| fetch1_1, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(0, kEntrySize)); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| |
| // Make a request using the second set of parameters, and provide a response. |
| auto [handle2_1, partition_id2_1] = this->RequestTrustedSignals(params2); |
| auto fetch2_1 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2_1, params2, /*expected_compression_group_id=*/0, |
| partition_id2_1); |
| RespondToFetchWithSuccess( |
| fetch2_1, auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(1, kEntrySize)); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 2u); |
| |
| // Send a second request using each set of parameters. The responses should be |
| // reused. |
| auto [handle1_2, partition_id1_2] = this->RequestTrustedSignals(params1); |
| EXPECT_EQ(handle1_1->compression_group_token(), |
| handle1_2->compression_group_token()); |
| EXPECT_EQ(partition_id1_1, partition_id1_2); |
| auto [handle2_2, partition_id2_2] = this->RequestTrustedSignals(params2); |
| EXPECT_EQ(handle2_1->compression_group_token(), |
| handle2_2->compression_group_token()); |
| EXPECT_EQ(partition_id2_1, partition_id2_2); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 2u); |
| EXPECT_EQ(this->trusted_signals_cache_->num_pending_fetches(), 0u); |
| |
| // Destroy the original two Handles. The data should be retained. |
| handle1_1.reset(); |
| handle2_1.reset(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 2u); |
| |
| // Verify the responses are still cached. |
| TestTrustedSignalsCacheClient(handle1_2.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(0, kEntrySize)); |
| TestTrustedSignalsCacheClient(handle2_2.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(1, kEntrySize)); |
| |
| // Destroy the second Handle for request 2. This should result in it being |
| // removed from the cache, since the size of the two entries combined exceeds |
| // the maximum size. |
| handle2_2.reset(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| // Verify the first response is still in the cache. |
| TestTrustedSignalsCacheClient(handle1_2.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(0, kEntrySize)); |
| |
| // Making another request with `params2` should result in a new fetch. |
| auto [handle2_3, partition_id2_3] = this->RequestTrustedSignals(params2); |
| auto fetch2_3 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2_3, params2, /*expected_compression_group_id=*/0, |
| partition_id2_3); |
| |
| // Destroy the second Handle for request 1. The data should not be evicted |
| // from the cache, since it's the only thing in the cache, and does not exceed |
| // the maximum size. |
| handle1_2.reset(); |
| // Note that this includes the pending fetch for the second group. |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 2u); |
| |
| // Verify the first response is still in the cache by re-requesting it, and |
| // retrieving it. |
| auto [handle1_3, partition_id1_3] = this->RequestTrustedSignals(params1); |
| TestTrustedSignalsCacheClient(handle1_3.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess( |
| auction_worklet::mojom::TrustedSignalsCompressionScheme::kGzip, |
| CreateString(0, kEntrySize)); |
| } |
| |
| // Test that a single entry is still usable after the Handle has been destroyed |
| // for less than `kMinUnusedCleanupTime` or `kMaxUnusedCleanupTime`. The latter |
| // won't always be the case - the guarantee is just that unused entries will be |
| // cleaned sometime between `kMinUnusedCleanupTime` and `kMaxUnusedCleanupTime`, |
| // but when there's a single entry, it will be expired at exactly |
| // `kMaxUnusedCleanupTime`. |
| // |
| // Also, wait a variable amount of time before destroying the Handle, to make |
| // sure that the timer doesn't start until the Handle is released |
| TYPED_TEST(TrustedSignalsCacheTest, CleanupTimerBeforeTimerTriggers) { |
| auto params = this->CreateDefaultParams(); |
| |
| for (base::TimeDelta time_to_wait_before_destroying_handle : |
| {base::TimeDelta(), TrustedSignalsCacheImpl::kMinUnusedCleanupTime, |
| TrustedSignalsCacheImpl::kMaxUnusedCleanupTime, |
| 20 * TrustedSignalsCacheImpl::kMaxUnusedCleanupTime}) { |
| for (base::TimeDelta time_to_wait_after_destroying_handle : |
| {TrustedSignalsCacheImpl::kMinUnusedCleanupTime - kTinyTime, |
| TrustedSignalsCacheImpl::kMaxUnusedCleanupTime - kTinyTime}) { |
| // Start with a clean slate for each test. |
| this->CreateCache(); |
| |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| |
| // Destroy the Handle and wait for `time_to_wait`, get a new Handle, and |
| // verify the data is accessible. Do this several times, to check that |
| // accessing the entry resets the timer. |
| for (int i = 0; i < 5; ++i) { |
| this->task_environment_.FastForwardBy( |
| time_to_wait_before_destroying_handle); |
| handle.reset(); |
| this->task_environment_.FastForwardBy( |
| time_to_wait_after_destroying_handle); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| |
| // Check the result is still usable. |
| handle = std::move(this->RequestTrustedSignals(params).first); |
| TestTrustedSignalsCacheClient(handle.get(), this->cache_mojo_pipe_) |
| .WaitForSuccess(); |
| } |
| } |
| } |
| } |
| |
| // Test that the cleanup timer triggers at exactly `kMaxUnusedCleanupTime` after |
| // a lone entry has been destroyed. Adding multiple entries would potentially |
| // affect when the timeout timer triggers. |
| TYPED_TEST(TrustedSignalsCacheTest, CleanupTimerTriggers) { |
| auto params = this->CreateDefaultParams(); |
| |
| for (base::TimeDelta time_to_wait_before_destroying_handle : |
| {base::TimeDelta(), TrustedSignalsCacheImpl::kMinUnusedCleanupTime, |
| TrustedSignalsCacheImpl::kMaxUnusedCleanupTime, |
| 20 * TrustedSignalsCacheImpl::kMaxUnusedCleanupTime}) { |
| // Start with a clean slate for each test. |
| this->CreateCache(); |
| |
| auto [handle1, partition_id1] = this->RequestTrustedSignals(params); |
| auto fetch1 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch1, params, /*expected_compression_group_id=*/0, |
| partition_id1); |
| RespondToFetchWithSuccess(fetch1); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| |
| // Destroy the Handle and wait for `time_to_wait_before_destroying_handle`. |
| // The timer should only start after the Handle has been destroyed. |
| this->task_environment_.FastForwardBy( |
| time_to_wait_before_destroying_handle); |
| handle1.reset(); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| |
| // Wait until just before the timer triggers. The compression group should |
| // still exist. Can't request it through a new Handle, since that would |
| // extend the lifetime. Could access it through a cached compression group |
| // token with no live Handle, but that's not a guaranteed part of the API. |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kMaxUnusedCleanupTime - kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 1u); |
| |
| // Check that the data is destroyed on schedule, and that trying to access |
| // it results in a new fetch. |
| this->task_environment_.FastForwardBy(kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 0u); |
| auto [handle2, partition_id2] = this->RequestTrustedSignals(params); |
| auto fetch2 = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch2, params, /*expected_compression_group_id=*/0, |
| partition_id2); |
| } |
| } |
| |
| // Add several entries within `kCleanupInterval` of each other, and test that |
| // the cleanup timer clears them all at once. |
| TYPED_TEST(TrustedSignalsCacheTest, CleanupTimerCleansMultipleEntries) { |
| auto params = this->CreateDefaultParams(); |
| |
| // Create 3 requests within `kCleanupInterval` of each other. Destroy each |
| // Handle immediately after providing the response. |
| for (size_t i = 0u; i < 3u; ++i) { |
| SCOPED_TRACE(i); |
| |
| // It doesn't matter if time is fast forwarded or not before the first entry |
| // is added. |
| if (i > 0u) { |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kCleanupInterval / 2); |
| } |
| |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), i + 1); |
| } |
| |
| // Wait until just before `kMaxUnusedCleanupTime` (which is `kCleanupInterval` |
| // + `kMinUnusedCleanupTime`) has passed since the first request's Handle was |
| // destroyed. The cleanup timer should not have triggered yet. |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kMinUnusedCleanupTime - kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 3u); |
| |
| // Wait just long enough for the cleanup timer to trigger, which should delete |
| // all entries. |
| this->task_environment_.FastForwardBy(kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 0u); |
| |
| // Verify no entries were in the cache by making sure re-requesting them |
| // triggers a signals fetch. |
| for (size_t i = 0u; i < 3u; ++i) { |
| SCOPED_TRACE(i); |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| } |
| } |
| |
| // Add several entries that each expire just after `kMaxUnusedCleanupTime` has |
| // passed since the previous entry has expired, so when the cleanup timer |
| // triggers, only a single entry will be destroyed. |
| TYPED_TEST(TrustedSignalsCacheTest, |
| CleanupTimerCleansSingleEntriesSuccessively) { |
| // Near the smallest time between Handle destruction of different entries |
| // where the cleanup timer for the previous entry won't destroy the next one |
| // as well. |
| const base::TimeDelta kTimeDelta = |
| TrustedSignalsCacheImpl::kCleanupInterval + kTinyTime; |
| auto params = this->CreateDefaultParams(); |
| |
| // Create 3 requests `kTimeDelta` apart. Destroy each Handle immediately after |
| // providing the response. |
| for (size_t i = 0u; i < 3u; ++i) { |
| SCOPED_TRACE(i); |
| |
| // It doesn't matter if time is fast forwarded or not before the first entry |
| // is added. |
| if (i > 0u) { |
| this->task_environment_.FastForwardBy(kTimeDelta); |
| } |
| |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| RespondToFetchWithSuccess(fetch); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), i + 1); |
| } |
| |
| // Wait until just before `kMinUnusedCleanupTime - kTinyTime` has passed |
| // since the first request's Handle was destroyed. At this point, the first |
| // entry should be removed at `kTimeDelta`, and each of the other two should |
| // be removed `kTimeDelta` after the previous entry. |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kMinUnusedCleanupTime - 2 * kTimeDelta - |
| kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 3u); |
| |
| // Wait for each entry to be destroyed, one-by-one, and check that a new |
| // request for it will indeed trigger a fetch. Can't check that other entries |
| // are still in the cache, other than probing `num_groups_for_testing()`, |
| // since actually creating a new Handle and fetching the response will extend |
| // the life of the response in the cache. Could cache the compression group |
| // tokens and partition IDs the Handle before destroying it the first time, |
| // but prefer not to relying on IDs persisting and being fetchable when there |
| // are no matching Handles. |
| for (size_t i = 0; i < 3u; ++i) { |
| SCOPED_TRACE(i); |
| this->task_environment_.FastForwardBy( |
| TrustedSignalsCacheImpl::kCleanupInterval); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 3u - i); |
| this->task_environment_.FastForwardBy(kTinyTime); |
| EXPECT_EQ(this->trusted_signals_cache_->num_groups_for_testing(), 2u - i); |
| |
| params.trusted_signals_url = CreateUrl(i); |
| auto [handle, partition_id] = this->RequestTrustedSignals(params); |
| auto fetch = this->WaitForSignalsFetch(); |
| ValidateFetchParams(fetch, params, /*expected_compression_group_id=*/0, |
| partition_id); |
| } |
| } |
| |
| } // namespace |
| } // namespace content |