blob: 2474b5d8d10bb070d55056b960dc964170a1f092 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/services/auction_worklet/trusted_signals_request_manager.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "content/services/auction_worklet/trusted_signals.h"
#include "content/services/auction_worklet/worklet_test_util.h"
#include "net/http/http_status_code.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-forward.h"
namespace auction_worklet {
namespace {
// Very short time used by some tests that want to wait until just before a
// timer triggers.
constexpr base::TimeDelta kTinyTime = base::Microseconds(1);
// Common JSON used for most bidding signals tests.
const char kBaseBiddingJson[] = R"(
{
"key1": 1,
"key2": [2],
"key3": "3"
}
)";
// Common JSON used for most scoring signals tests.
const char kBaseScoringJson[] = R"(
{
"renderUrls": {
"https://foo.test/": 1,
"https://bar.test/": [2]
},
"adComponentRenderUrls": {
"https://foosub.test/": 2,
"https://barsub.test/": [3],
"https://bazsub.test/": "4"
}
}
)";
const char kTopLevelOrigin[] = "https://publisher";
// Callback for loading signals that stores the result and runs a closure to
// exit a message loop.
void LoadSignalsCallback(scoped_refptr<TrustedSignals::Result>* results_out,
absl::optional<std::string>* error_msg_out,
base::OnceClosure quit_closure,
scoped_refptr<TrustedSignals::Result> result,
absl::optional<std::string> error_msg) {
*results_out = std::move(result);
*error_msg_out = std::move(error_msg);
EXPECT_EQ(results_out->get() == nullptr, error_msg_out->has_value());
std::move(quit_closure).Run();
}
// Callback that should never be invoked, for cancellation tests.
void NeverInvokedLoadSignalsCallback(
scoped_refptr<TrustedSignals::Result> result,
absl::optional<std::string> error_msg) {
ADD_FAILURE() << "This should not be invoked";
}
class TrustedSignalsRequestManagerTest : public testing::Test {
public:
TrustedSignalsRequestManagerTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
v8_helper_(
AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner())),
bidding_request_manager_(
TrustedSignalsRequestManager::Type::kBiddingSignals,
&url_loader_factory_,
/*automatically_send_requests=*/false,
url::Origin::Create(GURL(kTopLevelOrigin)),
trusted_signals_url_,
v8_helper_.get()),
scoring_request_manager_(
TrustedSignalsRequestManager::Type::kScoringSignals,
&url_loader_factory_,
/*automatically_send_requests=*/false,
url::Origin::Create(GURL(kTopLevelOrigin)),
trusted_signals_url_,
v8_helper_.get()) {}
~TrustedSignalsRequestManagerTest() override {
task_environment_.RunUntilIdle();
}
// Sets the HTTP response and then fetches bidding signals and waits for
// completion. Returns nullptr on failure.
scoped_refptr<TrustedSignals::Result> FetchBiddingSignalsWithResponse(
const GURL& url,
const std::string& response,
const std::vector<std::string>& trusted_bidding_signals_keys) {
AddJsonResponse(&url_loader_factory_, url, response);
return FetchBiddingSignals(trusted_bidding_signals_keys);
}
// Fetches bidding signals and waits for completion. Returns nullptr on
// failure.
scoped_refptr<TrustedSignals::Result> FetchBiddingSignals(
const std::vector<std::string>& trusted_bidding_signals_keys) {
scoped_refptr<TrustedSignals::Result> signals;
base::RunLoop run_loop;
auto request = bidding_request_manager_.RequestBiddingSignals(
std::move(trusted_bidding_signals_keys),
base::BindOnce(&LoadSignalsCallback, &signals, &error_msg_,
run_loop.QuitClosure()));
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
run_loop.Run();
return signals;
}
// Sets the HTTP response and then fetches scoring signals and waits for
// completion. Returns nullptr on failure.
scoped_refptr<TrustedSignals::Result> FetchScoringSignalsWithResponse(
const GURL& url,
const std::string& response,
const GURL& render_url,
const std::vector<std::string>& ad_component_render_urls) {
AddJsonResponse(&url_loader_factory_, url, response);
return FetchScoringSignals(render_url, ad_component_render_urls);
}
// Fetches scoring signals and waits for completion. Returns nullptr on
// failure.
scoped_refptr<TrustedSignals::Result> FetchScoringSignals(
const GURL& render_url,
const std::vector<std::string>& ad_component_render_urls) {
scoped_refptr<TrustedSignals::Result> signals;
base::RunLoop run_loop;
auto request = scoring_request_manager_.RequestScoringSignals(
render_url, ad_component_render_urls,
base::BindOnce(&LoadSignalsCallback, &signals, &error_msg_,
run_loop.QuitClosure()));
scoring_request_manager_.StartBatchedTrustedSignalsRequest();
run_loop.Run();
return signals;
}
// Returns the results of calling TrustedSignals::Result::GetBiddingSignals()
// with `trusted_bidding_signals_keys`. Returns value as a JSON std::string,
// for easy testing.
std::string ExtractBiddingSignals(
TrustedSignals::Result* signals,
std::vector<std::string> trusted_bidding_signals_keys) {
base::RunLoop run_loop;
std::string result;
v8_helper_->v8_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
v8::Isolate* isolate = v8_helper_->isolate();
// Could use the scratch context, but using a separate one more
// closely resembles actual use.
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope context_scope(context);
v8::Local<v8::Value> value = signals->GetBiddingSignals(
v8_helper_.get(), context, trusted_bidding_signals_keys);
if (!v8_helper_->ExtractJson(context, value, &result)) {
result = "JSON extraction failed.";
}
run_loop.Quit();
}));
run_loop.Run();
return result;
}
// Returns the results of calling TrustedSignals::Result::GetScoringSignals()
// with the provided parameters. Returns value as a JSON std::string, for easy
// testing.
std::string ExtractScoringSignals(
TrustedSignals::Result* signals,
const GURL& render_url,
const std::vector<std::string>& ad_component_render_urls) {
base::RunLoop run_loop;
std::string result;
v8_helper_->v8_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
v8::Isolate* isolate = v8_helper_->isolate();
// Could use the scratch context, but using a separate one more
// closely resembles actual use.
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope context_scope(context);
v8::Local<v8::Value> value = signals->GetScoringSignals(
v8_helper_.get(), context, render_url, ad_component_render_urls);
if (!v8_helper_->ExtractJson(context, value, &result)) {
result = "JSON extraction failed.";
}
run_loop.Quit();
}));
run_loop.Run();
return result;
}
protected:
base::test::TaskEnvironment task_environment_;
// URL without query params attached.
const GURL trusted_signals_url_ = GURL("https://url.test/");
// The fetch helpers store the most recent error message, if any, here.
absl::optional<std::string> error_msg_;
network::TestURLLoaderFactory url_loader_factory_;
scoped_refptr<AuctionV8Helper> v8_helper_;
TrustedSignalsRequestManager bidding_request_manager_;
TrustedSignalsRequestManager scoring_request_manager_;
};
TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsError) {
url_loader_factory_.AddResponse(
"https://url.test/?hostname=publisher&keys=key1", kBaseBiddingJson,
net::HTTP_NOT_FOUND);
EXPECT_FALSE(FetchBiddingSignals({"key1"}));
EXPECT_EQ(
"Failed to load https://url.test/?hostname=publisher&keys=key1 "
"HTTP status = 404 Not Found.",
error_msg_);
}
TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsError) {
url_loader_factory_.AddResponse(
"https://url.test/"
"?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F",
kBaseScoringJson, net::HTTP_NOT_FOUND);
EXPECT_FALSE(FetchScoringSignals(GURL("https://foo.test/"),
/*ad_component_render_urls=*/{}));
EXPECT_EQ(
"Failed to load "
"https://url.test/"
"?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F "
"HTTP status = 404 Not Found.",
error_msg_);
}
TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsBatchedRequestError) {
url_loader_factory_.AddResponse(
"https://url.test/?hostname=publisher&keys=key1,key2", kBaseBiddingJson,
net::HTTP_NOT_FOUND);
base::RunLoop run_loop1;
scoped_refptr<TrustedSignals::Result> signals1;
absl::optional<std::string> error_msg1;
auto request1 = bidding_request_manager_.RequestBiddingSignals(
{"key1"}, base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1,
run_loop1.QuitClosure()));
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager_.RequestBiddingSignals(
{"key2"}, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
const char kExpectedError[] =
"Failed to load https://url.test/?hostname=publisher&keys=key1,key2 "
"HTTP status = 404 Not Found.";
run_loop1.Run();
EXPECT_FALSE(signals1);
EXPECT_EQ(kExpectedError, error_msg1);
run_loop2.Run();
EXPECT_FALSE(signals2);
EXPECT_EQ(kExpectedError, error_msg2);
}
TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsBatchedRequestError) {
url_loader_factory_.AddResponse(
"https://url.test/?hostname=publisher&"
"renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F",
kBaseScoringJson, net::HTTP_NOT_FOUND);
base::RunLoop run_loop1;
scoped_refptr<TrustedSignals::Result> signals1;
absl::optional<std::string> error_msg1;
auto request1 = scoring_request_manager_.RequestScoringSignals(
GURL("https://foo.test/"),
/*ad_component_render_urls=*/{},
base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1,
run_loop1.QuitClosure()));
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = scoring_request_manager_.RequestScoringSignals(
GURL("https://bar.test/"),
/*ad_component_render_urls=*/{},
base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
scoring_request_manager_.StartBatchedTrustedSignalsRequest();
const char kExpectedError[] =
"Failed to load https://url.test/?hostname=publisher"
"&renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F "
"HTTP status = 404 Not Found.";
run_loop1.Run();
EXPECT_FALSE(signals1);
EXPECT_EQ(kExpectedError, error_msg1);
run_loop2.Run();
EXPECT_FALSE(signals2);
EXPECT_EQ(kExpectedError, error_msg2);
}
TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsOneRequest) {
const std::vector<std::string> kKeys{"key2", "key1"};
scoped_refptr<TrustedSignals::Result> signals =
FetchBiddingSignalsWithResponse(
GURL("https://url.test/?hostname=publisher&keys=key1,key2"),
kBaseBiddingJson, kKeys);
ASSERT_TRUE(signals);
EXPECT_FALSE(error_msg_.has_value());
EXPECT_EQ(R"({"key2":[2],"key1":1})",
ExtractBiddingSignals(signals.get(), kKeys));
}
TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsOneRequest) {
const GURL kRenderUrl = GURL("https://foo.test/");
const std::vector<std::string> kAdComponentRenderUrls{
"https://foosub.test/", "https://barsub.test/", "https://bazsub.test/"};
// URLs are currently added in lexical order.
scoped_refptr<TrustedSignals::Result> signals =
FetchScoringSignalsWithResponse(
GURL("https://url.test/?hostname=publisher"
"&renderUrls=https%3A%2F%2Ffoo.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F,"
"https%3A%2F%2Fbazsub.test%2F,https%3A%2F%2Ffoosub.test%2F"),
kBaseScoringJson, kRenderUrl, kAdComponentRenderUrls);
ASSERT_TRUE(signals);
EXPECT_FALSE(error_msg_.has_value());
EXPECT_EQ(
R"({"renderUrl":{"https://foo.test/":1},")"
R"(adComponentRenderUrls":{"https://foosub.test/":2,)"
R"("https://barsub.test/":[3],"https://bazsub.test/":"4"}})",
ExtractScoringSignals(signals.get(), kRenderUrl, kAdComponentRenderUrls));
}
TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsSequentialRequests) {
// Use partially overlapping keys, to cover both the shared and distinct key
// cases.
const std::vector<std::string> kKeys1{"key1", "key3"};
const std::vector<std::string> kKeys2{"key2", "key3"};
// Note that these responses use different values for the shared key.
scoped_refptr<TrustedSignals::Result> signals1 =
FetchBiddingSignalsWithResponse(
GURL("https://url.test/?hostname=publisher&keys=key1,key3"),
R"({"key1":1,"key3":3})", kKeys1);
ASSERT_TRUE(signals1);
EXPECT_FALSE(error_msg_.has_value());
EXPECT_EQ(R"({"key1":1,"key3":3})",
ExtractBiddingSignals(signals1.get(), kKeys1));
scoped_refptr<TrustedSignals::Result> signals2 =
FetchBiddingSignalsWithResponse(
GURL("https://url.test/?hostname=publisher&keys=key2,key3"),
R"({"key2":[2],"key3":[3]})", kKeys2);
ASSERT_TRUE(signals1);
EXPECT_FALSE(error_msg_.has_value());
EXPECT_EQ(R"({"key2":[2],"key3":[3]})",
ExtractBiddingSignals(signals2.get(), kKeys2));
}
TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsSequentialRequests) {
// Use partially overlapping keys, to cover both the shared and distinct key
// cases.
const GURL kRenderUrl1 = GURL("https://foo.test/");
const std::vector<std::string> kAdComponentRenderUrls1{
"https://foosub.test/", "https://bazsub.test/"};
const GURL kRenderUrl2 = GURL("https://bar.test/");
const std::vector<std::string> kAdComponentRenderUrls2{
"https://barsub.test/", "https://bazsub.test/"};
// Note that these responses use different values for the shared key.
scoped_refptr<TrustedSignals::Result> signals1 =
FetchScoringSignalsWithResponse(
GURL("https://url.test/?hostname=publisher"
"&renderUrls=https%3A%2F%2Ffoo.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fbazsub.test%2F,"
"https%3A%2F%2Ffoosub.test%2F"),
R"(
{
"renderUrls": {
"https://foo.test/": 1
},
"adComponentRenderUrls": {
"https://foosub.test/": 2,
"https://bazsub.test/": 3
}
}
)",
kRenderUrl1, kAdComponentRenderUrls1);
ASSERT_TRUE(signals1);
EXPECT_FALSE(error_msg_.has_value());
EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1},")"
R"(adComponentRenderUrls":{"https://foosub.test/":2,)"
R"("https://bazsub.test/":3}})",
ExtractScoringSignals(signals1.get(), kRenderUrl1,
kAdComponentRenderUrls1));
scoped_refptr<TrustedSignals::Result> signals2 =
FetchScoringSignalsWithResponse(
GURL("https://url.test/?hostname=publisher"
"&renderUrls=https%3A%2F%2Fbar.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F,"
"https%3A%2F%2Fbazsub.test%2F"),
R"(
{
"renderUrls": {
"https://bar.test/": 4
},
"adComponentRenderUrls": {
"https://barsub.test/": 5,
"https://bazsub.test/": 6
}
}
)",
kRenderUrl2, kAdComponentRenderUrls2);
ASSERT_TRUE(signals2);
EXPECT_FALSE(error_msg_.has_value());
EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":4},")"
R"(adComponentRenderUrls":{"https://barsub.test/":5,)"
R"("https://bazsub.test/":6}})",
ExtractScoringSignals(signals2.get(), kRenderUrl2,
kAdComponentRenderUrls2));
}
// Test the case where there are multiple network requests live at once.
TEST_F(TrustedSignalsRequestManagerTest,
BiddingSignalsSimultaneousNetworkRequests) {
// Use partially overlapping keys, to cover both the shared and distinct key
// cases.
const std::vector<std::string> kKeys1{"key1", "key3"};
const GURL kUrl1 =
GURL("https://url.test/?hostname=publisher&keys=key1,key3");
const std::vector<std::string> kKeys2{"key2", "key3"};
const GURL kUrl2 =
GURL("https://url.test/?hostname=publisher&keys=key2,key3");
base::RunLoop run_loop1;
scoped_refptr<TrustedSignals::Result> signals1;
absl::optional<std::string> error_msg1;
auto request1 = bidding_request_manager_.RequestBiddingSignals(
kKeys1, base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1,
run_loop1.QuitClosure()));
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager_.RequestBiddingSignals(
kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(url_loader_factory_.IsPending(kUrl1.spec()));
ASSERT_TRUE(url_loader_factory_.IsPending(kUrl2.spec()));
// Note that these responses use different values for the shared key.
AddJsonResponse(&url_loader_factory_, kUrl1, R"({"key1":1,"key3":3})");
AddJsonResponse(&url_loader_factory_, kUrl2, R"({"key2":[2],"key3":[3]})");
run_loop1.Run();
EXPECT_FALSE(error_msg1);
ASSERT_TRUE(signals1);
EXPECT_EQ(R"({"key1":1,"key3":3})",
ExtractBiddingSignals(signals1.get(), kKeys1));
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"key2":[2],"key3":[3]})",
ExtractBiddingSignals(signals2.get(), kKeys2));
}
// Test the case where there are multiple network requests live at once.
TEST_F(TrustedSignalsRequestManagerTest,
ScoringSignalsSimultaneousNetworkRequests) {
// Use partially overlapping keys, to cover both the shared and distinct key
// cases.
const GURL kRenderUrl1 = GURL("https://foo.test/");
const std::vector<std::string> kAdComponentRenderUrls1{
"https://foosub.test/", "https://bazsub.test/"};
const GURL kSignalsUrl1 = GURL(
"https://url.test/?hostname=publisher"
"&renderUrls=https%3A%2F%2Ffoo.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fbazsub.test%2F,"
"https%3A%2F%2Ffoosub.test%2F");
const GURL kRenderUrl2 = GURL("https://bar.test/");
const std::vector<std::string> kAdComponentRenderUrls2{
"https://barsub.test/", "https://bazsub.test/"};
const GURL kSignalsUrl2 = GURL(
"https://url.test/?hostname=publisher"
"&renderUrls=https%3A%2F%2Fbar.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F,"
"https%3A%2F%2Fbazsub.test%2F");
base::RunLoop run_loop1;
scoped_refptr<TrustedSignals::Result> signals1;
absl::optional<std::string> error_msg1;
auto request1 = scoring_request_manager_.RequestScoringSignals(
kRenderUrl1, kAdComponentRenderUrls1,
base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1,
run_loop1.QuitClosure()));
scoring_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = scoring_request_manager_.RequestScoringSignals(
kRenderUrl2, kAdComponentRenderUrls2,
base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
scoring_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl1.spec()));
ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl2.spec()));
// Note that these responses use different values for the shared key.
AddJsonResponse(&url_loader_factory_, kSignalsUrl1, R"(
{
"renderUrls": {
"https://foo.test/": 1
},
"adComponentRenderUrls": {
"https://foosub.test/": 2,
"https://bazsub.test/": 3
}
}
)");
AddJsonResponse(&url_loader_factory_, kSignalsUrl2, R"(
{
"renderUrls": {
"https://bar.test/": 4
},
"adComponentRenderUrls": {
"https://barsub.test/": 5,
"https://bazsub.test/": 6
}
}
)");
run_loop1.Run();
EXPECT_FALSE(error_msg1);
ASSERT_TRUE(signals1);
EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1},")"
R"(adComponentRenderUrls":{"https://foosub.test/":2,)"
R"("https://bazsub.test/":3}})",
ExtractScoringSignals(signals1.get(), kRenderUrl1,
kAdComponentRenderUrls1));
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":4},")"
R"(adComponentRenderUrls":{"https://barsub.test/":5,)"
R"("https://bazsub.test/":6}})",
ExtractScoringSignals(signals2.get(), kRenderUrl2,
kAdComponentRenderUrls2));
}
TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsBatchedRequests) {
// Use partially overlapping keys, to cover both the shared and distinct key
// cases.
const std::vector<std::string> kKeys1{"key1", "key3"};
const std::vector<std::string> kKeys2{"key2", "key3"};
AddJsonResponse(
&url_loader_factory_,
GURL("https://url.test/?hostname=publisher&keys=key1,key2,key3"),
kBaseBiddingJson);
base::RunLoop run_loop1;
scoped_refptr<TrustedSignals::Result> signals1;
absl::optional<std::string> error_msg1;
auto request1 = bidding_request_manager_.RequestBiddingSignals(
kKeys1, base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1,
run_loop1.QuitClosure()));
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager_.RequestBiddingSignals(
kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
run_loop1.Run();
EXPECT_FALSE(error_msg1);
ASSERT_TRUE(signals1);
EXPECT_EQ(R"({"key1":1,"key3":"3"})",
ExtractBiddingSignals(signals1.get(), kKeys1));
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"key2":[2],"key3":"3"})",
ExtractBiddingSignals(signals2.get(), kKeys2));
}
TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsBatchedRequests) {
// Use partially overlapping keys, to cover both the shared and distinct key
// cases.
const GURL kRenderUrl1 = GURL("https://foo.test/");
const std::vector<std::string> kAdComponentRenderUrls1{
"https://foosub.test/", "https://bazsub.test/"};
const GURL kRenderUrl2 = GURL("https://bar.test/");
const std::vector<std::string> kAdComponentRenderUrls2{
"https://barsub.test/", "https://bazsub.test/"};
const GURL kRenderUrl3 = GURL("https://foo.test/");
const std::vector<std::string> kAdComponentRenderUrls3{};
AddJsonResponse(
&url_loader_factory_,
GURL("https://url.test/?hostname=publisher"
"&renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F,"
"https%3A%2F%2Fbazsub.test%2F,https%3A%2F%2Ffoosub.test%2F"),
kBaseScoringJson);
base::RunLoop run_loop1;
scoped_refptr<TrustedSignals::Result> signals1;
absl::optional<std::string> error_msg1;
auto request1 = scoring_request_manager_.RequestScoringSignals(
kRenderUrl1, kAdComponentRenderUrls1,
base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1,
run_loop1.QuitClosure()));
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = scoring_request_manager_.RequestScoringSignals(
kRenderUrl2, kAdComponentRenderUrls2,
base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
base::RunLoop run_loop3;
scoped_refptr<TrustedSignals::Result> signals3;
absl::optional<std::string> error_msg3;
auto request3 = scoring_request_manager_.RequestScoringSignals(
kRenderUrl3, kAdComponentRenderUrls3,
base::BindOnce(&LoadSignalsCallback, &signals3, &error_msg3,
run_loop3.QuitClosure()));
scoring_request_manager_.StartBatchedTrustedSignalsRequest();
run_loop1.Run();
EXPECT_FALSE(error_msg1);
ASSERT_TRUE(signals1);
EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1},")"
R"(adComponentRenderUrls":{"https://foosub.test/":2,)"
R"("https://bazsub.test/":"4"}})",
ExtractScoringSignals(signals1.get(), kRenderUrl1,
kAdComponentRenderUrls1));
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":[2]},")"
R"(adComponentRenderUrls":{"https://barsub.test/":[3],)"
R"("https://bazsub.test/":"4"}})",
ExtractScoringSignals(signals2.get(), kRenderUrl2,
kAdComponentRenderUrls2));
run_loop3.Run();
EXPECT_FALSE(error_msg3);
ASSERT_TRUE(signals3);
EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1}})",
ExtractScoringSignals(signals3.get(), kRenderUrl3,
kAdComponentRenderUrls3));
}
// Make two requests, cancel both, then try to start a network request. No
// requests should be made. Only test bidders, since sellers have no significant
// differences in this path.
TEST_F(TrustedSignalsRequestManagerTest, CancelAllQueuedRequests) {
const std::vector<std::string> kKeys1{"key1"};
const std::vector<std::string> kKeys2{"key2"};
auto request1 = bidding_request_manager_.RequestBiddingSignals(
kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback));
auto request2 = bidding_request_manager_.RequestBiddingSignals(
kKeys2, base::BindOnce(&NeverInvokedLoadSignalsCallback));
request1.reset();
request2.reset();
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, url_loader_factory_.NumPending());
}
// Make two requests, cancel the first one, then try to start a network request.
// A request should be made, but only for the key in the request that was not
// cancelled. Only test bidders, since sellers have no significant differences
// in this path.
TEST_F(TrustedSignalsRequestManagerTest, CancelOneRequest) {
const std::vector<std::string> kKeys1{"key1"};
const std::vector<std::string> kKeys2{"key2"};
// The request for `key1` will be cancelled before the network request is
// created.
AddJsonResponse(&url_loader_factory_,
GURL("https://url.test/?hostname=publisher&keys=key2"),
kBaseBiddingJson);
auto request1 = bidding_request_manager_.RequestBiddingSignals(
kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback));
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager_.RequestBiddingSignals(
kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
request1.reset();
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop().RunUntilIdle();
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals2.get(), kKeys2));
}
// Make two requests, try to start a network request, then cancel both requests.
// The network request should be cancelled. Only test bidders, since sellers
// have no significant differences in this path.
TEST_F(TrustedSignalsRequestManagerTest, CancelAllLiveRequests) {
const std::vector<std::string> kKeys1{"key1"};
const std::vector<std::string> kKeys2{"key2"};
const GURL kSignalsUrl =
GURL("https://url.test/?hostname=publisher&keys=key1,key2");
auto request1 = bidding_request_manager_.RequestBiddingSignals(
kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback));
auto request2 = bidding_request_manager_.RequestBiddingSignals(
kKeys2, base::BindOnce(&NeverInvokedLoadSignalsCallback));
// Wait for network request to be made, which should include both keys in the
// URLs.
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl.spec()));
// Cancel both requests, which should cancel the network request.
request1.reset();
request2.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(
(*url_loader_factory_.pending_requests())[0].client.is_connected());
}
// Make two requests, try to start a network request, then cancel the first one.
// The request that was not cancelled should complete normally. Only test
// bidders, since sellers have no significant differences in this path.
TEST_F(TrustedSignalsRequestManagerTest, CancelOneLiveRequest) {
const std::vector<std::string> kKeys1{"key1"};
const std::vector<std::string> kKeys2{"key2"};
const GURL kSignalsUrl =
GURL("https://url.test/?hostname=publisher&keys=key1,key2");
auto request1 = bidding_request_manager_.RequestBiddingSignals(
kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback));
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager_.RequestBiddingSignals(
kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
// Wait for network request to be made, which should include both keys in the
// URLs.
bidding_request_manager_.StartBatchedTrustedSignalsRequest();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl.spec()));
// Cancel `request1` and then serve the JSON.
request1.reset();
AddJsonResponse(&url_loader_factory_, kSignalsUrl, kBaseBiddingJson);
// `request2` should still complete.
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals2.get(), kKeys2));
// The callback of `request1` should not be invoked, since it was cancelled.
base::RunLoop().RunUntilIdle();
}
// Test that when `automatically_send_requests` is false, requests are not
// automatically started.
TEST_F(TrustedSignalsRequestManagerTest, AutomaticallySendRequestsDisabled) {
const std::vector<std::string> kKeys{"key1"};
auto request = bidding_request_manager_.RequestBiddingSignals(
kKeys, base::BindOnce(&NeverInvokedLoadSignalsCallback));
task_environment_.FastForwardBy(base::Hours(1));
EXPECT_EQ(0, url_loader_factory_.NumPending());
}
TEST_F(TrustedSignalsRequestManagerTest, AutomaticallySendRequestsEnabled) {
const std::vector<std::string> kKeys1{"key1"};
const std::vector<std::string> kKeys2{"key2"};
const std::vector<std::string> kKeys3{"key3"};
// Create a new bidding request manager with `automatically_send_requests`
// enabled.
TrustedSignalsRequestManager bidding_request_manager(
TrustedSignalsRequestManager::Type::kBiddingSignals, &url_loader_factory_,
/*automatically_send_requests=*/true,
url::Origin::Create(GURL(kTopLevelOrigin)), trusted_signals_url_,
v8_helper_.get());
// Create one Request.
base::RunLoop run_loop1;
scoped_refptr<TrustedSignals::Result> signals1;
absl::optional<std::string> error_msg1;
auto request1 = bidding_request_manager.RequestBiddingSignals(
kKeys1, base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1,
run_loop1.QuitClosure()));
// Wait until just before the timer triggers. No network requests should be
// made.
task_environment_.FastForwardBy(TrustedSignalsRequestManager::kAutoSendDelay -
kTinyTime);
EXPECT_EQ(0, url_loader_factory_.NumPending());
// Create another Request.
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager.RequestBiddingSignals(
kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
// Wait until the exact time the timer should trigger. A single network
// request should be made, covering both Requests.
task_environment_.FastForwardBy(kTinyTime);
ASSERT_EQ(1, url_loader_factory_.NumPending());
AddJsonResponse(&url_loader_factory_,
GURL("https://url.test/?hostname=publisher&keys=key1,key2"),
kBaseBiddingJson);
run_loop1.Run();
EXPECT_FALSE(error_msg1);
ASSERT_TRUE(signals1);
EXPECT_EQ(R"({"key1":1})", ExtractBiddingSignals(signals1.get(), kKeys1));
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals2.get(), kKeys2));
// Create one more request, and wait for the timer to trigger again, to make
// sure it restarts correctly.
base::RunLoop run_loop3;
scoped_refptr<TrustedSignals::Result> signals3;
absl::optional<std::string> error_msg3;
auto request3 = bidding_request_manager.RequestBiddingSignals(
kKeys3, base::BindOnce(&LoadSignalsCallback, &signals3, &error_msg3,
run_loop3.QuitClosure()));
task_environment_.FastForwardBy(TrustedSignalsRequestManager::kAutoSendDelay);
EXPECT_EQ(1, url_loader_factory_.NumPending());
// Complete the request.
AddJsonResponse(&url_loader_factory_,
GURL("https://url.test/?hostname=publisher&keys=key3"),
kBaseBiddingJson);
run_loop3.Run();
EXPECT_FALSE(error_msg3);
ASSERT_TRUE(signals3);
EXPECT_EQ(R"({"key3":"3"})", ExtractBiddingSignals(signals3.get(), kKeys3));
}
TEST_F(TrustedSignalsRequestManagerTest,
AutomaticallySendRequestsCancelAllRequestsRestartsTimer) {
const std::vector<std::string> kKeys1{"key1"};
const std::vector<std::string> kKeys2{"key2"};
// Create a new bidding request manager with `automatically_send_requests`
// enabled.
TrustedSignalsRequestManager bidding_request_manager(
TrustedSignalsRequestManager::Type::kBiddingSignals, &url_loader_factory_,
/*automatically_send_requests=*/true,
url::Origin::Create(GURL(kTopLevelOrigin)), trusted_signals_url_,
v8_helper_.get());
// Create one Request.
auto request1 = bidding_request_manager.RequestBiddingSignals(
kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback));
// Wait until just before the timer triggers. No network requests should be
// made.
task_environment_.FastForwardBy(TrustedSignalsRequestManager::kAutoSendDelay -
kTinyTime);
EXPECT_EQ(0, url_loader_factory_.NumPending());
// Cancel the request. The timer should be stopped.
request1.reset();
// Create another Request.
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager.RequestBiddingSignals(
kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
// Wait until just before the timer triggers if it were correctly restarted.
// No network requests should be made.
task_environment_.FastForwardBy(TrustedSignalsRequestManager::kAutoSendDelay -
kTinyTime);
ASSERT_EQ(0, url_loader_factory_.NumPending());
// Wait until the exact time the timer should trigger. A single network
// request should be made for just the second Request.
task_environment_.FastForwardBy(kTinyTime);
ASSERT_EQ(1, url_loader_factory_.NumPending());
AddJsonResponse(&url_loader_factory_,
GURL("https://url.test/?hostname=publisher&keys=key2"),
kBaseBiddingJson);
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals2.get(), kKeys2));
}
TEST_F(TrustedSignalsRequestManagerTest,
AutomaticallySendRequestsCancelSomeRequestsDoesNotRestartTimer) {
const std::vector<std::string> kKeys1{"key1"};
const std::vector<std::string> kKeys2{"key2"};
// Create a new bidding request manager with `automatically_send_requests`
// enabled.
TrustedSignalsRequestManager bidding_request_manager(
TrustedSignalsRequestManager::Type::kBiddingSignals, &url_loader_factory_,
/*automatically_send_requests=*/true,
url::Origin::Create(GURL(kTopLevelOrigin)), trusted_signals_url_,
v8_helper_.get());
// Create one Request.
auto request1 = bidding_request_manager.RequestBiddingSignals(
kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback));
// Wait until just before the timer triggers. No network requests should be
// made.
task_environment_.FastForwardBy(TrustedSignalsRequestManager::kAutoSendDelay -
kTinyTime);
EXPECT_EQ(0, url_loader_factory_.NumPending());
// Create another Request.
base::RunLoop run_loop2;
scoped_refptr<TrustedSignals::Result> signals2;
absl::optional<std::string> error_msg2;
auto request2 = bidding_request_manager.RequestBiddingSignals(
kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2,
run_loop2.QuitClosure()));
// Cancel the first request. The timer should not be stopped, since there's
// still a request pending.
request1.reset();
// Wait until the exact time the timer should trigger. A single network
// request should be made for just the second Request.
task_environment_.FastForwardBy(kTinyTime);
ASSERT_EQ(1, url_loader_factory_.NumPending());
AddJsonResponse(&url_loader_factory_,
GURL("https://url.test/?hostname=publisher&keys=key2"),
kBaseBiddingJson);
run_loop2.Run();
EXPECT_FALSE(error_msg2);
ASSERT_TRUE(signals2);
EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals2.get(), kKeys2));
}
} // namespace
} // namespace auction_worklet