1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.adservices.service.adselection; 18 19 import static android.adservices.adselection.AdSelectionFromOutcomesConfigFixture.SAMPLE_SELLER; 20 import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR; 21 import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT; 22 23 import static com.android.adservices.common.CommonFlagsValues.EXTENDED_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS; 24 import static com.android.adservices.common.CommonFlagsValues.EXTENDED_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS; 25 import static com.android.adservices.data.adselection.AdSelectionDatabase.DATABASE_NAME; 26 import static com.android.adservices.service.FlagsConstants.KEY_DISABLE_FLEDGE_ENROLLMENT_CHECK; 27 import static com.android.adservices.service.FlagsConstants.KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_OVERRIDE; 28 import static com.android.adservices.service.FlagsConstants.KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_REPORT_IMPRESSION; 29 import static com.android.adservices.service.FlagsConstants.KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_RUN_AD_SELECTION; 30 import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS; 31 import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_AD_SELECTION_PREBUILT_URI_ENABLED; 32 import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS; 33 import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_SELECT_ADS_FROM_OUTCOMES_API_METRICS_ENABLED; 34 import static com.android.adservices.service.FlagsConstants.KEY_SDK_REQUEST_PERMITS_PER_SECOND; 35 import static com.android.adservices.service.adselection.AdOutcomeSelectorImpl.OUTCOME_SELECTION_JS_RETURNED_UNEXPECTED_RESULT; 36 import static com.android.adservices.service.adselection.OutcomeSelectionRunner.SELECTED_OUTCOME_MUST_BE_ONE_OF_THE_INPUTS; 37 import static com.android.adservices.service.adselection.PrebuiltLogicGenerator.AD_OUTCOME_SELECTION_WATERFALL_MEDIATION_TRUNCATION; 38 import static com.android.adservices.service.adselection.PrebuiltLogicGenerator.AD_SELECTION_FROM_OUTCOMES_USE_CASE; 39 import static com.android.adservices.service.adselection.PrebuiltLogicGenerator.AD_SELECTION_PREBUILT_SCHEMA; 40 import static com.android.adservices.service.stats.AdSelectionExecutionLoggerTestFixture.DB_AD_SELECTION_FILE_SIZE; 41 import static com.android.adservices.service.stats.AdServicesLoggerUtil.FIELD_UNSET; 42 import static com.android.adservices.service.stats.AdsRelevanceStatusUtils.JS_RUN_STATUS_SUCCESS; 43 import static com.android.adservices.service.stats.AdsRelevanceStatusUtils.JS_RUN_STATUS_UNSET; 44 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; 45 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 46 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 47 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 48 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 49 50 import static com.google.common.truth.Truth.assertThat; 51 52 import static org.junit.Assert.assertEquals; 53 import static org.mockito.Mockito.spy; 54 55 import android.adservices.adselection.AdSelectionCallback; 56 import android.adservices.adselection.AdSelectionFromOutcomesConfig; 57 import android.adservices.adselection.AdSelectionFromOutcomesConfigFixture; 58 import android.adservices.adselection.AdSelectionFromOutcomesInput; 59 import android.adservices.adselection.AdSelectionResponse; 60 import android.adservices.adselection.AdSelectionService; 61 import android.adservices.adselection.CustomAudienceSignalsFixture; 62 import android.adservices.common.AdSelectionSignals; 63 import android.adservices.common.AdTechIdentifier; 64 import android.adservices.common.CallerMetadata; 65 import android.adservices.common.CallingAppUidSupplierProcessImpl; 66 import android.adservices.common.CommonFixture; 67 import android.adservices.common.FledgeErrorResponse; 68 import android.adservices.http.MockWebServerRule; 69 import android.net.Uri; 70 import android.os.Process; 71 import android.os.RemoteException; 72 import android.os.SystemClock; 73 74 import androidx.room.Room; 75 import androidx.test.core.app.ApplicationProvider; 76 77 import com.android.adservices.MockWebServerRuleFactory; 78 import com.android.adservices.common.AdServicesExtendedMockitoTestCase; 79 import com.android.adservices.common.DbTestUtil; 80 import com.android.adservices.common.WebViewSupportUtil; 81 import com.android.adservices.concurrency.AdServicesExecutors; 82 import com.android.adservices.data.adselection.AdSelectionDatabase; 83 import com.android.adservices.data.adselection.AdSelectionDebugReportDao; 84 import com.android.adservices.data.adselection.AdSelectionEntryDao; 85 import com.android.adservices.data.adselection.AdSelectionServerDatabase; 86 import com.android.adservices.data.adselection.AppInstallDao; 87 import com.android.adservices.data.adselection.ConsentedDebugConfigurationDao; 88 import com.android.adservices.data.adselection.CustomAudienceSignals; 89 import com.android.adservices.data.adselection.DBAdSelection; 90 import com.android.adservices.data.adselection.FrequencyCapDao; 91 import com.android.adservices.data.adselection.SharedStorageDatabase; 92 import com.android.adservices.data.adselection.datahandlers.AdSelectionInitialization; 93 import com.android.adservices.data.adselection.datahandlers.AdSelectionResultBidAndUri; 94 import com.android.adservices.data.adselection.datahandlers.WinningCustomAudience; 95 import com.android.adservices.data.customaudience.CustomAudienceDao; 96 import com.android.adservices.data.customaudience.CustomAudienceDatabase; 97 import com.android.adservices.data.customaudience.DBCustomAudience; 98 import com.android.adservices.data.encryptionkey.EncryptionKeyDao; 99 import com.android.adservices.data.enrollment.EnrollmentDao; 100 import com.android.adservices.data.signals.EncodedPayloadDao; 101 import com.android.adservices.data.signals.ProtectedSignalsDatabase; 102 import com.android.adservices.service.DebugFlags; 103 import com.android.adservices.service.Flags; 104 import com.android.adservices.service.FlagsFactory; 105 import com.android.adservices.service.adselection.debug.AuctionServerDebugConfigurationGenerator; 106 import com.android.adservices.service.adselection.debug.ConsentedDebugConfigurationGeneratorFactory; 107 import com.android.adservices.service.adselection.encryption.ObliviousHttpEncryptor; 108 import com.android.adservices.service.adselection.encryption.ServerAuctionCoordinatorUriStrategyFactory; 109 import com.android.adservices.service.common.AdSelectionServiceFilter; 110 import com.android.adservices.service.common.FledgeAuthorizationFilter; 111 import com.android.adservices.service.common.RetryStrategyFactory; 112 import com.android.adservices.service.common.Throttler; 113 import com.android.adservices.service.common.cache.CacheProviderFactory; 114 import com.android.adservices.service.common.httpclient.AdServicesHttpsClient; 115 import com.android.adservices.service.consent.ConsentManager; 116 import com.android.adservices.service.devapi.DevContext; 117 import com.android.adservices.service.devapi.DevContextFilter; 118 import com.android.adservices.service.kanon.KAnonSignJoinFactory; 119 import com.android.adservices.service.stats.AdServicesLogger; 120 import com.android.adservices.service.stats.AdServicesLoggerImpl; 121 import com.android.adservices.service.stats.AdServicesStatsLog; 122 import com.android.adservices.service.stats.SelectAdsFromOutcomesApiCalledStats; 123 import com.android.adservices.shared.testing.SupportedByConditionRule; 124 import com.android.adservices.shared.testing.annotations.SetFlagTrue; 125 import com.android.adservices.shared.testing.annotations.SetFloatFlag; 126 import com.android.adservices.shared.testing.annotations.SetLongFlag; 127 import com.android.adservices.shared.util.Clock; 128 import com.android.dx.mockito.inline.extended.ExtendedMockito; 129 import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic; 130 131 import com.google.mockwebserver.Dispatcher; 132 import com.google.mockwebserver.MockResponse; 133 import com.google.mockwebserver.MockWebServer; 134 import com.google.mockwebserver.RecordedRequest; 135 136 import org.junit.Before; 137 import org.junit.Rule; 138 import org.junit.Test; 139 import org.mockito.ArgumentCaptor; 140 import org.mockito.Mock; 141 142 import java.io.File; 143 import java.time.Instant; 144 import java.util.ArrayList; 145 import java.util.Collections; 146 import java.util.List; 147 import java.util.Map; 148 import java.util.concurrent.CountDownLatch; 149 import java.util.concurrent.ExecutorService; 150 import java.util.concurrent.ScheduledThreadPoolExecutor; 151 152 @SetFlagTrue(KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_RUN_AD_SELECTION) 153 @SetFlagTrue(KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_REPORT_IMPRESSION) 154 @SetFlagTrue(KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_OVERRIDE) 155 @SetFlagTrue(KEY_DISABLE_FLEDGE_ENROLLMENT_CHECK) 156 @SetLongFlag( 157 name = KEY_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS, 158 value = EXTENDED_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS) 159 @SetLongFlag( 160 name = KEY_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS, 161 value = EXTENDED_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS) 162 // Unlimited rate for unit tests to avoid flake in tests due to rate 163 // limiting 164 @SetFloatFlag(name = KEY_SDK_REQUEST_PERMITS_PER_SECOND, value = -1) 165 @SetFlagTrue(KEY_FLEDGE_AD_SELECTION_PREBUILT_URI_ENABLED) 166 @SetFlagTrue(KEY_FLEDGE_SELECT_ADS_FROM_OUTCOMES_API_METRICS_ENABLED) 167 @SpyStatic(FlagsFactory.class) 168 @SpyStatic(DebugFlags.class) 169 public final class AdSelectionFromOutcomesIntegrationTest 170 extends AdServicesExtendedMockitoTestCase { 171 private static final int CALLER_UID = Process.myUid(); 172 private static final String SELECTION_PICK_HIGHEST_LOGIC_JS_PATH = "/selectionPickHighestJS/"; 173 private static final String SELECTION_PICK_NONE_LOGIC_JS_PATH = "/selectionPickNoneJS/"; 174 static final String SELECTION_WATERFALL_LOGIC_JS_PATH = "/selectionWaterfallJS/"; 175 private static final String SELECTION_FAULTY_LOGIC_JS_PATH = "/selectionFaultyJS/"; 176 private static final String SELECTION_PICK_HIGHEST_LOGIC_JS = 177 "function selectOutcome(outcomes, selection_signals) {\n" 178 + " let max_bid = 0;\n" 179 + " let winner_outcome = null;\n" 180 + " for (let outcome of outcomes) {\n" 181 + " if (outcome.bid > max_bid) {\n" 182 + " max_bid = outcome.bid;\n" 183 + " winner_outcome = outcome;\n" 184 + " }\n" 185 + " }\n" 186 + " return {'status': 0, 'result': winner_outcome};\n" 187 + "}"; 188 private static final String SELECTION_PICK_NONE_LOGIC_JS = 189 "function selectOutcome(outcomes, selection_signals) {\n" 190 + " return {'status': 0, 'result': null};\n" 191 + "}"; 192 static final String SELECTION_WATERFALL_LOGIC_JS = 193 "function selectOutcome(outcomes, selection_signals) {\n" 194 + " if (outcomes.length != 1 || selection_signals.bid_floor ==" 195 + " undefined) return null;\n" 196 + "\n" 197 + " const outcome_1p = outcomes[0];\n" 198 + " return {'status': 0, 'result': (outcome_1p.bid >" 199 + " selection_signals.bid_floor) ? outcome_1p : null};\n" 200 + "}"; 201 private static final String SELECTION_FAULTY_LOGIC_JS = 202 "function selectOutcome(outcomes, selection_signals) {\n" 203 + " return {'status': 0, 'result': {\"id\": outcomes[0].id + 1, \"bid\": " 204 + "outcomes[0].bid}};\n" 205 + "}"; 206 static final String BID_FLOOR_SELECTION_SIGNAL_TEMPLATE = "{\"bid_floor\":%s}"; 207 208 private static final AdTechIdentifier SELLER_INCONSISTENT_WITH_SELECTION_URI = 209 AdTechIdentifier.fromString("inconsistent.developer.android.com"); 210 private static final long BINDER_ELAPSED_TIME_MS = 100L; 211 private static final String CALLER_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME; 212 private static final long AD_SELECTION_ID_1 = 12345L; 213 private static final long AD_SELECTION_ID_2 = 123456L; 214 private static final long AD_SELECTION_ID_3 = 1234567L; 215 private static final long AD_SELECTION_ID_4 = 12345678L; 216 private static final boolean CONSOLE_MESSAGE_IN_LOGS_ENABLED = true; 217 218 private static final int SUCCESS_DOWNLOAD_RESULT_CODE = 200; 219 220 private final AdServicesLogger mAdServicesLoggerMock = 221 ExtendedMockito.mock(AdServicesLoggerImpl.class); 222 223 // Every test in this class requires that the JS Sandbox be available. The JS Sandbox 224 // availability depends on an external component (the system webview) being higher than a 225 // certain minimum version. 226 @Rule(order = 1) 227 public final SupportedByConditionRule webViewSupportsJSSandbox = 228 WebViewSupportUtil.createJSSandboxAvailableRule( 229 ApplicationProvider.getApplicationContext()); 230 231 @Rule(order = 2) 232 public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps(); 233 234 // Mocking DevContextFilter to test behavior with and without override api authorization 235 @Mock DevContextFilter mDevContextFilter; 236 @Mock CallerMetadata mMockCallerMetadata; 237 @Mock private File mMockDBAdSelectionFile; 238 @Mock private ConsentManager mConsentManagerMock; 239 @Mock private KAnonSignJoinFactory mUnusedKAnonSignJoinFactory; 240 @Mock private Clock mMockClock; 241 242 FledgeAuthorizationFilter mFledgeAuthorizationFilter = 243 new FledgeAuthorizationFilter( 244 mSpyContext.getPackageManager(), 245 new EnrollmentDao( 246 mSpyContext, 247 DbTestUtil.getSharedDbHelperForTest(), 248 mFakeFlags, 249 mMockClock), 250 mAdServicesLoggerMock); 251 252 private ExecutorService mLightweightExecutorService; 253 private ExecutorService mBackgroundExecutorService; 254 private ScheduledThreadPoolExecutor mScheduledExecutor; 255 private CustomAudienceDao mCustomAudienceDao; 256 private EncodedPayloadDao mEncodedPayloadDao; 257 private AppInstallDao mAppInstallDao; 258 private FrequencyCapDao mFrequencyCapDao; 259 private AdSelectionEntryDao mAdSelectionEntryDaoSpy; 260 private EnrollmentDao mEnrollmentDao; 261 private EncryptionKeyDao mEncryptionKeyDao; 262 private AdServicesHttpsClient mAdServicesHttpsClient; 263 private AdSelectionServiceImpl mAdSelectionService; 264 private Dispatcher mDispatcher; 265 private AdFilteringFeatureFactory mAdFilteringFeatureFactory; 266 @Mock private AdSelectionServiceFilter mAdSelectionServiceFilter; 267 @Mock private ObliviousHttpEncryptor mObliviousHttpEncryptor; 268 @Mock private AdSelectionDebugReportDao mAdSelectionDebugReportDao; 269 @Mock private AdIdFetcher mAdIdFetcher; 270 private RetryStrategyFactory mRetryStrategyFactory; 271 private AuctionServerDebugConfigurationGenerator mAuctionServerDebugConfigurationGenerator; 272 private ServerAuctionCoordinatorUriStrategyFactory mServerAuctionCoordinatorUriStrategyFactory; 273 274 @Before setUp()275 public void setUp() throws Exception { 276 doReturn(mFakeFlags).when(FlagsFactory::getFlags); 277 mocker.mockGetFlags(mFakeFlags); 278 mocker.mockGetDebugFlags(mFakeDebugFlags); 279 280 mAdSelectionEntryDaoSpy = 281 spy( 282 Room.inMemoryDatabaseBuilder(mSpyContext, AdSelectionDatabase.class) 283 .build() 284 .adSelectionEntryDao()); 285 286 // Initialize dependencies for the AdSelectionService 287 mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor(); 288 mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor(); 289 mScheduledExecutor = AdServicesExecutors.getScheduler(); 290 mCustomAudienceDao = 291 Room.inMemoryDatabaseBuilder(mSpyContext, CustomAudienceDatabase.class) 292 .addTypeConverter(new DBCustomAudience.Converters(true, true, true)) 293 .build() 294 .customAudienceDao(); 295 mEncodedPayloadDao = 296 Room.inMemoryDatabaseBuilder(mSpyContext, ProtectedSignalsDatabase.class) 297 .build() 298 .getEncodedPayloadDao(); 299 SharedStorageDatabase sharedDb = 300 Room.inMemoryDatabaseBuilder(mSpyContext, SharedStorageDatabase.class).build(); 301 mAppInstallDao = sharedDb.appInstallDao(); 302 mFrequencyCapDao = sharedDb.frequencyCapDao(); 303 AdSelectionServerDatabase serverDb = 304 Room.inMemoryDatabaseBuilder(mSpyContext, AdSelectionServerDatabase.class).build(); 305 mEnrollmentDao = EnrollmentDao.getInstance(); 306 mEncryptionKeyDao = EncryptionKeyDao.getInstance(); 307 mAdFilteringFeatureFactory = 308 new AdFilteringFeatureFactory(mAppInstallDao, mFrequencyCapDao, mFakeFlags); 309 mAdServicesHttpsClient = 310 new AdServicesHttpsClient( 311 AdServicesExecutors.getBlockingExecutor(), 312 CacheProviderFactory.createNoOpCache()); 313 mRetryStrategyFactory = RetryStrategyFactory.createInstanceForTesting(); 314 ConsentedDebugConfigurationDao consentedDebugConfigurationDao = 315 Room.inMemoryDatabaseBuilder(mSpyContext, AdSelectionDatabase.class) 316 .build() 317 .consentedDebugConfigurationDao(); 318 ConsentedDebugConfigurationGeneratorFactory consentedDebugConfigurationGeneratorFactory = 319 new ConsentedDebugConfigurationGeneratorFactory( 320 false, consentedDebugConfigurationDao); 321 mAuctionServerDebugConfigurationGenerator = 322 new AuctionServerDebugConfigurationGenerator( 323 Flags.ADID_KILL_SWITCH, 324 Flags.DEFAULT_AUCTION_SERVER_AD_ID_FETCHER_TIMEOUT_MS, 325 Flags.FLEDGE_AUCTION_SERVER_ENABLE_DEBUG_REPORTING, 326 Flags.DEFAULT_FLEDGE_AUCTION_SERVER_ENABLE_PAS_UNLIMITED_EGRESS, 327 Flags.DEFAULT_PROD_DEBUG_IN_AUCTION_SERVER, 328 mAdIdFetcher, 329 consentedDebugConfigurationGeneratorFactory.create(), 330 mLightweightExecutorService); 331 when(mDevContextFilter.createDevContext()) 332 .thenReturn(DevContext.createForDevOptionsDisabled()); 333 when(mMockCallerMetadata.getBinderElapsedTimestamp()) 334 .thenReturn(SystemClock.elapsedRealtime() - BINDER_ELAPSED_TIME_MS); 335 mServerAuctionCoordinatorUriStrategyFactory = 336 new ServerAuctionCoordinatorUriStrategyFactory( 337 mFakeFlags.getFledgeAuctionServerCoordinatorUrlAllowlist()); 338 339 // Create an instance of AdSelection Service with real dependencies 340 mAdSelectionService = 341 new AdSelectionServiceImpl( 342 mAdSelectionEntryDaoSpy, 343 mAppInstallDao, 344 mCustomAudienceDao, 345 mEncodedPayloadDao, 346 mFrequencyCapDao, 347 mEncryptionKeyDao, 348 mEnrollmentDao, 349 mAdServicesHttpsClient, 350 mDevContextFilter, 351 mLightweightExecutorService, 352 mBackgroundExecutorService, 353 mScheduledExecutor, 354 mSpyContext, 355 mAdServicesLoggerMock, 356 mFakeFlags, 357 mFakeDebugFlags, 358 CallingAppUidSupplierProcessImpl.create(), 359 mFledgeAuthorizationFilter, 360 mAdSelectionServiceFilter, 361 mAdFilteringFeatureFactory, 362 mConsentManagerMock, 363 mObliviousHttpEncryptor, 364 mAdSelectionDebugReportDao, 365 mAdIdFetcher, 366 mUnusedKAnonSignJoinFactory, 367 false, 368 mRetryStrategyFactory, 369 CONSOLE_MESSAGE_IN_LOGS_ENABLED, 370 mAuctionServerDebugConfigurationGenerator, 371 mServerAuctionCoordinatorUriStrategyFactory); 372 373 // Create a dispatcher that helps map a request -> response in mockWebServer 374 mDispatcher = 375 new Dispatcher() { 376 @Override 377 public MockResponse dispatch(RecordedRequest request) { 378 switch (request.getPath()) { 379 case SELECTION_PICK_HIGHEST_LOGIC_JS_PATH: 380 return new MockResponse().setBody(SELECTION_PICK_HIGHEST_LOGIC_JS); 381 case SELECTION_PICK_NONE_LOGIC_JS_PATH: 382 return new MockResponse().setBody(SELECTION_PICK_NONE_LOGIC_JS); 383 case SELECTION_WATERFALL_LOGIC_JS_PATH: 384 return new MockResponse().setBody(SELECTION_WATERFALL_LOGIC_JS); 385 case SELECTION_FAULTY_LOGIC_JS_PATH: 386 return new MockResponse().setBody(SELECTION_FAULTY_LOGIC_JS); 387 default: 388 return new MockResponse().setResponseCode(404); 389 } 390 } 391 }; 392 393 when(mSpyContext.getDatabasePath(DATABASE_NAME)).thenReturn(mMockDBAdSelectionFile); 394 when(mMockDBAdSelectionFile.length()).thenReturn(DB_AD_SELECTION_FILE_SIZE); 395 doNothing() 396 .when(mAdSelectionServiceFilter) 397 .filterRequest( 398 SAMPLE_SELLER, 399 CALLER_PACKAGE_NAME, 400 false, 401 true, 402 true, 403 CALLER_UID, 404 AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, 405 Throttler.ApiKey.FLEDGE_API_SELECT_ADS, 406 DevContext.createForDevOptionsDisabled()); 407 } 408 409 @Test testSelectAdsFromOutcomesPickHighestSuccess()410 public void testSelectAdsFromOutcomesPickHighestSuccess() throws Exception { 411 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 412 final String selectionLogicPath = SELECTION_PICK_HIGHEST_LOGIC_JS_PATH; 413 414 CountDownLatch loggingLatch = new CountDownLatch(1); 415 ExtendedMockito.doAnswer( 416 unused -> { 417 loggingLatch.countDown(); 418 return null; 419 }) 420 .when(mAdServicesLoggerMock) 421 .logSelectAdsFromOutcomesApiCalledStats(any()); 422 423 Map<Long, Double> adSelectionIdToBidMap = 424 Map.of( 425 AD_SELECTION_ID_1, 10.0, 426 AD_SELECTION_ID_2, 20.0, 427 AD_SELECTION_ID_3, 30.0); 428 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 429 430 AdSelectionFromOutcomesConfig config = 431 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 432 List.of(AD_SELECTION_ID_1, AD_SELECTION_ID_2, AD_SELECTION_ID_3), 433 AdSelectionSignals.EMPTY, 434 mMockWebServerRule.uriForPath(selectionLogicPath)); 435 436 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 437 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 438 439 assertThat(resultsCallback.mIsSuccess).isTrue(); 440 assertThat(resultsCallback.mAdSelectionResponse).isNotNull(); 441 assertEquals(resultsCallback.mAdSelectionResponse.getAdSelectionId(), AD_SELECTION_ID_3); 442 mMockWebServerRule.verifyMockServerRequests( 443 server, 1, Collections.singletonList(selectionLogicPath), String::equals); 444 445 loggingLatch.await(); 446 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 447 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 448 verify(mAdServicesLoggerMock) 449 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 450 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 451 assertThat(stats.getCountIds()).isEqualTo(3); 452 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 453 assertThat(stats.getUsedPrebuilt()).isEqualTo(false); 454 assertThat(stats.getDownloadLatencyMillis()).isNotEqualTo(FIELD_UNSET); 455 assertThat(stats.getDownloadResultCode()).isEqualTo(SUCCESS_DOWNLOAD_RESULT_CODE); 456 assertThat(stats.getExecutionLatencyMillis()).isNotEqualTo(FIELD_UNSET); 457 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_SUCCESS); 458 } 459 460 @Test testSelectAdsFromOutcomesPickHighestSuccessDifferentTables()461 public void testSelectAdsFromOutcomesPickHighestSuccessDifferentTables() throws Exception { 462 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 463 String selectionLogicPath = SELECTION_PICK_HIGHEST_LOGIC_JS_PATH; 464 465 CountDownLatch loggingLatch = new CountDownLatch(1); 466 ExtendedMockito.doAnswer( 467 unused -> { 468 loggingLatch.countDown(); 469 return null; 470 }) 471 .when(mAdServicesLoggerMock) 472 .logSelectAdsFromOutcomesApiCalledStats(any()); 473 474 // On-device ids 475 Map<Long, Double> onDeviceAdSelectionIdToBid = 476 Map.of( 477 AD_SELECTION_ID_1, 10.0, 478 AD_SELECTION_ID_2, 20.0, 479 AD_SELECTION_ID_3, 30.0); 480 persistAdSelectionEntryDaoResults(onDeviceAdSelectionIdToBid); 481 Map<Long, Double> serverAdSelectionIdToBid = Map.of(AD_SELECTION_ID_4, 40.0); 482 persistAdSelectionEntryInServerAuctionTable(serverAdSelectionIdToBid); 483 484 List<Long> adSelectionIds = new ArrayList<>(onDeviceAdSelectionIdToBid.keySet()); 485 adSelectionIds.addAll(serverAdSelectionIdToBid.keySet()); 486 AdSelectionFromOutcomesConfig config = 487 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 488 adSelectionIds, 489 AdSelectionSignals.EMPTY, 490 mMockWebServerRule.uriForPath(selectionLogicPath)); 491 492 AdSelectionService adSelectionService = 493 new AdSelectionServiceImpl( 494 mAdSelectionEntryDaoSpy, 495 mAppInstallDao, 496 mCustomAudienceDao, 497 mEncodedPayloadDao, 498 mFrequencyCapDao, 499 mEncryptionKeyDao, 500 mEnrollmentDao, 501 mAdServicesHttpsClient, 502 mDevContextFilter, 503 mLightweightExecutorService, 504 mBackgroundExecutorService, 505 mScheduledExecutor, 506 mSpyContext, 507 mAdServicesLoggerMock, 508 mFakeFlags, 509 mFakeDebugFlags, 510 CallingAppUidSupplierProcessImpl.create(), 511 mFledgeAuthorizationFilter, 512 mAdSelectionServiceFilter, 513 mAdFilteringFeatureFactory, 514 mConsentManagerMock, 515 mObliviousHttpEncryptor, 516 mAdSelectionDebugReportDao, 517 mAdIdFetcher, 518 mUnusedKAnonSignJoinFactory, 519 false, 520 mRetryStrategyFactory, 521 CONSOLE_MESSAGE_IN_LOGS_ENABLED, 522 mAuctionServerDebugConfigurationGenerator, 523 mServerAuctionCoordinatorUriStrategyFactory); 524 525 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 526 invokeSelectAdsFromOutcomes(adSelectionService, config, CALLER_PACKAGE_NAME); 527 528 assertThat(resultsCallback.mIsSuccess).isTrue(); 529 assertThat(resultsCallback.mAdSelectionResponse).isNotNull(); 530 assertEquals(AD_SELECTION_ID_3, resultsCallback.mAdSelectionResponse.getAdSelectionId()); 531 mMockWebServerRule.verifyMockServerRequests( 532 server, 1, Collections.singletonList(selectionLogicPath), String::equals); 533 534 loggingLatch.await(); 535 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 536 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 537 verify(mAdServicesLoggerMock) 538 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 539 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 540 assertThat(stats.getCountIds()).isEqualTo(4); 541 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 542 assertThat(stats.getUsedPrebuilt()).isEqualTo(false); 543 assertThat(stats.getDownloadLatencyMillis()).isNotEqualTo(FIELD_UNSET); 544 assertThat(stats.getDownloadResultCode()).isEqualTo(SUCCESS_DOWNLOAD_RESULT_CODE); 545 assertThat(stats.getExecutionLatencyMillis()).isNotEqualTo(FIELD_UNSET); 546 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_SUCCESS); 547 } 548 549 @Test testSelectAdsFromOutcomesPickHighestSuccessUnifiedTables()550 public void testSelectAdsFromOutcomesPickHighestSuccessUnifiedTables() throws Exception { 551 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 552 final String selectionLogicPath = SELECTION_PICK_HIGHEST_LOGIC_JS_PATH; 553 554 CountDownLatch loggingLatch = new CountDownLatch(1); 555 ExtendedMockito.doAnswer( 556 unused -> { 557 loggingLatch.countDown(); 558 return null; 559 }) 560 .when(mAdServicesLoggerMock) 561 .logSelectAdsFromOutcomesApiCalledStats(any()); 562 563 Map<Long, Double> adSelectionIdToBidMap = 564 Map.of( 565 AD_SELECTION_ID_1, 10.0, 566 AD_SELECTION_ID_2, 20.0, 567 AD_SELECTION_ID_3, 30.0); 568 persistAdSelectionEntryInUnifiedTable(adSelectionIdToBidMap); 569 570 AdSelectionFromOutcomesConfig config = 571 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 572 List.of(AD_SELECTION_ID_1, AD_SELECTION_ID_2, AD_SELECTION_ID_3), 573 AdSelectionSignals.EMPTY, 574 mMockWebServerRule.uriForPath(selectionLogicPath)); 575 576 mAdSelectionService = 577 new AdSelectionServiceImpl( 578 mAdSelectionEntryDaoSpy, 579 mAppInstallDao, 580 mCustomAudienceDao, 581 mEncodedPayloadDao, 582 mFrequencyCapDao, 583 mEncryptionKeyDao, 584 mEnrollmentDao, 585 mAdServicesHttpsClient, 586 mDevContextFilter, 587 mLightweightExecutorService, 588 mBackgroundExecutorService, 589 mScheduledExecutor, 590 mSpyContext, 591 mAdServicesLoggerMock, 592 mFakeFlags, 593 mFakeDebugFlags, 594 CallingAppUidSupplierProcessImpl.create(), 595 mFledgeAuthorizationFilter, 596 mAdSelectionServiceFilter, 597 mAdFilteringFeatureFactory, 598 mConsentManagerMock, 599 mObliviousHttpEncryptor, 600 mAdSelectionDebugReportDao, 601 mAdIdFetcher, 602 mUnusedKAnonSignJoinFactory, 603 true, 604 mRetryStrategyFactory, 605 CONSOLE_MESSAGE_IN_LOGS_ENABLED, 606 mAuctionServerDebugConfigurationGenerator, 607 mServerAuctionCoordinatorUriStrategyFactory); 608 609 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 610 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 611 612 assertThat(resultsCallback.mIsSuccess).isTrue(); 613 assertThat(resultsCallback.mAdSelectionResponse).isNotNull(); 614 assertEquals(AD_SELECTION_ID_3, resultsCallback.mAdSelectionResponse.getAdSelectionId()); 615 verify(mAdSelectionEntryDaoSpy).getWinningBidAndUriForIdsUnifiedTables(any()); 616 verify(mAdSelectionEntryDaoSpy) 617 .getAdSelectionIdsWithCallerPackageNameFromUnifiedTable(any(), any()); 618 mMockWebServerRule.verifyMockServerRequests( 619 server, 1, Collections.singletonList(selectionLogicPath), String::equals); 620 621 loggingLatch.await(); 622 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 623 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 624 verify(mAdServicesLoggerMock) 625 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 626 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 627 assertThat(stats.getCountIds()).isEqualTo(3); 628 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 629 assertThat(stats.getUsedPrebuilt()).isEqualTo(false); 630 assertThat(stats.getDownloadLatencyMillis()).isNotEqualTo(FIELD_UNSET); 631 assertThat(stats.getDownloadResultCode()).isEqualTo(SUCCESS_DOWNLOAD_RESULT_CODE); 632 assertThat(stats.getExecutionLatencyMillis()).isNotEqualTo(FIELD_UNSET); 633 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_SUCCESS); 634 } 635 636 @Test testSelectAdsFromOutcomesWaterfallMediationAdBidHigherThanBidFloorSuccess()637 public void testSelectAdsFromOutcomesWaterfallMediationAdBidHigherThanBidFloorSuccess() 638 throws Exception { 639 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 640 final String selectionLogicPath = SELECTION_WATERFALL_LOGIC_JS_PATH; 641 642 CountDownLatch loggingLatch = new CountDownLatch(1); 643 ExtendedMockito.doAnswer( 644 unused -> { 645 loggingLatch.countDown(); 646 return null; 647 }) 648 .when(mAdServicesLoggerMock) 649 .logSelectAdsFromOutcomesApiCalledStats(any()); 650 651 Map<Long, Double> adSelectionIdToBidMap = Map.of(AD_SELECTION_ID_1, 10.0); 652 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 653 654 AdSelectionFromOutcomesConfig config = 655 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 656 Collections.singletonList(AD_SELECTION_ID_1), 657 AdSelectionSignals.fromString( 658 String.format(BID_FLOOR_SELECTION_SIGNAL_TEMPLATE, 9)), 659 mMockWebServerRule.uriForPath(selectionLogicPath)); 660 661 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 662 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 663 664 assertThat(resultsCallback.mIsSuccess).isTrue(); 665 assertThat(resultsCallback.mAdSelectionResponse).isNotNull(); 666 assertEquals(resultsCallback.mAdSelectionResponse.getAdSelectionId(), AD_SELECTION_ID_1); 667 mMockWebServerRule.verifyMockServerRequests( 668 server, 1, Collections.singletonList(selectionLogicPath), String::equals); 669 670 loggingLatch.await(); 671 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 672 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 673 verify(mAdServicesLoggerMock) 674 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 675 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 676 assertThat(stats.getCountIds()).isEqualTo(1); 677 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 678 assertThat(stats.getUsedPrebuilt()).isEqualTo(false); 679 assertThat(stats.getDownloadLatencyMillis()).isNotEqualTo(FIELD_UNSET); 680 assertThat(stats.getDownloadResultCode()).isEqualTo(SUCCESS_DOWNLOAD_RESULT_CODE); 681 assertThat(stats.getExecutionLatencyMillis()).isNotEqualTo(FIELD_UNSET); 682 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_SUCCESS); 683 } 684 685 @Test testSelectAdsFromOutcomesWaterfallMediationPrebuiltUriSuccess()686 public void testSelectAdsFromOutcomesWaterfallMediationPrebuiltUriSuccess() throws Exception { 687 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 688 689 CountDownLatch loggingLatch = new CountDownLatch(1); 690 ExtendedMockito.doAnswer( 691 unused -> { 692 loggingLatch.countDown(); 693 return null; 694 }) 695 .when(mAdServicesLoggerMock) 696 .logSelectAdsFromOutcomesApiCalledStats(any()); 697 698 Map<Long, Double> adSelectionIdToBidMap = Map.of(AD_SELECTION_ID_1, 10.0); 699 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 700 701 String paramKey = "bidFloor"; 702 String paramValue = "bid_floor"; 703 Uri prebuiltUri = 704 Uri.parse( 705 String.format( 706 "%s://%s/%s/?%s=%s", 707 AD_SELECTION_PREBUILT_SCHEMA, 708 AD_SELECTION_FROM_OUTCOMES_USE_CASE, 709 AD_OUTCOME_SELECTION_WATERFALL_MEDIATION_TRUNCATION, 710 paramKey, 711 paramValue)); 712 713 AdSelectionFromOutcomesConfig config = 714 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 715 Collections.singletonList(AD_SELECTION_ID_1), 716 AdSelectionSignals.fromString( 717 String.format(BID_FLOOR_SELECTION_SIGNAL_TEMPLATE, 9)), 718 prebuiltUri); 719 720 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 721 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 722 723 assertThat(resultsCallback.mIsSuccess).isTrue(); 724 assertThat(resultsCallback.mAdSelectionResponse).isNotNull(); 725 assertEquals(resultsCallback.mAdSelectionResponse.getAdSelectionId(), AD_SELECTION_ID_1); 726 mMockWebServerRule.verifyMockServerRequests( 727 server, 0, Collections.emptyList(), String::equals); 728 729 loggingLatch.await(); 730 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 731 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 732 verify(mAdServicesLoggerMock) 733 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 734 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 735 assertThat(stats.getCountIds()).isEqualTo(1); 736 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 737 assertThat(stats.getUsedPrebuilt()).isEqualTo(true); 738 assertThat(stats.getDownloadLatencyMillis()).isEqualTo(FIELD_UNSET); 739 assertThat(stats.getDownloadResultCode()).isEqualTo(FIELD_UNSET); 740 assertThat(stats.getExecutionLatencyMillis()).isNotEqualTo(FIELD_UNSET); 741 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_SUCCESS); 742 } 743 744 @Test testSelectAdsFromOutcomesWaterfallMediationAdBidLowerThanBidFloorSuccess()745 public void testSelectAdsFromOutcomesWaterfallMediationAdBidLowerThanBidFloorSuccess() 746 throws Exception { 747 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 748 final String selectionLogicPath = SELECTION_WATERFALL_LOGIC_JS_PATH; 749 750 CountDownLatch loggingLatch = new CountDownLatch(1); 751 ExtendedMockito.doAnswer( 752 unused -> { 753 loggingLatch.countDown(); 754 return null; 755 }) 756 .when(mAdServicesLoggerMock) 757 .logSelectAdsFromOutcomesApiCalledStats(any()); 758 759 Map<Long, Double> adSelectionIdToBidMap = Map.of(AD_SELECTION_ID_1, 10.0); 760 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 761 762 AdSelectionFromOutcomesConfig config = 763 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 764 Collections.singletonList(AD_SELECTION_ID_1), 765 AdSelectionSignals.fromString( 766 String.format(BID_FLOOR_SELECTION_SIGNAL_TEMPLATE, 11)), 767 mMockWebServerRule.uriForPath(selectionLogicPath)); 768 769 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 770 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 771 772 assertThat(resultsCallback.mIsSuccess).isTrue(); 773 assertThat(resultsCallback.mAdSelectionResponse).isNull(); 774 mMockWebServerRule.verifyMockServerRequests( 775 server, 1, Collections.singletonList(selectionLogicPath), String::equals); 776 777 loggingLatch.await(); 778 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 779 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 780 verify(mAdServicesLoggerMock) 781 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 782 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 783 assertThat(stats.getCountIds()).isEqualTo(1); 784 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 785 assertThat(stats.getUsedPrebuilt()).isEqualTo(false); 786 assertThat(stats.getDownloadLatencyMillis()).isNotEqualTo(FIELD_UNSET); 787 assertThat(stats.getDownloadResultCode()).isEqualTo(SUCCESS_DOWNLOAD_RESULT_CODE); 788 assertThat(stats.getExecutionLatencyMillis()).isNotEqualTo(FIELD_UNSET); 789 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_SUCCESS); 790 } 791 792 @Test testSelectAdsFromOutcomesReturnsNullSuccess()793 public void testSelectAdsFromOutcomesReturnsNullSuccess() throws Exception { 794 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 795 final String selectionLogicPath = SELECTION_PICK_NONE_LOGIC_JS_PATH; 796 797 CountDownLatch loggingLatch = new CountDownLatch(1); 798 ExtendedMockito.doAnswer( 799 unused -> { 800 loggingLatch.countDown(); 801 return null; 802 }) 803 .when(mAdServicesLoggerMock) 804 .logSelectAdsFromOutcomesApiCalledStats(any()); 805 806 Map<Long, Double> adSelectionIdToBidMap = 807 Map.of( 808 AD_SELECTION_ID_1, 10.0, 809 AD_SELECTION_ID_2, 20.0, 810 AD_SELECTION_ID_3, 30.0); 811 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 812 813 AdSelectionFromOutcomesConfig config = 814 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 815 List.of(AD_SELECTION_ID_1, AD_SELECTION_ID_2, AD_SELECTION_ID_3), 816 AdSelectionSignals.EMPTY, 817 mMockWebServerRule.uriForPath(selectionLogicPath)); 818 819 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 820 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 821 822 assertThat(resultsCallback.mIsSuccess).isTrue(); 823 assertThat(resultsCallback.mAdSelectionResponse).isNull(); 824 mMockWebServerRule.verifyMockServerRequests( 825 server, 1, Collections.singletonList(selectionLogicPath), String::equals); 826 827 loggingLatch.await(); 828 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 829 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 830 verify(mAdServicesLoggerMock) 831 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 832 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 833 assertThat(stats.getCountIds()).isEqualTo(3); 834 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 835 assertThat(stats.getUsedPrebuilt()).isEqualTo(false); 836 assertThat(stats.getDownloadLatencyMillis()).isNotEqualTo(FIELD_UNSET); 837 assertThat(stats.getDownloadResultCode()).isEqualTo(SUCCESS_DOWNLOAD_RESULT_CODE); 838 assertThat(stats.getExecutionLatencyMillis()).isNotEqualTo(FIELD_UNSET); 839 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_SUCCESS); 840 } 841 842 @Test testSelectAdsFromOutcomesInvalidAdSelectionConfigFromOutcomes()843 public void testSelectAdsFromOutcomesInvalidAdSelectionConfigFromOutcomes() throws Exception { 844 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 845 final String selectionLogicPath = "/unreachableLogicJS/"; 846 847 long adSelectionId1 = 12345L; 848 long adSelectionId2 = 123456L; 849 long adSelectionId3 = 1234567L; 850 851 Map<Long, Double> adSelectionIdToBidMap = 852 Map.of( 853 adSelectionId1, 10.0, 854 adSelectionId2, 20.0, 855 adSelectionId3, 30.0); 856 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 857 858 AdSelectionFromOutcomesConfig config = 859 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 860 SELLER_INCONSISTENT_WITH_SELECTION_URI, 861 List.of(adSelectionId1, adSelectionId2, adSelectionId3), 862 AdSelectionSignals.EMPTY, 863 mMockWebServerRule.uriForPath(selectionLogicPath)); 864 865 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 866 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 867 868 assertThat(resultsCallback.mIsSuccess).isFalse(); 869 assertThat(resultsCallback.mFledgeErrorResponse).isNotNull(); 870 assertThat(resultsCallback.mFledgeErrorResponse.getStatusCode()) 871 .isEqualTo(STATUS_INVALID_ARGUMENT); 872 mMockWebServerRule.verifyMockServerRequests( 873 server, 0, Collections.emptyList(), String::equals); 874 } 875 876 @Test testSelectAdsFromOutcomesJsReturnsFaultyAdSelectionIdFailure()877 public void testSelectAdsFromOutcomesJsReturnsFaultyAdSelectionIdFailure() throws Exception { 878 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 879 final String selectionLogicPath = SELECTION_FAULTY_LOGIC_JS_PATH; 880 881 Map<Long, Double> adSelectionIdToBidMap = 882 Map.of( 883 AD_SELECTION_ID_1, 10.0, 884 AD_SELECTION_ID_2, 20.0, 885 AD_SELECTION_ID_3, 30.0); 886 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 887 888 AdSelectionFromOutcomesConfig config = 889 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 890 List.of(AD_SELECTION_ID_1, AD_SELECTION_ID_2, AD_SELECTION_ID_3), 891 AdSelectionSignals.EMPTY, 892 mMockWebServerRule.uriForPath(selectionLogicPath)); 893 894 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 895 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 896 897 assertThat(resultsCallback.mIsSuccess).isFalse(); 898 assertThat(resultsCallback.mFledgeErrorResponse).isNotNull(); 899 assertThat(resultsCallback.mFledgeErrorResponse.getStatusCode()) 900 .isEqualTo(STATUS_INTERNAL_ERROR); 901 assertThat(resultsCallback.mFledgeErrorResponse.getErrorMessage()) 902 .contains(SELECTED_OUTCOME_MUST_BE_ONE_OF_THE_INPUTS); 903 mMockWebServerRule.verifyMockServerRequests( 904 server, 1, Collections.singletonList(selectionLogicPath), String::equals); 905 } 906 907 @Test testSelectAdsFromOutcomesWaterfallMalformedPrebuiltUriFailed()908 public void testSelectAdsFromOutcomesWaterfallMalformedPrebuiltUriFailed() throws Exception { 909 MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher); 910 911 CountDownLatch loggingLatch = new CountDownLatch(1); 912 ExtendedMockito.doAnswer( 913 unused -> { 914 loggingLatch.countDown(); 915 return null; 916 }) 917 .when(mAdServicesLoggerMock) 918 .logSelectAdsFromOutcomesApiCalledStats(any()); 919 920 Map<Long, Double> adSelectionIdToBidMap = Map.of(AD_SELECTION_ID_1, 10.0); 921 persistAdSelectionEntryDaoResults(adSelectionIdToBidMap); 922 923 String unknownUseCase = "unknown-usecase"; 924 Uri prebuiltUri = 925 Uri.parse(String.format("%s://%s/", AD_SELECTION_PREBUILT_SCHEMA, unknownUseCase)); 926 927 AdSelectionFromOutcomesConfig config = 928 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig( 929 Collections.singletonList(AD_SELECTION_ID_1), 930 AdSelectionSignals.EMPTY, 931 prebuiltUri); 932 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback resultsCallback = 933 invokeSelectAdsFromOutcomes(mAdSelectionService, config, CALLER_PACKAGE_NAME); 934 935 assertThat(resultsCallback.mIsSuccess).isFalse(); 936 assertThat(resultsCallback.mFledgeErrorResponse).isNotNull(); 937 assertThat(resultsCallback.mFledgeErrorResponse.getStatusCode()) 938 .isEqualTo(STATUS_INTERNAL_ERROR); 939 assertThat(resultsCallback.mFledgeErrorResponse.getErrorMessage()) 940 .contains(OUTCOME_SELECTION_JS_RETURNED_UNEXPECTED_RESULT); 941 mMockWebServerRule.verifyMockServerRequests( 942 server, 0, Collections.emptyList(), String::equals); 943 944 loggingLatch.await(); 945 ArgumentCaptor<SelectAdsFromOutcomesApiCalledStats> argumentCaptor = 946 ArgumentCaptor.forClass(SelectAdsFromOutcomesApiCalledStats.class); 947 verify(mAdServicesLoggerMock) 948 .logSelectAdsFromOutcomesApiCalledStats(argumentCaptor.capture()); 949 SelectAdsFromOutcomesApiCalledStats stats = argumentCaptor.getValue(); 950 assertThat(stats.getCountIds()).isEqualTo(1); 951 assertThat(stats.getCountNonExistingIds()).isEqualTo(0); 952 assertThat(stats.getUsedPrebuilt()).isEqualTo(true); 953 assertThat(stats.getDownloadLatencyMillis()).isEqualTo(FIELD_UNSET); 954 assertThat(stats.getDownloadResultCode()).isEqualTo(FIELD_UNSET); 955 assertThat(stats.getExecutionLatencyMillis()).isEqualTo(FIELD_UNSET); 956 assertThat(stats.getExecutionResultCode()).isEqualTo(JS_RUN_STATUS_UNSET); 957 } 958 959 private AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback invokeSelectAdsFromOutcomes( AdSelectionService adSelectionService, AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, String callerPackageName)960 invokeSelectAdsFromOutcomes( 961 AdSelectionService adSelectionService, 962 AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, 963 String callerPackageName) 964 throws InterruptedException, RemoteException { 965 966 CountDownLatch countdownLatch = new CountDownLatch(1); 967 AdSelectionFromOutcomesIntegrationTest.AdSelectionFromOutcomesTestCallback 968 adSelectionTestCallback = 969 new AdSelectionFromOutcomesIntegrationTest 970 .AdSelectionFromOutcomesTestCallback(countdownLatch); 971 972 AdSelectionFromOutcomesInput input = 973 new AdSelectionFromOutcomesInput.Builder() 974 .setAdSelectionFromOutcomesConfig(adSelectionFromOutcomesConfig) 975 .setCallerPackageName(callerPackageName) 976 .build(); 977 978 adSelectionService.selectAdsFromOutcomes( 979 input, mMockCallerMetadata, adSelectionTestCallback); 980 adSelectionTestCallback.mCountDownLatch.await(); 981 return adSelectionTestCallback; 982 } 983 persistAdSelectionEntryDaoResults(Map<Long, Double> adSelectionIdToBidMap)984 private void persistAdSelectionEntryDaoResults(Map<Long, Double> adSelectionIdToBidMap) { 985 final Uri biddingLogicUri1 = Uri.parse("https://www.domain.com/logic/1"); 986 final Uri renderUri = Uri.parse("https://www.domain.com/advert/"); 987 final Instant activationTime = Instant.now(); 988 final String contextualSignals = "contextual_signals"; 989 final CustomAudienceSignals customAudienceSignals = 990 CustomAudienceSignalsFixture.aCustomAudienceSignals(); 991 992 for (Map.Entry<Long, Double> entry : adSelectionIdToBidMap.entrySet()) { 993 final DBAdSelection dbAdSelectionEntry = 994 new DBAdSelection.Builder() 995 .setAdSelectionId(entry.getKey()) 996 .setCustomAudienceSignals(customAudienceSignals) 997 .setBuyerContextualSignals(contextualSignals) 998 .setBiddingLogicUri(biddingLogicUri1) 999 .setWinningAdRenderUri(renderUri) 1000 .setWinningAdBid(entry.getValue()) 1001 .setCreationTimestamp(activationTime) 1002 .setCallerPackageName(CALLER_PACKAGE_NAME) 1003 .build(); 1004 mAdSelectionEntryDaoSpy.persistAdSelection(dbAdSelectionEntry); 1005 } 1006 } 1007 persistAdSelectionEntryInServerAuctionTable( Map<Long, Double> adSelectionIdToBidMap)1008 private void persistAdSelectionEntryInServerAuctionTable( 1009 Map<Long, Double> adSelectionIdToBidMap) { 1010 final Uri renderUri = Uri.parse("https://www.domain.com/advert/"); 1011 for (Map.Entry<Long, Double> entry : adSelectionIdToBidMap.entrySet()) { 1012 final AdSelectionInitialization adSelectionInitialization = 1013 AdSelectionInitialization.builder() 1014 .setSeller(SAMPLE_SELLER) 1015 .setCallerPackageName(CALLER_PACKAGE_NAME) 1016 .setCreationInstant(Instant.now()) 1017 .build(); 1018 final AdSelectionResultBidAndUri idWithBidAndRenderUri = 1019 AdSelectionResultBidAndUri.builder() 1020 .setAdSelectionId(entry.getKey()) 1021 .setWinningAdBid(entry.getValue()) 1022 .setWinningAdRenderUri(renderUri) 1023 .build(); 1024 mAdSelectionEntryDaoSpy.persistAdSelectionInitialization( 1025 idWithBidAndRenderUri.getAdSelectionId(), adSelectionInitialization); 1026 } 1027 } 1028 persistAdSelectionEntryInUnifiedTable(Map<Long, Double> adSelectionIdToBidMap)1029 private void persistAdSelectionEntryInUnifiedTable(Map<Long, Double> adSelectionIdToBidMap) { 1030 final Uri renderUri = Uri.parse("https://www.domain.com/advert/"); 1031 final CustomAudienceSignals customAudienceSignals = 1032 CustomAudienceSignalsFixture.aCustomAudienceSignals(); 1033 for (Map.Entry<Long, Double> entry : adSelectionIdToBidMap.entrySet()) { 1034 final AdSelectionInitialization adSelectionInitialization = 1035 AdSelectionInitialization.builder() 1036 .setSeller(SAMPLE_SELLER) 1037 .setCallerPackageName(CALLER_PACKAGE_NAME) 1038 .setCreationInstant(Instant.now()) 1039 .build(); 1040 final AdSelectionResultBidAndUri idWithBidAndRenderUri = 1041 AdSelectionResultBidAndUri.builder() 1042 .setAdSelectionId(entry.getKey()) 1043 .setWinningAdBid(entry.getValue()) 1044 .setWinningAdRenderUri(renderUri) 1045 .build(); 1046 final WinningCustomAudience winningCustomAudience = 1047 WinningCustomAudience.builder() 1048 .setOwner(customAudienceSignals.getOwner()) 1049 .setName(customAudienceSignals.getName()) 1050 .build(); 1051 mAdSelectionEntryDaoSpy.persistAdSelectionInitialization( 1052 idWithBidAndRenderUri.getAdSelectionId(), adSelectionInitialization); 1053 mAdSelectionEntryDaoSpy.persistAdSelectionResultForCustomAudience( 1054 entry.getKey(), 1055 idWithBidAndRenderUri, 1056 customAudienceSignals.getBuyer(), 1057 winningCustomAudience); 1058 } 1059 } 1060 1061 static class AdSelectionFromOutcomesTestCallback extends AdSelectionCallback.Stub { 1062 1063 final CountDownLatch mCountDownLatch; 1064 boolean mIsSuccess = false; 1065 AdSelectionResponse mAdSelectionResponse; 1066 FledgeErrorResponse mFledgeErrorResponse; 1067 AdSelectionFromOutcomesTestCallback(CountDownLatch countDownLatch)1068 AdSelectionFromOutcomesTestCallback(CountDownLatch countDownLatch) { 1069 mCountDownLatch = countDownLatch; 1070 mAdSelectionResponse = null; 1071 mFledgeErrorResponse = null; 1072 } 1073 1074 @Override onSuccess(AdSelectionResponse adSelectionResponse)1075 public void onSuccess(AdSelectionResponse adSelectionResponse) throws RemoteException { 1076 mIsSuccess = true; 1077 mAdSelectionResponse = adSelectionResponse; 1078 mCountDownLatch.countDown(); 1079 } 1080 1081 @Override onFailure(FledgeErrorResponse fledgeErrorResponse)1082 public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException { 1083 mIsSuccess = false; 1084 mFledgeErrorResponse = fledgeErrorResponse; 1085 mCountDownLatch.countDown(); 1086 } 1087 } 1088 } 1089