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