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