blob: 5657dc539980745cea34ccfb70a4dbbfedc35473 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.service.adselection;
import static android.adservices.adselection.CustomAudienceBiddingInfoFixture.DATA_VERSION_1;
import static android.adservices.adselection.CustomAudienceBiddingInfoFixture.DATA_VERSION_2;
import static android.adservices.common.AdServicesStatusUtils.RATE_LIMIT_REACHED_ERROR_MESSAGE;
import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
import static android.adservices.common.AdServicesStatusUtils.STATUS_TIMEOUT;
import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
import static com.android.adservices.data.adselection.AdSelectionDatabase.DATABASE_NAME;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_CONNECT_TIMEOUT_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_READ_TIMEOUT_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_REPORT_IMPRESSION_OVERALL_TIMEOUT_MS;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_AD_SELECTION_FAILURE;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_NO_BUYERS_OR_CONTEXTUAL_ADS_AVAILABLE;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_NO_CA_AND_CONTEXTUAL_ADS_AVAILABLE;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_NO_VALID_BIDS_OR_CONTEXTUAL_ADS_FOR_SCORING;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_NO_WINNING_AD_FOUND;
import static com.android.adservices.service.adselection.AdSelectionScriptEngine.NUM_BITS_STOCHASTIC_ROUNDING;
import static com.android.adservices.service.adselection.AdsScoreGeneratorImpl.MISSING_TRUSTED_SCORING_SIGNALS;
import static com.android.adservices.service.adselection.AdsScoreGeneratorImpl.SCORING_TIMED_OUT;
import static com.android.adservices.service.adselection.DataVersionFetcher.DATA_VERSION_HEADER_BIDDING_KEY;
import static com.android.adservices.service.adselection.DataVersionFetcher.DATA_VERSION_HEADER_SCORING_KEY;
import static com.android.adservices.service.adselection.JsFetcher.MISSING_SCORING_LOGIC;
import static com.android.adservices.service.adselection.PrebuiltLogicGenerator.AD_SELECTION_HIGHEST_BID_WINS;
import static com.android.adservices.service.adselection.PrebuiltLogicGenerator.AD_SELECTION_PREBUILT_SCHEMA;
import static com.android.adservices.service.adselection.PrebuiltLogicGenerator.AD_SELECTION_USE_CASE;
import static com.android.adservices.service.adselection.PrebuiltLogicGenerator.PREBUILT_FEATURE_IS_DISABLED;
import static com.android.adservices.service.stats.AdSelectionExecutionLoggerTest.DB_AD_SELECTION_FILE_SIZE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.isA;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalMatchers.geq;
import static org.mockito.Mockito.timeout;
import android.adservices.adid.AdId;
import android.adservices.adselection.AdSelectionCallback;
import android.adservices.adselection.AdSelectionConfig;
import android.adservices.adselection.AdSelectionConfigFixture;
import android.adservices.adselection.AdSelectionInput;
import android.adservices.adselection.AdSelectionResponse;
import android.adservices.adselection.ReportImpressionCallback;
import android.adservices.adselection.ReportImpressionInput;
import android.adservices.adselection.SignedContextualAds;
import android.adservices.adselection.SignedContextualAdsFixture;
import android.adservices.common.AdDataFixture;
import android.adservices.common.AdSelectionSignals;
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.common.AdTechIdentifier;
import android.adservices.common.CallerMetadata;
import android.adservices.common.CallingAppUidSupplierProcessImpl;
import android.adservices.common.CommonFixture;
import android.adservices.common.FledgeErrorResponse;
import android.adservices.customaudience.CustomAudienceFixture;
import android.adservices.customaudience.TrustedBiddingDataFixture;
import android.adservices.http.MockWebServerRule;
import android.content.Context;
import android.net.Uri;
import android.os.LimitExceededException;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.webkit.WebView;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.LoggerFactory;
import com.android.adservices.MockWebServerRuleFactory;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.adselection.AdSelectionDatabase;
import com.android.adservices.data.adselection.AdSelectionDebugReportDao;
import com.android.adservices.data.adselection.AdSelectionDebugReportingDatabase;
import com.android.adservices.data.adselection.AdSelectionEntryDao;
import com.android.adservices.data.adselection.AdSelectionServerDatabase;
import com.android.adservices.data.adselection.AppInstallDao;
import com.android.adservices.data.adselection.DBAdSelectionOverride;
import com.android.adservices.data.adselection.DBBuyerDecisionOverride;
import com.android.adservices.data.adselection.EncryptionContextDao;
import com.android.adservices.data.adselection.EncryptionKeyDao;
import com.android.adservices.data.adselection.FrequencyCapDao;
import com.android.adservices.data.adselection.SharedStorageDatabase;
import com.android.adservices.data.common.DBAdData;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
import com.android.adservices.data.customaudience.DBCustomAudience;
import com.android.adservices.data.customaudience.DBCustomAudienceOverride;
import com.android.adservices.data.customaudience.DBTrustedBiddingData;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.signals.EncodedPayloadDao;
import com.android.adservices.data.signals.ProtectedSignalsDatabase;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.adid.AdIdCacheManager;
import com.android.adservices.service.adselection.encryption.ObliviousHttpEncryptor;
import com.android.adservices.service.common.AdSelectionServiceFilter;
import com.android.adservices.service.common.FledgeAuthorizationFilter;
import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.common.cache.CacheProviderFactory;
import com.android.adservices.service.common.cache.FledgeHttpCache;
import com.android.adservices.service.common.cache.HttpCache;
import com.android.adservices.service.common.httpclient.AdServicesHttpsClient;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.devapi.AdSelectionDevOverridesHelper;
import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.devapi.DevContextFilter;
import com.android.adservices.service.exception.FilterException;
import com.android.adservices.service.js.JSScriptEngine;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.AdServicesStatsLog;
import com.android.adservices.service.stats.RunAdBiddingProcessReportedStats;
import com.android.adservices.service.stats.RunAdScoringProcessReportedStats;
import com.android.adservices.service.stats.RunAdSelectionProcessReportedStats;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.mockwebserver.Dispatcher;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
import com.google.mockwebserver.RecordedRequest;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.io.File;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* This test the actual flow of Ad Selection internal flow without any mocking. The dependencies in
* this test are invoked and used in real time.
*/
public class AdSelectionE2ETest {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
private static final int CALLER_UID = Process.myUid();
private static final String ERROR_SCORE_AD_LOGIC_MISSING = "scoreAd is not defined";
private static final AdTechIdentifier BUYER_1 = AdSelectionConfigFixture.BUYER_1;
private static final AdTechIdentifier BUYER_2 = AdSelectionConfigFixture.BUYER_2;
private static final AdTechIdentifier BUYER_3 = AdSelectionConfigFixture.BUYER_3;
private static final String BUYER = "buyer";
private static final String AD_URI_PREFIX = "http://www.domain.com/adverts/123/";
private static final String DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX = "";
private static final String BUYER_BIDDING_LOGIC_URI_PATH = "/buyer/bidding/logic/";
private static final String BUYER_BIDDING_LOGIC_URI_PATH_AD_COST =
"/buyer/bidding/logic/adCost/";
private static final String BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION =
"/kv/buyer/withDataVersion/signals/";
private static final String BUYER_TRUSTED_SIGNAL_URI_PATH = "/kv/buyer/signals/";
private static final String BUYER_TRUSTED_SIGNAL_PARAMS =
"?keys=example%2Cvalid%2Clist%2Cof%2Ckeys";
private static final String SELLER_DECISION_LOGIC_URI_PATH = "/ssp/decision/logic/";
private static final String SELLER_TRUSTED_SIGNAL_URI_PATH = "/kv/seller/signals/";
private static final String SELLER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION =
"/kv/seller/withDataVersion/signals/";
private static final String SELLER_TRUSTED_SIGNAL_PARAMS = "?renderuris=";
private static final String SELLER_REPORTING_URI_PATH = "ssp/reporting/";
private static final String BUYER_REPORTING_URI_PATH = "dsp/reporting/";
public static final String READ_BID_FROM_AD_METADATA_JS =
"function generateBid(ad, auction_signals, per_buyer_signals,"
+ " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
+ "\n"
+ "function reportWin(ad_selection_signals, per_buyer_signals,"
+ " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ " return {'status': 0, 'results': {'reporting_uri': '%s' } };\n"
+ "}";
public static final String READ_BID_FROM_AD_METADATA_JS_WITH_AD_COST =
"function generateBid(ad, auction_signals, per_buyer_signals, trusted_bidding_signals,"
+ " contextual_signals, custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result, 'adCost':"
+ " ad.metadata.adCost };\n"
+ "}\n"
+ "\n"
+ "function reportWin(ad_selection_signals, per_buyer_signals, signals_for_buyer,"
+ " contextual_signals, custom_audience_signals) { \n"
+ " return {'status': 0, 'results': {'reporting_uri': '%s' } };\n"
+ "}";
private static final String READ_BID_FROM_AD_METADATA_JS_V3 =
"function generateBid(custom_audience, auction_signals, per_buyer_signals,\n"
+ " trusted_bidding_signals, contextual_signals) {\n"
+ " const ads = custom_audience.ads;\n"
+ " let result = null;\n"
+ " for (const ad of ads) {\n"
+ " if (!result || ad.metadata.result > result.metadata.result) {\n"
+ " result = ad;\n"
+ " }\n"
+ " }\n"
+ " return { 'status': 0, 'ad': result, 'bid': result.metadata.result, "
+ "'render': result.render_uri };\n"
+ "}";
private static final String READ_BID_FROM_AD_METADATA_JS_V3_WITH_AD_COST =
"function generateBid(custom_audience, auction_signals, per_buyer_signals,\n"
+ " trusted_bidding_signals, contextual_signals) {\n"
+ " const ads = custom_audience.ads;\n"
+ " let result = null;\n"
+ " for (const ad of ads) {\n"
+ " if (!result || ad.metadata.result > result.metadata.result) {\n"
+ " result = ad;\n"
+ " }\n"
+ " }\n"
+ " return { 'status': 0, 'ad': result, 'bid': result.metadata.result, "
+ "'render': result.render_uri, 'adCost': result.metadata.adCost };\n"
+ "}";
public static final String USE_BID_AS_SCORE_JS =
"//From dispatcher USE_BID_AS_SCORE_JS\n"
+ "function scoreAd(ad, bid, auction_config, seller_signals,"
+ " trusted_scoring_signals, contextual_signal, user_signal,"
+ " custom_audience_signal) { \n"
+ " return {'status': 0, 'score': bid };\n"
+ "}\n"
+ "\n"
+ "function reportResult(ad_selection_config, render_uri, bid, contextual_signals)"
+ " { \n"
+ " return {'status': 0, 'results': {'signals_for_buyer':"
+ " '{\"signals_for_buyer\":1}', 'reporting_uri': '%s' } };\n"
+ "}";
private static final String SELECTION_PICK_HIGHEST_LOGIC_JS_PATH =
"/selectionPickHighestLogicJS/";
private static final String SELECTION_PICK_NONE_LOGIC_JS_PATH = "/selectionPickNoneLogicJS/";
private static final String SELECTION_WATERFALL_LOGIC_JS_PATH = "/selectionWaterfallLogicJS/";
private static final String SELECTION_PICK_HIGHEST_LOGIC_JS =
"function selectOutcome(outcomes, selection_signals) {\n"
+ " let max_bid = 0;\n"
+ " let winner_outcome = null;\n"
+ " for (let outcome of outcomes) {\n"
+ " if (outcome.bid > max_bid) {\n"
+ " max_bid = outcome.bid;\n"
+ " winner_outcome = outcome;\n"
+ " }\n"
+ " }\n"
+ " return {'status': 0, 'result': winner_outcome};\n"
+ "}";
private static final String SELECTION_PICK_NONE_LOGIC_JS =
"function selectOutcome(outcomes, selection_signals) {\n"
+ " return {'status': 0, 'result': null};\n"
+ "}";
private static final String SELECTION_WATERFALL_LOGIC_JS =
"function selectOutcome(outcomes, selection_signals) {\n"
+ " if (outcomes.length != 1 || selection_signals.bid_floor =="
+ " undefined) return null;\n"
+ "\n"
+ " const outcome_1p = outcomes[0];\n"
+ " return {'status': 0, 'result': (outcome_1p.bid >"
+ " selection_signals.bid_floor) ? outcome_1p : null};\n"
+ "}";
private static final Map<String, String> TRUSTED_BIDDING_SIGNALS_SERVER_DATA =
new ImmutableMap.Builder<String, String>()
.put("example", "example")
.put("valid", "Also valid")
.put("list", "list")
.put("of", "of")
.put("keys", "trusted bidding signal Values")
.build();
private static final AdSelectionSignals TRUSTED_SCORING_SIGNALS =
AdSelectionSignals.fromString(
"{\n"
+ "\t\"render_uri_1\": \"signals_for_1\",\n"
+ "\t\"render_uri_2\": \"signals_for_2\"\n"
+ "}");
private static final MockWebServerRule.RequestMatcher<String> REQUEST_PREFIX_MATCHER =
(a, b) -> !b.isEmpty() && a.startsWith(b);
private static final AdCost AD_COST_1 = new AdCost(1.2, NUM_BITS_STOCHASTIC_ROUNDING);
private static final AdCost AD_COST_2 = new AdCost(2.2, NUM_BITS_STOCHASTIC_ROUNDING);
// TODO(b/275657377) Refactor duplicate dispatchers
public static final Dispatcher DISPATCHER_V2_ONLY_BIDDING_LOGIC =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().equals(SELECTION_PICK_HIGHEST_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_HIGHEST_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_PICK_NONE_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_NONE_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_WATERFALL_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_WATERFALL_LOGIC_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
sLogger.w("Unexpected call to MockWebServer " + request.getPath());
return new MockResponse().setResponseCode(404);
}
};
public static final Dispatcher DISPATCHER_V3_BIDDING_LOGIC =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
String headerName =
JsVersionHelper.getVersionHeaderName(
JsVersionHelper.JS_PAYLOAD_TYPE_BUYER_BIDDING_LOGIC_JS);
long versionFromHeader = Long.parseLong(request.getHeader(headerName));
if (JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3
== versionFromHeader) {
return new MockResponse()
.setBody(READ_BID_FROM_AD_METADATA_JS_V3)
.setHeader(headerName, versionFromHeader);
}
} else if ((BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_2.toString())
.equals(request.getPath())) {
String headerName =
JsVersionHelper.getVersionHeaderName(
JsVersionHelper.JS_PAYLOAD_TYPE_BUYER_BIDDING_LOGIC_JS);
long versionFromHeader = Long.parseLong(request.getHeader(headerName));
if (JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3
== versionFromHeader) {
return new MockResponse()
.setBody(READ_BID_FROM_AD_METADATA_JS_V3_WITH_AD_COST)
.setHeader(headerName, versionFromHeader);
}
} else if (request.getPath().equals(SELECTION_PICK_HIGHEST_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_HIGHEST_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_PICK_NONE_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_NONE_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_WATERFALL_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_WATERFALL_LOGIC_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
} else if (request.getPath()
.startsWith(
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION
+ BUYER_1.toString())) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse()
.setBody(new JSONObject(jsonMap).toString())
.addHeader(DATA_VERSION_HEADER_BIDDING_KEY, DATA_VERSION_1);
} else if (request.getPath()
.startsWith(
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION
+ BUYER_2.toString())) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse()
.setBody(new JSONObject(jsonMap).toString())
.addHeader(DATA_VERSION_HEADER_BIDDING_KEY, DATA_VERSION_2);
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
} else if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse()
.setBody(TRUSTED_SCORING_SIGNALS.toString())
.addHeader(DATA_VERSION_HEADER_SCORING_KEY, DATA_VERSION_1);
}
sLogger.w("Unexpected call to MockWebServer " + request.getPath());
return new MockResponse().setResponseCode(404);
}
};
public static final Dispatcher DISPATCHER_V3_BIDDING_LOGIC_HEADER_WITH_V2_LOGIC =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
String headerName =
JsVersionHelper.getVersionHeaderName(
JsVersionHelper.JS_PAYLOAD_TYPE_BUYER_BIDDING_LOGIC_JS);
return new MockResponse()
.setBody(READ_BID_FROM_AD_METADATA_JS)
.setHeader(
headerName,
JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3);
} else if (request.getPath().equals(SELECTION_PICK_HIGHEST_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_HIGHEST_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_PICK_NONE_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_NONE_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_WATERFALL_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_WATERFALL_LOGIC_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
sLogger.w("Unexpected call to MockWebServer " + request.getPath());
return new MockResponse().setResponseCode(404);
}
};
public static final Dispatcher DISPATCHER_TOO_HIGH_BIDDING_LOGIC_JS_VERSION =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
String headerName =
JsVersionHelper.getVersionHeaderName(
JsVersionHelper.JS_PAYLOAD_TYPE_BUYER_BIDDING_LOGIC_JS);
return new MockResponse()
.setBody(READ_BID_FROM_AD_METADATA_JS)
.setHeader(
headerName,
JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3
+ 1);
} else if (request.getPath().equals(SELECTION_PICK_HIGHEST_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_HIGHEST_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_PICK_NONE_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_NONE_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_WATERFALL_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_WATERFALL_LOGIC_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
sLogger.w("Unexpected call to MockWebServer " + request.getPath());
return new MockResponse().setResponseCode(404);
}
};
private static final AdTechIdentifier SELLER_VALID =
AdTechIdentifier.fromString("developer.android.com");
private static final Uri DECISION_LOGIC_URI_INCONSISTENT =
Uri.parse("https://developer%$android.com/test/decisions_logic_uris");
private static final long BINDER_ELAPSED_TIME_MS = 100L;
private static final String CALLER_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME;
private static final String MY_APP_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME;
private final AdServicesLogger mAdServicesLoggerMock =
ExtendedMockito.mock(AdServicesLoggerImpl.class);
private Flags mFlags = new AdSelectionE2ETestFlags();
@Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
// Mocking DevContextFilter to test behavior with and without override api authorization
@Mock DevContextFilter mDevContextFilter;
@Mock CallerMetadata mMockCallerMetadata;
@Mock FledgeHttpCache.HttpCacheObserver mCacheObserver;
@Spy private Context mContext = ApplicationProvider.getApplicationContext();
@Mock private File mMockDBAdSelectionFile;
@Mock private ConsentManager mConsentManagerMock;
private FledgeAuthorizationFilter mFledgeAuthorizationFilterSpy =
spy(
new FledgeAuthorizationFilter(
mContext.getPackageManager(),
new EnrollmentDao(
mContext, DbTestUtil.getSharedDbHelperForTest(), mFlags),
mAdServicesLoggerMock));
private MockitoSession mStaticMockSession = null;
private ExecutorService mLightweightExecutorService;
private ExecutorService mBackgroundExecutorService;
private ScheduledThreadPoolExecutor mScheduledExecutor;
private CustomAudienceDao mCustomAudienceDao;
private EncodedPayloadDao mEncodedPayloadDao;
private AppInstallDao mAppInstallDao;
private FrequencyCapDao mFrequencyCapDao;
private EncryptionKeyDao mEncryptionKeyDao;
private EncryptionContextDao mEncryptionContextDao;
@Spy private AdSelectionEntryDao mAdSelectionEntryDaoSpy;
private AdServicesHttpsClient mAdServicesHttpsClient;
private AdSelectionConfig mAdSelectionConfig;
private AdSelectionServiceImpl mAdSelectionService;
private Dispatcher mDispatcher;
private AdTechIdentifier mSeller;
private AdFilteringFeatureFactory mAdFilteringFeatureFactory;
@Mock private AdSelectionServiceFilter mAdSelectionServiceFilter;
@Mock private ObliviousHttpEncryptor mObliviousHttpEncryptor;
private AdSelectionDebugReportDao mAdSelectionDebugReportDao;
private MockAdIdWorker mMockAdIdWorker;
private AdIdFetcher mAdIdFetcher;
@Before
public void setUp() throws Exception {
// Every test in this class requires that the JS Sandbox be available. The JS Sandbox
// availability depends on an external component (the system webview) being higher than a
// certain minimum version. Marking that as an assumption that the test is making.
Assume.assumeTrue(JSScriptEngine.AvailabilityChecker.isJSSandboxAvailable());
mAdSelectionEntryDaoSpy =
Room.inMemoryDatabaseBuilder(mContext, AdSelectionDatabase.class)
.build()
.adSelectionEntryDao();
mAppInstallDao =
Room.inMemoryDatabaseBuilder(mContext, SharedStorageDatabase.class)
.build()
.appInstallDao();
mFrequencyCapDao =
Room.inMemoryDatabaseBuilder(mContext, SharedStorageDatabase.class)
.build()
.frequencyCapDao();
AdSelectionServerDatabase serverDb =
Room.inMemoryDatabaseBuilder(mContext, AdSelectionServerDatabase.class).build();
mEncryptionContextDao = serverDb.encryptionContextDao();
mEncryptionKeyDao = serverDb.encryptionKeyDao();
mAdFilteringFeatureFactory =
new AdFilteringFeatureFactory(mAppInstallDao, mFrequencyCapDao, mFlags);
// Test applications don't have the required permissions to read config P/H flags, and
// injecting mocked flags everywhere is annoying and non-trivial for static methods
mStaticMockSession =
ExtendedMockito.mockitoSession()
.spyStatic(FlagsFactory.class)
.spyStatic(WebView.class)
.initMocks(this)
.strictness(Strictness.LENIENT)
.startMocking();
// Initialize dependencies for the AdSelectionService
mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor();
mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor();
mScheduledExecutor = AdServicesExecutors.getScheduler();
mCustomAudienceDao =
Room.inMemoryDatabaseBuilder(mContext, CustomAudienceDatabase.class)
.addTypeConverter(new DBCustomAudience.Converters(true, true))
.build()
.customAudienceDao();
mEncodedPayloadDao =
Room.inMemoryDatabaseBuilder(mContext, ProtectedSignalsDatabase.class)
.build()
.getEncodedPayloadDao();
mAdServicesHttpsClient =
new AdServicesHttpsClient(
AdServicesExecutors.getBlockingExecutor(),
CacheProviderFactory.createNoOpCache());
mAdSelectionDebugReportDao =
Room.inMemoryDatabaseBuilder(mContext, AdSelectionDebugReportingDatabase.class)
.build()
.getAdSelectionDebugReportDao();
mMockAdIdWorker = new MockAdIdWorker(new AdIdCacheManager(mContext));
mAdIdFetcher =
new AdIdFetcher(mMockAdIdWorker, mLightweightExecutorService, mScheduledExecutor);
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
when(mMockCallerMetadata.getBinderElapsedTimestamp())
.thenReturn(SystemClock.elapsedRealtime() - BINDER_ELAPSED_TIME_MS);
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Create a dispatcher that helps map a request -> response in mockWebServer
Uri uriPathForScoringWithReportResults =
mMockWebServerRule.uriForPath(SELLER_REPORTING_URI_PATH);
Uri uriPathForBiddingWithReportResults =
mMockWebServerRule.uriForPath(BUYER_REPORTING_URI_PATH);
mDispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse()
.setBody(
String.format(
USE_BID_AS_SCORE_JS,
uriPathForScoringWithReportResults));
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse()
.setBody(
String.format(
READ_BID_FROM_AD_METADATA_JS,
uriPathForBiddingWithReportResults));
} else if ((BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse()
.setBody(READ_BID_FROM_AD_METADATA_JS_WITH_AD_COST);
} else if (request.getPath()
.startsWith(
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION
+ BUYER_1.toString())) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse()
.setBody(new JSONObject(jsonMap).toString())
.addHeader(DATA_VERSION_HEADER_BIDDING_KEY, DATA_VERSION_1);
} else if (request.getPath()
.startsWith(
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION
+ BUYER_2.toString())) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse()
.setBody(new JSONObject(jsonMap).toString())
.addHeader(DATA_VERSION_HEADER_BIDDING_KEY, DATA_VERSION_2);
} else if (request.getPath().equals(SELECTION_PICK_HIGHEST_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_HIGHEST_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_PICK_NONE_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_PICK_NONE_LOGIC_JS);
} else if (request.getPath().equals(SELECTION_WATERFALL_LOGIC_JS_PATH)) {
return new MockResponse().setBody(SELECTION_WATERFALL_LOGIC_JS);
} else if (SELLER_REPORTING_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody("");
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
} else if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse()
.setBody(TRUSTED_SCORING_SIGNALS.toString())
.addHeader(DATA_VERSION_HEADER_SCORING_KEY, DATA_VERSION_1);
}
sLogger.w("Unexpected call to MockWebServer " + request.getPath());
return new MockResponse().setResponseCode(404);
}
};
mSeller =
AdTechIdentifier.fromString(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH).getHost());
// Create an Ad Selection Config with the buyers and decision logic URI
// the URI points to a JS with score generation logic
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
when(mContext.getDatabasePath(DATABASE_NAME)).thenReturn(mMockDBAdSelectionFile);
when(mMockDBAdSelectionFile.length()).thenReturn(DB_AD_SELECTION_FILE_SIZE);
doNothing()
.when(mAdSelectionServiceFilter)
.filterRequest(
mSeller,
CALLER_PACKAGE_NAME,
true,
true,
CALLER_UID,
AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
Throttler.ApiKey.FLEDGE_API_SELECT_ADS,
DevContext.createForDevOptionsDisabled());
mMockAdIdWorker.setResult(AdId.ZERO_OUT, true);
}
@After
public void tearDown() {
if (mStaticMockSession != null) {
mStaticMockSession.finishMocking();
}
}
@Test
public void testRunAdSelectionSuccess_preV3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_preV3BiddingLogicWithAdCostCpcBillingEnabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithCPCEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeCpcBillingEnabled() {
return true;
}
};
doReturn(flagsWithCPCEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithCPCEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudienceWithAdCost(
BUYER_1,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_1),
bidsForBuyer1,
AD_COST_1.getAdCost());
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudienceWithAdCost(
BUYER_2,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_2),
bidsForBuyer2,
AD_COST_2.getAdCost());
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert adCost was propagated through
String expectedBuyerContextualSignals =
BuyerContextualSignals.builder().setAdCost(AD_COST_2).build().toString();
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_preV3BiddingLogicWithDataVersionHeaderFlagEnabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithDataVersionHeaderEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeDataVersionHeaderEnabled() {
return true;
}
};
doReturn(flagsWithDataVersionHeaderEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithDataVersionHeaderEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_1);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_2);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
// Instantiate new adSelectionConfig with data version header in trusted seller signals
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(
SELLER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert data version was propagated through to buyer
String expectedBuyerContextualSignals =
BuyerContextualSignals.builder().setDataVersion(DATA_VERSION_2).build().toString();
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
// Assert data version was propagated through to seller
String expectedSellerContextualSignals =
SellerContextualSignals.builder().setDataVersion(DATA_VERSION_1).build().toString();
assertEquals(
expectedSellerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getSellerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_preV3BiddingLogicWithDataVersionHeaderFlagDisabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithDataVersionHeaderEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeDataVersionHeaderEnabled() {
return false;
}
};
doReturn(flagsWithDataVersionHeaderEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithDataVersionHeaderEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_1);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_2);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
// Instantiate new adSelectionConfig with data version header in trusted seller signals
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(
SELLER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert data version was not propagated through to buyer
assertEquals(
AdSelectionSignals.EMPTY.toString(),
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
// Assert data version was not propagated through to buyer
assertEquals(
AdSelectionSignals.EMPTY.toString(),
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getSellerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_preV3BiddingLogicWinnerWithoutBuyerDataVersionHeader()
throws Exception {
AdSelectionE2ETestFlags flagsWithDataVersionHeaderEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeDataVersionHeaderEnabled() {
return true;
}
};
doReturn(flagsWithDataVersionHeaderEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithDataVersionHeaderEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_1);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH + BUYER_2);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert data version was not propagated through since buyer 2 wins
String expectedBuyerContextualSignals = BuyerContextualSignals.builder().build().toString();
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_preV3BiddingLogicWithAdCostCpcBillingDisabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithCPCDisabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeCpcBillingEnabled() {
return false;
}
};
doReturn(flagsWithCPCDisabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithCPCDisabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudienceWithAdCost(
BUYER_1,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_1),
bidsForBuyer1,
AD_COST_1.getAdCost());
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudienceWithAdCost(
BUYER_2,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_2),
bidsForBuyer2,
AD_COST_2.getAdCost());
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert adCost was not propagated through due to flag disabled
String expectedBuyerContextualSignals = "{}";
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_prebuiltScoringLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
String paramKey = "reportingUrl";
String paramValue = "https://www.test.com/reporting/seller";
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
Uri.parse(
String.format(
"%s://%s/%s/?%s=%s",
AD_SELECTION_PREBUILT_SCHEMA,
AD_SELECTION_USE_CASE,
AD_SELECTION_HIGHEST_BID_WINS,
paramKey,
paramValue)))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_prebuiltFeatureDisabled_failure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
Flags prebuiltDisabledFlags =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeAdSelectionPrebuiltUriEnabled() {
return false;
}
};
AdSelectionServiceImpl adSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
prebuiltDisabledFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
String paramKey = "reportingUrl";
String paramValue = "https://www.test.com/reporting/seller";
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
Uri.parse(
String.format(
"%s://%s/%s/?%s=%s",
AD_SELECTION_PREBUILT_SCHEMA,
AD_SELECTION_USE_CASE,
AD_SELECTION_HIGHEST_BID_WINS,
paramKey,
paramValue)))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(adSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
assertTrue(
resultsCallback
.mFledgeErrorResponse
.getErrorMessage()
.contains(PREBUILT_FEATURE_IS_DISABLED));
assertEquals(STATUS_INVALID_ARGUMENT, resultsCallback.mFledgeErrorResponse.getStatusCode());
}
@Test
public void testRunAdSelectionSuccess_flagToPreV3_preV3BiddingLogic() throws Exception {
mFlags = new AdSelectionE2ETestFlags(2, false);
doReturn(mFlags).when(FlagsFactory::getFlags);
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V2_ONLY_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_v3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_v3BiddingLogicWithAdCostCpcBillingEnabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithCPCEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeCpcBillingEnabled() {
return true;
}
};
doReturn(flagsWithCPCEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithCPCEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudienceWithAdCost(
BUYER_1,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_1),
bidsForBuyer1,
AD_COST_1.getAdCost());
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudienceWithAdCost(
BUYER_2,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_2),
bidsForBuyer2,
AD_COST_2.getAdCost());
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert adCost was propagated through
String expectedBuyerContextualSignals =
BuyerContextualSignals.builder().setAdCost(AD_COST_2).build().toString();
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_v3BiddingLogicWithDataVersionHeaderFlagEnabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithDataVersionHeaderEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeDataVersionHeaderEnabled() {
return true;
}
};
doReturn(flagsWithDataVersionHeaderEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithDataVersionHeaderEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_1);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_2);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
// Instantiate new adSelectionConfig with data version header in trusted seller signals
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(
SELLER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert data version was propagated through to buyer
String expectedBuyerContextualSignals =
BuyerContextualSignals.builder().setDataVersion(DATA_VERSION_2).build().toString();
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
// Assert data version was propagated through to seller
String expectedSellerContextualSignals =
SellerContextualSignals.builder().setDataVersion(DATA_VERSION_1).build().toString();
assertEquals(
expectedSellerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getSellerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_v3BiddingLogicWithDataVersionHeaderFlagDisabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithDataVersionHeaderEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeDataVersionHeaderEnabled() {
return false;
}
};
doReturn(flagsWithDataVersionHeaderEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithDataVersionHeaderEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_1);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_2);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
// Instantiate new adSelectionConfig with data version header in trusted seller signals
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(
SELLER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert data version was not propagated through to buyer
assertEquals(
AdSelectionSignals.EMPTY.toString(),
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
// Assert data version was not propagated through to seller
assertEquals(
AdSelectionSignals.EMPTY.toString(),
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getSellerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_v3BiddingLogicWinnerWithoutBuyerDataVersionHeader()
throws Exception {
AdSelectionE2ETestFlags flagsWithDataVersionHeaderEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeDataVersionHeaderEnabled() {
return true;
}
};
doReturn(flagsWithDataVersionHeaderEnabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithDataVersionHeaderEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH_WITH_DATA_VERSION + BUYER_1);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH + BUYER_2);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert data version was not propagated through since buyer 2 wins
String expectedBuyerContextualSignals = BuyerContextualSignals.builder().build().toString();
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_v3BiddingLogicWithAdCostCpcBillingDisabled()
throws Exception {
AdSelectionE2ETestFlags flagsWithCPCDisabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeCpcBillingEnabled() {
return false;
}
};
doReturn(flagsWithCPCDisabled).when(FlagsFactory::getFlags);
// Re init adSelection service with new flags
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithCPCDisabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudienceWithAdCost(
BUYER_1,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_1),
bidsForBuyer1,
AD_COST_1.getAdCost());
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudienceWithAdCost(
BUYER_2,
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH_AD_COST + BUYER_2),
bidsForBuyer2,
AD_COST_2.getAdCost());
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Assert adCost was not propagated through due to flag disabled
String expectedBuyerContextualSignals = "{}";
assertEquals(
expectedBuyerContextualSignals,
mAdSelectionEntryDaoSpy
.getAdSelectionEntityById(resultSelectionId)
.getBuyerContextualSignals());
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSuccess_preV3BiddingLogicWithV3Header_scriptFail()
throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC_HEADER_WITH_V2_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelection_getTooHighHeader_failWithError() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_TOO_HIGH_BIDDING_LOGIC_JS_VERSION);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionWithOverride_getTooHighHeader_failWithError() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_TOO_HIGH_BIDDING_LOGIC_JS_VERSION);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
DBCustomAudienceOverride dbCustomAudienceOverride1 =
DBCustomAudienceOverride.builder()
.setOwner(dBCustomAudienceForBuyer1.getOwner())
.setBuyer(dBCustomAudienceForBuyer1.getBuyer())
.setName(dBCustomAudienceForBuyer1.getName())
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setBiddingLogicJS(READ_BID_FROM_AD_METADATA_JS)
.setBiddingLogicJsVersion(
JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3 + 1)
.setTrustedBiddingData(
new JSONObject(TRUSTED_BIDDING_SIGNALS_SERVER_DATA).toString())
.build();
DBCustomAudienceOverride dbCustomAudienceOverride2 =
DBCustomAudienceOverride.builder()
.setOwner(dBCustomAudienceForBuyer2.getOwner())
.setBuyer(dBCustomAudienceForBuyer2.getBuyer())
.setName(dBCustomAudienceForBuyer2.getName())
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setBiddingLogicJS(READ_BID_FROM_AD_METADATA_JS)
.setBiddingLogicJsVersion(
JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3 + 1)
.setTrustedBiddingData(
new JSONObject(TRUSTED_BIDDING_SIGNALS_SERVER_DATA).toString())
.build();
mCustomAudienceDao.persistCustomAudienceOverride(dbCustomAudienceOverride1);
mCustomAudienceDao.persistCustomAudienceOverride(dbCustomAudienceOverride2);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionContextualAds_Success() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigWithContextualAdsBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.setBuyerSignedContextualAds(createContextualAds())
.build();
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// The contextual Ad with maximum bid should have won
assertEquals(
AdDataFixture.getValidRenderUriByBuyer(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2)
.getHost()),
500)
.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionContextualAds_UseOverrides_Success() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigWithContextualAdsBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.setBuyerSignedContextualAds(createContextualAds())
.build();
MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
final String fakeDecisionLogicForBuyer = "\"reportWin() { completely fake }\"";
// Set dev override for ad selection
DBAdSelectionOverride adSelectionOverride =
DBAdSelectionOverride.builder()
.setAdSelectionConfigId(
AdSelectionDevOverridesHelper.calculateAdSelectionConfigId(
mAdSelectionConfig))
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setDecisionLogicJS(USE_BID_AS_SCORE_JS)
.setTrustedScoringSignals(TRUSTED_SCORING_SIGNALS.toString())
.build();
mAdSelectionEntryDaoSpy.persistAdSelectionOverride(adSelectionOverride);
DBBuyerDecisionOverride buyerDecisionOverride =
DBBuyerDecisionOverride.builder()
.setAdSelectionConfigId(
AdSelectionDevOverridesHelper.calculateAdSelectionConfigId(
mAdSelectionConfig))
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setDecisionLogic(fakeDecisionLogicForBuyer)
.setBuyer(BUYER_2)
.build();
mAdSelectionEntryDaoSpy.persistBuyersDecisionLogicOverride(
ImmutableList.of(buyerDecisionOverride));
// Set dev override for custom audience
DBCustomAudienceOverride dbCustomAudienceOverride =
DBCustomAudienceOverride.builder()
.setOwner(dBCustomAudienceForBuyer2.getOwner())
.setBuyer(dBCustomAudienceForBuyer2.getBuyer())
.setName(dBCustomAudienceForBuyer2.getName())
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setBiddingLogicJS(READ_BID_FROM_AD_METADATA_JS)
.setTrustedBiddingData(
new JSONObject(TRUSTED_BIDDING_SIGNALS_SERVER_DATA).toString())
.build();
mCustomAudienceDao.persistCustomAudienceOverride(dbCustomAudienceOverride);
when(mDevContextFilter.createDevContext())
.thenReturn(
DevContext.builder()
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
// Creating new instance of service with new DevContextFilter
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// The contextual Ad with maximum bid should have won
assertEquals(
AdDataFixture.getValidRenderUriByBuyer(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2)
.getHost()),
500)
.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
// No calls to fetch Contextual decision logic should have been made to the server, as
// overrides are set
mMockWebServerRule.verifyMockServerRequests(
server,
2,
ImmutableList.of(
BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1, BUYER_TRUSTED_SIGNAL_URI_PATH),
REQUEST_PREFIX_MATCHER);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionContextualAds_Disabled_Success() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigWithContextualAdsBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.setBuyerSignedContextualAds(createContextualAds())
.build();
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
Flags flagsWithContextualAdsDisabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getFledgeAdSelectionContextualAdsEnabled() {
return false;
}
};
AdSelectionServiceImpl adSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithContextualAdsDisabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(adSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// The remarketing ad with maximum bid should have won, as contextual ads are excluded
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionOnlyContextualAds_NoBuyers_Success() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigWithContextualAdsBuilder()
.setCustomAudienceBuyers(ImmutableList.of())
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.setBuyerSignedContextualAds(createContextualAds())
.build();
mMockWebServerRule.startMockWebServer(mDispatcher);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// The contextual Ad with maximum bid should have won
assertEquals(
AdDataFixture.getValidRenderUriByBuyer(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2)
.getHost()),
500)
.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionOnlyContextualAds_NoCAs_Success() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigWithContextualAdsBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.setBuyerSignedContextualAds(createContextualAds())
.build();
mMockWebServerRule.startMockWebServer(mDispatcher);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// The contextual Ad with maximum bid should have won
assertEquals(
AdDataFixture.getValidRenderUriByBuyer(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2)
.getHost()),
500)
.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
ReportImpressionTestCallback reportingCallback =
invokeReporting(
resultsCallback.mAdSelectionResponse.getAdSelectionId(),
mAdSelectionService,
adSelectionConfig,
CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(reportingCallback);
}
@Test
public void testRunAdSelectionOnlyContextualAds_NoCAsNoNetworkCall_Success() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher);
String sellerReportingPath = "/seller/report";
String paramKey = "reportingUrl";
String paramValue = mMockWebServerRule.uriForPath(sellerReportingPath).toString();
Uri prebuiltUri =
Uri.parse(
String.format(
"%s://%s/%s/?%s=%s",
AD_SELECTION_PREBUILT_SCHEMA,
AD_SELECTION_USE_CASE,
AD_SELECTION_HIGHEST_BID_WINS,
paramKey,
paramValue));
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigWithContextualAdsBuilder()
.setCustomAudienceBuyers(Collections.emptyList())
.setSeller(mSeller)
.setDecisionLogicUri(prebuiltUri)
.setBuyerSignedContextualAds(createContextualAds())
.setTrustedScoringSignalsUri(Uri.EMPTY)
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// The contextual Ad with maximum bid should have won
assertEquals(
AdDataFixture.getValidRenderUriByBuyer(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2)
.getHost()),
500)
.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
mMockWebServerRule.verifyMockServerRequests(
server, 0, Collections.emptyList(), String::equals);
ReportImpressionTestCallback reportingCallback =
invokeReporting(
resultsCallback.mAdSelectionResponse.getAdSelectionId(),
mAdSelectionService,
adSelectionConfig,
CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(reportingCallback);
}
@Test
public void testRunAdSelectionNoContextualAds_NoCAs_Failure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigWithContextualAdsBuilder()
.setCustomAudienceBuyers(ImmutableList.of(BUYER_1, BUYER_2, BUYER_3))
.setSeller(mSeller)
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.setBuyerSignedContextualAds(createContextualAds())
.build();
mMockWebServerRule.startMockWebServer(mDispatcher);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// The contextual Ad with maximum bid should have won
assertEquals(
AdDataFixture.getValidRenderUriByBuyer(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2)
.getHost()),
500)
.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionWithRevokedUserConsentSuccess() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
doThrow(new FilterException(new ConsentManager.RevokedConsentException()))
.when(mAdSelectionServiceFilter)
.filterRequest(
mSeller,
CALLER_PACKAGE_NAME,
true,
true,
CALLER_UID,
AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
Throttler.ApiKey.FLEDGE_API_SELECT_ADS,
DevContext.createForDevOptionsDisabled());
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertFalse(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
Uri.EMPTY.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
// Confirm a duplicate log entry does not exist.
// AdSelectionServiceFilter ensures the failing assertion is logged internally.
verify(mAdServicesLoggerMock, never())
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_USER_CONSENT_REVOKED),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMultipleCAsSuccess_preV3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
for (int i = 3; i <= 50; i++) {
AdTechIdentifier buyerX = AdTechIdentifier.fromString(BUYER + i + ".com");
DBCustomAudience dBCustomAudienceForBuyerX =
createDBCustomAudience(
buyerX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyerX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(buyerX));
participatingBuyers.add(buyerX);
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMultipleCAsSuccess_v3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
for (int i = 3; i <= 50; i++) {
AdTechIdentifier buyerX = AdTechIdentifier.fromString(BUYER + i + ".com");
DBCustomAudience dBCustomAudienceForBuyerX =
createDBCustomAudience(
buyerX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyerX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(buyerX));
participatingBuyers.add(buyerX);
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMultipleCAsNoCachingSuccess_preV3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
int extraCustomAudienceCount = 100;
for (int i = 1; i <= extraCustomAudienceCount; i++) {
DBCustomAudience dBCustomAudienceX =
createDBCustomAudience(
BUYER_1,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX + i,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
// Creating client which does not have caching
AdServicesHttpsClient httpClientWithNoCaching =
new AdServicesHttpsClient(
AdServicesExecutors.getBlockingExecutor(),
CacheProviderFactory.createNoOpCache());
AdSelectionServiceImpl adSelectionServiceNoCache =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
httpClientWithNoCaching,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
AdSelectionTestCallback resultsCallbackNoCache =
invokeSelectAds(adSelectionServiceNoCache, adSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallbackNoCache);
long resultSelectionId = resultsCallbackNoCache.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallbackNoCache.mAdSelectionResponse.getRenderUri().toString());
// 1 call per CA bidJs + 2 calls per buyer signals + 2 calls per seller scoreAdJS, signals
int expectedNetworkCalls = (102 * 1) + 2 + 2;
int serverCallsCountNoCaching = server.getRequestCount();
Assert.assertEquals(
"Server calls mismatch", expectedNetworkCalls, serverCallsCountNoCaching);
}
@Test
public void testRunAdSelectionMultipleCAsNoCachingSuccess_v3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
MockWebServer server = mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
int extraCustomAudienceCount = 100;
for (int i = 1; i <= extraCustomAudienceCount; i++) {
DBCustomAudience dBCustomAudienceX =
createDBCustomAudience(
BUYER_1,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX + i,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
// Creating client which does not have caching
AdServicesHttpsClient httpClientWithNoCaching =
new AdServicesHttpsClient(
AdServicesExecutors.getBlockingExecutor(),
CacheProviderFactory.createNoOpCache());
AdSelectionServiceImpl adSelectionServiceNoCache =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
httpClientWithNoCaching,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
AdSelectionTestCallback resultsCallbackNoCache =
invokeSelectAds(adSelectionServiceNoCache, adSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallbackNoCache);
long resultSelectionId = resultsCallbackNoCache.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallbackNoCache.mAdSelectionResponse.getRenderUri().toString());
// 1 call per CA bidJs + 2 calls per buyer signals + 2 calls per seller scoreAdJS, signals
int expectedNetworkCalls = (102 * 1) + 2 + 2;
int serverCallsCountNoCaching = server.getRequestCount();
Assert.assertEquals(
"Server calls mismatch", expectedNetworkCalls, serverCallsCountNoCaching);
}
@Test
public void testRunAdSelectionMultipleCAsJSCachedSuccess_preV3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
int extraCustomAudienceCount = 100;
for (int i = 1; i <= extraCustomAudienceCount; i++) {
DBCustomAudience dBCustomAudienceX =
createDBCustomAudience(
BUYER_1,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX + i,
// All these CAs use the same uri, therefore JS should be cached
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
HttpCache cache = CacheProviderFactory.create(mContext, mFlags);
cache.addObserver(mCacheObserver);
// Creating client which has caching
AdServicesHttpsClient httpClientWithCaching =
new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor(), cache);
AdSelectionServiceImpl adSelectionServiceWithCache =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
httpClientWithCaching,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// We call selectAds again to verify that scoring logic was also cached
AdSelectionTestCallback resultsCallbackWithCaching =
invokeSelectAds(
adSelectionServiceWithCache, adSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallbackWithCaching);
// 1 call per CA bidJs + 2 calls per buyer signals + 2 calls per seller scoreAdJS, signals
int expectedNetworkCallsNoCaching = (102 * 1) + 2 + 2;
int serverCallsCountWithCaching = server.getRequestCount();
assertTrue("Some requests should have been cached", cache.getCachedEntriesCount() > 0);
assertTrue(
"Network calls with caching should have been lesser",
serverCallsCountWithCaching < expectedNetworkCallsNoCaching);
// Cache cleanup should have been eventually invoked
verify(mCacheObserver, timeout(3000)).update(HttpCache.CacheEventType.CLEANUP);
cache.delete();
}
@Test
public void testRunAdSelectionMultipleCAsJSCachedSuccess_v3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
MockWebServer server = mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
int extraCustomAudienceCount = 100;
for (int i = 1; i <= extraCustomAudienceCount; i++) {
DBCustomAudience dBCustomAudienceX =
createDBCustomAudience(
BUYER_1,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX + i,
// All these CAs use the same uri, therefore JS should be cached
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
HttpCache cache = CacheProviderFactory.create(mContext, mFlags);
cache.addObserver(mCacheObserver);
// Creating client which has caching
AdServicesHttpsClient httpClientWithCaching =
new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor(), cache);
AdSelectionServiceImpl adSelectionServiceWithCache =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
httpClientWithCaching,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// We call selectAds again to verify that scoring logic was also cached
AdSelectionTestCallback resultsCallbackWithCaching =
invokeSelectAds(
adSelectionServiceWithCache, adSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallbackWithCaching);
// 1 call per CA bidJs + 2 calls per buyer signals + 2 calls per seller scoreAdJS, signals
int expectedNetworkCallsNoCaching = (102 * 1) + 2 + 2;
int serverCallsCountWithCaching = server.getRequestCount();
assertTrue("Some requests should have been cached", cache.getCachedEntriesCount() > 0);
assertTrue(
"Network calls with caching should have been lesser",
serverCallsCountWithCaching < expectedNetworkCallsNoCaching);
// Cache cleanup should have been eventually invoked
verify(mCacheObserver, timeout(3000)).update(HttpCache.CacheEventType.CLEANUP);
cache.delete();
}
@Test
public void testRunAdSelectionSucceedsWithOverride_preV3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
mMockWebServerRule.startMockWebServer(mDispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Set dev override for ad selection
DBAdSelectionOverride adSelectionOverride =
DBAdSelectionOverride.builder()
.setAdSelectionConfigId(
AdSelectionDevOverridesHelper.calculateAdSelectionConfigId(
mAdSelectionConfig))
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setDecisionLogicJS(USE_BID_AS_SCORE_JS)
.setTrustedScoringSignals(TRUSTED_SCORING_SIGNALS.toString())
.build();
mAdSelectionEntryDaoSpy.persistAdSelectionOverride(adSelectionOverride);
// Set dev override for custom audience
DBCustomAudienceOverride dbCustomAudienceOverride =
DBCustomAudienceOverride.builder()
.setOwner(dBCustomAudienceForBuyer2.getOwner())
.setBuyer(dBCustomAudienceForBuyer2.getBuyer())
.setName(dBCustomAudienceForBuyer2.getName())
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setBiddingLogicJS(READ_BID_FROM_AD_METADATA_JS)
.setTrustedBiddingData(
new JSONObject(TRUSTED_BIDDING_SIGNALS_SERVER_DATA).toString())
.build();
mCustomAudienceDao.persistCustomAudienceOverride(dbCustomAudienceOverride);
when(mDevContextFilter.createDevContext())
.thenReturn(
DevContext.builder()
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
// Creating new instance of service with new DevContextFilter
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionSucceedsWithOverride_v3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
mMockWebServerRule.startMockWebServer(mDispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Set dev override for ad selection
DBAdSelectionOverride adSelectionOverride =
DBAdSelectionOverride.builder()
.setAdSelectionConfigId(
AdSelectionDevOverridesHelper.calculateAdSelectionConfigId(
mAdSelectionConfig))
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setDecisionLogicJS(USE_BID_AS_SCORE_JS)
.setTrustedScoringSignals(TRUSTED_SCORING_SIGNALS.toString())
.build();
mAdSelectionEntryDaoSpy.persistAdSelectionOverride(adSelectionOverride);
// Set dev override for custom audience
DBCustomAudienceOverride dbCustomAudienceOverride =
DBCustomAudienceOverride.builder()
.setOwner(dBCustomAudienceForBuyer2.getOwner())
.setBuyer(dBCustomAudienceForBuyer2.getBuyer())
.setName(dBCustomAudienceForBuyer2.getName())
.setAppPackageName(MY_APP_PACKAGE_NAME)
.setBiddingLogicJS(READ_BID_FROM_AD_METADATA_JS_V3)
.setBiddingLogicJsVersion(
JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3)
.setTrustedBiddingData(
new JSONObject(TRUSTED_BIDDING_SIGNALS_SERVER_DATA).toString())
.build();
mCustomAudienceDao.persistCustomAudienceOverride(dbCustomAudienceOverride);
when(mDevContextFilter.createDevContext())
.thenReturn(
DevContext.builder()
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
// Creating new instance of service with new DevContextFilter
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionActiveCAs() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(0.9, 0.45);
List<Double> bidsForBuyer2 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer3 = ImmutableList.of(10.0, 100.0);
DBCustomAudience dbCustomAudienceActive =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceInactive =
createDBCustomAudience(
BUYER_2,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CustomAudienceFixture.VALID_LAST_UPDATE_TIME_24_HRS_BEFORE,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceExpired =
createDBCustomAudience(
BUYER_3,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_3),
bidsForBuyer3,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.INVALID_NOW_EXPIRATION_TIME,
CustomAudienceFixture.VALID_LAST_UPDATE_TIME_24_HRS_BEFORE,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dbCustomAudienceActive,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceInactive,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceExpired,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_3));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad1",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionNoCAsActive() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
List<Double> bidsForBuyer3 = ImmutableList.of(4.3, 6.0, 10.0);
DBCustomAudience dBCustomAudienceInactive =
createDBCustomAudience(
BUYER_1,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CustomAudienceFixture.VALID_LAST_UPDATE_TIME_24_HRS_BEFORE,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceExpired =
createDBCustomAudience(
BUYER_2,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.INVALID_NOW_EXPIRATION_TIME,
CustomAudienceFixture.VALID_LAST_UPDATE_TIME_24_HRS_BEFORE,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceOutdated =
createDBCustomAudience(
BUYER_3,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_3),
bidsForBuyer3,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CustomAudienceFixture.INVALID_LAST_UPDATE_TIME_72_DAYS_BEFORE,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceInactive,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceExpired,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceOutdated,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_3));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
assertEquals(resultsCallback.mFledgeErrorResponse.getStatusCode(), STATUS_INTERNAL_ERROR);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
String.format(
Locale.ENGLISH,
AdSelectionRunner.AD_SELECTION_ERROR_PATTERN,
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
ERROR_NO_CA_AND_CONTEXTUAL_ADS_AVAILABLE));
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock, never()).logRunAdScoringProcessReportedStats(any());
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionNoCAsFailure() throws Exception {
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Do not populate CustomAudience DAO
mMockWebServerRule.startMockWebServer(mDispatcher);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
ERROR_NO_CA_AND_CONTEXTUAL_ADS_AVAILABLE);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock, never()).logRunAdScoringProcessReportedStats(any());
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionNoBuyersFailure() throws Exception {
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Do not populate buyers in AdSelectionConfig
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.setCustomAudienceBuyers(Collections.emptyList())
.build();
mMockWebServerRule.startMockWebServer(mDispatcher);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
ERROR_NO_BUYERS_OR_CONTEXTUAL_ADS_AVAILABLE);
verify(mAdServicesLoggerMock, never()).logRunAdScoringProcessReportedStats(any());
verify(mAdServicesLoggerMock, never()).logRunAdBiddingProcessReportedStats(any());
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INVALID_ARGUMENT),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionPartialAdsExcludedBidding() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
// Setting bids which are partially non-positive
List<Double> bidsForBuyer1 = ImmutableList.of(-1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(-4.5, -6.7, -10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
private void assertCallbackIsSuccessful(AdSelectionTestCallback resultsCallback) {
assertTrue(
resultsCallback.mFledgeErrorResponse != null
? String.format(
Locale.ENGLISH,
"Expected callback to succeed but it failed with status %d and"
+ " message '%s'",
resultsCallback.mFledgeErrorResponse.getStatusCode(),
resultsCallback.mFledgeErrorResponse.getErrorMessage())
: "Expected callback to succeed but it failed with no details",
resultsCallback.mIsSuccess);
}
private void assertCallbackIsSuccessful(ReportImpressionTestCallback resultsCallback) {
assertTrue(
resultsCallback.mFledgeErrorResponse != null
? String.format(
Locale.ENGLISH,
"Expected callback to succeed but it failed with status %d and"
+ " message '%s'",
resultsCallback.mFledgeErrorResponse.getStatusCode(),
resultsCallback.mFledgeErrorResponse.getErrorMessage())
: "Expected callback to succeed but it failed with no details",
resultsCallback.mIsSuccess);
}
private void assertCallbackFailed(AdSelectionTestCallback resultsCallback) {
assertFalse("Expected callback to fail but succeeded", resultsCallback.mIsSuccess);
}
@Test
public void testRunAdSelectionMissingBiddingLogicFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
// In this case the Buyers have no bidding logic response in dispatcher
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody("");
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
ERROR_NO_VALID_BIDS_OR_CONTEXTUAL_ADS_FOR_SCORING);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock, never()).logRunAdScoringProcessReportedStats(any());
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMissingScoringLogicFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
// In this case the Seller has no scoring logic in dispatcher
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody("");
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
ERROR_SCORE_AD_LOGIC_MISSING);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionErrorFetchingScoringLogicFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
// In this case the web server returns failure for scoring
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setResponseCode(404);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(), MISSING_SCORING_LOGIC);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionPartialMissingBiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
// In this case the Buyer 2 has no bidding logic response in dispatcher
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody("");
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionPartialNonPositiveScoring() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Setting bids, in this case the odd bids will be made negative by scoring logic
List<Double> bidsForBuyer1 = ImmutableList.of(1.0, 2.0);
List<Double> bidsForBuyer2 = ImmutableList.of(3.0, 5.0, 7.0);
// This scoring logic assigns negative score to odd bids
String makeOddBidsNegativeScoreJs =
"function scoreAd(ad, bid, auction_config, seller_signals, "
+ "trusted_scoring_signals, contextual_signal, user_signal, "
+ "custom_audience_signal) { \n"
+ " return {'status': 0, 'score': (bid % 2 == 0) ? bid : -bid };\n"
+ "}";
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(makeOddBidsNegativeScoreJs);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionNonPositiveScoringFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Setting bids, in this case the odd bids will be made negative by scoring logic
List<Double> bidsForBuyer1 = ImmutableList.of(1.0, 9.0);
List<Double> bidsForBuyer2 = ImmutableList.of(3.0, 5.0, 7.0);
// This scoring logic assigns negative score to odd bids
String makeOddBidsNegativeScoreJs =
"function scoreAd(ad, bid, auction_config, seller_signals, "
+ "trusted_scoring_signals, contextual_signal, user_signal, "
+ "custom_audience_signal) { \n"
+ " return {'status': 0, 'score': (bid % 2 == 0) ? bid : -bid };\n"
+ "}";
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(makeOddBidsNegativeScoreJs);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(), ERROR_NO_WINNING_AD_FOUND);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionBiddingTimesOutForCA() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
Flags flagsWithSmallerLimits =
new AdSelectionE2ETestFlags() {
@Override
public long getAdSelectionBiddingTimeoutPerCaMs() {
return 1500;
}
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
@Override
public float getSdkRequestPermitsPerSecond() {
// Unlimited rate for unit tests to avoid flake in tests due to rate
// limiting
return -1;
}
};
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithSmallerLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
String jsWaitMoreThanAllowedForBiddingPerCa =
insertJsWait(2 * mFlags.getAdSelectionBiddingTimeoutPerCaMs());
String readBidFromAdMetadataWithDelayJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
+ " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ jsWaitMoreThanAllowedForBiddingPerCa
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}";
// In this case the one buyer's logic takes more than the bidding time limit
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(readBidFromAdMetadataWithDelayJs);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
// buyer1/ad3 is clear winner but will time out
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2, 15.0);
// due to timeout buyer2/ad3 will win
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionImposesPerBuyerBiddingTimeout_preV3BiddingLogic()
throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(6);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
Long lenientPerBuyerTimeOutLimit = 50000L;
Long tightPerBuyerTimeOutLimit = 2000L;
int largeCACountForBuyer = 300;
Flags flagsWithLenientBuyerBiddingLimits =
new AdSelectionE2ETestFlags() {
@Override
public long getAdSelectionBiddingTimeoutPerBuyerMs() {
return lenientPerBuyerTimeOutLimit;
}
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
@Override
public float getSdkRequestPermitsPerSecond() {
// Unlimited rate for unit tests to avoid flake in tests due to rate
// limiting
return -1;
}
@Override
public long getAdSelectionOverallTimeoutMs() {
return lenientPerBuyerTimeOutLimit * 3;
}
};
MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
participatingBuyers.add(BUYER_3);
for (int i = 1; i <= largeCACountForBuyer; i++) {
DBCustomAudience dBCustomAudienceX =
createDBCustomAudience(
BUYER_3,
"-" + i,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_3));
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
// Create an instance of AdSelection Service with lenient dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithLenientBuyerBiddingLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
int networkRequestCountWithLenientTimeout = server.getRequestCount();
// Now we run the same Ad selection with tight per buyer timeout limits
Flags flagsWithTightBuyerBiddingLimits =
new AdSelectionE2ETestFlags() {
@Override
public long getAdSelectionBiddingTimeoutPerBuyerMs() {
return tightPerBuyerTimeOutLimit;
}
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
@Override
public float getSdkRequestPermitsPerSecond() {
// Unlimited rate for unit tests to avoid flake in tests due to rate
// limiting
return -1;
}
@Override
public long getAdSelectionOverallTimeoutMs() {
return lenientPerBuyerTimeOutLimit * 3;
}
@Override
public int getAdSelectionMaxConcurrentBiddingCount() {
return 1;
}
};
// Create an instance of AdSelection Service with tight dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithTightBuyerBiddingLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
int networkRequestCountWithTightTimeout =
server.getRequestCount() - networkRequestCountWithLenientTimeout;
sLogger.v(
String.format(
"Network calls with buyer timeout :%d, network calls with"
+ " lenient timeout :%d",
networkRequestCountWithTightTimeout,
networkRequestCountWithLenientTimeout));
assertTrue(
String.format(
"Network calls with buyer timeout :%d, are not less than network calls with"
+ " lenient timeout :%d",
networkRequestCountWithTightTimeout, networkRequestCountWithLenientTimeout),
networkRequestCountWithTightTimeout < networkRequestCountWithLenientTimeout);
verify(mAdServicesLoggerMock, times(2))
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock, times(2))
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock, times(2))
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock, times(2))
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionImposesPerBuyerBiddingTimeout_v3BiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(6);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
Long lenientPerBuyerTimeOutLimit = 50000L;
Long tightPerBuyerTimeOutLimit = 2000L;
int largeCACountForBuyer = 300;
Flags flagsWithLenientBuyerBiddingLimits =
new AdSelectionE2ETestFlags() {
@Override
public long getAdSelectionBiddingTimeoutPerBuyerMs() {
return lenientPerBuyerTimeOutLimit;
}
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
@Override
public float getSdkRequestPermitsPerSecond() {
// Unlimited rate for unit tests to avoid flake in tests due to rate
// limiting
return -1;
}
@Override
public long getAdSelectionOverallTimeoutMs() {
return lenientPerBuyerTimeOutLimit * 3;
}
};
MockWebServer server = mMockWebServerRule.startMockWebServer(DISPATCHER_V3_BIDDING_LOGIC);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
participatingBuyers.add(BUYER_1);
participatingBuyers.add(BUYER_2);
participatingBuyers.add(BUYER_3);
for (int i = 1; i <= largeCACountForBuyer; i++) {
DBCustomAudience dBCustomAudienceX =
createDBCustomAudience(
BUYER_3,
"-" + i,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
BUYER_TRUSTED_SIGNAL_URI_PATH);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceX,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_3));
}
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(participatingBuyers)
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
.getHost()))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
.build();
// Create an instance of AdSelection Service with lenient dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithLenientBuyerBiddingLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
int networkRequestCountWithLenientTimeout = server.getRequestCount();
// Now we run the same Ad selection with tight per buyer timeout limits
Flags flagsWithTightBuyerBiddingLimits =
new AdSelectionE2ETestFlags() {
@Override
public long getAdSelectionBiddingTimeoutPerBuyerMs() {
return tightPerBuyerTimeOutLimit;
}
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
@Override
public float getSdkRequestPermitsPerSecond() {
// Unlimited rate for unit tests to avoid flake in tests due to rate
// limiting
return -1;
}
@Override
public long getAdSelectionOverallTimeoutMs() {
return lenientPerBuyerTimeOutLimit * 3;
}
@Override
public int getAdSelectionMaxConcurrentBiddingCount() {
return 1;
}
};
// Create an instance of AdSelection Service with tight dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithTightBuyerBiddingLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
resultsCallback =
invokeSelectAds(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
int networkRequestCountWithTightTimeout =
server.getRequestCount() - networkRequestCountWithLenientTimeout;
sLogger.v(
String.format(
"Network calls with buyer timeout :%d, network calls with"
+ " lenient timeout :%d",
networkRequestCountWithTightTimeout,
networkRequestCountWithLenientTimeout));
assertTrue(
String.format(
"Network calls with buyer timeout :%d, are not less than network calls with"
+ " lenient timeout :%d",
networkRequestCountWithTightTimeout, networkRequestCountWithLenientTimeout),
networkRequestCountWithTightTimeout < networkRequestCountWithLenientTimeout);
verify(mAdServicesLoggerMock, times(2))
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock, times(2))
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock, times(2))
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock, times(2))
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionScoringTimesOut() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
Flags flagsWithSmallerLimits =
new AdSelectionE2ETestFlags() {
@Override
public long getAdSelectionScoringTimeoutMs() {
return 1500;
}
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
@Override
public float getSdkRequestPermitsPerSecond() {
// Unlimited rate for unit tests to avoid flake in tests due to rate
// limiting
return -1;
}
};
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithSmallerLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
String jsWaitMoreThanAllowedForScoring =
insertJsWait(2 * mFlags.getAdSelectionScoringTimeoutMs());
String useBidAsScoringWithDelayJs =
"function scoreAd(ad, bid, auction_config, seller_signals, "
+ "trusted_scoring_signals, contextual_signal, user_signal, "
+ "custom_audience_signal) { \n"
+ jsWaitMoreThanAllowedForScoring
+ " return {'status': 0, 'score': bid };\n"
+ "}";
// In this case the one buyer's logic takes more than the bidding time limit
Dispatcher dispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(useBidAsScoringWithDelayJs);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(dispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2, 15.0);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
Assert.assertFalse(resultsCallback.mIsSuccess);
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
verifyErrorMessageIsCorrect(response.getErrorMessage(), SCORING_TIMED_OUT);
Assert.assertEquals(
"Error response code mismatch",
AdServicesStatusUtils.STATUS_TIMEOUT,
response.getStatusCode());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_TIMEOUT),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testAdSelectionConfigInvalidInput() throws Exception {
doThrow(new IllegalArgumentException())
.when(mAdSelectionServiceFilter)
.filterRequest(
mSeller,
CALLER_PACKAGE_NAME,
true,
true,
CALLER_UID,
AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
Throttler.ApiKey.FLEDGE_API_SELECT_ADS,
DevContext.createForDevOptionsDisabled());
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
AdSelectionConfig invalidAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setSeller(SELLER_VALID)
.setDecisionLogicUri(DECISION_LOGIC_URI_INCONSISTENT)
.build();
Mockito.lenient()
.when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, invalidAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertFalse(resultsCallback.mIsSuccess);
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
assertEquals(
"Error response code mismatch", STATUS_INVALID_ARGUMENT, response.getStatusCode());
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INVALID_ARGUMENT),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMissingBiddingSignalsFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(2);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Create a dispatcher without buyer trusted Signal end point
Dispatcher missingBiddingSignalsDispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(missingBiddingSignalsDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
ERROR_NO_VALID_BIDS_OR_CONTEXTUAL_ADS_FOR_SCORING);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock, never()).logRunAdScoringProcessReportedStats(any());
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMissingScoringSignalsFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Create a dispatcher without buyer trusted Signal end point
Dispatcher missingScoringSignalsDispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(missingScoringSignalsDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackFailed(resultsCallback);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
MISSING_TRUSTED_SCORING_SIGNALS);
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMissingPartialBiddingSignalsSuccess() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
// Create a dispatcher with valid end points
Dispatcher missingBiddingSignalsDispatcher =
new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
if (SELLER_DECISION_LOGIC_URI_PATH.equals(request.getPath())) {
return new MockResponse().setBody(USE_BID_AS_SCORE_JS);
} else if ((BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1.toString())
.equals(request.getPath())
|| (BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2.toString())
.equals(request.getPath())) {
return new MockResponse().setBody(READ_BID_FROM_AD_METADATA_JS);
} else if (request.getPath().startsWith(BUYER_TRUSTED_SIGNAL_URI_PATH)) {
String[] keys =
Uri.parse(request.getPath())
.getQueryParameter(
DBTrustedBiddingData.QUERY_PARAM_KEYS)
.split(",");
Map<String, String> jsonMap = new HashMap<>();
for (String key : keys) {
jsonMap.put(key, TRUSTED_BIDDING_SIGNALS_SERVER_DATA.get(key));
}
return new MockResponse().setBody(new JSONObject(jsonMap).toString());
}
// The seller params vary based on runtime, so we are returning trusted
// signals based on correct path prefix
if (request.getPath()
.startsWith(
SELLER_TRUSTED_SIGNAL_URI_PATH
+ SELLER_TRUSTED_SIGNAL_PARAMS)) {
return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
}
return new MockResponse().setResponseCode(404);
}
};
mMockWebServerRule.startMockWebServer(missingBiddingSignalsDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
// Invalid trusted bidding signal path for buyer 2
mMockWebServerRule.uriForPath(""),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Given buyer 2 will be excluded from bidding for missing signals, Buyer 1 : Ad 2 will win
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionFailsWithInvalidPackageName() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
String invalidPackageName = CALLER_PACKAGE_NAME + "invalidPackageName";
doThrow(new FilterException(new FledgeAuthorizationFilter.CallerMismatchException()))
.when(mAdSelectionServiceFilter)
.filterRequest(
mSeller,
invalidPackageName,
true,
true,
CALLER_UID,
AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
Throttler.ApiKey.FLEDGE_API_SELECT_ADS,
DevContext.createForDevOptionsDisabled());
// Bypass the permission check since it's enforced before the package name check
doNothing()
.when(mFledgeAuthorizationFilterSpy)
.assertAppDeclaredCustomAudiencePermission(
mContext, invalidPackageName, AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, invalidPackageName);
runAdSelectionProcessLoggerLatch.await();
Assert.assertFalse(resultsCallback.mIsSuccess);
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
assertEquals("Error response code mismatch", STATUS_UNAUTHORIZED, response.getStatusCode());
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
String.format(
AdSelectionRunner.AD_SELECTION_ERROR_PATTERN,
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
AdServicesStatusUtils
.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
// Confirm a duplicate log entry does not exist.
// AdSelectionServiceFilter ensures the failing assertion is logged internally.
verify(mAdServicesLoggerMock, never())
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_UNAUTHORIZED),
geq(0));
verify(mFledgeAuthorizationFilterSpy)
.assertAppDeclaredCustomAudiencePermission(
mContext, invalidPackageName, AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS);
}
@Test
public void testRunAdSelectionFailsWhenAppCannotUsePPApi() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
doThrow(new FilterException(new FledgeAuthorizationFilter.AdTechNotAllowedException()))
.when(mAdSelectionServiceFilter)
.filterRequest(
mSeller,
CALLER_PACKAGE_NAME,
true,
true,
CALLER_UID,
AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
Throttler.ApiKey.FLEDGE_API_SELECT_ADS,
DevContext.createForDevOptionsDisabled());
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
Assert.assertFalse(resultsCallback.mIsSuccess);
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
assertEquals(
"Error response code mismatch",
STATUS_CALLER_NOT_ALLOWED,
response.getStatusCode());
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
String.format(
AdSelectionRunner.AD_SELECTION_ERROR_PATTERN,
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
// Confirm a duplicate log entry does not exist.
// AdSelectionServiceFilter ensures the failing assertion is logged internally.
verify(mAdServicesLoggerMock, never())
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_CALLER_NOT_ALLOWED),
geq(0));
}
@Test
public void testRunAdSelectionFailsWhenAdTechFailsEnrollmentCheck() throws Exception {
Flags flagsWithEnrollmentCheckEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return false;
}
};
doReturn(flagsWithEnrollmentCheckEnabled).when(FlagsFactory::getFlags);
doThrow(new FilterException(new FledgeAuthorizationFilter.AdTechNotAllowedException()))
.when(mAdSelectionServiceFilter)
.filterRequest(
mSeller,
CALLER_PACKAGE_NAME,
true,
true,
CALLER_UID,
AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
Throttler.ApiKey.FLEDGE_API_SELECT_ADS,
DevContext.createForDevOptionsDisabled());
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithEnrollmentCheckEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
Assert.assertFalse(resultsCallback.mIsSuccess);
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
assertEquals(
"Error response code mismatch",
STATUS_CALLER_NOT_ALLOWED,
response.getStatusCode());
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
String.format(
AdSelectionRunner.AD_SELECTION_ERROR_PATTERN,
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
// Confirm a duplicate log entry does not exist.
// AdSelectionServiceFilter ensures the failing assertion is logged internally.
verify(mAdServicesLoggerMock, never())
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_CALLER_NOT_ALLOWED),
geq(0));
}
@Test
public void testRunAdSelectionThrottledSubsequentCallFailure() throws Exception {
doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
class FlagsWithThrottling extends AdSelectionE2ETestFlags implements Flags {
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getEnforceForegroundStatusForFledgeRunAdSelection() {
return true;
}
@Override
public boolean getEnforceForegroundStatusForFledgeReportImpression() {
return true;
}
@Override
public boolean getEnforceForegroundStatusForFledgeOverrides() {
return true;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
// Testing the default throttling limit
@Override
public float getSdkRequestPermitsPerSecond() {
return 1;
}
}
Throttler.destroyExistingThrottler();
Flags throttlingFlags = new FlagsWithThrottling();
AdSelectionServiceImpl adSelectionServiceWithThrottling =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
throttlingFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(1);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
// First call to Ad Selection should succeed
AdSelectionTestCallback resultsCallbackFirstCall =
invokeSelectAds(
adSelectionServiceWithThrottling, mAdSelectionConfig, CALLER_PACKAGE_NAME);
doThrow(new FilterException(new LimitExceededException(RATE_LIMIT_REACHED_ERROR_MESSAGE)))
.when(mAdSelectionServiceFilter)
.filterRequest(
mSeller,
CALLER_PACKAGE_NAME,
true,
true,
CALLER_UID,
AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
Throttler.ApiKey.FLEDGE_API_SELECT_ADS,
DevContext.createForDevOptionsDisabled());
// Immediately made subsequent call should fail
AdSelectionTestCallback resultsCallbackSecondCall =
invokeSelectAds(
adSelectionServiceWithThrottling, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallbackFirstCall);
long resultSelectionId = resultsCallbackFirstCall.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallbackFirstCall.mAdSelectionResponse.getRenderUri().toString());
assertCallbackFailed(resultsCallbackSecondCall);
FledgeErrorResponse response = resultsCallbackSecondCall.mFledgeErrorResponse;
assertEquals(
"Error response code mismatch",
AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED,
response.getStatusCode());
verifyErrorMessageIsCorrect(
resultsCallbackSecondCall.mFledgeErrorResponse.getErrorMessage(),
String.format(
AdSelectionRunner.AD_SELECTION_ERROR_PATTERN,
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
RATE_LIMIT_REACHED_ERROR_MESSAGE));
resetThrottlerToNoRateLimits();
}
/**
* Given Throttler is singleton, & shared across tests, this method should be invoked after
* tests that impose restrictive rate limits.
*/
private void resetThrottlerToNoRateLimits() {
Throttler.destroyExistingThrottler();
final float noRateLimit = -1;
Flags mockNoRateLimitFlags = mock(Flags.class);
doReturn(noRateLimit).when(mockNoRateLimitFlags).getSdkRequestPermitsPerSecond();
Throttler.getInstance(mockNoRateLimitFlags);
}
@Test
public void testRunAdSelectionSucceedsWhenAdTechPassesEnrollmentCheck() throws Exception {
Flags flagsWithEnrollmentCheckEnabled =
new AdSelectionE2ETestFlags() {
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return false;
}
};
doReturn(flagsWithEnrollmentCheckEnabled).when(FlagsFactory::getFlags);
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
flagsWithEnrollmentCheckEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
// Logger calls come after the callback is returned
CountDownLatch runAdSelectionProcessLoggerLatch = new CountDownLatch(3);
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(any());
doAnswer(
unusedInvocation -> {
runAdSelectionProcessLoggerLatch.countDown();
return null;
})
.when(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(any());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
AdSelectionTestCallback resultsCallback =
invokeSelectAds(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
runAdSelectionProcessLoggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
verify(mAdServicesLoggerMock)
.logRunAdBiddingProcessReportedStats(isA(RunAdBiddingProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdScoringProcessReportedStats(isA(RunAdScoringProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logRunAdSelectionProcessReportedStats(
isA(RunAdSelectionProcessReportedStats.class));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_SUCCESS),
geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelection_webViewNotInstalled_failsGracefully() throws Exception {
// A null package means WebView is not installed
doReturn(null).when(WebView::getCurrentWebViewPackage);
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
// Shut down any running JSScriptEngine to ensure the new singleton gets picked up
JSScriptEngine.getInstance(mContext, LoggerFactory.getFledgeLogger()).shutdown();
try {
// Create a new local service impl so that the WebView stub takes effect
AdSelectionServiceImpl adSelectionServiceImpl =
new AdSelectionServiceImpl(
mAdSelectionEntryDaoSpy,
mAppInstallDao,
mCustomAudienceDao,
mEncodedPayloadDao,
mFrequencyCapDao,
mEncryptionContextDao,
mEncryptionKeyDao,
mAdServicesHttpsClient,
mDevContextFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
mScheduledExecutor,
mContext,
mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
mAdSelectionServiceFilter,
mAdFilteringFeatureFactory,
mConsentManagerMock,
mObliviousHttpEncryptor,
mAdSelectionDebugReportDao,
mAdIdFetcher,
false);
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
DBCustomAudience dBCustomAudienceForBuyer1 =
createDBCustomAudience(
BUYER_1,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
BUYER_TRUSTED_SIGNAL_URI_PATH);
DBCustomAudience dBCustomAudienceForBuyer2 =
createDBCustomAudience(
BUYER_2,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
BUYER_TRUSTED_SIGNAL_URI_PATH);
// Populating the Custom Audience DB
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer1,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dBCustomAudienceForBuyer2,
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
// Ad selection should fail gracefully and not crash
AdSelectionTestCallback resultsCallback =
invokeSelectAds(
adSelectionServiceImpl, mAdSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackFailed(resultsCallback);
assertWithMessage("Error code")
.that(resultsCallback.mFledgeErrorResponse.getStatusCode())
.isEqualTo(STATUS_INTERNAL_ERROR);
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
eq(STATUS_INTERNAL_ERROR),
geq((int) BINDER_ELAPSED_TIME_MS));
} finally {
// Shut down any running JSScriptEngine to ensure the new singleton gets picked up
JSScriptEngine.getInstance(mContext, LoggerFactory.getFledgeLogger()).shutdown();
}
}
/**
* @param buyer The name of the buyer for this Custom Audience
* @param biddingUri path from where the bidding logic for this CA can be fetched from
* @param bids these bids, are added to its metadata. Our JS logic then picks this value and
* creates ad with the provided value as bid
* @param activationTime is the activation time of the Custom Audience
* @param expirationTime is the expiration time of the Custom Audience
* @param lastUpdateTime is the last time of the Custom Audience ads and bidding data got
* updated
* @param trustedSignalUriPath uri path for trusted signals
* @return a real Custom Audience object that can be persisted and used in bidding and scoring
*/
private DBCustomAudience createDBCustomAudience(
final AdTechIdentifier buyer,
final String nameSuffix,
final Uri biddingUri,
List<Double> bids,
Instant activationTime,
Instant expirationTime,
Instant lastUpdateTime,
String trustedSignalUriPath) {
// Generate ads for with bids provided
List<DBAdData> ads = new ArrayList<>();
// Create ads with the buyer name and bid number as the ad URI
// Add the bid value to the metadata
for (int i = 0; i < bids.size(); i++) {
// TODO(b/266015983) Add real data
ads.add(
new DBAdData(
Uri.parse(AD_URI_PREFIX + buyer + "/ad" + (i + 1)),
"{\"result\":" + bids.get(i) + "}",
Collections.EMPTY_SET,
null,
null));
}
return new DBCustomAudience.Builder()
.setOwner(buyer + CustomAudienceFixture.VALID_OWNER)
.setBuyer(buyer)
.setName(buyer.toString() + CustomAudienceFixture.VALID_NAME + nameSuffix)
.setActivationTime(activationTime)
.setExpirationTime(expirationTime)
.setCreationTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
.setLastAdsAndBiddingDataUpdatedTime(lastUpdateTime)
.setUserBiddingSignals(CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS)
.setTrustedBiddingData(
new DBTrustedBiddingData.Builder()
.setUri(mMockWebServerRule.uriForPath(trustedSignalUriPath))
.setKeys(TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build())
.setBiddingLogicUri(biddingUri)
.setAds(ads)
.build();
}
/**
* @param buyer The name of the buyer for this Custom Audience
* @param biddingUri path from where the bidding logic for this CA can be fetched from
* @param bids these bids, are added to its metadata. Our JS logic then picks this value and
* creates ad with the provided value as bid
* @param activationTime is the activation time of the Custom Audience
* @param expirationTime is the expiration time of the Custom Audience
* @param lastUpdateTime is the last time of the Custom Audience ads and bidding data got
* updated
* @param adCost The click cost of the ad
* @return a real Custom Audience object that can be persisted and used in bidding and scoring
*/
private DBCustomAudience createDBCustomAudienceWithAdCost(
final AdTechIdentifier buyer,
final String nameSuffix,
final Uri biddingUri,
List<Double> bids,
Instant activationTime,
Instant expirationTime,
Instant lastUpdateTime,
double adCost) {
// Generate ads for with bids provided
List<DBAdData> ads = new ArrayList<>();
// Create ads with the buyer name and bid number as the ad URI
// Add the bid value to the metadata
for (int i = 0; i < bids.size(); i++) {
// TODO(b/266015983) Add real data
ads.add(
new DBAdData(
Uri.parse(AD_URI_PREFIX + buyer + "/ad" + (i + 1)),
"{\"result\":" + bids.get(i) + ",\"adCost\":" + adCost + "}",
Collections.EMPTY_SET,
null,
null));
}
return new DBCustomAudience.Builder()
.setOwner(buyer + CustomAudienceFixture.VALID_OWNER)
.setBuyer(buyer)
.setName(buyer.toString() + CustomAudienceFixture.VALID_NAME + nameSuffix)
.setActivationTime(activationTime)
.setExpirationTime(expirationTime)
.setCreationTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
.setLastAdsAndBiddingDataUpdatedTime(lastUpdateTime)
.setUserBiddingSignals(CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS)
.setTrustedBiddingData(
new DBTrustedBiddingData.Builder()
.setUri(
mMockWebServerRule.uriForPath(
BUYER_TRUSTED_SIGNAL_URI_PATH))
.setKeys(TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build())
.setBiddingLogicUri(biddingUri)
.setAds(ads)
.build();
}
/**
* @param buyer The name of the buyer for this Custom Audience
* @param biddingUri path from where the bidding logic for this CA can be fetched from
* @param bids these bids, are added to its metadata. Our JS logic then picks this value and
* creates ad with the provided value as bid
* @param trustedSignalUriPath uri path for trusted signals
* @return a real Custom Audience object that can be persisted and used in bidding and scoring
*/
private DBCustomAudience createDBCustomAudience(
final AdTechIdentifier buyer,
final Uri biddingUri,
List<Double> bids,
String trustedSignalUriPath) {
return createDBCustomAudience(
buyer,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
biddingUri,
bids,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
trustedSignalUriPath);
}
/**
* @param buyer The name of the buyer for this Custom Audience
* @param biddingUri path from where the bidding logic for this CA can be fetched from
* @param bids these bids, are added to its metadata. Our JS logic then picks this value and
* creates ad with the provided value as bid
* @param adCost The click cost of the ad
* @return a real Custom Audience object that can be persisted and used in bidding and scoring
*/
private DBCustomAudience createDBCustomAudienceWithAdCost(
final AdTechIdentifier buyer, final Uri biddingUri, List<Double> bids, double adCost) {
return createDBCustomAudienceWithAdCost(
buyer,
DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
biddingUri,
bids,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
CustomAudienceFixture.VALID_EXPIRATION_TIME,
CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
adCost);
}
private Map<AdTechIdentifier, SignedContextualAds> createContextualAds() {
Map<AdTechIdentifier, SignedContextualAds> buyerContextualAds = new HashMap<>();
// In order to meet ETLd+1 requirements creating Contextual ads with MockWebserver's host
AdTechIdentifier buyer2 =
AdTechIdentifier.fromString(
mMockWebServerRule
.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2)
.getHost());
SignedContextualAds contextualAds2 =
SignedContextualAdsFixture.generateSignedContextualAds(
buyer2, ImmutableList.of(100.0, 200.0, 300.0, 400.0, 500.0))
.setDecisionLogicUri(
mMockWebServerRule.uriForPath(
BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2))
.build();
buyerContextualAds.put(buyer2, contextualAds2);
return buyerContextualAds;
}
private void verifyErrorMessageIsCorrect(
final String actualErrorMassage, final String expectedErrorReason) {
assertTrue(
String.format(
Locale.ENGLISH,
"Actual error [%s] does not begin with [%s]",
actualErrorMassage,
ERROR_AD_SELECTION_FAILURE),
actualErrorMassage.startsWith(ERROR_AD_SELECTION_FAILURE));
assertTrue(
String.format(
Locale.ENGLISH,
"Actual error [%s] does not contain expected message: [%s]",
actualErrorMassage,
expectedErrorReason),
actualErrorMassage.contains(expectedErrorReason));
}
private AdSelectionTestCallback invokeSelectAds(
AdSelectionServiceImpl adSelectionService,
AdSelectionConfig adSelectionConfig,
String callerPackageName)
throws InterruptedException {
CountDownLatch countdownLatch = new CountDownLatch(1);
AdSelectionTestCallback adSelectionTestCallback =
new AdSelectionTestCallback(countdownLatch);
AdSelectionInput input =
new AdSelectionInput.Builder()
.setAdSelectionConfig(adSelectionConfig)
.setCallerPackageName(callerPackageName)
.build();
adSelectionService.selectAds(input, mMockCallerMetadata, adSelectionTestCallback);
adSelectionTestCallback.mCountDownLatch.await();
return adSelectionTestCallback;
}
private ReportImpressionTestCallback invokeReporting(
long adSelectionId,
AdSelectionServiceImpl adSelectionService,
AdSelectionConfig adSelectionConfig,
String callerPackageName)
throws InterruptedException {
CountDownLatch countdownLatch = new CountDownLatch(1);
ReportImpressionTestCallback reportImpressionCallback =
new ReportImpressionTestCallback(countdownLatch);
ReportImpressionInput input =
new ReportImpressionInput.Builder()
.setAdSelectionId(adSelectionId)
.setAdSelectionConfig(adSelectionConfig)
.setCallerPackageName(callerPackageName)
.build();
adSelectionService.reportImpression(input, reportImpressionCallback);
reportImpressionCallback.mCountDownLatch.await();
return reportImpressionCallback;
}
private String insertJsWait(long waitTime) {
return " const wait = (ms) => {\n"
+ " var start = new Date().getTime();\n"
+ " var end = start;\n"
+ " while(end < start + ms) {\n"
+ " end = new Date().getTime();\n"
+ " }\n"
+ " }\n"
+ String.format(Locale.ENGLISH, " wait(\"%d\");\n", waitTime);
}
static class AdSelectionTestCallback extends AdSelectionCallback.Stub {
final CountDownLatch mCountDownLatch;
boolean mIsSuccess = false;
AdSelectionResponse mAdSelectionResponse;
FledgeErrorResponse mFledgeErrorResponse;
AdSelectionTestCallback(CountDownLatch countDownLatch) {
mCountDownLatch = countDownLatch;
mAdSelectionResponse = null;
mFledgeErrorResponse = null;
}
@Override
public void onSuccess(AdSelectionResponse adSelectionResponse) throws RemoteException {
mIsSuccess = true;
mAdSelectionResponse = adSelectionResponse;
mCountDownLatch.countDown();
}
@Override
public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException {
mIsSuccess = false;
mFledgeErrorResponse = fledgeErrorResponse;
mCountDownLatch.countDown();
}
@Override
public String toString() {
return "AdSelectionTestCallback{"
+ "mCountDownLatch="
+ mCountDownLatch
+ ", mIsSuccess="
+ mIsSuccess
+ ", mAdSelectionResponse="
+ mAdSelectionResponse
+ ", mFledgeErrorResponse="
+ mFledgeErrorResponse
+ '}';
}
}
public static class ReportImpressionTestCallback extends ReportImpressionCallback.Stub {
private final CountDownLatch mCountDownLatch;
boolean mIsSuccess = false;
FledgeErrorResponse mFledgeErrorResponse;
public ReportImpressionTestCallback(CountDownLatch countDownLatch) {
mCountDownLatch = countDownLatch;
}
@Override
public void onSuccess() throws RemoteException {
mIsSuccess = true;
mCountDownLatch.countDown();
}
@Override
public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException {
mFledgeErrorResponse = fledgeErrorResponse;
mCountDownLatch.countDown();
}
}
private static class AdSelectionE2ETestFlags implements Flags {
private final long mBiddingLogicVersion;
private final boolean mDebugReportingEnabled;
AdSelectionE2ETestFlags() {
this(JsVersionRegister.BUYER_BIDDING_LOGIC_VERSION_VERSION_3, false);
}
AdSelectionE2ETestFlags(long biddingLogicVersion, boolean enableDebugReporting) {
mBiddingLogicVersion = biddingLogicVersion;
mDebugReportingEnabled = enableDebugReporting;
}
@Override
public boolean getEnforceIsolateMaxHeapSize() {
return false;
}
@Override
public boolean getEnforceForegroundStatusForFledgeRunAdSelection() {
return true;
}
@Override
public boolean getEnforceForegroundStatusForFledgeReportImpression() {
return true;
}
@Override
public boolean getEnforceForegroundStatusForFledgeOverrides() {
return true;
}
@Override
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
@Override
public boolean getFledgeOnDeviceAuctionKillSwitch() {
return false;
}
@Override
public long getAdSelectionBiddingTimeoutPerCaMs() {
return EXTENDED_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS;
}
@Override
public long getAdSelectionScoringTimeoutMs() {
return EXTENDED_FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS;
}
@Override
public long getAdSelectionSelectingOutcomeTimeoutMs() {
return EXTENDED_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS;
}
@Override
public long getAdSelectionOverallTimeoutMs() {
return EXTENDED_FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS;
}
@Override
public long getAdSelectionFromOutcomesOverallTimeoutMs() {
return EXTENDED_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS;
}
@Override
public long getReportImpressionOverallTimeoutMs() {
return EXTENDED_FLEDGE_REPORT_IMPRESSION_OVERALL_TIMEOUT_MS;
}
@Override
public int getFledgeBackgroundFetchNetworkConnectTimeoutMs() {
return EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_CONNECT_TIMEOUT_MS;
}
@Override
public int getFledgeBackgroundFetchNetworkReadTimeoutMs() {
return EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_READ_TIMEOUT_MS;
}
@Override
public float getSdkRequestPermitsPerSecond() {
// Unlimited rate for unit tests to avoid flake in tests due to rate
// limiting
return -1;
}
@Override
public boolean getFledgeAdSelectionFilteringEnabled() {
return false;
}
@Override
public long getFledgeAdSelectionBiddingLogicJsVersion() {
return mBiddingLogicVersion;
}
@Override
public boolean getFledgeAdSelectionContextualAdsEnabled() {
return true;
}
@Override
public boolean getFledgeAdSelectionPrebuiltUriEnabled() {
return true;
}
@Override
public boolean getFledgeCpcBillingEnabled() {
return false;
}
@Override
public boolean getFledgeEventLevelDebugReportingEnabled() {
return mDebugReportingEnabled;
}
@Override
public boolean getFledgeEventLevelDebugReportSendImmediately() {
return false;
}
}
}