diff options
Diffstat (limited to 'adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java')
-rw-r--r-- | adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java new file mode 100644 index 0000000000..4223ac1cf5 --- /dev/null +++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.adservices.service.adselection; + +import static com.android.adservices.service.adselection.TrustedServerAdSelectionRunner.GZIP; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; + +import android.adservices.adselection.AdSelectionConfig; +import android.adservices.adselection.AdSelectionConfigFixture; +import android.adservices.common.AdTechIdentifier; +import android.adservices.common.CommonFixture; +import android.adservices.customaudience.CustomAudienceFixture; +import android.content.Context; +import android.net.Uri; +import android.os.Process; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.adservices.concurrency.AdServicesExecutors; +import com.android.adservices.customaudience.DBCustomAudienceFixture; +import com.android.adservices.data.adselection.AdSelectionEntryDao; +import com.android.adservices.data.adselection.DBAdSelection; +import com.android.adservices.data.customaudience.CustomAudienceDao; +import com.android.adservices.data.customaudience.DBCustomAudience; +import com.android.adservices.service.Flags; +import com.android.adservices.service.FlagsFactory; +import com.android.adservices.service.adselection.AdSelectionRunner.AdSelectionOrchestrationResult; +import com.android.adservices.service.common.AppImportanceFilter; +import com.android.adservices.service.common.FledgeAllowListsFilter; +import com.android.adservices.service.common.FledgeAuthorizationFilter; +import com.android.adservices.service.common.Throttler; +import com.android.adservices.service.consent.ConsentManager; +import com.android.adservices.service.proto.SellerFrontEndGrpc; +import com.android.adservices.service.proto.SellerFrontEndGrpc.SellerFrontEndFutureStub; +import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdRequest; +import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdRequest.SelectWinningAdRawRequest; +import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdResponse; +import com.android.adservices.service.stats.AdServicesLogger; +import com.android.adservices.service.stats.ApiServiceLatencyCalculator; +import com.android.dx.mockito.inline.extended.ExtendedMockito; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoSession; + +import java.time.Clock; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import io.grpc.ManagedChannel; +import io.grpc.okhttp.OkHttpChannelBuilder; + +public class TrustedServerAdSelectionRunnerTest { + private static final AdTechIdentifier BUYER_1 = AdSelectionConfigFixture.BUYER_1; + private static final AdTechIdentifier BUYER_2 = AdSelectionConfigFixture.BUYER_2; + private static final Long AD_SELECTION_ID = 1234L; + private static final String MY_APP_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME; + private static final AdTechIdentifier SELLER_VALID = + AdTechIdentifier.fromString("developer.android.com"); + private static final Uri DECISION_LOGIC_URI = + Uri.parse("https://developer.android.com/test/decisions_logic_uris"); + private static final Uri TRUSTED_SIGNALS_URI = + Uri.parse("https://developer.android.com/test/trusted_signals_uri"); + private static final int CALLER_UID = Process.myUid(); + private static final ListeningExecutorService sLightweightExecutorService = + AdServicesExecutors.getLightWeightExecutor(); + private static final ListeningExecutorService sBackgroundExecutorService = + AdServicesExecutors.getBackgroundExecutor(); + private static final ScheduledThreadPoolExecutor sScheduledExecutor = + AdServicesExecutors.getScheduler(); + + private static final AdSelectionConfig.Builder sAdSelectionConfigBuilder = + AdSelectionConfigFixture.anAdSelectionConfigBuilder() + .setSeller(SELLER_VALID) + .setCustomAudienceBuyers(Arrays.asList(BUYER_1, BUYER_2)) + .setDecisionLogicUri(DECISION_LOGIC_URI) + .setTrustedScoringSignalsUri(TRUSTED_SIGNALS_URI); + private static final DBCustomAudience sDBCustomAudience = createDBCustomAudience(BUYER_1); + + private static final AdScoringOutcome sAdScoringOutcome = + AdScoringOutcomeFixture.anAdScoringBuilder(BUYER_1, 2.0).build(); + private static final SelectWinningAdResponse sSelectWinningAdResponse = + SelectWinningAdResponse.newBuilder() + .setRawResponse( + SelectWinningAdResponse.SelectWinningAdRawResponse.newBuilder() + .setAdRenderUrl("valid.example.com/testing/hello/test.com") + .setScore(1) + .setCustomAudienceName(CustomAudienceFixture.VALID_NAME) + .setBidPrice(1)) + .build(); + + private MockitoSession mStaticMockSession = null; + private Context mContext = ApplicationProvider.getApplicationContext(); + private Flags mFlags = + new Flags() { + @Override + public long getAdSelectionOverallTimeoutMs() { + return 300; + } + }; + private TrustedServerAdSelectionRunner mAdSelectionRunner; + + @Mock private AppImportanceFilter mAppImportanceFilter; + @Mock private Clock mClock; + @Mock private ConsentManager mConsentManagerMock; + @Mock private Supplier<Throttler> mThrottlerSupplier; + @Mock private AdServicesLogger mAdServicesLoggerSpy; + @Mock private FledgeAuthorizationFilter mFledgeAuthorizationFilter; + @Mock private FledgeAllowListsFilter mFledgeAllowListsFilter; + @Mock private CustomAudienceDao mCustomAudienceDao; + @Mock private AdSelectionEntryDao mAdSelectionEntryDao; + @Mock private JsFetcher mJsFetcher; + @Mock private AdSelectionIdGenerator mMockAdSelectionIdGenerator; + @Mock private OkHttpChannelBuilder mChannelBuilder; + @Mock private ManagedChannel mManagedChannel; + @Mock private SellerFrontEndFutureStub mStub; + private SellerFrontEndFutureStub mStubWithCompression = + Mockito.mock(SellerFrontEndFutureStub.class, "mStubWithCompression"); + @Mock private ApiServiceLatencyCalculator mApiServiceLatencyCalculator; + + @Before + public void setUp() { + mStaticMockSession = + ExtendedMockito.mockitoSession() + .spyStatic(FlagsFactory.class) + .mockStatic(OkHttpChannelBuilder.class) + .mockStatic(SellerFrontEndGrpc.class) + .initMocks(this) + .startMocking(); + } + + private static DBCustomAudience createDBCustomAudience(final AdTechIdentifier buyer) { + return DBCustomAudienceFixture.getValidBuilderByBuyer(buyer) + .setOwner(buyer.toString() + CustomAudienceFixture.VALID_OWNER) + .setName(CustomAudienceFixture.VALID_NAME) + .setCreationTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI) + .setLastAdsAndBiddingDataUpdatedTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI) + .build(); + } + + @Test + public void testRunAdSelectionSuccess() { + doReturn(mChannelBuilder) + .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt())); + doReturn(mManagedChannel).when(mChannelBuilder).build(); + doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel)); + doReturn(mStubWithCompression).when(mStub).withCompression(GZIP); + doReturn(Futures.immediateFuture(sSelectWinningAdResponse)) + .when(mStubWithCompression) + .selectWinningAd(any(SelectWinningAdRequest.class)); + + AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build(); + when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID); + + FluentFuture js = FluentFuture.from(Futures.immediateFuture("js")); + when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js); + + mAdSelectionRunner = + new TrustedServerAdSelectionRunner( + mContext, + mCustomAudienceDao, + mAdSelectionEntryDao, + sLightweightExecutorService, + sBackgroundExecutorService, + sScheduledExecutor, + mConsentManagerMock, + mMockAdSelectionIdGenerator, + mClock, + mAdServicesLoggerSpy, + mAppImportanceFilter, + mFlags, + mThrottlerSupplier, + CALLER_UID, + mFledgeAuthorizationFilter, + mFledgeAllowListsFilter, + mJsFetcher, + mApiServiceLatencyCalculator); + AdSelectionOrchestrationResult adSelectionOrchestrationResult = + invokeRunAdSelection( + mAdSelectionRunner, + adSelectionConfig, + MY_APP_PACKAGE_NAME, + ImmutableList.of(sDBCustomAudience)); + + Uri expectedWinningAdRenderUri = + sAdScoringOutcome.getAdWithScore().getAdWithBid().getAdData().getRenderUri(); + double expectedWinningAdBid = sAdScoringOutcome.getAdWithScore().getAdWithBid().getBid(); + + // Set adSelectionId/timestamp to anything to be able to build the object; an error is + // thrown if the fields aren't set. + adSelectionOrchestrationResult.mDbAdSelectionBuilder.setAdSelectionId(AD_SELECTION_ID); + adSelectionOrchestrationResult.mDbAdSelectionBuilder.setCreationTimestamp(Instant.now()); + DBAdSelection dbAdSelection = adSelectionOrchestrationResult.mDbAdSelectionBuilder.build(); + + assertEquals(expectedWinningAdRenderUri, dbAdSelection.getWinningAdRenderUri()); + assertEquals(expectedWinningAdBid, dbAdSelection.getWinningAdBid(), 0); + + SellerFrontEndFutureStub unused = verify(mStub).withCompression(GZIP); + } + + @SuppressWarnings("FutureReturnValueIgnored") + @Test + public void verifyNoCANameInBiddingSignalKeys() { + doReturn(mChannelBuilder) + .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt())); + doReturn(mManagedChannel).when(mChannelBuilder).build(); + doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel)); + doReturn(mStubWithCompression).when(mStub).withCompression(GZIP); + doReturn(Futures.immediateFuture(sSelectWinningAdResponse)) + .when(mStubWithCompression) + .selectWinningAd(any(SelectWinningAdRequest.class)); + + AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build(); + when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID); + + FluentFuture js = FluentFuture.from(Futures.immediateFuture("js")); + when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js); + + mAdSelectionRunner = + new TrustedServerAdSelectionRunner( + mContext, + mCustomAudienceDao, + mAdSelectionEntryDao, + sLightweightExecutorService, + sBackgroundExecutorService, + sScheduledExecutor, + mConsentManagerMock, + mMockAdSelectionIdGenerator, + mClock, + mAdServicesLoggerSpy, + mAppImportanceFilter, + mFlags, + mThrottlerSupplier, + CALLER_UID, + mFledgeAuthorizationFilter, + mFledgeAllowListsFilter, + mJsFetcher, + mApiServiceLatencyCalculator); + + // Add CA name to bidding data keys and later verify we don't send it to the server. + DBCustomAudience customAudience = createDBCustomAudience(BUYER_1); + customAudience.getTrustedBiddingData().getKeys().add(customAudience.getName()); + + ArgumentCaptor<SelectWinningAdRequest> captor = + ArgumentCaptor.forClass(SelectWinningAdRequest.class); + + invokeRunAdSelection( + mAdSelectionRunner, + adSelectionConfig, + MY_APP_PACKAGE_NAME, + ImmutableList.of(customAudience)); + + // Verify the bidding signal keys list does *not* contain the CA name. + verify(mStubWithCompression).selectWinningAd(captor.capture()); + SelectWinningAdRawRequest req = captor.getValue().getRawRequest(); + List<String> biddingSignalKeys = + req.getRawBuyerInputMap() + .get(customAudience.getBuyer().toString()) + .getCustomAudiences(0) + .getBiddingSignalsKeysList(); + assertThat(biddingSignalKeys).doesNotContain(customAudience.getName()); + } + + @SuppressWarnings("FutureReturnValueIgnored") + @Test + public void verifyEmptyBiddingSignalKeys() { + doReturn(mChannelBuilder) + .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt())); + doReturn(mManagedChannel).when(mChannelBuilder).build(); + doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel)); + doReturn(mStubWithCompression).when(mStub).withCompression(GZIP); + doReturn(Futures.immediateFuture(sSelectWinningAdResponse)) + .when(mStubWithCompression) + .selectWinningAd(any(SelectWinningAdRequest.class)); + + AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build(); + when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID); + + FluentFuture js = FluentFuture.from(Futures.immediateFuture("js")); + when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js); + + mAdSelectionRunner = + new TrustedServerAdSelectionRunner( + mContext, + mCustomAudienceDao, + mAdSelectionEntryDao, + sLightweightExecutorService, + sBackgroundExecutorService, + sScheduledExecutor, + mConsentManagerMock, + mMockAdSelectionIdGenerator, + mClock, + mAdServicesLoggerSpy, + mAppImportanceFilter, + mFlags, + mThrottlerSupplier, + CALLER_UID, + mFledgeAuthorizationFilter, + mFledgeAllowListsFilter, + mJsFetcher, + mApiServiceLatencyCalculator); + + // Add CA name to bidding data keys and later verify we don't send it to the server. + DBCustomAudience customAudience = createDBCustomAudience(BUYER_1); + customAudience.getTrustedBiddingData().getKeys().clear(); + customAudience.getTrustedBiddingData().getKeys().add(customAudience.getName()); + + ArgumentCaptor<SelectWinningAdRequest> captor = + ArgumentCaptor.forClass(SelectWinningAdRequest.class); + + invokeRunAdSelection( + mAdSelectionRunner, + adSelectionConfig, + MY_APP_PACKAGE_NAME, + ImmutableList.of(customAudience)); + + // Verify the bidding signal keys list does *not* contain the CA name. + verify(mStubWithCompression).selectWinningAd(captor.capture()); + SelectWinningAdRawRequest req = captor.getValue().getRawRequest(); + List<String> biddingSignalKeys = + req.getRawBuyerInputMap() + .get(customAudience.getBuyer().toString()) + .getCustomAudiences(0) + .getBiddingSignalsKeysList(); + assertThat(biddingSignalKeys).isEmpty(); + } + + @Test(expected = RuntimeException.class) + public void verifyRuntimeExceptionOnBuyerJsFetchFail() { + doReturn(mChannelBuilder) + .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt())); + doReturn(mManagedChannel).when(mChannelBuilder).build(); + doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel)); + doReturn(mStubWithCompression).when(mStub).withCompression(GZIP); + doReturn(Futures.immediateFuture(sSelectWinningAdResponse)) + .when(mStubWithCompression) + .selectWinningAd(any(SelectWinningAdRequest.class)); + + AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build(); + when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID); + + doThrow(new IllegalStateException()) + .when(mJsFetcher) + .getBuyerDecisionLogic( + sDBCustomAudience.getBiddingLogicUri(), + sDBCustomAudience.getOwner(), + sDBCustomAudience.getBuyer(), + sDBCustomAudience.getName()); + + mAdSelectionRunner = + new TrustedServerAdSelectionRunner( + mContext, + mCustomAudienceDao, + mAdSelectionEntryDao, + sLightweightExecutorService, + sBackgroundExecutorService, + sScheduledExecutor, + mConsentManagerMock, + mMockAdSelectionIdGenerator, + mClock, + mAdServicesLoggerSpy, + mAppImportanceFilter, + mFlags, + mThrottlerSupplier, + CALLER_UID, + mFledgeAuthorizationFilter, + mFledgeAllowListsFilter, + mJsFetcher, + mApiServiceLatencyCalculator); + + invokeRunAdSelection( + mAdSelectionRunner, + adSelectionConfig, + MY_APP_PACKAGE_NAME, + ImmutableList.of(sDBCustomAudience)); + } + + @Test + public void verifyNoRequestCompressionWhenFlagDisabled() { + Flags flags = + new Flags() { + @Override + public boolean getAdSelectionOffDeviceRequestCompressionEnabled() { + return false; + } + }; + + doReturn(mChannelBuilder) + .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt())); + doReturn(mManagedChannel).when(mChannelBuilder).build(); + doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel)); + doReturn(Futures.immediateFuture(sSelectWinningAdResponse)) + .when(mStub) + .selectWinningAd(any(SelectWinningAdRequest.class)); + + AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build(); + when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID); + + FluentFuture js = FluentFuture.from(Futures.immediateFuture("js")); + when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js); + + mAdSelectionRunner = + new TrustedServerAdSelectionRunner( + mContext, + mCustomAudienceDao, + mAdSelectionEntryDao, + sLightweightExecutorService, + sBackgroundExecutorService, + sScheduledExecutor, + mConsentManagerMock, + mMockAdSelectionIdGenerator, + mClock, + mAdServicesLoggerSpy, + mAppImportanceFilter, + flags, + mThrottlerSupplier, + CALLER_UID, + mFledgeAuthorizationFilter, + mFledgeAllowListsFilter, + mJsFetcher, + mApiServiceLatencyCalculator); + invokeRunAdSelection( + mAdSelectionRunner, + adSelectionConfig, + MY_APP_PACKAGE_NAME, + ImmutableList.of(sDBCustomAudience)); + + SellerFrontEndFutureStub unused = verify(mStub, times(0)).withCompression(GZIP); + } + + private AdSelectionOrchestrationResult invokeRunAdSelection( + TrustedServerAdSelectionRunner adSelectionRunner, + AdSelectionConfig adSelectionConfig, + String callerPackageName, + List<DBCustomAudience> buyerCustomAudience) { + + try { + ListenableFuture<AdSelectionOrchestrationResult> dbAdSelection = + adSelectionRunner.orchestrateAdSelection( + adSelectionConfig, + callerPackageName, + Futures.immediateFuture(buyerCustomAudience)); + return dbAdSelection.get(1000, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @After + public void tearDown() { + if (mStaticMockSession != null) { + mStaticMockSession.finishMocking(); + } + } +} |