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