summaryrefslogtreecommitdiff
path: root/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement
diff options
context:
space:
mode:
Diffstat (limited to 'adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement')
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java283
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java1787
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java25
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java68
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java286
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java21
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java31
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java247
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.java84
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java284
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java506
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java840
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java198
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java74
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/FilterDataTest.java (renamed from adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateFilterDataTest.java)32
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java28
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java1436
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java36
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java144
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java80
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java118
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java18
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java4
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java44
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/EventReportingJob.java (renamed from adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/ReportingJob.java)9
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java1
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java18
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java19
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java7
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java1
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java47
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java44
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java22
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java1001
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java63
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java2635
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java2797
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java54
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java1937
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java155
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java1231
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java108
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java89
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java67
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java14
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java90
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java84
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java287
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java81
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java141
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java18
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java7
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java131
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java75
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java133
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java105
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java71
62 files changed, 12410 insertions, 5836 deletions
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java
new file mode 100644
index 0000000000..36249ea321
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java
@@ -0,0 +1,283 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.AdServicesConfig.ASYNC_REGISTRATION_QUEUE_JOB_ID;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.Context;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+public class AsyncRegistrationQueueJobServiceTest {
+
+ private static final long WAIT_IN_MILLIS = 50L;
+ private JobScheduler mMockJobScheduler;
+ private AsyncRegistrationQueueJobService mSpyService;
+ private DatastoreManager mMockDatastoreManager;
+
+ @Before
+ public void setUp() {
+ mSpyService = spy(new AsyncRegistrationQueueJobService());
+ mMockJobScheduler = mock(JobScheduler.class);
+ }
+
+ @Test
+ public void onStartJob_killSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(mock(JobParameters.class));
+
+ // Validate
+ assertFalse(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mSpyService, times(1)).jobFinished(any(), eq(false));
+ verify(mMockJobScheduler, times(1)).cancel(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void onStartJob_killSwitchOff() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+ ExtendedMockito.doNothing()
+ .when(
+ () ->
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ any(), anyBoolean()));
+
+ // Execute
+ boolean result = mSpyService.onStartJob(mock(JobParameters.class));
+
+ // Validate
+ assertTrue(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(mSpyService, times(1)).jobFinished(any(), anyBoolean());
+ verify(mMockJobScheduler, never()).cancel(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, never())
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ true);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()),
+ times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ doReturn(/* noJobInfo = */ null)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(mockContext, false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()),
+ times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(AsyncRegistrationQueueJobService.class)
+ .spyStatic(AdServicesLoggerImpl.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ try {
+ // Setup mock everything in job
+ mMockDatastoreManager = mock(DatastoreManager.class);
+ doReturn(Optional.empty())
+ .when(mMockDatastoreManager)
+ .runInTransactionWithResult(any());
+ doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
+ doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
+ ExtendedMockito.doReturn(mock(AdServicesLoggerImpl.class))
+ .when(AdServicesLoggerImpl::getInstance);
+ ExtendedMockito.doNothing()
+ .when(() -> AsyncRegistrationQueueJobService.schedule(any(), any()));
+ ExtendedMockito.doReturn(mMockDatastoreManager)
+ .when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
+
+ // Execute
+ execute.run();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private void enableKillSwitch() {
+ toggleKillSwitch(true);
+ }
+
+ private void disableKillSwitch() {
+ toggleKillSwitch(false);
+ }
+
+ private void toggleKillSwitch(boolean value) {
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getRegistrationJobQueueKillSwitch();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java
new file mode 100644
index 0000000000..5912fb3418
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java
@@ -0,0 +1,1787 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.measurement.attribution.TriggerContentProvider.TRIGGER_URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyShort;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.IMeasurementDao;
+import com.android.adservices.data.measurement.ITransaction;
+import com.android.adservices.data.measurement.MeasurementTables;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/** Unit tests for {@link AsyncRegistrationQueueRunnerTest} */
+public class AsyncRegistrationQueueRunnerTest {
+ private static final Context sDefaultContext = ApplicationProvider.getApplicationContext();
+ private static final String DEFAULT_ENROLLMENT_ID = "enrollment_id";
+ private static final Uri DEFAULT_REGISTRANT = Uri.parse("android-app://com.registrant");
+ private static final Uri DEFAULT_VERIFIED_DESTINATION = Uri.parse("android-app://com.example");
+ private static final Uri APP_TOP_ORIGIN =
+ Uri.parse("android-app://" + sDefaultContext.getPackageName());
+ private static final Uri WEB_TOP_ORIGIN = Uri.parse("https://example.com");
+ private static final Uri REGISTRATION_URI = Uri.parse("https://foo.com/bar?ad=134");
+ private static final String LIST_TYPE_REDIRECT_URI_1 = "https://foo.com";
+ private static final String LIST_TYPE_REDIRECT_URI_2 = "https://bar.com";
+ private static final String LOCATION_TYPE_REDIRECT_URI = "https://baz.com";
+ private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
+ private static final Uri APP_DESTINATION = Uri.parse("android-app://com.app_destination");
+ private static final Source SOURCE_1 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(1L))
+ .setPublisher(APP_TOP_ORIGIN)
+ .setAppDestination(Uri.parse("android-app://com.destination1"))
+ .setWebDestination(Uri.parse("https://web-destination1.com"))
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrant(Uri.parse("android-app://com.example"))
+ .setEventTime(new Random().nextLong())
+ .setExpiryTime(8640000010L)
+ .setPriority(100L)
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setDebugKey(new UnsignedLong(47823478789L))
+ .build();
+
+ private AsyncSourceFetcher mAsyncSourceFetcher;
+ private AsyncTriggerFetcher mAsyncTriggerFetcher;
+
+ @Mock private IMeasurementDao mMeasurementDao;
+ @Mock private Source mMockedSource;
+ @Mock private Trigger mMockedTrigger;
+ @Mock private ITransaction mTransaction;
+ @Mock private EnrollmentDao mEnrollmentDao;
+ @Mock private ContentResolver mContentResolver;
+ @Mock private ContentProviderClient mMockContentProviderClient;
+
+ private MockitoSession mStaticMockSession;
+
+ private static EnrollmentData getEnrollment(String enrollmentId) {
+ return new EnrollmentData.Builder().setEnrollmentId(enrollmentId).build();
+ }
+
+ class FakeDatastoreManager extends DatastoreManager {
+
+ @Override
+ public ITransaction createNewTransaction() {
+ return mTransaction;
+ }
+
+ @Override
+ public IMeasurementDao getMeasurementDao() {
+ return mMeasurementDao;
+ }
+ }
+
+ @After
+ public void cleanup() {
+ SQLiteDatabase db = DbHelper.getInstance(sDefaultContext).safeGetWritableDatabase();
+ for (String table : MeasurementTables.ALL_MSMT_TABLES) {
+ db.delete(table, null, null);
+ }
+ mStaticMockSession.finishMocking();
+ }
+
+ @Before
+ public void before() throws RemoteException {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+
+ mAsyncSourceFetcher = spy(new AsyncSourceFetcher(sDefaultContext));
+ mAsyncTriggerFetcher = spy(new AsyncTriggerFetcher(sDefaultContext));
+ MockitoAnnotations.initMocks(this);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any()))
+ .thenReturn(getEnrollment(DEFAULT_ENROLLMENT_ID));
+ when(mContentResolver.acquireContentProviderClient(TRIGGER_URI))
+ .thenReturn(mMockContentProviderClient);
+ when(mMockContentProviderClient.insert(any(), any())).thenReturn(TRIGGER_URI);
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_success() throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ // Tests for redirect types
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appSource_defaultRegistration_redirectTypeList()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+ Answer<Optional<Source>> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LIST_TYPE_REDIRECT_URI_1),
+ Uri.parse(LIST_TYPE_REDIRECT_URI_2)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.NONE);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ List<Source.FakeReport> eventReportList = Collections.singletonList(
+ new Source.FakeReport(new UnsignedLong(1L), 1L, APP_DESTINATION));
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'none' type redirect and values
+ Assert.assertEquals(2, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg1 = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_1), asyncReg1.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg1.getRedirectType());
+ Assert.assertEquals(1, asyncReg1.getRedirectCount());
+
+ AsyncRegistration asyncReg2 = asyncRegistrationArgumentCaptor.getAllValues().get(1);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_2), asyncReg2.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg2.getRedirectType());
+ Assert.assertEquals(1, asyncReg2.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appSource_defaultRegistration_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+ Answer<Optional<Source>> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ List<Source.FakeReport> eventReportList = Collections.singletonList(
+ new Source.FakeReport(new UnsignedLong(1L), 1L, APP_DESTINATION));
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and value
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(1, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appSource_middleRegistration_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource(
+ AsyncRegistration.RedirectType.DAISY_CHAIN, 3);
+ Answer<Optional<Source>> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ List<Source.FakeReport> eventReportList = Collections.singletonList(
+ new Source.FakeReport(new UnsignedLong(1L), 1L, APP_DESTINATION));
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and value
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(4, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appTrigger_defaultRegistration_redirectTypeList()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<Optional<Trigger>> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LIST_TYPE_REDIRECT_URI_1),
+ Uri.parse(LIST_TYPE_REDIRECT_URI_2)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.NONE);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'none' type redirect and values
+ Assert.assertEquals(2, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg1 = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_1), asyncReg1.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg1.getRedirectType());
+ Assert.assertEquals(1, asyncReg1.getRedirectCount());
+
+ AsyncRegistration asyncReg2 = asyncRegistrationArgumentCaptor.getAllValues().get(1);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_2), asyncReg2.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg2.getRedirectType());
+ Assert.assertEquals(1, asyncReg2.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appTrigger_defaultReg_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<Optional<Trigger>> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and values
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(1, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appTrigger_middleRegistration_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger(
+ AsyncRegistration.RedirectType.DAISY_CHAIN, 4);
+
+ Answer<Optional<Trigger>> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and values
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(5, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ // End tests for redirect types
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_noRedirects_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_NetworkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_noRedirects_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_networkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_success() throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_NetworkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_networkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void insertSource_withFakeReportsFalseAppAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ int fakeReportsCount = 2;
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
+ .setWebDestination(null)
+ .build());
+ List<Source.FakeReport> fakeReports =
+ createFakeReports(
+ source,
+ fakeReportsCount,
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ Answer<?> falseAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.FALSELY);
+ return fakeReports;
+ };
+ doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, times(2)).insertEventReport(any());
+ verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getAppDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getValue());
+ }
+
+ @Test
+ public void insertSource_withFakeReportsFalseWebAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ int fakeReportsCount = 2;
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(null)
+ .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
+ .build());
+ List<Source.FakeReport> fakeReports =
+ createFakeReports(
+ source, fakeReportsCount, SourceFixture.ValidSourceParams.WEB_DESTINATION);
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ Answer<?> falseAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.FALSELY);
+ return fakeReports;
+ };
+ doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, times(2)).insertEventReport(any());
+ verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getWebDestination().toString())
+ .setDestinationSite(source.getWebDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getValue());
+ }
+
+ @Test
+ public void insertSource_withFalseAppAndWebAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ int fakeReportsCount = 2;
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
+ .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
+ .build());
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ List<Source.FakeReport> fakeReports =
+ createFakeReports(
+ source,
+ fakeReportsCount,
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
+
+ Answer<?> falseAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.FALSELY);
+ return fakeReports;
+ };
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+ doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, times(2)).insertEventReport(any());
+ verify(mMeasurementDao, times(2))
+ .insertAttribution(attributionRateLimitArgCaptor.capture());
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getAppDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getAllValues().get(0));
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getWebDestination().toString())
+ .setDestinationSite(source.getWebDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getAllValues().get(1));
+ }
+
+ @Test
+ public void insertSource_withFakeReportsNeverAppAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
+ .setWebDestination(null)
+ .build());
+ List<Source.FakeReport> fakeReports = Collections.emptyList();
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ Answer<?> neverAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.NEVER);
+ return fakeReports;
+ };
+ doAnswer(neverAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getAppDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getValue());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_sourceFetchSuccess() throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assertions
+ verify(mMeasurementDao, times(2))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, times(2))
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_exceedsPrivacyParam_destination()
+ throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, never())
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_exceedsMaxSourcesLimit()
+ throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ doReturn(SystemHealthParams.MAX_SOURCES_PER_PUBLISHER)
+ .when(mMeasurementDao)
+ .getNumSourcesPerPublisher(any(), anyInt());
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1)).getNumSourcesPerPublisher(any(), anyInt());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_exceedsPrivacyParam_adTech()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, times(1))
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegisterWebSource_exceedsPrivacyParam_destination()
+ throws RemoteException, DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMockContentProviderClient, never()).insert(any(), any());
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, never())
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegisterWebSource_exceedsPrivacyParam_adTech() throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, times(1))
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegisterWebSource_exceedsMaxSourcesLimit() throws DatastoreException {
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+ doReturn(SystemHealthParams.MAX_SOURCES_PER_PUBLISHER)
+ .when(mMeasurementDao)
+ .getNumSourcesPerPublisher(any(), anyInt());
+
+ // Execution
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assertions
+ assertFalse(status);
+ }
+
+ @Test
+ public void testRegisterWebSource_LimitsMaxSources_ForWebPublisher_WitheTLDMatch()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ doReturn(SystemHealthParams.MAX_SOURCES_PER_PUBLISHER)
+ .when(mMeasurementDao)
+ .getNumSourcesPerPublisher(any(), anyInt());
+
+ // Execution
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assertions
+ assertFalse(status);
+ }
+
+ private List<Source.FakeReport> createFakeReports(Source source, int count, Uri destination) {
+ return IntStream.range(0, count)
+ .mapToObj(
+ x ->
+ new Source.FakeReport(
+ new UnsignedLong(0L),
+ source.getReportingTimeForNoising(0),
+ destination))
+ .collect(Collectors.toList());
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppSource() {
+ return createAsyncRegistrationForAppSource(AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppSource(
+ @AsyncRegistration.RedirectType int redirectType, int redirectCount) {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ // null .setWebDestination(webDestination)
+ // null .setOsDestination(osDestination)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ // null .setVerifiedDestination(null)
+ .setTopOrigin(APP_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.APP_SOURCE.ordinal())
+ .setSourceType(Source.SourceType.EVENT)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppTrigger() {
+ return createAsyncRegistrationForAppTrigger(AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppTrigger(
+ @AsyncRegistration.RedirectType int redirectType, int redirectCount) {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ // null .setWebDestination(webDestination)
+ // null .setOsDestination(osDestination)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ // null .setVerifiedDestination(null)
+ .setTopOrigin(APP_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.APP_TRIGGER.ordinal())
+ // null .setSourceType(null)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForWebSource() {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ .setWebDestination(WEB_DESTINATION)
+ .setOsDestination(APP_DESTINATION)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ .setVerifiedDestination(DEFAULT_VERIFIED_DESTINATION)
+ .setTopOrigin(WEB_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.WEB_SOURCE.ordinal())
+ .setSourceType(Source.SourceType.EVENT)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(AsyncRegistration.RedirectType.NONE)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForWebTrigger() {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ // null .setWebDestination(webDestination)
+ // null .setOsDestination(osDestination)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ // null .setVerifiedDestination(null)
+ .setTopOrigin(WEB_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.WEB_TRIGGER.ordinal())
+ // null .setSourceType(null)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(AsyncRegistration.RedirectType.NONE)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private AsyncRegistrationQueueRunner getSpyAsyncRegistrationQueueRunner() {
+ return spy(new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java
index 56893ca647..8067da5e71 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java
@@ -22,6 +22,8 @@ import static org.junit.Assert.assertThrows;
import org.junit.Test;
+import java.util.UUID;
+
public class AttributionTest {
private static final String ID = "AR1";
private static final String DESTINATION_ORIGIN = "https://destination.com/origin";
@@ -33,6 +35,8 @@ public class AttributionTest {
private static final long TRIGGER_TIME = 10000L;
private static final String SOME_OTHER_STRING = "some_other";
private static final long SOME_OTHER_LONG = 1L;
+ private static final String SOURCE_ID = UUID.randomUUID().toString();
+ private static final String TRIGGER_ID = UUID.randomUUID().toString();
@Test
public void equals_pass() {
@@ -75,6 +79,12 @@ public class AttributionTest {
assertNotEquals(
createExampleAttributionBuilder().setTriggerTime(SOME_OTHER_LONG).build(),
createExampleAttributionBuilder().build());
+ assertNotEquals(
+ createExampleAttributionBuilder().setSourceId(SOME_OTHER_STRING).build(),
+ createExampleAttributionBuilder().build());
+ assertNotEquals(
+ createExampleAttributionBuilder().setTriggerId(SOME_OTHER_STRING).build(),
+ createExampleAttributionBuilder().build());
}
@Test
@@ -128,6 +138,15 @@ public class AttributionTest {
.build()
.hashCode(),
createExampleAttributionBuilder().build().hashCode());
+ assertNotEquals(
+ createExampleAttributionBuilder().setSourceId(SOME_OTHER_STRING).build().hashCode(),
+ createExampleAttributionBuilder().build().hashCode());
+ assertNotEquals(
+ createExampleAttributionBuilder()
+ .setTriggerId(SOME_OTHER_STRING)
+ .build()
+ .hashCode(),
+ createExampleAttributionBuilder().build().hashCode());
}
@Test
@@ -150,6 +169,8 @@ public class AttributionTest {
assertEquals(
ENROLLMENT_ID,
createExampleAttributionBuilder().build().getEnrollmentId());
+ assertEquals(SOURCE_ID, createExampleAttributionBuilder().build().getSourceId());
+ assertEquals(TRIGGER_ID, createExampleAttributionBuilder().build().getTriggerId());
}
@Test
@@ -186,6 +207,8 @@ public class AttributionTest {
.setDestinationOrigin(DESTINATION_ORIGIN)
.setDestinationSite(DESTINATION_SITE)
.setSourceOrigin(PUBLISHER_ORIGIN)
- .setSourceSite(PUBLISHER_SITE);
+ .setSourceSite(PUBLISHER_SITE)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java
index 4d3a471fcd..a515361a69 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE_EXPIRED_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,30 +36,28 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import java.util.concurrent.TimeUnit;
+
/**
* Unit test for {@link DeleteExpiredJobService
*/
public class DeleteExpiredJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final long WAIT_IN_MILLIS = 50L;
private DatastoreManager mMockDatastoreManager;
@@ -75,10 +74,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -95,10 +95,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ disableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -115,11 +116,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -146,11 +147,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -177,11 +178,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -208,11 +209,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -234,11 +235,28 @@ public class DeleteExpiredJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ DeleteExpiredJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
.spyStatic(DatastoreManagerFactory.class)
.spyStatic(DeleteExpiredJobService.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -247,6 +265,8 @@ public class DeleteExpiredJobServiceTest {
doReturn(true).when(mMockDatastoreManager).runInTransaction(any());
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementDeleteExpiredJobPeriodMs);
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing().when(() -> DeleteExpiredJobService.schedule(any(), any()));
@@ -267,10 +287,8 @@ public class DeleteExpiredJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_delete_expired_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobDeleteExpiredKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java
new file mode 100644
index 0000000000..e0efb75298
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE_UNINSTALLED_JOB_ID;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.Context;
+
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.Spy;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.TimeUnit;
+
+public class DeleteUninstalledJobServiceTest {
+ private static final long WAIT_IN_MILLIS = 50L;
+
+ @Mock private JobScheduler mMockJobScheduler;
+ @Mock private MeasurementImpl mMockMeasurementImpl;
+
+ @Spy private DeleteUninstalledJobService mSpyService;
+
+ @Test
+ public void onStartJob_killSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertFalse(result);
+
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockMeasurementImpl, never()).deleteAllUninstalledMeasurementData();
+ verify(mSpyService, times(1)).jobFinished(any(), eq(false));
+ verify(mMockJobScheduler, times(1))
+ .cancel(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void onStartJob_killSwitchOff() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertTrue(result);
+
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockMeasurementImpl, times(1)).deleteAllUninstalledMeasurementData();
+ verify(mSpyService, times(1)).jobFinished(any(), anyBoolean());
+ verify(mMockJobScheduler, never())
+ .cancel(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, never())
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ true);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ doReturn(/* noJobInfo = */ null)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(mockContext, false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ DeleteUninstalledJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ }
+
+ private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .spyStatic(AdServicesConfig.class)
+ .spyStatic(MeasurementImpl.class)
+ .spyStatic(DeleteUninstalledJobService.class)
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ try {
+ // Setup mock everything in job
+ ExtendedMockito.doReturn(mMockMeasurementImpl)
+ .when(() -> MeasurementImpl.getInstance(any()));
+ doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
+ doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementDeleteUninstalledJobPeriodMs);
+ ExtendedMockito.doNothing()
+ .when(() -> DeleteUninstalledJobService.schedule(any(), any()));
+
+ // Execute
+ execute.run();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private void enableKillSwitch() {
+ toggleKillSwitch(true);
+ }
+
+ private void disableKillSwitch() {
+ toggleKillSwitch(false);
+ }
+
+ private void toggleKillSwitch(boolean value) {
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobDeleteUninstalledKillSwitch();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java
index d0871327ec..8b79440f80 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java
@@ -35,22 +35,29 @@ import java.util.Collection;
public class E2EDenoisedMockTest extends E2EMockTest {
private static final String TEST_DIR_NAME = "msmt_e2e_tests";
- @Parameterized.Parameters(name = "{2}")
+ @Parameterized.Parameters(name = "{3}")
public static Collection<Object[]> getData() throws IOException, JSONException {
return data(TEST_DIR_NAME);
}
public E2EDenoisedMockTest(Collection<Action> actions, ReportObjects expectedOutput,
- String name) throws DatastoreException {
- super(actions, expectedOutput, name);
+ PrivacyParamsProvider privacyParamsProvider, String name) throws DatastoreException {
+ super(actions, expectedOutput, privacyParamsProvider, name);
mAttributionHelper = TestObjectProvider.getAttributionJobHandler(sDatastoreManager);
mMeasurementImpl =
TestObjectProvider.getMeasurementImpl(
- TestObjectProvider.Type.DENOISED,
sDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
mClickVerifier,
- mFlags);
+ mFlags,
+ mMeasurementDataDeleter,
+ sEnrollmentDao);
+
+ mAsyncRegistrationQueueRunner =
+ TestObjectProvider.getAsyncRegistrationQueueRunner(
+ TestObjectProvider.Type.DENOISED,
+ sDatastoreManager,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ sEnrollmentDao);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java
index c01225741b..9970418349 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java
@@ -51,23 +51,29 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
String TRIGGER_DATA = "trigger_data";
}
- @Parameterized.Parameters(name = "{2}")
+ @Parameterized.Parameters(name = "{3}")
public static Collection<Object[]> getData() throws IOException, JSONException {
return data(TEST_DIR_NAME);
}
public E2EImpressionNoiseMockTest(Collection<Action> actions, ReportObjects expectedOutput,
- String name) throws DatastoreException {
- super(actions, expectedOutput, name);
+ PrivacyParamsProvider privacyParamsProvider, String name) throws DatastoreException {
+ super(actions, expectedOutput, privacyParamsProvider, name);
mAttributionHelper = TestObjectProvider.getAttributionJobHandler(sDatastoreManager);
mMeasurementImpl =
TestObjectProvider.getMeasurementImpl(
- TestObjectProvider.Type.NOISY,
sDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
mClickVerifier,
- mFlags);
+ mFlags,
+ mMeasurementDataDeleter,
+ sEnrollmentDao);
+ mAsyncRegistrationQueueRunner =
+ TestObjectProvider.getAsyncRegistrationQueueRunner(
+ TestObjectProvider.Type.NOISY,
+ sDatastoreManager,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ sEnrollmentDao);
getExpectedTriggerDataDistributions();
}
@@ -79,7 +85,7 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
// is currently supporting only one reporting job, which batches multiple reports at once,
// although each is a separate network request.
for (int i = 0; i < destinations.size(); i++) {
- String uri = destinations.get(i).toString();
+ String uri = getReportUrl(ReportType.EVENT, destinations.get(i).toString());
JSONObject payload = payloads.get(i);
String eventId = payload.getString(PayloadKeys.EVENT_ID);
String triggerData = payload.getString(PayloadKeys.TRIGGER_DATA);
@@ -94,7 +100,8 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
for (String key : mActualTriggerDataDistributions.keySet()) {
if (!mExpectedTriggerDataDistributions.containsKey(key)) {
Assert.assertTrue(getTestFailureMessage(
- "Missing key in expected trigger data distributions"), false);
+ "Missing key in expected trigger data distributions"
+ + getDatastoreState()), false);
}
}
boolean testPassed = false;
@@ -113,8 +120,10 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
}
}
}
- Assert.assertTrue(getTestFailureMessage(
- "Trigger data distributions were the same"), testPassed);
+ Assert.assertTrue(
+ getTestFailureMessage(
+ "Trigger data distributions were the same " + getDatastoreState()),
+ testPassed);
}
private void getExpectedTriggerDataDistributions() {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java
new file mode 100644
index 0000000000..43dde18842
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+import static com.android.adservices.service.measurement.PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.service.measurement.actions.Action;
+import com.android.adservices.service.measurement.actions.RegisterSource;
+import com.android.adservices.service.measurement.actions.RegisterTrigger;
+import com.android.adservices.service.measurement.actions.ReportObjects;
+import com.android.adservices.service.measurement.util.Enrollment;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.adservices.service.measurement.util.Web;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * End-to-end test from source and trigger registration to attribution reporting, using mocked HTTP
+ * requests.
+ *
+ * Tests in assets/msmt_interop_tests/ directory were copied from
+ * https://source.chromium.org/chromium/chromium/src/+/main:content/test/data/attribution_reporting/interop/
+ * on October 15, 2022
+ */
+@RunWith(Parameterized.class)
+public class E2EInteropMockTest extends E2EMockTest {
+ private static final String LOG_TAG = "msmt_e2e_interop_mock_test";
+ private static final String TEST_DIR_NAME = "msmt_interop_tests";
+ private static final String ANDROID_APP_SCHEME = "android-app";
+
+ @Parameterized.Parameters(name = "{3}")
+ public static Collection<Object[]> getData() throws IOException, JSONException {
+ return data(TEST_DIR_NAME);
+ }
+
+ public E2EInteropMockTest(Collection<Action> actions, ReportObjects expectedOutput,
+ PrivacyParamsProvider privacyParamsProvider, String name) throws DatastoreException {
+ super(actions, expectedOutput, privacyParamsProvider, name);
+ mAttributionHelper = TestObjectProvider.getAttributionJobHandler(sDatastoreManager);
+ mMeasurementImpl =
+ TestObjectProvider.getMeasurementImpl(
+ sDatastoreManager,
+ mClickVerifier,
+ mFlags,
+ mMeasurementDataDeleter,
+ sEnrollmentDao);
+ mAsyncRegistrationQueueRunner =
+ TestObjectProvider.getAsyncRegistrationQueueRunner(
+ TestObjectProvider.Type.DENOISED,
+ sDatastoreManager,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ sEnrollmentDao);
+ }
+
+ @Override
+ void processAction(RegisterSource sourceRegistration) throws IOException {
+ RegistrationRequest request = sourceRegistration.mRegistrationRequest;
+ // For interop tests, we currently expect only one HTTPS response per registration with no
+ // redirects, partly due to differences in redirect handling across attribution APIs.
+ for (String uri : sourceRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
+ Source source = getSource(
+ sourceRegistration.getPublisher(),
+ sourceRegistration.mTimestamp,
+ uri,
+ request,
+ getNextResponse(sourceRegistration.mUriToResponseHeadersMap, uri));
+ Assert.assertTrue(
+ "measurementDao.insertSource failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao -> {
+ if (AsyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ source,
+ source.getPublisher(),
+ EventSurfaceType.WEB,
+ measurementDao)) {
+ measurementDao.insertSource(source);
+ }
+ }));
+ }
+ }
+
+ @Override
+ void processAction(RegisterTrigger triggerRegistration) throws IOException {
+ RegistrationRequest request = triggerRegistration.mRegistrationRequest;
+ // For interop tests, we currently expect only one HTTPS response per registration with no
+ // redirects, partly due to differences in redirect handling across attribution APIs.
+ for (String uri : triggerRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
+ Trigger trigger = getTrigger(
+ triggerRegistration.getDestination(),
+ triggerRegistration.mTimestamp,
+ uri,
+ request,
+ getNextResponse(triggerRegistration.mUriToResponseHeadersMap, uri));
+ Assert.assertTrue(
+ "measurementDao.insertTrigger failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao ->
+ measurementDao.insertTrigger(trigger)));
+ }
+ Assert.assertTrue("AttributionJobHandler.performPendingAttributions returned false",
+ mAttributionHelper.performPendingAttributions());
+ }
+
+ private static Source getSource(String publisher, long timestamp, String uri,
+ RegistrationRequest request, Map<String, List<String>> headers) {
+ try {
+ Source.Builder sourceBuilder = new Source.Builder();
+ String enrollmentId =
+ Enrollment.maybeGetEnrollmentId(Uri.parse(uri), sEnrollmentDao).get();
+ sourceBuilder.setEnrollmentId(enrollmentId);
+ sourceBuilder.setPublisher(Uri.parse(publisher));
+ sourceBuilder.setPublisherType(EventSurfaceType.WEB);
+ sourceBuilder.setEventTime(timestamp);
+ sourceBuilder.setSourceType(getSourceType(request));
+ sourceBuilder.setAttributionMode(Source.AttributionMode.TRUTHFULLY);
+ sourceBuilder.setRegistrant(getRegistrant(request.getPackageName()));
+ List<String> field = headers.get("Attribution-Reporting-Register-Source");
+ JSONObject json = new JSONObject(field.get(0));
+ sourceBuilder.setEventId(new UnsignedLong(json.getString("source_event_id")));
+ if (!json.isNull("expiry")) {
+ long offset =
+ TimeUnit.SECONDS.toMillis(
+ extractValidNumberInRange(
+ json.getLong("expiry"),
+ MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS,
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS));
+ sourceBuilder.setExpiryTime(timestamp + offset);
+ } else {
+ sourceBuilder.setExpiryTime(
+ timestamp
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS));
+ }
+ if (!json.isNull("priority")) {
+ sourceBuilder.setPriority(json.getLong("priority"));
+ }
+ if (!json.isNull("debug_key")) {
+ sourceBuilder.setDebugKey(new UnsignedLong(json.getString("debug_key")));
+ }
+ if (!json.isNull("filter_data")) {
+ sourceBuilder.setFilterData(json.getJSONObject("filter_data").toString());
+ }
+ sourceBuilder.setWebDestination(Web.topPrivateDomainAndScheme(
+ Uri.parse(json.getString("destination"))).get());
+ if (!json.isNull("aggregation_keys")) {
+ sourceBuilder.setAggregateSource(json.getJSONObject("aggregation_keys").toString());
+ }
+ return sourceBuilder.build();
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, "Failed to parse source registration. %s", e);
+ return null;
+ }
+ }
+
+ private static Trigger getTrigger(String destination, long timestamp, String uri,
+ RegistrationRequest request, Map<String, List<String>> headers) {
+ try {
+ Trigger.Builder triggerBuilder = new Trigger.Builder();
+ String enrollmentId =
+ Enrollment.maybeGetEnrollmentId(Uri.parse(uri), sEnrollmentDao).get();
+ triggerBuilder.setEnrollmentId(enrollmentId);
+ triggerBuilder.setAttributionDestination(Uri.parse(destination));
+ triggerBuilder.setDestinationType(EventSurfaceType.WEB);
+ triggerBuilder.setTriggerTime(timestamp);
+ triggerBuilder.setRegistrant(getRegistrant(request.getPackageName()));
+ List<String> field = headers.get("Attribution-Reporting-Register-Trigger");
+ JSONObject json = new JSONObject(field.get(0));
+ if (!json.isNull("event_trigger_data")) {
+ triggerBuilder.setEventTriggers(json.getJSONArray("event_trigger_data").toString());
+ }
+ if (!json.isNull("aggregatable_trigger_data")) {
+ triggerBuilder.setAggregateTriggerData(
+ json.getJSONArray("aggregatable_trigger_data").toString());
+ }
+ if (!json.isNull("aggregatable_values")) {
+ triggerBuilder.setAggregateValues(
+ json.getJSONObject("aggregatable_values").toString());
+ }
+ if (!json.isNull("filters")) {
+ triggerBuilder.setFilters(json.getString("filters"));
+ }
+ if (!json.isNull("not_filters")) {
+ triggerBuilder.setNotFilters(json.getString("not_filters"));
+ }
+ if (!json.isNull("debug_key")) {
+ triggerBuilder.setDebugKey(new UnsignedLong(json.getString("debug_key")));
+ }
+ return triggerBuilder.build();
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, "Failed to parse trigger registration. %s", e);
+ return null;
+ }
+ }
+
+ private static Source.SourceType getSourceType(RegistrationRequest request) {
+ return request.getInputEvent() == null
+ ? Source.SourceType.EVENT
+ : Source.SourceType.NAVIGATION;
+ }
+
+ private static Uri getRegistrant(String packageName) {
+ return Uri.parse(ANDROID_APP_SCHEME + "://" + packageName);
+ }
+
+ private static long extractValidNumberInRange(long value, long lowerLimit, long upperLimit) {
+ if (value < lowerLimit) {
+ return lowerLimit;
+ } else if (value > upperLimit) {
+ return upperLimit;
+ }
+
+ return value;
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.java
new file mode 100644
index 0000000000..372558b3d0
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.java
@@ -0,0 +1,84 @@
+/*
+ * 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.measurement;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.modules.utils.testing.StaticMockFixture;
+import com.android.modules.utils.testing.StaticMockFixtureRule;
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import org.mockito.stubbing.Answer;
+
+/**
+ * Combines TestableDeviceConfig with other needed static mocks.
+ */
+public final class E2EMockStatic implements StaticMockFixture {
+
+ private final E2ETest.PrivacyParamsProvider mPrivacyParams;
+
+ public E2EMockStatic(E2ETest.PrivacyParamsProvider privacyParamsProvider) {
+ mPrivacyParams = privacyParamsProvider;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ sessionBuilder.spyStatic(PrivacyParams.class);
+ return sessionBuilder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUpMockBehaviors() {
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxAttributionPerRateLimitWindow())
+ .when(() -> PrivacyParams.getMaxAttributionPerRateLimitWindow());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getNavigationTriggerDataCardinality())
+ .when(() -> PrivacyParams.getNavigationTriggerDataCardinality());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution())
+ .when(() -> PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource())
+ .when(() -> PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxDistinctEnrollmentsPerPublisherXDestinationInSource())
+ .when(() -> PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown() { }
+
+ public static class E2EMockStaticRule extends StaticMockFixtureRule {
+ public E2EMockStaticRule(E2ETest.PrivacyParamsProvider privacyParamsProvider) {
+ super(TestableDeviceConfig::new, () -> new E2EMockStatic(privacyParamsProvider));
+ }
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java
index d51526d285..07ece39b7f 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement;
+import static com.android.adservices.ResultCode.RESULT_OK;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
@@ -23,24 +25,41 @@ import static org.mockito.Mockito.when;
import android.net.Uri;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.HpkeJni;
+import com.android.adservices.data.DbTestUtil;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.actions.Action;
+import com.android.adservices.service.measurement.actions.AggregateReportingJob;
+import com.android.adservices.service.measurement.actions.EventReportingJob;
+import com.android.adservices.service.measurement.actions.InstallApp;
import com.android.adservices.service.measurement.actions.RegisterSource;
import com.android.adservices.service.measurement.actions.RegisterTrigger;
import com.android.adservices.service.measurement.actions.RegisterWebSource;
import com.android.adservices.service.measurement.actions.RegisterWebTrigger;
import com.android.adservices.service.measurement.actions.ReportObjects;
-import com.android.adservices.service.measurement.actions.ReportingJob;
+import com.android.adservices.service.measurement.actions.UninstallApp;
+import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
+import com.android.adservices.service.measurement.attribution.AttributionJobHandlerWrapper;
import com.android.adservices.service.measurement.inputverification.ClickVerifier;
-import com.android.adservices.service.measurement.registration.SourceFetcher;
-import com.android.adservices.service.measurement.registration.TriggerFetcher;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
import com.android.adservices.service.measurement.reporting.AggregateReportingJobHandlerWrapper;
import com.android.adservices.service.measurement.reporting.EventReportingJobHandlerWrapper;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Rule;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
@@ -52,8 +71,11 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.HttpsURLConnection;
@@ -71,73 +93,184 @@ import co.nstant.in.cbor.model.UnicodeString;
* <p>Consider @RunWith(Parameterized.class)
*/
public abstract class E2EMockTest extends E2ETest {
- SourceFetcher mSourceFetcher;
- TriggerFetcher mTriggerFetcher;
+
+ static EnrollmentDao sEnrollmentDao =
+ new EnrollmentDao(
+ ApplicationProvider.getApplicationContext(), DbTestUtil.getDbHelperForTest());
+ static DatastoreManager sDatastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+
+ // Class extensions may choose to disable or enable added noise.
+ AttributionJobHandlerWrapper mAttributionHelper;
+ MeasurementImpl mMeasurementImpl;
ClickVerifier mClickVerifier;
+ MeasurementDataDeleter mMeasurementDataDeleter;
Flags mFlags;
+ AsyncRegistrationQueueRunner mAsyncRegistrationQueueRunner;
+ AsyncSourceFetcher mAsyncSourceFetcher;
+ AsyncTriggerFetcher mAsyncTriggerFetcher;
- E2EMockTest(Collection<Action> actions, ReportObjects expectedOutput, String name) {
+ private static final long MAX_RECORDS_PROCESSED = 20L;
+ private static final short ASYNC_REG_RETRY_LIMIT = 1;
+ private final AtomicInteger mEnrollmentCount = new AtomicInteger();
+ private final Set<String> mSeenUris = new HashSet<>();
+ private final Map<String, String> mUriToEnrollmentId = new HashMap<>();
+
+ @Rule
+ public final E2EMockStatic.E2EMockStaticRule mE2EMockStaticRule;
+
+ E2EMockTest(Collection<Action> actions, ReportObjects expectedOutput,
+ PrivacyParamsProvider privacyParamsProvider, String name) {
super(actions, expectedOutput, name);
- mSourceFetcher = Mockito.spy(new SourceFetcher(sContext));
- mTriggerFetcher = Mockito.spy(new TriggerFetcher(sContext));
mClickVerifier = Mockito.mock(ClickVerifier.class);
mFlags = FlagsFactory.getFlagsForTest();
+ mE2EMockStaticRule = new E2EMockStatic.E2EMockStaticRule(privacyParamsProvider);
+ mMeasurementDataDeleter = Mockito.spy(new MeasurementDataDeleter(sDatastoreManager));
+ mAsyncSourceFetcher =
+ Mockito.spy(
+ new AsyncSourceFetcher(
+ sEnrollmentDao,
+ FlagsFactory.getFlagsForTest(),
+ AdServicesLoggerImpl.getInstance()));
+ mAsyncTriggerFetcher =
+ Mockito.spy(
+ new AsyncTriggerFetcher(
+ sEnrollmentDao,
+ FlagsFactory.getFlagsForTest(),
+ AdServicesLoggerImpl.getInstance()));
when(mClickVerifier.isInputEventVerifiable(any(), anyLong())).thenReturn(true);
}
@Override
void prepareRegistrationServer(RegisterSource sourceRegistration) throws IOException {
for (String uri : sourceRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation -> getNextResponse(sourceRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mSourceFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncSourceFetcher).openUrl(new URL(uri));
}
}
@Override
- void prepareRegistrationServer(RegisterTrigger triggerRegistration)
- throws IOException {
+ void prepareRegistrationServer(RegisterTrigger triggerRegistration) throws IOException {
for (String uri : triggerRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation ->
getNextResponse(triggerRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mTriggerFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncTriggerFetcher).openUrl(new URL(uri));
}
}
@Override
void prepareRegistrationServer(RegisterWebSource sourceRegistration) throws IOException {
for (String uri : sourceRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation -> getNextResponse(sourceRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mSourceFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncSourceFetcher).openUrl(new URL(uri));
}
}
@Override
void prepareRegistrationServer(RegisterWebTrigger triggerRegistration) throws IOException {
for (String uri : triggerRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation ->
getNextResponse(triggerRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mTriggerFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncTriggerFetcher).openUrl(new URL(uri));
}
}
@Override
- void processAction(ReportingJob reportingJob) throws IOException, JSONException {
+ void processAction(RegisterSource sourceRegistration) throws IOException {
+ prepareRegistrationServer(sourceRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.register source failed",
+ RESULT_OK,
+ mMeasurementImpl.register(
+ sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ }
+
+ @Override
+ void processAction(RegisterWebSource sourceRegistration) throws IOException {
+ prepareRegistrationServer(sourceRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.registerWebSource failed",
+ RESULT_OK,
+ mMeasurementImpl.registerWebSource(
+ sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ }
+
+ @Override
+ void processAction(RegisterTrigger triggerRegistration) throws IOException {
+ prepareRegistrationServer(triggerRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.register trigger failed",
+ RESULT_OK,
+ mMeasurementImpl.register(
+ triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ Assert.assertTrue("AttributionJobHandler.performPendingAttributions returned false",
+ mAttributionHelper.performPendingAttributions());
+ }
+
+ @Override
+ void processAction(RegisterWebTrigger triggerRegistration) throws IOException {
+ prepareRegistrationServer(triggerRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.registerWebTrigger failed",
+ RESULT_OK,
+ mMeasurementImpl.registerWebTrigger(
+ triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ Assert.assertTrue(
+ "AttributionJobHandler.performPendingAttributions returned false",
+ mAttributionHelper.performPendingAttributions());
+ }
+
+ @Override
+ void processAction(InstallApp installApp) {
+ Assert.assertTrue(
+ "measurementDao.doInstallAttribution failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao ->
+ measurementDao.doInstallAttribution(
+ installApp.mUri, installApp.mTimestamp)));
+ }
+
+ @Override
+ void processAction(UninstallApp uninstallApp) {
+ Assert.assertTrue(
+ "measurementDao.undoInstallAttribution failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao -> {
+ measurementDao.deleteAppRecords(uninstallApp.mUri);
+ measurementDao.undoInstallAttribution(uninstallApp.mUri);
+ }));
+ }
+
+ @Override
+ void processAction(EventReportingJob reportingJob) throws IOException, JSONException {
Object[] eventCaptures = EventReportingJobHandlerWrapper
.spyPerformScheduledPendingReportsInWindow(
sEnrollmentDao,
@@ -146,19 +279,22 @@ public abstract class E2EMockTest extends E2ETest {
- SystemHealthParams.MAX_EVENT_REPORT_UPLOAD_RETRY_WINDOW_MS,
reportingJob.mTimestamp);
+ processEventReports(
+ (List<EventReport>) eventCaptures[0],
+ (List<Uri>) eventCaptures[1],
+ (List<JSONObject>) eventCaptures[2]);
+ }
+
+ @Override
+ void processAction(AggregateReportingJob reportingJob) throws IOException, JSONException {
Object[] aggregateCaptures = AggregateReportingJobHandlerWrapper
.spyPerformScheduledPendingReportsInWindow(
sEnrollmentDao,
sDatastoreManager,
reportingJob.mTimestamp
- - SystemHealthParams.MAX_EVENT_REPORT_UPLOAD_RETRY_WINDOW_MS,
+ - SystemHealthParams.MAX_AGGREGATE_REPORT_UPLOAD_RETRY_WINDOW_MS,
reportingJob.mTimestamp);
- processEventReports(
- (List<EventReport>) eventCaptures[0],
- (List<Uri>) eventCaptures[1],
- (List<JSONObject>) eventCaptures[2]);
-
processAggregateReports(
(List<Uri>) aggregateCaptures[0],
(List<JSONObject>) aggregateCaptures[1]);
@@ -198,8 +334,8 @@ public abstract class E2EMockTest extends E2ETest {
for (int i = 0; i < destinations.size(); i++) {
JSONObject sharedInfo = new JSONObject(payloads.get(i).getString("shared_info"));
result.add(new JSONObject()
- .put(TestFormatJsonMapping.REPORT_TIME_KEY,
- sharedInfo.getLong("scheduled_report_time") * 1000)
+ .put(TestFormatJsonMapping.REPORT_TIME_KEY, String.valueOf(
+ sharedInfo.getLong("scheduled_report_time") * 1000))
.put(TestFormatJsonMapping.REPORT_TO_KEY, destinations.get(i).toString())
.put(TestFormatJsonMapping.PAYLOAD_KEY,
getAggregatablePayloadForTest(sharedInfo, payloads.get(i))));
@@ -212,37 +348,55 @@ public abstract class E2EMockTest extends E2ETest {
String payload =
data.getJSONArray("aggregation_service_payloads")
.getJSONObject(0)
- .getString("debug_cleartext_payload");
- return new JSONObject()
- .put(
- AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION,
- sharedInfo.getString("attribution_destination"))
- .put(AggregateReportPayloadKeys.HISTOGRAMS, getAggregateHistograms(payload));
+ .getString("payload");
+
+ final byte[] decryptedPayload =
+ HpkeJni.decrypt(
+ decode(AggregateCryptoFixture.getPrivateKeyBase64()),
+ decode(payload),
+ (AggregateCryptoFixture.getSharedInfoPrefix() + sharedInfo.toString())
+ .getBytes());
+
+ String sourceDebugKey = data.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY);
+ String triggerDebugKey = data.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY);
+ JSONObject aggregateJson =
+ new JSONObject()
+ .put(
+ AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION,
+ sharedInfo.getString("attribution_destination"))
+ .put(
+ AggregateReportPayloadKeys.HISTOGRAMS,
+ getAggregateHistograms(decryptedPayload));
+ if (!sourceDebugKey.isEmpty()) {
+ aggregateJson.put(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, sourceDebugKey);
+ }
+ if (!triggerDebugKey.isEmpty()) {
+ aggregateJson.put(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, triggerDebugKey);
+ }
+ return aggregateJson;
}
- private static JSONArray getAggregateHistograms(String payloadJsonBase64) throws JSONException {
+ private static JSONArray getAggregateHistograms(byte[] encodedCborPayload)
+ throws JSONException {
List<JSONObject> result = new ArrayList<>();
try {
- final byte[] payloadJson = Base64.getDecoder().decode(payloadJsonBase64);
final List<DataItem> dataItems =
- new CborDecoder(new ByteArrayInputStream(payloadJson)).decode();
+ new CborDecoder(new ByteArrayInputStream(encodedCborPayload)).decode();
final co.nstant.in.cbor.model.Map payload =
(co.nstant.in.cbor.model.Map) dataItems.get(0);
final Array payloadArray = (Array) payload.get(new UnicodeString("data"));
for (DataItem i : payloadArray.getDataItems()) {
co.nstant.in.cbor.model.Map m = (co.nstant.in.cbor.model.Map) i;
+ Object value =
+ "0x"
+ + new BigInteger(
+ ((ByteString) m.get(new UnicodeString("bucket")))
+ .getBytes())
+ .toString(16);
result.add(
new JSONObject()
- .put(
- AggregateHistogramKeys.BUCKET,
- new BigInteger(
- ((ByteString)
- m.get(
- new UnicodeString(
- "bucket")))
- .getBytes())
- .toString())
+ .put(AggregateHistogramKeys.BUCKET, value)
.put(
AggregateHistogramKeys.VALUE,
new BigInteger(
@@ -251,7 +405,7 @@ public abstract class E2EMockTest extends E2ETest {
new UnicodeString(
"value")))
.getBytes())
- .toString()));
+ .intValue()));
}
} catch (CborException e) {
throw new JSONException(e);
@@ -260,9 +414,53 @@ public abstract class E2EMockTest extends E2ETest {
return new JSONArray(result);
}
- private static Map<String, List<String>> getNextResponse(
+ protected static Map<String, List<String>> getNextResponse(
Map<String, List<Map<String, List<String>>>> uriToResponseHeadersMap, String uri) {
List<Map<String, List<String>>> responseList = uriToResponseHeadersMap.get(uri);
return responseList.remove(0);
}
+
+ void updateEnrollment(String uri) {
+ if (mSeenUris.contains(uri)) {
+ return;
+ }
+ mSeenUris.add(uri);
+ String enrollmentId = getEnrollmentId(uri);
+ Set<String> attributionRegistrationUrls;
+ EnrollmentData enrollmentData = sEnrollmentDao.getEnrollmentData(enrollmentId);
+ if (enrollmentData != null) {
+ sEnrollmentDao.delete(enrollmentId);
+ attributionRegistrationUrls = new HashSet<>(
+ enrollmentData.getAttributionSourceRegistrationUrl());
+ attributionRegistrationUrls.addAll(
+ enrollmentData.getAttributionTriggerRegistrationUrl());
+ attributionRegistrationUrls.add(uri);
+ } else {
+ attributionRegistrationUrls = Set.of(uri);
+ }
+ Uri registrationUri = Uri.parse(uri);
+ String reportingUrl = registrationUri.getScheme() + "://" + registrationUri.getAuthority();
+ insertEnrollment(enrollmentId, reportingUrl, new ArrayList<>(attributionRegistrationUrls));
+ }
+
+ private void insertEnrollment(String enrollmentId, String reportingUrl,
+ List<String> attributionRegistrationUrls) {
+ EnrollmentData enrollmentData = new EnrollmentData.Builder()
+ .setEnrollmentId(enrollmentId)
+ .setAttributionSourceRegistrationUrl(attributionRegistrationUrls)
+ .setAttributionTriggerRegistrationUrl(attributionRegistrationUrls)
+ .setAttributionReportingUrl(List.of(reportingUrl))
+ .build();
+ Assert.assertTrue(sEnrollmentDao.insert(enrollmentData));
+ }
+
+ private String getEnrollmentId(String uri) {
+ String authority = Uri.parse(uri).getAuthority();
+ return mUriToEnrollmentId.computeIfAbsent(authority, k ->
+ "enrollment-id-" + mEnrollmentCount.incrementAndGet());
+ }
+
+ private static byte[] decode(String value) {
+ return Base64.getDecoder().decode(value.getBytes());
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
index be05aa9275..ca35680758 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
@@ -19,11 +19,14 @@ package com.android.adservices.service.measurement;
import static android.view.MotionEvent.ACTION_BUTTON_PRESS;
import static android.view.MotionEvent.obtain;
-import static com.android.adservices.ResultCode.RESULT_OK;
+import static com.android.adservices.service.measurement.reporting.AggregateReportSender.AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
+import static com.android.adservices.service.measurement.reporting.EventReportSender.EVENT_ATTRIBUTION_REPORT_URI_PATH;
import android.content.AttributionSource;
import android.content.Context;
import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -33,22 +36,17 @@ import android.view.MotionEvent.PointerProperties;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
-import com.android.adservices.data.DbHelper;
-import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
-import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.service.measurement.actions.Action;
+import com.android.adservices.service.measurement.actions.AggregateReportingJob;
+import com.android.adservices.service.measurement.actions.EventReportingJob;
import com.android.adservices.service.measurement.actions.InstallApp;
import com.android.adservices.service.measurement.actions.RegisterSource;
import com.android.adservices.service.measurement.actions.RegisterTrigger;
import com.android.adservices.service.measurement.actions.RegisterWebSource;
import com.android.adservices.service.measurement.actions.RegisterWebTrigger;
import com.android.adservices.service.measurement.actions.ReportObjects;
-import com.android.adservices.service.measurement.actions.ReportingJob;
import com.android.adservices.service.measurement.actions.UninstallApp;
-import com.android.adservices.service.measurement.attribution.AttributionJobHandlerWrapper;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import com.google.common.collect.ImmutableList;
@@ -56,7 +54,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert;
-import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
@@ -73,7 +70,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
@@ -84,61 +80,24 @@ import java.util.concurrent.TimeUnit;
* Consider @RunWith(Parameterized.class)
*/
public abstract class E2ETest {
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
// Used to fuzzy-match expected report (not delivery) time
private static final long REPORT_TIME_EPSILON = TimeUnit.HOURS.toMillis(2);
- private static final EnrollmentData AD_TECH_1 =
- new EnrollmentData.Builder()
- .setEnrollmentId(UUID.randomUUID().toString())
- .setCompanyId("ad-tech-1")
- .setSdkNames("sdk")
- .setAttributionSourceRegistrationUrl(Arrays.asList("https://www.ad-tech1.com"))
- .setAttributionTriggerRegistrationUrl(Arrays.asList("https://www.ad-tech1.com"))
- .setAttributionReportingUrl(Arrays.asList("https://www.ad-tech1.com"))
- .setRemarketingResponseBasedRegistrationUrl(
- Arrays.asList("https://www.ad-tech1.com"))
- .setEncryptionKeyUrl(Arrays.asList("https://www.ad-tech1.com/keys"))
- .build();
- private static final EnrollmentData AD_TECH_2 =
- new EnrollmentData.Builder()
- .setEnrollmentId(UUID.randomUUID().toString())
- .setCompanyId("ad-tech-2")
- .setSdkNames("sdk")
- .setAttributionSourceRegistrationUrl(Arrays.asList("https://www.ad-tech2.com"))
- .setAttributionTriggerRegistrationUrl(Arrays.asList("https://www.ad-tech2.com"))
- .setAttributionReportingUrl(Arrays.asList("https://www.ad-tech2.com"))
- .setRemarketingResponseBasedRegistrationUrl(
- Arrays.asList("https://www.ad-tech2.com"))
- .setEncryptionKeyUrl(Arrays.asList("https://www.ad-tech2.com/keys"))
- .build();
- private static final EnrollmentData AD_TECH_3 =
- new EnrollmentData.Builder()
- .setEnrollmentId(UUID.randomUUID().toString())
- .setCompanyId("ad-tech-3")
- .setSdkNames("sdk")
- .setAttributionSourceRegistrationUrl(Arrays.asList("https://www.ad-tech3.com"))
- .setAttributionTriggerRegistrationUrl(Arrays.asList("https://www.ad-tech3.com"))
- .setAttributionReportingUrl(Arrays.asList("https://www.ad-tech3.com"))
- .setRemarketingResponseBasedRegistrationUrl(
- Arrays.asList("https://www.ad-tech3.com"))
- .setEncryptionKeyUrl(Arrays.asList("https://www.ad-tech3.com/keys"))
- .build();
static final Context sContext = ApplicationProvider.getApplicationContext();
- static final EnrollmentDao sEnrollmentDao = EnrollmentDao.getInstance(
- ApplicationProvider.getApplicationContext());
- static final DatastoreManager sDatastoreManager = DatastoreManagerFactory.getDatastoreManager(
- ApplicationProvider.getApplicationContext());
private final Collection<Action> mActionsList;
final ReportObjects mExpectedOutput;
// Extenders of the class populate in their own ways this container for actual output.
final ReportObjects mActualOutput;
- // Class extensions may choose to disable or enable added noise.
- AttributionJobHandlerWrapper mAttributionHelper;
- MeasurementImpl mMeasurementImpl;
+
+ enum ReportType {
+ EVENT,
+ AGGREGATE
+ }
+
+ private enum OutputType {
+ EXPECTED,
+ ACTUAL
+ }
private interface EventReportPayloadKeys {
// Keys used to compare actual with expected output
@@ -153,6 +112,8 @@ public abstract class E2ETest {
interface AggregateReportPayloadKeys {
String ATTRIBUTION_DESTINATION = "attribution_destination";
String HISTOGRAMS = "histograms";
+ String SOURCE_DEBUG_KEY = "source_debug_key";
+ String TRIGGER_DEBUG_KEY = "trigger_debug_key";
}
interface AggregateHistogramKeys {
@@ -161,6 +122,7 @@ public abstract class E2ETest {
}
public interface TestFormatJsonMapping {
+ String API_CONFIG_KEY = "api_config";
String TEST_INPUT_KEY = "input";
String TEST_OUTPUT_KEY = "output";
String SOURCE_REGISTRATIONS_KEY = "sources";
@@ -174,12 +136,14 @@ public abstract class E2ETest {
String URI_TO_RESPONSE_HEADERS_RESPONSE_KEY = "response";
String REGISTRATION_REQUEST_KEY = "registration_request";
String ATTRIBUTION_SOURCE_KEY = "registrant";
+ String ATTRIBUTION_SOURCE_DEFAULT = "com.interop.app";
String SOURCE_TOP_ORIGIN_URI_KEY = "source_origin";
String TRIGGER_TOP_ORIGIN_URI_KEY = "destination_origin";
String SOURCE_APP_DESTINATION_URI_KEY = "app_destination";
String SOURCE_WEB_DESTINATION_URI_KEY = "web_destination";
String SOURCE_VERIFIED_DESTINATION_URI_KEY = "verified_destination";
String REGISTRATION_URI_KEY = "attribution_src_url";
+ String IS_ADID_PERMISSION_GRANTED_KEY = "is_adid_permission_granted";
String INPUT_EVENT_KEY = "source_type";
String SOURCE_VIEW_TYPE = "event";
String TIMESTAMP_KEY = "timestamp";
@@ -192,7 +156,90 @@ public abstract class E2ETest {
String REPORT_TIME_KEY = "report_time";
String REPORT_TO_KEY = "report_url";
String PAYLOAD_KEY = "payload";
- String DEBUG_KEY = "debug_key";
+ }
+
+ private interface ApiConfigKeys {
+ String RATE_LIMIT_MAX_ATTRIBUTIONS = "rate_limit_max_attributions";
+ String NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY =
+ "navigation_source_trigger_data_cardinality";
+ String RATE_LIMIT_MAX_ATTRIBUTION_REPORTING_ORIGINS =
+ "rate_limit_max_attribution_reporting_origins";
+ String MAX_DESTINATIONS_PER_SOURCE_SITE_REPORTING_ORIGIN =
+ "max_destinations_per_source_site_reporting_origin";
+ String RATE_LIMIT_MAX_SOURCE_REGISTRATION_REPORTING_ORIGINS =
+ "rate_limit_max_source_registration_reporting_origins";
+ }
+
+ public static class PrivacyParamsProvider {
+ private Integer mMaxAttributionPerRateLimitWindow;
+ private Integer mNavigationTriggerDataCardinality;
+ private Integer mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution;
+ private Integer mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource;
+ private Integer mMaxDistinctEnrollmentsPerPublisherXDestinationInSource;
+
+ public PrivacyParamsProvider(JSONObject json) throws JSONException {
+ if (!json.isNull(ApiConfigKeys.RATE_LIMIT_MAX_ATTRIBUTIONS)) {
+ mMaxAttributionPerRateLimitWindow = json.getInt(
+ ApiConfigKeys.RATE_LIMIT_MAX_ATTRIBUTIONS);
+ } else {
+ mMaxAttributionPerRateLimitWindow =
+ PrivacyParams.getMaxAttributionPerRateLimitWindow();
+ }
+ if (!json.isNull(ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY)) {
+ mNavigationTriggerDataCardinality = json.getInt(
+ ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY);
+ } else {
+ mNavigationTriggerDataCardinality =
+ PrivacyParams.getNavigationTriggerDataCardinality();
+ }
+ if (!json.isNull(ApiConfigKeys
+ .RATE_LIMIT_MAX_ATTRIBUTION_REPORTING_ORIGINS)) {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution = json.getInt(
+ ApiConfigKeys.RATE_LIMIT_MAX_ATTRIBUTION_REPORTING_ORIGINS);
+ } else {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution =
+ PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution();
+ }
+ if (!json.isNull(ApiConfigKeys
+ .MAX_DESTINATIONS_PER_SOURCE_SITE_REPORTING_ORIGIN)) {
+ mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource = json.getInt(
+ ApiConfigKeys.MAX_DESTINATIONS_PER_SOURCE_SITE_REPORTING_ORIGIN);
+ } else {
+ mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource =
+ PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource();
+ }
+ if (!json.isNull(ApiConfigKeys
+ .RATE_LIMIT_MAX_SOURCE_REGISTRATION_REPORTING_ORIGINS)) {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInSource = json.getInt(
+ ApiConfigKeys.RATE_LIMIT_MAX_SOURCE_REGISTRATION_REPORTING_ORIGINS);
+ } else {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInSource =
+ PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource();
+ }
+ }
+
+ public Integer getMaxAttributionPerRateLimitWindow() {
+ return mMaxAttributionPerRateLimitWindow;
+ }
+
+ public Integer getNavigationTriggerDataCardinality() {
+ return mNavigationTriggerDataCardinality;
+ }
+
+ public Integer getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution() {
+ return mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution;
+ }
+
+ public Integer getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource() {
+ return mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource;
+ }
+
+ public Integer getMaxDistinctEnrollmentsPerPublisherXDestinationInSource() {
+ return mMaxDistinctEnrollmentsPerPublisherXDestinationInSource;
+ }
}
static Collection<Object[]> data(String testDirName) throws IOException, JSONException {
@@ -205,8 +252,8 @@ public abstract class E2ETest {
return getTestCasesFrom(inputStreams, testDirectoryList);
}
- public static Map<String, List<Map<String, List<String>>>>
- getUriToResponseHeadersMap(JSONObject obj) throws JSONException {
+ public static Map<String, List<Map<String, List<String>>>> getUriToResponseHeadersMap(
+ JSONObject obj) throws JSONException {
JSONArray uriToResArray = obj.getJSONArray(
TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY);
Map<String, List<Map<String, List<String>>>> uriToResponseHeadersMap = new HashMap<>();
@@ -270,8 +317,16 @@ public abstract class E2ETest {
0 /*int flags*/);
}
+ static String getReportUrl(ReportType reportType, String origin) {
+ return origin
+ + "/"
+ + (reportType == ReportType.EVENT
+ ? EVENT_ATTRIBUTION_REPORT_URI_PATH
+ : AGGREGATE_ATTRIBUTION_REPORT_URI_PATH);
+ }
+
static void clearDatabase() {
- SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().getWritableDatabase();
emptyTables(db);
}
@@ -286,8 +341,6 @@ public abstract class E2ETest {
@Test
public void runTest() throws IOException, JSONException {
clearDatabase();
- unseedEnrollment();
- seedEnrollment();
for (Action action : mActionsList) {
if (action instanceof RegisterSource) {
processAction((RegisterSource) action);
@@ -297,8 +350,10 @@ public abstract class E2ETest {
processAction((RegisterWebSource) action);
} else if (action instanceof RegisterWebTrigger) {
processAction((RegisterWebTrigger) action);
- } else if (action instanceof ReportingJob) {
- processAction((ReportingJob) action);
+ } else if (action instanceof EventReportingJob) {
+ processAction((EventReportingJob) action);
+ } else if (action instanceof AggregateReportingJob) {
+ processAction((AggregateReportingJob) action);
} else if (action instanceof InstallApp) {
processAction((InstallApp) action);
} else if (action instanceof UninstallApp) {
@@ -307,14 +362,20 @@ public abstract class E2ETest {
}
evaluateResults();
clearDatabase();
- unseedEnrollment();
}
/**
* The reporting job may be handled differently depending on whether network requests are mocked
* or a test server is used.
*/
- abstract void processAction(ReportingJob reportingJob) throws IOException, JSONException;
+ abstract void processAction(EventReportingJob reportingJob) throws IOException, JSONException;
+
+ /**
+ * The reporting job may be handled differently depending on whether network requests are mocked
+ * or a test server is used.
+ */
+ abstract void processAction(AggregateReportingJob reportingJob)
+ throws IOException, JSONException;
/**
* Override with HTTP response mocks, for example.
@@ -336,44 +397,57 @@ public abstract class E2ETest {
abstract void prepareRegistrationServer(RegisterWebTrigger triggerRegistration)
throws IOException;
- private static int hashForEventReportObject(JSONObject obj) {
+ private static int hashForEventReportObject(OutputType outputType, JSONObject obj) {
int n = EventReportPayloadKeys.STRINGS.size();
Object[] objArray = new Object[n + 2];
// We cannot use report time due to fuzzy matching between actual and expected output.
- objArray[0] = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ objArray[0] =
+ outputType == OutputType.EXPECTED ? url : getReportUrl(ReportType.EVENT, url);
JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
- objArray[1] = payload.optDouble(EventReportPayloadKeys.DOUBLE, 0);
+ objArray[1] = normaliseDouble(payload.optDouble(EventReportPayloadKeys.DOUBLE, 0));
for (int i = 0; i < n; i++) {
objArray[i + 2] = payload.optString(EventReportPayloadKeys.STRINGS.get(i), "");
}
return Arrays.hashCode(objArray);
}
- private static int hashForAggregateReportObject(JSONObject obj) {
- Object[] objArray = new Object[3];
+ private static int hashForAggregateReportObject(OutputType outputType,
+ JSONObject obj) {
+ Object[] objArray = new Object[5];
// We cannot use report time due to fuzzy matching between actual and expected output.
- objArray[0] = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ objArray[0] =
+ outputType == OutputType.EXPECTED ? url : getReportUrl(ReportType.AGGREGATE, url);
JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
objArray[1] = payload.optString(AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION, "");
// To compare histograms, we already converted them to an ordered string of value pairs.
objArray[2] = getComparableHistograms(
payload.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS));
+ objArray[3] = payload.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, "");
+ objArray[4] = payload.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, "");
return Arrays.hashCode(objArray);
}
+ // Used in interop tests, where we have known discrepancies.
+ private static double normaliseDouble(double d) {
+ return d == 0.0024263D ? 0.0024D : d;
+ }
+
private static long reportTimeFrom(JSONObject obj) {
return obj.optLong(TestFormatJsonMapping.REPORT_TIME_KEY, 0);
}
- private static boolean matchReportTimeAndReportTo(JSONObject obj1, JSONObject obj2)
- throws JSONException {
+ // 'obj1' is the expected result, 'obj2' is the actual result.
+ private static boolean matchReportTimeAndReportTo(ReportType reportType, JSONObject obj1,
+ JSONObject obj2) throws JSONException {
if (Math.abs(obj1.getLong(TestFormatJsonMapping.REPORT_TIME_KEY)
- obj2.getLong(TestFormatJsonMapping.REPORT_TIME_KEY))
> REPORT_TIME_EPSILON) {
return false;
}
if (!obj1.getString(TestFormatJsonMapping.REPORT_TO_KEY).equals(
- obj2.getString(TestFormatJsonMapping.REPORT_TO_KEY))) {
+ getReportUrl(reportType, obj2.getString(TestFormatJsonMapping.REPORT_TO_KEY)))) {
return false;
}
return true;
@@ -383,8 +457,8 @@ public abstract class E2ETest {
throws JSONException {
JSONObject payload1 = obj1.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
JSONObject payload2 = obj2.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
- if (payload1.getDouble(EventReportPayloadKeys.DOUBLE)
- != payload2.getDouble(EventReportPayloadKeys.DOUBLE)) {
+ if (normaliseDouble(payload1.getDouble(EventReportPayloadKeys.DOUBLE))
+ != normaliseDouble(payload2.getDouble(EventReportPayloadKeys.DOUBLE))) {
return false;
}
for (String key : EventReportPayloadKeys.STRINGS) {
@@ -392,7 +466,7 @@ public abstract class E2ETest {
return false;
}
}
- return matchReportTimeAndReportTo(obj1, obj2);
+ return matchReportTimeAndReportTo(ReportType.EVENT, obj1, obj2);
}
private static boolean areEqualAggregateReportJsons(JSONObject obj1, JSONObject obj2)
@@ -403,12 +477,20 @@ public abstract class E2ETest {
payload2.optString(AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION, ""))) {
return false;
}
+ if (!payload1.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, "")
+ .equals(payload2.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, ""))) {
+ return false;
+ }
+ if (!payload1.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, "")
+ .equals(payload2.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, ""))) {
+ return false;
+ }
JSONArray histograms1 = payload1.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS);
JSONArray histograms2 = payload2.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS);
if (!getComparableHistograms(histograms1).equals(getComparableHistograms(histograms2))) {
return false;
}
- return matchReportTimeAndReportTo(obj1, obj2);
+ return matchReportTimeAndReportTo(ReportType.AGGREGATE, obj1, obj2);
}
private static String getComparableHistograms(@Nullable JSONArray arr) {
@@ -429,20 +511,24 @@ public abstract class E2ETest {
}
}
- private static void sortEventReportObjects(List<JSONObject> eventReportObjects) {
+ private static void sortEventReportObjects(OutputType outputType,
+ List<JSONObject> eventReportObjects) {
eventReportObjects.sort(
// Report time can vary across implementations so cannot be included in the hash;
// they should be similarly ordered, however, so we can use them to sort.
Comparator.comparing(E2ETest::reportTimeFrom)
- .thenComparing(E2ETest::hashForEventReportObject));
+ .thenComparing(obj -> hashForEventReportObject(outputType, obj)));
}
- private static void sortAggregateReportObjects(List<JSONObject> aggregateReportObjects) {
+ private static void sortAggregateReportObjects(OutputType outputType,
+ List<JSONObject> aggregateReportObjects) {
aggregateReportObjects.sort(
- // Report time can vary across implementations so cannot be included in the hash;
- // they should be similarly ordered, however, so we can use them to sort.
- Comparator.comparing(E2ETest::reportTimeFrom)
- .thenComparing(E2ETest::hashForAggregateReportObject));
+ // Unlike event reports (sorted elsewhere in this file), aggregate reports are
+ // scheduled with randomised times, and using report time for sorting can result
+ // in unexpected variations in the sort order, depending on test timing. Without
+ // time ordering, we rely on other data across the reports to yield different
+ // hash codes.
+ Comparator.comparing(obj -> hashForAggregateReportObject(outputType, obj)));
}
private static boolean areEqual(ReportObjects p1, ReportObjects p2) throws JSONException {
@@ -467,13 +553,22 @@ public abstract class E2ETest {
private static String getTestFailureMessage(ReportObjects expectedOutput,
ReportObjects actualOutput) {
- return String.format("Actual output does not match expected.\n\n"
- + "(Note that report IDs are ignored in comparisons since they are not known in"
- + " advance.)\n\nEvent report objects:\n%s\n\n"
- + "Expected aggregate report objects: %s\n\n"
- + "Actual aggregate report objects: %s\n",
- prettify(expectedOutput.mEventReportObjects, actualOutput.mEventReportObjects),
- expectedOutput.mAggregateReportObjects, actualOutput.mAggregateReportObjects);
+ return String.format(
+ "Actual output does not match expected.\n\n"
+ + "(Note that displayed randomized_trigger_rate and report_url are not"
+ + " normalised.\n"
+ + "Note that report IDs are ignored in comparisons since they are not"
+ + " known in advance.)\n\n"
+ + "Event report objects:\n"
+ + "%s\n\n"
+ + "Expected aggregate report objects: %s\n\n"
+ + "Actual aggregate report objects: %s\n",
+ prettify(
+ expectedOutput.mEventReportObjects,
+ actualOutput.mEventReportObjects),
+ expectedOutput.mAggregateReportObjects,
+ actualOutput.mAggregateReportObjects)
+ + getDatastoreState();
}
private static String prettify(List<JSONObject> expected, List<JSONObject> actual) {
@@ -502,6 +597,11 @@ public abstract class E2ETest {
.append(" ::: ")
.append(obj2.optString(TestFormatJsonMapping.REPORT_TIME_KEY))
.append("\n");
+ result.append(TestFormatJsonMapping.REPORT_TO_KEY + ": ")
+ .append(obj1.optString(TestFormatJsonMapping.REPORT_TO_KEY))
+ .append(" ::: ")
+ .append(obj2.optString(TestFormatJsonMapping.REPORT_TO_KEY))
+ .append("\n");
JSONObject payload1 = obj1.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
JSONObject payload2 = obj2.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
for (String key : EventReportPayloadKeys.STRINGS) {
@@ -535,6 +635,47 @@ public abstract class E2ETest {
return result.toString();
}
+ protected static String getDatastoreState() {
+ StringBuilder result = new StringBuilder();
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().getWritableDatabase();
+ List<String> tableNames =
+ ImmutableList.of(
+ "msmt_source",
+ "msmt_trigger",
+ "msmt_attribution",
+ "msmt_event_report",
+ "msmt_aggregate_report",
+ "enrollment_data",
+ "msmt_async_registration_contract");
+ for (String tableName : tableNames) {
+ result.append("\n" + tableName + ":\n");
+ result.append(getTableState(db, tableName));
+ }
+ return result.toString();
+ }
+
+ private static String getTableState(SQLiteDatabase db, String tableName) {
+ Cursor cursor = getAllRows(db, tableName);
+ StringBuilder result = new StringBuilder();
+ while (cursor.moveToNext()) {
+ result.append("\n" + DatabaseUtils.dumpCurrentRowToString(cursor));
+ }
+ return result.toString();
+ }
+
+ private static Cursor getAllRows(SQLiteDatabase db, String tableName) {
+ return db.query(
+ /* boolean distinct */ false,
+ tableName,
+ /* String[] columns */ null,
+ /* String selection */ null,
+ /* String[] selectionArgs */ null,
+ /* String groupBy */ null,
+ /* String having */ null,
+ /* String orderBy */ null,
+ /* String limit */ null);
+ }
+
private static Set<Long> getExpiryTimesFrom(
Collection<List<Map<String, List<String>>>> responseHeadersCollection)
throws JSONException {
@@ -556,7 +697,7 @@ public abstract class E2ETest {
return expiryTimes;
}
- private static Set<Action> maybeAddReportingJobTimes(
+ private static Set<Action> maybeAddEventReportingJobTimes(
long sourceTime, Collection<List<Map<String, List<String>>>> responseHeaders)
throws JSONException {
Set<Action> reportingJobsActions = new HashSet<>();
@@ -569,7 +710,8 @@ public abstract class E2ETest {
validExpiry = PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
}
long jobTime = sourceTime + 1000 * validExpiry + 3600000L;
- reportingJobsActions.add(new ReportingJob(jobTime));
+
+ reportingJobsActions.add(new EventReportingJob(jobTime));
}
return reportingJobsActions;
@@ -580,7 +722,8 @@ public abstract class E2ETest {
* tests.
*
* @return A collection of Object arrays, each with
- * {@code [Collection<Object> actions, ReportObjects expectedOutput, String name]}
+ * {@code [Collection<Object> actions, ReportObjects expectedOutput,
+ * PrivacyParamsProvider privacyParamsProvider, String name]}
*/
private static Collection<Object[]> getTestCasesFrom(List<InputStream> inputStreams,
String[] filenames) throws IOException, JSONException {
@@ -593,7 +736,7 @@ public abstract class E2ETest {
inputStreams.get(i).close();
String json = new String(buffer, StandardCharsets.UTF_8);
- JSONObject testObj = new JSONObject(json);
+ JSONObject testObj = new JSONObject(json.replaceAll("\\.test(?=[\"\\/])", ".com"));
String name = filenames[i];
JSONObject input = testObj.getJSONObject(TestFormatJsonMapping.TEST_INPUT_KEY);
JSONObject output = testObj.getJSONObject(TestFormatJsonMapping.TEST_OUTPUT_KEY);
@@ -602,7 +745,7 @@ public abstract class E2ETest {
List<Action> actions = new ArrayList<>();
actions.addAll(createSourceBasedActions(input));
- actions.addAll(createTriggerActions(input));
+ actions.addAll(createTriggerBasedActions(input));
actions.addAll(createInstallActions(input));
actions.addAll(createUninstallActions(input));
@@ -610,7 +753,13 @@ public abstract class E2ETest {
ReportObjects expectedOutput = getExpectedOutput(output);
- testCases.add(new Object[] {actions, expectedOutput, name});
+ JSONObject ApiConfigObj = testObj.isNull(TestFormatJsonMapping.API_CONFIG_KEY)
+ ? new JSONObject()
+ : testObj.getJSONObject(TestFormatJsonMapping.API_CONFIG_KEY);
+
+ PrivacyParamsProvider privacyParamsProvider = new PrivacyParamsProvider(ApiConfigObj);
+
+ testCases.add(new Object[] {actions, expectedOutput, privacyParamsProvider, name});
}
return testCases;
@@ -619,7 +768,7 @@ public abstract class E2ETest {
private static List<Action> createSourceBasedActions(JSONObject input) throws JSONException {
List<Action> actions = new ArrayList<>();
// Set avoids duplicate reporting times across sources to do attribution upon.
- Set<Action> reportingJobActions = new HashSet<>();
+ Set<Action> eventReportingJobActions = new HashSet<>();
if (!input.isNull(TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY)) {
JSONArray sourceRegistrationArray = input.getJSONArray(
TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY);
@@ -628,8 +777,8 @@ public abstract class E2ETest {
new RegisterSource(sourceRegistrationArray.getJSONObject(j));
actions.add(sourceRegistration);
// Add corresponding reporting job time actions
- reportingJobActions.addAll(
- maybeAddReportingJobTimes(
+ eventReportingJobActions.addAll(
+ maybeAddEventReportingJobTimes(
sourceRegistration.mTimestamp,
sourceRegistration.mUriToResponseHeadersMap.values()));
}
@@ -643,18 +792,20 @@ public abstract class E2ETest {
new RegisterWebSource(webSourceRegistrationArray.getJSONObject(j));
actions.add(webSource);
// Add corresponding reporting job time actions
- reportingJobActions.addAll(
- maybeAddReportingJobTimes(
+ eventReportingJobActions.addAll(
+ maybeAddEventReportingJobTimes(
webSource.mTimestamp, webSource.mUriToResponseHeadersMap.values()));
}
}
- actions.addAll(reportingJobActions);
+ actions.addAll(eventReportingJobActions);
return actions;
}
- private static List<Action> createTriggerActions(JSONObject input) throws JSONException {
+ private static List<Action> createTriggerBasedActions(JSONObject input) throws JSONException {
List<Action> actions = new ArrayList<>();
+ long firstTriggerTime = Long.MAX_VALUE;
+ long lastTriggerTime = -1;
if (!input.isNull(TestFormatJsonMapping.TRIGGER_KEY)) {
JSONArray triggerRegistrationArray =
input.getJSONArray(TestFormatJsonMapping.TRIGGER_KEY);
@@ -662,6 +813,8 @@ public abstract class E2ETest {
RegisterTrigger triggerRegistration =
new RegisterTrigger(triggerRegistrationArray.getJSONObject(j));
actions.add(triggerRegistration);
+ firstTriggerTime = Math.min(firstTriggerTime, triggerRegistration.mTimestamp);
+ lastTriggerTime = Math.max(lastTriggerTime, triggerRegistration.mTimestamp);
}
}
@@ -672,9 +825,31 @@ public abstract class E2ETest {
RegisterWebTrigger webTrigger =
new RegisterWebTrigger(webTriggerRegistrationArray.getJSONObject(j));
actions.add(webTrigger);
+ firstTriggerTime = Math.min(firstTriggerTime, webTrigger.mTimestamp);
+ lastTriggerTime = Math.max(lastTriggerTime, webTrigger.mTimestamp);
}
}
+ // Aggregate reports are scheduled close to trigger time. Add aggregate report jobs to cover
+ // the time span outlined by triggers.
+ List<Action> aggregateReportingJobActions = new ArrayList<>();
+ long window = SystemHealthParams.MAX_AGGREGATE_REPORT_UPLOAD_RETRY_WINDOW_MS - 10;
+ long t = firstTriggerTime;
+
+ do {
+ t += window;
+ aggregateReportingJobActions.add(new AggregateReportingJob(t));
+ } while (t <= lastTriggerTime);
+
+ // Account for edge case of t between lastTriggerTime and the latter's max report delay.
+ if (t <= lastTriggerTime + PrivacyParams.AGGREGATE_MAX_REPORT_DELAY) {
+ // t must be greater than lastTriggerTime so adding max report
+ // delay should be beyond the report delay for lastTriggerTime.
+ aggregateReportingJobActions.add(new AggregateReportingJob(t
+ + PrivacyParams.AGGREGATE_MAX_REPORT_DELAY));
+ }
+
+ actions.addAll(aggregateReportingJobActions);
return actions;
}
@@ -706,36 +881,28 @@ public abstract class E2ETest {
private static ReportObjects getExpectedOutput(JSONObject output) throws JSONException {
List<JSONObject> eventReportObjects = new ArrayList<>();
- JSONArray eventReportObjectsArray = output.getJSONArray(
- TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY);
- for (int i = 0; i < eventReportObjectsArray.length(); i++) {
- JSONObject obj = eventReportObjectsArray.getJSONObject(i);
- String adTechDomain = obj.getString(TestFormatJsonMapping.REPORT_TO_KEY);
- eventReportObjects.add(obj.put(TestFormatJsonMapping.REPORT_TO_KEY, adTechDomain));
+ if (!output.isNull(TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY)) {
+ JSONArray eventReportObjectsArray = output.getJSONArray(
+ TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY);
+ for (int i = 0; i < eventReportObjectsArray.length(); i++) {
+ JSONObject obj = eventReportObjectsArray.getJSONObject(i);
+ String adTechDomain = obj.getString(TestFormatJsonMapping.REPORT_TO_KEY);
+ eventReportObjects.add(obj.put(TestFormatJsonMapping.REPORT_TO_KEY, adTechDomain));
+ }
}
List<JSONObject> aggregateReportObjects = new ArrayList<>();
- JSONArray aggregateReportObjectsArray =
- output.getJSONArray(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY);
- for (int i = 0; i < aggregateReportObjectsArray.length(); i++) {
- aggregateReportObjects.add(aggregateReportObjectsArray.getJSONObject(i));
+ if (!output.isNull(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY)) {
+ JSONArray aggregateReportObjectsArray =
+ output.getJSONArray(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY);
+ for (int i = 0; i < aggregateReportObjectsArray.length(); i++) {
+ aggregateReportObjects.add(aggregateReportObjectsArray.getJSONObject(i));
+ }
}
return new ReportObjects(eventReportObjects, aggregateReportObjects);
}
- private static void seedEnrollment() {
- sEnrollmentDao.insert(AD_TECH_1);
- sEnrollmentDao.insert(AD_TECH_2);
- sEnrollmentDao.insert(AD_TECH_3);
- }
-
- private static void unseedEnrollment() {
- sEnrollmentDao.delete(AD_TECH_1.getEnrollmentId());
- sEnrollmentDao.delete(AD_TECH_2.getEnrollmentId());
- sEnrollmentDao.delete(AD_TECH_3.getEnrollmentId());
- }
-
/**
* Empties measurement database tables, used for test cleanup.
*/
@@ -745,72 +912,27 @@ public abstract class E2ETest {
db.delete("msmt_event_report", null, null);
db.delete("msmt_attribution", null, null);
db.delete("msmt_aggregate_report", null, null);
+ db.delete("enrollment_data", null, null);
+ db.delete("msmt_async_registration_contract", null, null);
}
- void processAction(RegisterSource sourceRegistration) throws IOException {
- prepareRegistrationServer(sourceRegistration);
- Assert.assertEquals(
- "MeasurementImpl.register source failed",
- RESULT_OK,
- mMeasurementImpl.register(
- sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
- }
-
- void processAction(RegisterWebSource sourceRegistration) throws IOException {
- prepareRegistrationServer(sourceRegistration);
- Assert.assertEquals(
- "MeasurementImpl.registerWebSource failed",
- RESULT_OK,
- mMeasurementImpl.registerWebSource(
- sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
- }
-
- void processAction(RegisterWebTrigger triggerRegistration) throws IOException {
- prepareRegistrationServer(triggerRegistration);
- Assert.assertEquals(
- "MeasurementImpl.registerWebTrigger failed",
- RESULT_OK,
- mMeasurementImpl.registerWebTrigger(
- triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
- Assert.assertTrue(
- "AttributionJobHandler.performPendingAttributions returned false",
- mAttributionHelper.performPendingAttributions());
- }
-
- void processAction(RegisterTrigger triggerRegistration) throws IOException {
- prepareRegistrationServer(triggerRegistration);
- Assert.assertEquals(
- "MeasurementImpl.register trigger failed",
- RESULT_OK,
- mMeasurementImpl.register(
- triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
- Assert.assertTrue("AttributionJobHandler.performPendingAttributions returned false",
- mAttributionHelper.performPendingAttributions());
- }
-
- void processAction(InstallApp installApp) {
- Assert.assertTrue(
- "measurementDao.doInstallAttribution failed",
- sDatastoreManager.runInTransaction(
- measurementDao ->
- measurementDao.doInstallAttribution(
- installApp.mUri, installApp.mTimestamp)));
- }
-
- void processAction(UninstallApp uninstallApp) {
- Assert.assertTrue("measurementDao.undoInstallAttribution failed",
- sDatastoreManager.runInTransaction(
- measurementDao -> {
- measurementDao.deleteAppRecords(uninstallApp.mUri);
- measurementDao.undoInstallAttribution(uninstallApp.mUri);
- }));
- }
+ abstract void processAction(RegisterSource sourceRegistration) throws IOException;
+
+ abstract void processAction(RegisterWebSource sourceRegistration) throws IOException;
+
+ abstract void processAction(RegisterTrigger triggerRegistration) throws IOException;
+
+ abstract void processAction(RegisterWebTrigger triggerRegistration) throws IOException;
+
+ abstract void processAction(InstallApp installApp);
+
+ abstract void processAction(UninstallApp uninstallApp);
void evaluateResults() throws JSONException {
- sortEventReportObjects(mExpectedOutput.mEventReportObjects);
- sortEventReportObjects(mActualOutput.mEventReportObjects);
- sortAggregateReportObjects(mExpectedOutput.mAggregateReportObjects);
- sortAggregateReportObjects(mActualOutput.mAggregateReportObjects);
+ sortEventReportObjects(OutputType.EXPECTED, mExpectedOutput.mEventReportObjects);
+ sortEventReportObjects(OutputType.ACTUAL, mActualOutput.mEventReportObjects);
+ sortAggregateReportObjects(OutputType.EXPECTED, mExpectedOutput.mAggregateReportObjects);
+ sortAggregateReportObjects(OutputType.ACTUAL, mActualOutput.mAggregateReportObjects);
Assert.assertTrue(getTestFailureMessage(mExpectedOutput, mActualOutput),
areEqual(mExpectedOutput, mActualOutput));
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java
new file mode 100644
index 0000000000..45ccbbbaca
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java
@@ -0,0 +1,840 @@
+/*
+ * 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.measurement;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.adservices.measurement.WebSourceParams;
+import android.adservices.measurement.WebSourceRegistrationRequest;
+import android.adservices.measurement.WebTriggerParams;
+import android.adservices.measurement.WebTriggerRegistrationRequest;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.view.InputEvent;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.data.DbTestUtil;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.MeasurementTables;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
+import com.android.adservices.data.measurement.SqliteObjectMapper;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.registration.EnqueueAsyncRegistration;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EnqueueAsyncRegistrationTest {
+
+ private static Context sDefaultContext = ApplicationProvider.getApplicationContext();
+ private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com/bar?ad=134");
+ private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo.com/bar?ad=256");
+ private static final String DEFAULT_ENROLLMENT = "enrollment-id";
+ private static final WebSourceParams INPUT_SOURCE_REGISTRATION_1 =
+ new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+
+ private static final WebSourceParams INPUT_SOURCE_REGISTRATION_2 =
+ new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+
+ private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_1 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+
+ private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_2 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+
+ private static List<WebSourceParams> sSourceParamsList = new ArrayList<>();
+
+ private static List<WebTriggerParams> sTriggerParamsList = new ArrayList<>();
+
+ static {
+ sSourceParamsList.add(INPUT_SOURCE_REGISTRATION_1);
+ sSourceParamsList.add(INPUT_SOURCE_REGISTRATION_2);
+ sTriggerParamsList.add(INPUT_TRIGGER_REGISTRATION_1);
+ sTriggerParamsList.add(INPUT_TRIGGER_REGISTRATION_2);
+ }
+
+ @Mock private DatastoreManager mDatastoreManagerMock;
+ @Mock private EnrollmentDao mEnrollmentDao;
+ @Mock private InputEvent mInputEvent;
+
+ private MockitoSession mStaticMockSession;
+
+ private static final WebSourceRegistrationRequest
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT =
+ new WebSourceRegistrationRequest.Builder(
+ sSourceParamsList, Uri.parse("android-app://example.com/aD1"))
+ .setWebDestination(Uri.parse("android-app://example.com/aD1"))
+ .setAppDestination(Uri.parse("android-app://example.com/aD1"))
+ .setVerifiedDestination(Uri.parse("android-app://example.com/aD1"))
+ .build();
+
+ private static final WebTriggerRegistrationRequest VALID_WEB_TRIGGER_REGISTRATION =
+ new WebTriggerRegistrationRequest.Builder(
+ sTriggerParamsList, Uri.parse("android-app://com.e.abc"))
+ .build();
+
+ private static EnrollmentData getEnrollment(String enrollmentId) {
+ return new EnrollmentData.Builder().setEnrollmentId(enrollmentId).build();
+ }
+
+ @After
+ public void cleanup() {
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().safeGetWritableDatabase();
+ for (String table : MeasurementTables.ALL_MSMT_TABLES) {
+ db.delete(table, null, null);
+ }
+ mStaticMockSession.finishMocking();
+ }
+
+ @Before
+ public void before() throws RemoteException {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+ MockitoAnnotations.initMocks(this);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any()))
+ .thenReturn(getEnrollment(DEFAULT_ENROLLMENT));
+ }
+
+ @Test
+ public void test_appSourceRegistrationRequest_event_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .build();
+
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getSourceType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.APP_SOURCE, asyncRegistration.getType());
+ }
+ }
+
+ @Test
+ public void test_appSourceRegistrationRequest_navigation_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .setInputEvent(mInputEvent)
+ .build();
+
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getSourceType());
+ Assert.assertEquals(Source.SourceType.NAVIGATION, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.APP_SOURCE, asyncRegistration.getType());
+ }
+ }
+
+ @Test
+ public void test_appTriggerRegistrationRequest_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .build();
+
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNull(asyncRegistration.getSourceType());
+ }
+ }
+
+ @Test
+ public void test_webSourceRegistrationRequest_event_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.webSourceRegistrationRequest(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT
+ .getSourceParams()
+ .get(0)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT
+ .getSourceParams()
+ .get(1)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT.getTopOriginUri(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else {
+ Assert.fail();
+ }
+ }
+ }
+
+ @Test
+ public void test_webSourceRegistrationRequest_navigation_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ List<WebSourceParams> sourceParamsList = new ArrayList<>();
+ sourceParamsList.add(INPUT_SOURCE_REGISTRATION_1);
+ sourceParamsList.add(INPUT_SOURCE_REGISTRATION_2);
+ WebSourceRegistrationRequest validWebSourceRegistration =
+ new WebSourceRegistrationRequest.Builder(
+ sourceParamsList, Uri.parse("android-app://example.com/aD1"))
+ .setWebDestination(Uri.parse("android-app://example.com/aD1"))
+ .setAppDestination(Uri.parse("android-app://example.com/aD1"))
+ .setVerifiedDestination(Uri.parse("android-app://example.com/aD1"))
+ .setInputEvent(mInputEvent)
+ .build();
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.webSourceRegistrationRequest(
+ validWebSourceRegistration,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ validWebSourceRegistration
+ .getSourceParams()
+ .get(0)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ validWebSourceRegistration.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ validWebSourceRegistration
+ .getSourceParams()
+ .get(1)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ validWebSourceRegistration.getTopOriginUri(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ validWebSourceRegistration.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else {
+ Assert.fail();
+ }
+ }
+ }
+
+ @Test
+ public void test_webTriggerRegistrationRequest_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.webTriggerRegistrationRequest(
+ VALID_WEB_TRIGGER_REGISTRATION,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_TRIGGER_REGISTRATION
+ .getTriggerParams()
+ .get(0)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(null, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(null, asyncRegistrationTwo.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistrationTwo.getType());
+ } else if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_TRIGGER_REGISTRATION
+ .getTriggerParams()
+ .get(1)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(null, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(null, asyncRegistrationTwo.getSourceType());
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistrationTwo.getType());
+ } else {
+ Assert.fail();
+ }
+ }
+ }
+
+ @Test
+ public void test_runInTransactionFail_inValid() {
+ when(mDatastoreManagerMock.runInTransaction(any())).thenReturn(false);
+ Assert.assertFalse(
+ EnqueueAsyncRegistration.webTriggerRegistrationRequest(
+ VALID_WEB_TRIGGER_REGISTRATION,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ mDatastoreManagerMock));
+ }
+
+ @Test
+ public void test_MissingEnrollmentData_inValid() {
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
+ Assert.assertFalse(
+ EnqueueAsyncRegistration.webTriggerRegistrationRequest(
+ VALID_WEB_TRIGGER_REGISTRATION,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ mDatastoreManagerMock));
+ }
+
+ /** Test that the AsyncRegistration is inserted correctly. */
+ @Test
+ public void test_verifyAsyncRegistrationStoredCorrectly() {
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .build();
+
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager);
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getSourceType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.APP_SOURCE, asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.ANY,
+ asyncRegistration.getRedirectType());
+ }
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java
index ffeed6a45c..4247a8b3b1 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java
@@ -25,16 +25,21 @@ import static com.android.adservices.service.measurement.PrivacyParams.NAVIGATIO
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.net.Uri;
import androidx.test.filters.SmallTest;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONException;
import org.junit.Test;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link EventReport} */
@@ -44,10 +49,12 @@ public final class EventReportTest {
private static final long ONE_HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
private static final double DOUBLE_MAX_DELTA = 0.0000001D;
private static final long TRIGGER_PRIORITY = 345678L;
- private static final Long TRIGGER_DEDUP_KEY = 2345678L;
- private static final Long TRIGGER_DATA = 4L;
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong TRIGGER_DEDUP_KEY = new UnsignedLong(2345678L);
+ private static final UnsignedLong TRIGGER_DATA = new UnsignedLong(4L);
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
+ private static final String SOURCE_ID = UUID.randomUUID().toString();
+ private static final String TRIGGER_ID = UUID.randomUUID().toString();
private static final Uri APP_DESTINATION = Uri.parse("android-app://example1.app");
private static final Uri WEB_DESTINATION = Uri.parse("https://example1.com");
private static final String EVENT_TRIGGERS =
@@ -73,72 +80,84 @@ public final class EventReportTest {
public void creation_success() {
EventReport eventReport = createExample();
assertEquals("1", eventReport.getId());
- assertEquals(21, eventReport.getSourceId());
+ assertEquals(new UnsignedLong(21L), eventReport.getSourceEventId());
assertEquals("enrollment-id", eventReport.getEnrollmentId());
assertEquals("https://bar.com", eventReport.getAttributionDestination().toString());
assertEquals(1000L, eventReport.getTriggerTime());
- assertEquals(8L, eventReport.getTriggerData());
+ assertEquals(new UnsignedLong(8L), eventReport.getTriggerData());
assertEquals(2L, eventReport.getTriggerPriority());
- assertEquals(Long.valueOf(3), eventReport.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(3L), eventReport.getTriggerDedupKey());
assertEquals(2000L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.PENDING, eventReport.getDebugReportStatus());
assertEquals(Source.SourceType.NAVIGATION, eventReport.getSourceType());
assertEquals(SOURCE_DEBUG_KEY, eventReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, eventReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, eventReport.getSourceId());
+ assertEquals(TRIGGER_ID, eventReport.getTriggerId());
}
@Test
public void creationSuccessSingleSourceDebugKey() {
EventReport eventReport = createExampleSingleSourceDebugKey();
assertEquals("1", eventReport.getId());
- assertEquals(21, eventReport.getSourceId());
+ assertEquals(new UnsignedLong(21L), eventReport.getSourceEventId());
assertEquals("enrollment-id", eventReport.getEnrollmentId());
assertEquals("https://bar.com", eventReport.getAttributionDestination().toString());
assertEquals(1000L, eventReport.getTriggerTime());
- assertEquals(8L, eventReport.getTriggerData());
+ assertEquals(new UnsignedLong(8L), eventReport.getTriggerData());
assertEquals(2L, eventReport.getTriggerPriority());
- assertEquals(Long.valueOf(3), eventReport.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(3L), eventReport.getTriggerDedupKey());
assertEquals(2000L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.PENDING, eventReport.getDebugReportStatus());
assertEquals(Source.SourceType.NAVIGATION, eventReport.getSourceType());
assertEquals(SOURCE_DEBUG_KEY, eventReport.getSourceDebugKey());
assertNull(eventReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, eventReport.getSourceId());
+ assertEquals(TRIGGER_ID, eventReport.getTriggerId());
}
@Test
public void creationSuccessSingleTriggerDebugKey() {
EventReport eventReport = createExampleSingleTriggerDebugKey();
assertEquals("1", eventReport.getId());
- assertEquals(21, eventReport.getSourceId());
+ assertEquals(new UnsignedLong(21L), eventReport.getSourceEventId());
assertEquals("enrollment-id", eventReport.getEnrollmentId());
assertEquals("https://bar.com", eventReport.getAttributionDestination().toString());
assertEquals(1000L, eventReport.getTriggerTime());
- assertEquals(8L, eventReport.getTriggerData());
+ assertEquals(new UnsignedLong(8L), eventReport.getTriggerData());
assertEquals(2L, eventReport.getTriggerPriority());
- assertEquals(Long.valueOf(3), eventReport.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(3L), eventReport.getTriggerDedupKey());
assertEquals(2000L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.PENDING, eventReport.getDebugReportStatus());
assertEquals(Source.SourceType.NAVIGATION, eventReport.getSourceType());
assertNull(eventReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, eventReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, eventReport.getSourceId());
+ assertEquals(TRIGGER_ID, eventReport.getTriggerId());
}
@Test
public void defaults_success() {
EventReport eventReport = new EventReport.Builder().build();
assertNull(eventReport.getId());
- assertEquals(0L, eventReport.getSourceId());
+ assertNull(eventReport.getSourceEventId());
assertNull(eventReport.getEnrollmentId());
assertNull(eventReport.getAttributionDestination());
assertEquals(0L, eventReport.getTriggerTime());
- assertEquals(0L, eventReport.getTriggerData());
+ assertNull(eventReport.getTriggerData());
assertEquals(0L, eventReport.getTriggerPriority());
assertNull(eventReport.getTriggerDedupKey());
assertEquals(0L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.NONE, eventReport.getDebugReportStatus());
assertNull(eventReport.getSourceType());
assertNull(eventReport.getSourceDebugKey());
assertNull(eventReport.getTriggerDebugKey());
+ assertNull(eventReport.getSourceId());
+ assertNull(eventReport.getTriggerId());
}
@Test
@@ -160,14 +179,87 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
// Truncated data 4 % 2 = 0
- assertEquals(0, report.getTriggerData());
+ assertEquals(new UnsignedLong(0L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
assertEquals(source.getSourceType(), report.getSourceType());
assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
+ }
+
+ @Test
+ public void testPopulateFromSourceAndTrigger_shouldSetTriggerData0WhenNull()
+ throws JSONException {
+ long baseTime = System.currentTimeMillis();
+ Source source =
+ createSourceForTest(
+ baseTime, Source.SourceType.EVENT, false, APP_DESTINATION, null);
+ Trigger trigger =
+ createTriggerForTest(baseTime + TimeUnit.SECONDS.toMillis(10), APP_DESTINATION);
+
+ List<EventTrigger> eventTriggers = trigger.parseEventTriggers();
+ EventTrigger eventTrigger = spy(eventTriggers.get(0));
+ when(eventTrigger.getTriggerData()).thenReturn(null);
+ EventReport report =
+ new EventReport.Builder()
+ .populateFromSourceAndTrigger(source, trigger, eventTrigger)
+ .build();
+
+ assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
+ assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(0L), report.getTriggerData());
+ assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
+ assertEquals(source.getEventId(), report.getSourceEventId());
+ assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
+ assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
+ assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
+ assertEquals(source.getSourceType(), report.getSourceType());
+ assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
+ }
+
+ @Test
+ public void testPopulateFromSourceAndTrigger_shouldTruncateTriggerDataWith64thBit()
+ throws JSONException {
+ long baseTime = System.currentTimeMillis();
+ Source source =
+ createSourceForTest(
+ baseTime, Source.SourceType.NAVIGATION, false, APP_DESTINATION, null);
+ Trigger trigger =
+ createTriggerForTest(baseTime + TimeUnit.SECONDS.toMillis(10), APP_DESTINATION);
+
+ List<EventTrigger> eventTriggers = trigger.parseEventTriggers();
+ EventTrigger eventTrigger = spy(eventTriggers.get(0));
+ when(eventTrigger.getTriggerData()).thenReturn(new UnsignedLong(-50003L));
+ EventReport report =
+ new EventReport.Builder()
+ .populateFromSourceAndTrigger(source, trigger, eventTrigger)
+ .build();
+
+ assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
+ assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
+ assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
+ // uint64 18446744073709501613 = long -50003
+ // Truncated data 18446744073709501613 % 8 = 5
+ assertEquals(new UnsignedLong(5L), report.getTriggerData());
+ assertEquals(source.getEventId(), report.getSourceEventId());
+ assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
+ assertEquals(APP_DESTINATION, report.getAttributionDestination());
+ assertEquals(
+ source.getEventTime()
+ + NAVIGATION_EARLY_REPORTING_WINDOW_MILLISECONDS[0]
+ + ONE_HOUR_IN_MILLIS,
+ report.getReportTime());
+ assertEquals(Source.SourceType.NAVIGATION, report.getSourceType());
+ assertEquals(
+ NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -189,14 +281,16 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
// Truncated data 4 % 2 = 0
- assertEquals(0, report.getTriggerData());
+ assertEquals(new UnsignedLong(0L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
assertEquals(Source.SourceType.EVENT, report.getSourceType());
assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -217,7 +311,7 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
@@ -226,6 +320,8 @@ public final class EventReportTest {
INSTALL_ATTR_EVENT_NOISE_PROBABILITY,
report.getRandomizedTriggerRate(),
DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -246,12 +342,14 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
assertEquals(Source.SourceType.EVENT, report.getSourceType());
assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -273,7 +371,7 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(APP_DESTINATION, report.getAttributionDestination());
assertEquals(
@@ -284,6 +382,8 @@ public final class EventReportTest {
assertEquals(Source.SourceType.NAVIGATION, report.getSourceType());
assertEquals(
NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -305,7 +405,7 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(
@@ -314,6 +414,8 @@ public final class EventReportTest {
assertEquals(source.getSourceType(), report.getSourceType());
assertEquals(
NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -334,9 +436,9 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
- assertEquals(4, report.getTriggerData());
+ assertEquals(new UnsignedLong(4L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
// One hour after install attributed navigation type window
@@ -350,6 +452,8 @@ public final class EventReportTest {
INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY,
report.getRandomizedTriggerRate(),
DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -370,9 +474,9 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
- assertEquals(4, report.getTriggerData());
+ assertEquals(new UnsignedLong(4L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
// One hour after regular navigation type window (without install attribution consideration)
@@ -384,6 +488,8 @@ public final class EventReportTest {
assertEquals(source.getSourceType(), report.getSourceType());
assertEquals(
NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -403,15 +509,16 @@ public final class EventReportTest {
final EventReport eventReport2 =
new EventReport.Builder()
.setId("1")
- .setSourceId(22)
+ .setSourceEventId(new UnsignedLong(22L))
.setEnrollmentId("another-enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.build();
final Set<EventReport> eventReportSet1 = Set.of(eventReport1);
@@ -428,7 +535,8 @@ public final class EventReportTest {
Uri appDestination,
Uri webDestination) {
return SourceFixture.getValidSourceBuilder()
- .setEventId(10)
+ .setId(SOURCE_ID)
+ .setEventId(new UnsignedLong(10L))
.setSourceType(sourceType)
.setInstallCooldownWindow(isInstallAttributable ? 100 : 0)
.setEventTime(eventTime)
@@ -441,6 +549,7 @@ public final class EventReportTest {
private Trigger createTriggerForTest(long eventTime, Uri destination) {
return TriggerFixture.getValidTriggerBuilder()
+ .setId(TRIGGER_ID)
.setTriggerTime(eventTime)
.setEventTriggers(EVENT_TRIGGERS)
.setEnrollmentId("enrollment-id")
@@ -451,52 +560,61 @@ public final class EventReportTest {
private EventReport createExample() {
return new EventReport.Builder()
.setId("1")
- .setSourceId(21)
+ .setSourceEventId(new UnsignedLong(21L))
.setEnrollmentId("enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
private EventReport createExampleSingleTriggerDebugKey() {
return new EventReport.Builder()
.setId("1")
- .setSourceId(21)
+ .setSourceEventId(new UnsignedLong(21L))
.setEnrollmentId("enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
private EventReport createExampleSingleSourceDebugKey() {
return new EventReport.Builder()
.setId("1")
- .setSourceId(21)
+ .setSourceEventId(new UnsignedLong(21L))
.setEnrollmentId("enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java
index d31d653bd7..10b4b4bf8b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java
@@ -18,7 +18,7 @@ package com.android.adservices.service.measurement;
import static org.junit.Assert.*;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
import org.json.JSONObject;
@@ -78,29 +78,29 @@ public class EventTriggerTest {
EventTrigger eventTrigger1 =
new EventTrigger.Builder()
.setTriggerPriority(1L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
EventTrigger eventTrigger2 =
new EventTrigger.Builder()
.setTriggerPriority(1L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
@@ -113,35 +113,35 @@ public class EventTriggerTest {
new EventTrigger.Builder().setTriggerPriority(1L).build(),
new EventTrigger.Builder().setTriggerPriority(2L).build());
assertNotEquals(
- new EventTrigger.Builder().setTriggerData(1L).build(),
- new EventTrigger.Builder().setTriggerData(2L).build());
+ new EventTrigger.Builder().setTriggerData(new UnsignedLong(1L)).build(),
+ new EventTrigger.Builder().setTriggerData(new UnsignedLong(2L)).build());
assertNotEquals(
- new EventTrigger.Builder().setDedupKey(1L).build(),
- new EventTrigger.Builder().setDedupKey(2L).build());
+ new EventTrigger.Builder().setDedupKey(new UnsignedLong(1L)).build(),
+ new EventTrigger.Builder().setDedupKey(new UnsignedLong(2L)).build());
assertNotEquals(
new EventTrigger.Builder()
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.build(),
new EventTrigger.Builder()
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData2)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData2)
.build())
.build());
assertNotEquals(
new EventTrigger.Builder()
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build(),
new EventTrigger.Builder()
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData2)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData2)
.build())
.build());
}
@@ -163,15 +163,15 @@ public class EventTriggerTest {
EventTrigger eventTrigger2 =
new EventTrigger.Builder()
.setTriggerPriority(2L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
Set<EventTrigger> eventTriggerSet1 = Set.of(eventTrigger1);
@@ -184,15 +184,15 @@ public class EventTriggerTest {
private EventTrigger createExample() throws JSONException {
return new EventTrigger.Builder()
.setTriggerPriority(1L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateFilterDataTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/FilterDataTest.java
index 644ef1a07a..034b42e7df 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateFilterDataTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/FilterDataTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.adservices.service.measurement.aggregation;
+package com.android.adservices.service.measurement;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -30,13 +30,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-/** Unit tests for {@link AggregateFilterData} */
+/** Unit tests for {@link FilterData} */
@SmallTest
-public final class AggregateFilterDataTest {
+public final class FilterDataTest {
@Test
public void testCreation() throws Exception {
- AggregateFilterData attributionFilterData = createExample();
+ FilterData attributionFilterData = createExample();
assertEquals(attributionFilterData.getAttributionFilterMap().size(), 2);
assertEquals(attributionFilterData.getAttributionFilterMap().get("type").size(), 4);
@@ -45,16 +45,16 @@ public final class AggregateFilterDataTest {
@Test
public void testDefaults() throws Exception {
- AggregateFilterData data = new AggregateFilterData.Builder().build();
+ FilterData data = new FilterData.Builder().build();
assertEquals(data.getAttributionFilterMap().size(), 0);
}
@Test
public void testHashCode_equals() throws Exception {
- final AggregateFilterData data1 = createExample();
- final AggregateFilterData data2 = createExample();
- final Set<AggregateFilterData> dataSet1 = Set.of(data1);
- final Set<AggregateFilterData> dataSet2 = Set.of(data2);
+ final FilterData data1 = createExample();
+ final FilterData data2 = createExample();
+ final Set<FilterData> dataSet1 = Set.of(data1);
+ final Set<FilterData> dataSet2 = Set.of(data2);
assertEquals(data1.hashCode(), data2.hashCode());
assertEquals(data1, data2);
assertEquals(dataSet1, dataSet2);
@@ -62,29 +62,29 @@ public final class AggregateFilterDataTest {
@Test
public void testHashCode_notEquals() throws Exception {
- final AggregateFilterData data1 = createExample();
+ final FilterData data1 = createExample();
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("type", Arrays.asList("2", "3", "4"));
attributionFilterMap.put("ctid", Collections.singletonList("id"));
- final AggregateFilterData data2 =
- new AggregateFilterData.Builder()
+ final FilterData data2 =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
- final Set<AggregateFilterData> dataSet1 = Set.of(data1);
- final Set<AggregateFilterData> dataSet2 = Set.of(data2);
+ final Set<FilterData> dataSet1 = Set.of(data1);
+ final Set<FilterData> dataSet2 = Set.of(data2);
assertNotEquals(data1.hashCode(), data2.hashCode());
assertNotEquals(data1, data2);
assertNotEquals(dataSet1, dataSet2);
}
- private AggregateFilterData createExample() {
+ private FilterData createExample() {
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("type", Arrays.asList("1", "2", "3", "4"));
attributionFilterMap.put("ctid", Collections.singletonList("id"));
- return new AggregateFilterData.Builder()
+ return new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java
index b52294d8c2..8ccb89a215 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java
@@ -28,6 +28,7 @@ import com.android.adservices.MockWebServerRuleFactory;
import com.android.modules.utils.testing.TestableDeviceConfig;
import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
import org.json.JSONObject;
import org.junit.Assert;
@@ -70,6 +71,33 @@ public final class MeasurementHttpClientTest {
}
@Test
+ public void testSetup_headersLeakingInfoAreOverridden() throws Exception {
+ final MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
+ MockWebServer server = null;
+ try {
+ server =
+ mMockWebServerRule.startMockWebServer(
+ request -> {
+ Assert.assertNotNull(request);
+ final String userAgentHeader = request.getHeader("user-agent");
+ Assert.assertNotNull(userAgentHeader);
+ Assert.assertEquals("", userAgentHeader);
+ return new MockResponse().setResponseCode(200);
+ });
+
+ final URL url = server.getUrl("/test");
+ final HttpURLConnection urlConnection =
+ (HttpURLConnection) mNetworkConnection.setup(url);
+
+ Assert.assertEquals(200, urlConnection.getResponseCode());
+ } finally {
+ if (server != null) {
+ server.shutdown();
+ }
+ }
+ }
+
+ @Test
public void testOpenAndSetupConnectionOverrideTimeoutValues_success() throws Exception {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_ADSERVICES,
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java
index 7cf2175b20..b81c5f7955 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java
@@ -18,73 +18,46 @@ package com.android.adservices.service.measurement;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
-import static android.adservices.common.AdServicesStatusUtils.STATUS_IO_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
import static android.view.MotionEvent.ACTION_BUTTON_PRESS;
-import static com.android.adservices.data.measurement.DatastoreManager.ThrowingCheckedConsumer;
import static com.android.adservices.service.measurement.attribution.TriggerContentProvider.TRIGGER_URI;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.adservices.measurement.DeletionParam;
import android.adservices.measurement.DeletionRequest;
-import android.adservices.measurement.MeasurementManager;
-import android.adservices.measurement.RegistrationRequest;
import android.adservices.measurement.WebSourceParams;
import android.adservices.measurement.WebSourceRegistrationRequest;
import android.adservices.measurement.WebSourceRegistrationRequestInternal;
import android.adservices.measurement.WebTriggerParams;
import android.adservices.measurement.WebTriggerRegistrationRequest;
import android.adservices.measurement.WebTriggerRegistrationRequestInternal;
-import android.content.AttributionSource;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.RemoteException;
-import android.test.mock.MockContext;
-import android.test.mock.MockPackageManager;
import android.view.InputEvent;
import android.view.MotionEvent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.data.measurement.DatastoreException;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
-import com.android.adservices.data.measurement.IMeasurementDao;
-import com.android.adservices.data.measurement.ITransaction;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
-import com.android.adservices.service.consent.AdServicesApiConsent;
-import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.inputverification.ClickVerifier;
-import com.android.adservices.service.measurement.registration.SourceFetcher;
-import com.android.adservices.service.measurement.registration.SourceRegistration;
-import com.android.adservices.service.measurement.registration.TriggerFetcher;
-import com.android.adservices.service.measurement.registration.TriggerRegistration;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.modules.utils.testing.TestableDeviceConfig;
@@ -92,26 +65,16 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.Spy;
import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
import java.time.Instant;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
/** Unit tests for {@link MeasurementImpl} */
@SmallTest
@@ -119,160 +82,48 @@ public final class MeasurementImplTest {
@Rule
public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final Context DEFAULT_CONTEXT = ApplicationProvider.getApplicationContext();
- private static final Uri URI_WITHOUT_APP_SCHEME = Uri.parse("com.example.abc");
private static final Uri DEFAULT_URI = Uri.parse("android-app://com.example.abc");
private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com/bar?ad=134");
private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo.com/bar?ad=256");
private static final String DEFAULT_ENROLLMENT = "enrollment-id";
+ private static final Uri INVALID_WEB_DESTINATION = Uri.parse("https://example.not_a_tld");
private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
- private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
- Uri.parse("https://subdomain.web-destination.com");
private static final Uri OTHER_WEB_DESTINATION = Uri.parse("https://other-web-destination.com");
- private static final Uri INVALID_WEB_DESTINATION = Uri.parse("https://example.not_a_tld");
private static final Uri APP_DESTINATION = Uri.parse("android-app://com.app_destination");
private static final Uri OTHER_APP_DESTINATION =
Uri.parse("android-app://com.other_app_destination");
- private static final String ANDROID_APP_SCHEME = "android-app://";
- private static final String VENDING_PREFIX = "https://play.google.com/store/apps/details?id=";
- private static final RegistrationRequest SOURCE_REGISTRATION_REQUEST =
- createRegistrationRequest(RegistrationRequest.REGISTER_SOURCE);
- private static final RegistrationRequest TRIGGER_REGISTRATION_REQUEST =
- createRegistrationRequest(RegistrationRequest.REGISTER_TRIGGER);
- private static final String TOP_LEVEL_FILTERS_JSON_STRING =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}\n";
- private static final long TRIGGER_PRIORITY = 345678L;
- private static final Long TRIGGER_DEDUP_KEY = 2345678L;
- private static final Long TRIGGER_DATA = 1L;
- private static final String EVENT_TRIGGERS =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \""
- + TRIGGER_DATA
- + "\",\n"
- + " \"priority\": \""
- + TRIGGER_PRIORITY
- + "\",\n"
- + " \"deduplication_key\": \""
- + TRIGGER_DEDUP_KEY
- + "\",\n"
- + " \"filters\": {\n"
- + " \"source_type\": [\"navigation\"],\n"
- + " \"key_1\": [\"value_1\"] \n"
- + " }\n"
- + "}"
- + "]\n";
- private static final TriggerRegistration VALID_TRIGGER_REGISTRATION =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse("https://foo.com"))
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setEventTriggers(EVENT_TRIGGERS)
- .setAggregateTriggerData(
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]")
- .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
- .setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
- .build();
- private static final SourceRegistration VALID_SOURCE_REGISTRATION_1 =
- new com.android.adservices.service.measurement.registration.SourceRegistration.Builder()
- .setSourceEventId(1L)
- .setSourcePriority(100L)
- .setAppDestination(Uri.parse("android-app://com.destination"))
- .setWebDestination(Uri.parse("https://web-destination.com"))
- .setExpiry(8640000010L)
- .setInstallAttributionWindow(841839879274L)
- .setInstallCooldownWindow(8418398274L)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setTopOrigin(Uri.parse("android-app://com.source"))
- .build();
- private static final SourceRegistration VALID_SOURCE_REGISTRATION_2 =
- new com.android.adservices.service.measurement.registration.SourceRegistration.Builder()
- .setSourceEventId(2)
- .setSourcePriority(200L)
- .setAppDestination(Uri.parse("android-app://com.destination2"))
- .setWebDestination(Uri.parse("https://web-destination2.com"))
- .setExpiry(865000010L)
- .setInstallAttributionWindow(841839879275L)
- .setInstallCooldownWindow(7418398274L)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setTopOrigin(Uri.parse("android-app://com.source2"))
- .build();
+
private static final WebSourceParams INPUT_SOURCE_REGISTRATION_1 =
new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
-
private static final WebSourceParams INPUT_SOURCE_REGISTRATION_2 =
new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
-
private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_1 =
new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
-
private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_2 =
new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
-
private static final long REQUEST_TIME = 10000L;
@Spy
private DatastoreManager mDatastoreManager =
- DatastoreManagerFactory.getDatastoreManager(DEFAULT_CONTEXT);
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
@Mock
private ContentProviderClient mMockContentProviderClient;
@Mock
private ContentResolver mContentResolver;
- @Mock
- private SourceFetcher mSourceFetcher;
- @Mock
- private TriggerFetcher mTriggerFetcher;
- @Mock
- private IMeasurementDao mMeasurementDao;
- @Mock
- private ConsentManager mConsentManager;
@Mock private ClickVerifier mClickVerifier;
private MeasurementImpl mMeasurementImpl;
@Mock
- ITransaction mTransaction;
- @Mock
EnrollmentDao mEnrollmentDao;
+ @Mock MeasurementDataDeleter mMeasurementDataDeleter;
private static EnrollmentData getEnrollment(String enrollmentId) {
return new EnrollmentData.Builder().setEnrollmentId(enrollmentId).build();
}
-
- class FakeDatastoreManager extends DatastoreManager {
-
- @Override
- public ITransaction createNewTransaction() {
- return mTransaction;
- }
-
- @Override
- public IMeasurementDao getMeasurementDao() {
- return mMeasurementDao;
- }
- }
-
- private interface AppVendorPackages {
- String PLAY_STORE = "com.android.vending";
- }
-
public static InputEvent getInputEvent() {
return MotionEvent.obtain(0, 0, ACTION_BUTTON_PRESS, 0, 0, 0);
}
-
- private static RegistrationRequest createRegistrationRequest(int type) {
- return new RegistrationRequest.Builder()
- .setRegistrationUri(REGISTRATION_URI_1)
- .setTopOriginUri(DEFAULT_URI)
- .setPackageName(DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setRegistrationType(type)
- .build();
- }
-
private static WebTriggerRegistrationRequestInternal createWebTriggerRegistrationRequest(
Uri destination) {
WebTriggerRegistrationRequest webTriggerRegistrationRequest =
@@ -284,29 +135,32 @@ public final class MeasurementImplTest {
return new WebTriggerRegistrationRequestInternal.Builder(
webTriggerRegistrationRequest,
DEFAULT_CONTEXT.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(true)
.build();
}
-
private static WebSourceRegistrationRequestInternal createWebSourceRegistrationRequest(
Uri appDestination, Uri webDestination, Uri verifiedDestination) {
-
+ return createWebSourceRegistrationRequest(
+ appDestination, webDestination, verifiedDestination, DEFAULT_URI);
+ }
+ private static WebSourceRegistrationRequestInternal createWebSourceRegistrationRequest(
+ Uri appDestination, Uri webDestination, Uri verifiedDestination, Uri topOriginUri) {
WebSourceRegistrationRequest sourceRegistrationRequest =
new WebSourceRegistrationRequest.Builder(
Arrays.asList(
INPUT_SOURCE_REGISTRATION_1, INPUT_SOURCE_REGISTRATION_2),
- DEFAULT_URI)
+ topOriginUri)
.setAppDestination(appDestination)
.setWebDestination(webDestination)
.setVerifiedDestination(verifiedDestination)
.build();
-
return new WebSourceRegistrationRequestInternal.Builder(
sourceRegistrationRequest,
DEFAULT_CONTEXT.getAttributionSource().getPackageName(),
REQUEST_TIME)
+ .setAdIdPermissionGranted(true)
.build();
}
-
@Before
public void before() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -317,264 +171,25 @@ public final class MeasurementImplTest {
spy(
new MeasurementImpl(
DEFAULT_CONTEXT,
- mContentResolver,
mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
+ mClickVerifier,
+ mMeasurementDataDeleter,
+ mEnrollmentDao));
doReturn(true).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any()))
.thenReturn(getEnrollment(DEFAULT_ENROLLMENT));
}
@Test
- public void testRegister_registrationTypeSource_sourceFetchSuccess()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- RegistrationRequest registrationRequest = SOURCE_REGISTRATION_REQUEST;
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurementImpl =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurementImpl).generateFakeEventReports(any());
- final int result = measurementImpl.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(datastoreManager, times(2))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(4)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(2, insertionLogicExecutor.size());
-
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_1,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void testRegister_registrationTypeSource_sourceFetchFailure() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(mMeasurementImpl).generateFakeEventReports(any());
- final int result =
- mMeasurementImpl.register(SOURCE_REGISTRATION_REQUEST, System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty()
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
- }
-
- @Test
- public void testRegister_registrationTypeSource_exceedsPrivacyParam_destination()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- RegistrationRequest registrationRequest = SOURCE_REGISTRATION_REQUEST;
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(datastoreManager, never())
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(2))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, never()).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
- }
-
- @Test
- public void testRegister_registrationTypeSource_exceedsPrivacyParam_adTech()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- RegistrationRequest registrationRequest = SOURCE_REGISTRATION_REQUEST;
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(datastoreManager, times(1))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(3)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(1, insertionLogicExecutor.size());
-
- // First registration was removed for exceeding the privacy bound.
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void testRegister_registrationTypeTrigger_triggerFetchSuccess() throws Exception {
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- final long triggerTime = System.currentTimeMillis();
- Answer<Optional<List<TriggerRegistration>>> populateTriggerRegistrations =
- invocation -> {
- List<TriggerRegistration> triggerRegs = new ArrayList<>();
- triggerRegs.add(VALID_TRIGGER_REGISTRATION);
- return Optional.of(triggerRegs);
- };
- doAnswer(populateTriggerRegistrations)
- .when(mTriggerFetcher)
- .fetchTrigger(TRIGGER_REGISTRATION_REQUEST);
-
- // Execution
- final int result = mMeasurementImpl.register(TRIGGER_REGISTRATION_REQUEST, triggerTime);
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
-
- // Assertions
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient).insert(any(), any());
- verify(mSourceFetcher, never()).fetchSource(any());
- verify(mTriggerFetcher, times(1)).fetchTrigger(any());
- Trigger trigger =
- createTrigger(
- triggerTime,
- DEFAULT_CONTEXT.getAttributionSource(),
- DEFAULT_URI,
- EventSurfaceType.APP);
- verify(mMeasurementDao).insertTrigger(trigger);
- }
-
- @Test
- public void testRegister_registrationTypeTrigger_triggerFetchFailure() throws RemoteException {
- when(mTriggerFetcher.fetchTrigger(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.register(TRIGGER_REGISTRATION_REQUEST, System.currentTimeMillis());
- assertEquals(STATUS_IO_ERROR, result);
-
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, never()).fetchSource(any());
- verify(mTriggerFetcher, times(1)).fetchTrigger(any());
- }
-
- @Test
public void testDeleteRegistrations_successfulNoOptionalParameters() {
MeasurementImpl measurement =
new MeasurementImpl(
DEFAULT_CONTEXT,
- mContentResolver,
- DatastoreManagerFactory.getDatastoreManager(DEFAULT_CONTEXT),
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest()),
+ mClickVerifier,
+ mMeasurementDataDeleter,
+ mEnrollmentDao);
+ doReturn(true).when(mMeasurementDataDeleter).delete(any());
final int result =
measurement.deleteRegistrations(
new DeletionParam.Builder()
@@ -589,7 +204,18 @@ public final class MeasurementImplTest {
}
@Test
+ public void testRegisterWebSource_verifiedDestination_webDestinationMismatch() {
+ final int result =
+ mMeasurementImpl.registerWebSource(
+ createWebSourceRegistrationRequest(
+ APP_DESTINATION, WEB_DESTINATION, OTHER_WEB_DESTINATION),
+ System.currentTimeMillis());
+ assertEquals(STATUS_INVALID_ARGUMENT, result);
+ }
+
+ @Test
public void testDeleteRegistrations_successfulWithRange() {
+ doReturn(true).when(mMeasurementDataDeleter).delete(any());
final int result =
mMeasurementImpl.deleteRegistrations(
new DeletionParam.Builder()
@@ -607,24 +233,25 @@ public final class MeasurementImplTest {
@Test
public void testDeleteRegistrations_successfulWithOrigin() {
- final int result =
- mMeasurementImpl.deleteRegistrations(
- new DeletionParam.Builder()
- .setPackageName(
- DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setDomainUris(Collections.emptyList())
- .setOriginUris(Collections.singletonList(DEFAULT_URI))
- .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
- .setDeletionMode(DeletionRequest.DELETION_MODE_ALL)
- .setStart(Instant.ofEpochMilli(Long.MIN_VALUE))
- .setEnd(Instant.ofEpochMilli(Long.MAX_VALUE))
- .build());
+ DeletionParam deletionParam =
+ new DeletionParam.Builder()
+ .setPackageName(DEFAULT_CONTEXT.getAttributionSource().getPackageName())
+ .setDomainUris(Collections.emptyList())
+ .setOriginUris(Collections.singletonList(DEFAULT_URI))
+ .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
+ .setDeletionMode(DeletionRequest.DELETION_MODE_ALL)
+ .setStart(Instant.ofEpochMilli(Long.MIN_VALUE))
+ .setEnd(Instant.ofEpochMilli(Long.MAX_VALUE))
+ .build();
+ when(mMeasurementDataDeleter.delete(deletionParam)).thenReturn(true);
+ final int result = mMeasurementImpl.deleteRegistrations(deletionParam);
assertEquals(STATUS_SUCCESS, result);
}
@Test
public void testDeleteRegistrations_internalError() {
doReturn(false).when(mDatastoreManager).runInTransaction(any());
+ doReturn(false).when(mMeasurementDataDeleter).delete(any());
final int result =
mMeasurementImpl.deleteRegistrations(
new DeletionParam.Builder()
@@ -638,607 +265,34 @@ public final class MeasurementImplTest {
.setEnd(Instant.MAX)
.build());
assertEquals(STATUS_INTERNAL_ERROR, result);
- }
-
- @Test
- public void testSourceRegistration_callsImpressionNoiseCreator() throws DatastoreException {
- long eventTime = System.currentTimeMillis();
- long expiry = TimeUnit.DAYS.toSeconds(20);
- // Creating source for easy comparison
- Source sampleSource =
- SourceFixture.getValidSourceBuilder()
- .setSourceType(Source.SourceType.NAVIGATION)
- .setExpiryTime(eventTime + TimeUnit.SECONDS.toMillis(expiry))
- .setEventTime(eventTime)
- .setPublisher(DEFAULT_URI)
- .setAppDestination(Uri.parse("android-app://com.example.abc"))
- .setEventId(123L)
- .build();
- // Mocking fetchSource call to populate source registrations.
- List<SourceRegistration> sourceRegistrations =
- Collections.singletonList(
- new SourceRegistration.Builder()
- .setSourceEventId(sampleSource.getEventId())
- .setAppDestination(sampleSource.getAppDestination())
- .setTopOrigin(sampleSource.getPublisher())
- .setExpiry(expiry)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .build());
- doReturn(Optional.of(sourceRegistrations)).when(mSourceFetcher).fetchSource(any());
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
-
- MeasurementImpl measurementImpl =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- new FakeDatastoreManager(),
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- InputEvent inputEvent = getInputEvent();
- final int result =
- measurementImpl.register(
- new RegistrationRequest.Builder()
- .setRegistrationUri(REGISTRATION_URI_1)
- .setTopOriginUri(DEFAULT_URI)
- .setPackageName(
- DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setInputEvent(inputEvent)
- .build(),
- eventTime);
- assertEquals(STATUS_SUCCESS, result);
- ArgumentCaptor<Source> sourceArgs = ArgumentCaptor.forClass(Source.class);
- verify(measurementImpl).generateFakeEventReports(sourceArgs.capture());
- Source capturedSource = sourceArgs.getValue();
- assertEquals(sampleSource.getSourceType(), capturedSource.getSourceType());
- assertEquals(sampleSource.getEventId(), capturedSource.getEventId());
- assertEquals(sampleSource.getEventTime(), capturedSource.getEventTime());
- assertEquals(sampleSource.getAggregateSource(), capturedSource.getAggregateSource());
- assertEquals(sampleSource.getAppDestination(), capturedSource.getAppDestination());
- assertEquals(sampleSource.getPublisher(), capturedSource.getPublisher());
- assertEquals(sampleSource.getPriority(), capturedSource.getPriority());
-
- // Check Attribution Mode assignment
- assertNotEquals(Source.AttributionMode.UNASSIGNED, capturedSource.getAttributionMode());
- }
-
- @Test
- public void testGetSourceEventReports() {
- long eventTime = System.currentTimeMillis();
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setEventId(123L)
- .setEventTime(eventTime)
- .setExpiryTime(eventTime + TimeUnit.DAYS.toMillis(20))
- .setSourceType(Source.SourceType.NAVIGATION)
- .setAppDestination(DEFAULT_URI)
- .setPublisher(DEFAULT_URI)
- .build());
- when(source.getRandomAttributionProbability()).thenReturn(1.1D);
- DatastoreManager mockDatastoreManager = Mockito.mock(DatastoreManager.class);
- SourceFetcher mockSourceFetcher = Mockito.mock(SourceFetcher.class);
- TriggerFetcher mockTriggerFetcher = Mockito.mock(TriggerFetcher.class);
-
- List<EventReport> fakeEventReports = mMeasurementImpl.generateFakeEventReports(source);
-
- // Generate valid report times
- Set<Long> reportingTimes = new HashSet<>();
- reportingTimes.add(
- source.getReportingTime(
- eventTime + TimeUnit.DAYS.toMillis(1), EventSurfaceType.APP));
- reportingTimes.add(
- source.getReportingTime(
- eventTime + TimeUnit.DAYS.toMillis(3), EventSurfaceType.APP));
- reportingTimes.add(
- source.getReportingTime(
- eventTime + TimeUnit.DAYS.toMillis(8), EventSurfaceType.APP));
-
- for (EventReport report : fakeEventReports) {
- Assert.assertEquals(source.getEventId(), report.getSourceId());
- Assert.assertTrue(reportingTimes.stream().anyMatch(x -> x == report.getReportTime()));
- Assert.assertEquals(source.getEventTime(), report.getTriggerTime());
- Assert.assertEquals(0, report.getTriggerPriority());
- Assert.assertEquals(source.getAppDestination(), report.getAttributionDestination());
- Assert.assertTrue(report.getTriggerData()
- < source.getTriggerDataCardinality());
- Assert.assertNull(report.getTriggerDedupKey());
- Assert.assertEquals(EventReport.Status.PENDING, report.getStatus());
- Assert.assertEquals(source.getSourceType(), report.getSourceType());
- Assert.assertEquals(source.getRandomAttributionProbability(),
- report.getRandomizedTriggerRate(), /* delta= */ 0.00001D);
- }
- }
-
- @Test
- public void testInstallAttribution() throws DatastoreException {
- // Setup
- long systemTime = System.currentTimeMillis();
-
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- // Execution
- mMeasurementImpl.doInstallAttribution(URI_WITHOUT_APP_SCHEME, systemTime);
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
-
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
-
- // Assertion
- verify(mMeasurementDao).doInstallAttribution(DEFAULT_URI, systemTime);
- }
-
- @Test
- public void testGetMeasurementApiStatus_enabled() {
- MockitoSession session =
- ExtendedMockito.mockitoSession()
- .spyStatic(ConsentManager.class)
- .initMocks(this)
- .startMocking();
- try {
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
- ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
- final int result = mMeasurementImpl.getMeasurementApiStatus();
- assertEquals(MeasurementManager.MEASUREMENT_API_STATE_ENABLED, result);
- } finally {
- session.finishMocking();
}
- }
-
- @Test
- public void testGetMeasurementApiStatus_disabled() {
- MockitoSession session =
- ExtendedMockito.mockitoSession()
- .spyStatic(ConsentManager.class)
- .initMocks(this)
- .startMocking();
- try {
- ExtendedMockito.doReturn(AdServicesApiConsent.REVOKED)
- .when(mConsentManager)
- .getConsent(any());
- ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
- final int result = mMeasurementImpl.getMeasurementApiStatus();
- assertEquals(MeasurementManager.MEASUREMENT_API_STATE_DISABLED, result);
- } finally {
- session.finishMocking();
- }
- }
-
- @Test
- public void testDeleteAllMeasurementData() throws DatastoreException {
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- // Execution
- mMeasurementImpl.deleteAllMeasurementData(Collections.emptyList());
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
-
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
- verify(mMeasurementDao, times(1)).deleteAllMeasurementData(any());
- }
-
- @Test
- public void registerWebSource_sourceFetchSuccess() throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- WebSourceRegistrationRequestInternal registrationRequest =
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null);
- doReturn(Optional.of(sourceRegistrationsOut))
- .when(mSourceFetcher)
- .fetchWebSources(any(), anyBoolean());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurementImpl =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurementImpl).generateFakeEventReports(any());
- final int result = measurementImpl.registerWebSource(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(datastoreManager, times(2))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(4)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(2, insertionLogicExecutor.size());
-
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_1,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void registerWebSource_sourceFetchFailure() {
- when(mSourceFetcher.fetchWebSources(any(), anyBoolean())).thenReturn(Optional.empty());
-
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(mMeasurementImpl).generateFakeEventReports(any());
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null),
- System.currentTimeMillis());
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
- }
@Test
- public void registerWebSource_invalidWebDestination() {
+ public void testRegisterWebSource_invalidWebDestination() {
final int result =
mMeasurementImpl.registerWebSource(
createWebSourceRegistrationRequest(null, INVALID_WEB_DESTINATION, null),
System.currentTimeMillis());
assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, never()).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_exceedsPrivacyParam_destination()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- WebSourceRegistrationRequestInternal registrationRequest =
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null);
- doReturn(Optional.of(sourceRegistrationsOut))
- .when(mSourceFetcher)
- .fetchWebSources(any(), anyBoolean());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.registerWebSource(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(datastoreManager, never())
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(2))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, never()).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_exceedsPrivacyParam_adTech()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- WebSourceRegistrationRequestInternal registrationRequest =
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null);
- doReturn(Optional.of(sourceRegistrationsOut))
- .when(mSourceFetcher)
- .fetchWebSources(any(), anyBoolean());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.registerWebSource(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(datastoreManager, times(1))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(3)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(1, insertionLogicExecutor.size());
-
- // First registration was removed for exceeding the privacy bound.
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void registerWebSource_webDestinationIsValidWhenNull() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(APP_DESTINATION, null, null),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_exactWebDestinationMatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, WEB_DESTINATION),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_topPrivateDomainMatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, WEB_DESTINATION_WITH_SUBDOMAIN),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
}
@Test
- public void registerWebSource_verifiedDestination_webDestinationMismatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
+ public void testRegisterWebTrigger_invalidDestination() throws RemoteException {
final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, OTHER_WEB_DESTINATION),
+ mMeasurementImpl.registerWebTrigger(
+ createWebTriggerRegistrationRequest(INVALID_WEB_DESTINATION),
System.currentTimeMillis());
assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, times(0)).fetchWebSources(any(), anyBoolean());
}
-
@Test
- public void registerWebSource_verifiedDestination_appDestinationMatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, APP_DESTINATION),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_appDestinationMismatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
+ public void testRegisterWebSource_verifiedDestination_appDestinationMismatch() {
final int result =
mMeasurementImpl.registerWebSource(
createWebSourceRegistrationRequest(
APP_DESTINATION, WEB_DESTINATION, OTHER_APP_DESTINATION),
System.currentTimeMillis());
assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, times(0)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_vendingMatch() {
- when(mSourceFetcher.fetchWebSources(any(), anyBoolean())).thenReturn(Optional.empty());
- Uri vendingUri = Uri.parse(VENDING_PREFIX + APP_DESTINATION.getHost());
- MeasurementImpl measurementImpl = getMeasurementImplWithMockedIntentResolution();
- final int result = measurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, vendingUri),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_vendingMismatch() {
- when(mSourceFetcher.fetchWebSources(any(), anyBoolean())).thenReturn(Optional.empty());
- Uri vendingUri = Uri.parse(VENDING_PREFIX + OTHER_APP_DESTINATION.getHost());
- MeasurementImpl measurementImpl = getMeasurementImplWithMockedIntentResolution();
- final int result = measurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, vendingUri),
- System.currentTimeMillis());
- assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, times(0)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebTrigger_triggerFetchSuccess() throws Exception {
- // Setup
- when(mTriggerFetcher.fetchWebTriggers(any(), anyBoolean()))
- .thenReturn(Optional.of(Collections.singletonList(VALID_TRIGGER_REGISTRATION)));
-
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- final long triggerTime = System.currentTimeMillis();
-
- // Execution
- final int result =
- mMeasurementImpl.registerWebTrigger(
- createWebTriggerRegistrationRequest(WEB_DESTINATION), triggerTime);
-
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
-
- // Assertions
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient).insert(any(), any());
- verify(mSourceFetcher, never()).fetchSource(any());
- verify(mTriggerFetcher, times(1)).fetchWebTriggers(any(), anyBoolean());
- verify(mMeasurementDao)
- .insertTrigger(
- eq(
- createTrigger(
- triggerTime,
- DEFAULT_CONTEXT.getAttributionSource(),
- WEB_DESTINATION,
- EventSurfaceType.WEB)));
- }
-
- @Test
- public void registerWebTrigger_triggerFetchFailure() throws RemoteException {
- when(mTriggerFetcher.fetchWebTriggers(any(), anyBoolean())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebTrigger(
- createWebTriggerRegistrationRequest(WEB_DESTINATION),
- System.currentTimeMillis());
-
- assertEquals(STATUS_IO_ERROR, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, never()).fetchWebSources(any(), anyBoolean());
- verify(mTriggerFetcher, times(1)).fetchWebTriggers(any(), anyBoolean());
- }
-
- @Test
-
- public void registerWebTrigger_invalidDestination() throws RemoteException {
- final int result =
- mMeasurementImpl.registerWebTrigger(
- createWebTriggerRegistrationRequest(INVALID_WEB_DESTINATION),
- System.currentTimeMillis());
- assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
}
-
- @Test
- public void testRegister_registrationTypeSource_clickNotVerifiedFailure() {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
-
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(mMeasurementImpl).generateFakeEventReports(any());
-
- doReturn(false).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
-
- RegistrationRequest registrationRequest =
- new RegistrationRequest.Builder()
- .setRegistrationUri(REGISTRATION_URI_1)
- .setTopOriginUri(DEFAULT_URI)
- .setPackageName(DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setInputEvent(getInputEvent())
- .build();
-
- long eventTime = System.currentTimeMillis();
- final int result = mMeasurementImpl.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mSourceFetcher, times(1)).fetchSource(any());
- }
-
@Test
public void testGetSourceType_verifiedInputEvent_returnsNavigationSourceType() {
doReturn(true).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
@@ -1246,12 +300,10 @@ public final class MeasurementImplTest {
Source.SourceType.NAVIGATION,
mMeasurementImpl.getSourceType(getInputEvent(), 1000L));
}
-
@Test
public void testGetSourceType_noInputEventGiven() {
assertEquals(Source.SourceType.EVENT, mMeasurementImpl.getSourceType(null, 1000L));
}
-
@Test
public void testGetSourceType_inputEventNotVerifiable_returnsEventSourceType() {
doReturn(false).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
@@ -1276,11 +328,10 @@ public final class MeasurementImplTest {
MeasurementImpl measurementImpl =
new MeasurementImpl(
DEFAULT_CONTEXT,
- mContentResolver,
mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mockClickVerifier);
+ mockClickVerifier,
+ mMeasurementDataDeleter,
+ mEnrollmentDao);
// Because click verification is disabled, the SourceType is NAVIGATION even if the
// input event is not verifiable.
@@ -1293,381 +344,4 @@ public final class MeasurementImplTest {
session.finishMocking();
}
}
-
- @Test
- public void insertSource_withFakeReportsFalseAppAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- int fakeReportsCount = 2;
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
- .setWebDestination(null)
- .build());
- List<Source.FakeReport> fakeReports =
- createFakeReports(
- source,
- fakeReportsCount,
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> falseAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.FALSELY);
- return fakeReports;
- };
- doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, times(2)).insertEventReport(any());
- verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getAppDestination().toString())
- .setDestinationSite(source.getAppDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getValue());
- }
-
- @Test
- public void insertSource_withFakeReportsFalseWebAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- int fakeReportsCount = 2;
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(null)
- .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
- .build());
- List<Source.FakeReport> fakeReports =
- createFakeReports(
- source, fakeReportsCount, SourceFixture.ValidSourceParams.WEB_DESTINATION);
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> falseAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.FALSELY);
- return fakeReports;
- };
- doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, times(2)).insertEventReport(any());
- verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getWebDestination().toString())
- .setDestinationSite(source.getWebDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getValue());
- }
-
- @Test
- public void insertSource_withFalseAppAndWebAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- int fakeReportsCount = 2;
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
- .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
- .build());
- List<Source.FakeReport> fakeReports =
- createFakeReports(
- source,
- fakeReportsCount,
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> falseAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.FALSELY);
- return fakeReports;
- };
- doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, times(2)).insertEventReport(any());
- verify(mMeasurementDao, times(2))
- .insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getAppDestination().toString())
- .setDestinationSite(source.getAppDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getAllValues().get(0));
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getWebDestination().toString())
- .setDestinationSite(source.getWebDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getAllValues().get(1));
- }
-
- @Test
- public void insertSource_withFakeReportsNeverAppAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
- .setWebDestination(null)
- .build());
- List<Source.FakeReport> fakeReports = Collections.emptyList();
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> neverAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.NEVER);
- return fakeReports;
- };
- doAnswer(neverAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, never()).insertEventReport(any());
- verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getAppDestination().toString())
- .setDestinationSite(source.getAppDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getValue());
- }
-
- private List<Source.FakeReport> createFakeReports(Source source, int count, Uri destination) {
- return IntStream.range(0, count)
- .mapToObj(
- x ->
- new Source.FakeReport(
- 0, source.getReportingTimeForNoising(0), destination))
- .collect(Collectors.toList());
- }
-
- private void verifyInsertSource(
- RegistrationRequest registrationRequest,
- SourceRegistration sourceRegistration,
- long eventTime,
- Uri firstSourceDestination,
- Uri firstSourceWebDestination)
- throws DatastoreException {
- Source source =
- createSource(
- sourceRegistration,
- eventTime,
- firstSourceDestination,
- firstSourceWebDestination,
- registrationRequest.getTopOriginUri(),
- EventSurfaceType.APP,
- registrationRequest.getPackageName());
- verify(mMeasurementDao).insertSource(source);
- }
-
- private void verifyInsertSource(
- WebSourceRegistrationRequestInternal registrationRequest,
- SourceRegistration sourceRegistration,
- long eventTime,
- Uri firstSourceDestination,
- Uri firstSourceWebDestination)
- throws DatastoreException {
- Source source =
- createSource(
- sourceRegistration,
- eventTime,
- firstSourceDestination,
- firstSourceWebDestination,
- registrationRequest.getSourceRegistrationRequest().getTopOriginUri(),
- EventSurfaceType.WEB,
- registrationRequest.getPackageName());
- verify(mMeasurementDao).insertSource(source);
- }
-
- private Source createSource(
- SourceRegistration sourceRegistration,
- long eventTime,
- Uri firstSourceDestination,
- Uri firstSourceWebDestination,
- Uri topOrigin,
- @EventSurfaceType int publisherType,
- String packageName) {
- return SourceFixture.getValidSourceBuilder()
- .setEventId(sourceRegistration.getSourceEventId())
- .setPublisher(topOrigin)
- .setPublisherType(publisherType)
- .setAppDestination(firstSourceDestination)
- .setWebDestination(firstSourceWebDestination)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setRegistrant(Uri.parse("android-app://" + packageName))
- .setEventTime(eventTime)
- .setExpiryTime(
- eventTime + TimeUnit.SECONDS.toMillis(sourceRegistration.getExpiry()))
- .setPriority(sourceRegistration.getSourcePriority())
- .setSourceType(Source.SourceType.EVENT)
- .setInstallAttributionWindow(
- TimeUnit.SECONDS.toMillis(sourceRegistration.getInstallAttributionWindow()))
- .setInstallCooldownWindow(
- TimeUnit.SECONDS.toMillis(sourceRegistration.getInstallCooldownWindow()))
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateSource(sourceRegistration.getAggregateSource())
- .setAggregateFilterData(sourceRegistration.getAggregateFilterData())
- .setDebugKey(sourceRegistration.getDebugKey())
- .build();
- }
-
- private Trigger createTrigger(long triggerTime, AttributionSource attributionSource,
- Uri destination, @EventSurfaceType int destinationType) {
- return TriggerFixture.getValidTriggerBuilder()
- .setAttributionDestination(destination)
- .setDestinationType(destinationType)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setRegistrant(Uri.parse(ANDROID_APP_SCHEME + attributionSource.getPackageName()))
- .setTriggerTime(triggerTime)
- .setEventTriggers(EVENT_TRIGGERS)
- .setAggregateTriggerData(
- MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getAggregateTriggerData())
- .setAggregateValues(
- MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getAggregateValues())
- .setFilters(MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getFilters())
- .setDebugKey(MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getDebugKey())
- .build();
- }
-
- private MeasurementImpl getMeasurementImplWithMockedIntentResolution() {
- ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.packageName = AppVendorPackages.PLAY_STORE;
- ActivityInfo activityInfo = new ActivityInfo();
- activityInfo.applicationInfo = applicationInfo;
- activityInfo.name = "non null String";
- ResolveInfo resolveInfo = new ResolveInfo();
- resolveInfo.activityInfo = activityInfo;
- MockPackageManager mockPackageManager =
- new MockPackageManager() {
- @Override
- public ResolveInfo resolveActivity(Intent intent, int flags) {
- return resolveInfo;
- }
- };
- MockContext mockContext =
- new MockContext() {
- @Override
- public PackageManager getPackageManager() {
- return mockPackageManager;
- }
- };
- return new MeasurementImpl(
- mockContext,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java
index 4811f2a5b7..4cd9613a30 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java
@@ -156,8 +156,7 @@ public final class MeasurementServiceImplTest {
when(mMockMeasurementImpl.registerWebSource(
any(WebSourceRegistrationRequestInternal.class), anyLong()))
.thenReturn(STATUS_SUCCESS);
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockFlags.getWebContextClientAppAllowList()).thenReturn("*");
when(mMockThrottler.tryAcquire(any(), any())).thenReturn(true).thenReturn(false);
when(mMockFlags.getWebContextClientAppAllowList()).thenReturn(ALLOW_ALL_PACKAGES);
@@ -1206,12 +1205,10 @@ public final class MeasurementServiceImplTest {
try {
ExtendedMockito.doReturn(1).when(Binder::getCallingUidOrThrow);
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
+ ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
MeasurementImpl measurementImpl =
- new MeasurementImpl(mMockContext, null, null, null, null, null);
+ new MeasurementImpl(mMockContext, null, null, null, null);
when(mMockFlags.getPpapiAppAllowList()).thenReturn("*");
CountDownLatch countDownLatch = new CountDownLatch(1);
final AtomicInteger resultWrapper = new AtomicInteger();
@@ -1259,9 +1256,7 @@ public final class MeasurementServiceImplTest {
try {
ExtendedMockito.doReturn(1).when(Binder::getCallingUidOrThrow);
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
+ ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doNothing()
.when(mMockAppImportanceFilter)
@@ -1269,7 +1264,7 @@ public final class MeasurementServiceImplTest {
ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
MeasurementImpl measurementImpl =
- new MeasurementImpl(mMockContext, null, null, null, null, null);
+ new MeasurementImpl(mMockContext, null, null, null, null);
when(mMockFlags.getPpapiAppAllowList()).thenReturn("*");
CountDownLatch countDownLatch = new CountDownLatch(1);
final AtomicInteger resultWrapper = new AtomicInteger();
@@ -1317,9 +1312,7 @@ public final class MeasurementServiceImplTest {
try {
ExtendedMockito.doReturn(1).when(Binder::getCallingUidOrThrow);
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
+ ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doThrow(new AppImportanceFilter.WrongCallingApplicationStateException())
.when(mMockAppImportanceFilter)
@@ -2137,8 +2130,7 @@ public final class MeasurementServiceImplTest {
public void testRegister_userRevokedConsent() {
try {
mockAccessControl(true, true, true);
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.REVOKED);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.REVOKED);
mMeasurementServiceImpl.register(
getDefaultRegistrationSourceRequest(),
mCallerMetadata,
@@ -2163,9 +2155,7 @@ public final class MeasurementServiceImplTest {
public void testRegisterWebSource_userRevokedConsent() {
try {
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.REVOKED)
- .when(mConsentManager)
- .getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent();
mMeasurementServiceImpl.registerWebSource(
createWebSourceRegistrationRequest(),
@@ -2191,7 +2181,7 @@ public final class MeasurementServiceImplTest {
public void testRegisterWebSource_packageNotAllowListed() throws InterruptedException {
try {
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doReturn(ALLOW_LIST_WITHOUT_TEST_PACKAGE)
.when(mMockFlags)
.getWebContextClientAppAllowList();
@@ -2226,9 +2216,7 @@ public final class MeasurementServiceImplTest {
public void testRegisterWebTrigger_userRevokedConsent() {
try {
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.REVOKED)
- .when(mConsentManager)
- .getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent();
mMeasurementServiceImpl.registerWebTrigger(
createWebTriggerRegistrationRequest(),
@@ -2256,7 +2244,7 @@ public final class MeasurementServiceImplTest {
try {
// Setup
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doReturn(ALLOW_LIST_WITHOUT_TEST_PACKAGE)
.when(mMockFlags)
.getWebContextClientAppAllowList();
@@ -2543,7 +2531,6 @@ public final class MeasurementServiceImplTest {
return new RegistrationRequest.Builder()
.setPackageName(PACKAGE_NAME)
.setRegistrationUri(Uri.parse("https://registration-uri.com"))
- .setTopOriginUri(Uri.parse("android-app://com.example"))
.setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
.build();
}
@@ -2552,7 +2539,6 @@ public final class MeasurementServiceImplTest {
return new RegistrationRequest.Builder()
.setPackageName(PACKAGE_NAME)
.setRegistrationUri(Uri.parse("https://registration-uri.com"))
- .setTopOriginUri(Uri.parse("android-app://com.example"))
.setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
.build();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
index 6bc9730a08..60f80cd8e8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
@@ -29,9 +29,9 @@ import android.net.Uri;
import androidx.annotation.Nullable;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.noising.ImpressionNoiseParams;
import com.android.adservices.service.measurement.noising.ImpressionNoiseUtil;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -51,8 +51,8 @@ import java.util.stream.LongStream;
public class SourceTest {
private static final double ZERO_DELTA = 0D;
- private static final Long DEBUG_KEY_1 = 81786463L;
- private static final Long DEBUG_KEY_2 = 23487834L;
+ private static final UnsignedLong DEBUG_KEY_1 = new UnsignedLong(81786463L);
+ private static final UnsignedLong DEBUG_KEY_2 = new UnsignedLong(23487834L);
@Test
public void testDefaults() {
@@ -77,10 +77,10 @@ public class SourceTest {
aggregateSource.put(jsonObject1);
aggregateSource.put(jsonObject2);
- JSONObject aggregateFilterData = new JSONObject();
- aggregateFilterData.put(
+ JSONObject filterData = new JSONObject();
+ filterData.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
- aggregateFilterData.put("product", Arrays.asList("1234", "2345"));
+ filterData.put("product", Arrays.asList("1234", "2345"));
assertEquals(
new Source.Builder()
.setEnrollmentId("enrollment-id")
@@ -89,15 +89,18 @@ public class SourceTest {
.setPublisher(Uri.parse("https://example.com/aS"))
.setPublisherType(EventSurfaceType.WEB)
.setId("1")
- .setEventId(2L)
+ .setEventId(new UnsignedLong(2L))
.setPriority(3L)
.setEventTime(5L)
.setExpiryTime(5L)
- .setDedupKeys(LongStream.range(0, 2).boxed().collect(Collectors.toList()))
+ .setDedupKeys(LongStream.range(0, 2)
+ .boxed()
+ .map(UnsignedLong::new)
+ .collect(Collectors.toList()))
.setStatus(Source.Status.ACTIVE)
.setSourceType(Source.SourceType.EVENT)
.setRegistrant(Uri.parse("android-app://com.example.abc"))
- .setAggregateFilterData(aggregateFilterData.toString())
+ .setFilterData(filterData.toString())
.setAggregateSource(aggregateSource.toString())
.setAggregateContributions(50001)
.setDebugKey(DEBUG_KEY_1)
@@ -111,15 +114,18 @@ public class SourceTest {
.setPublisher(Uri.parse("https://example.com/aS"))
.setPublisherType(EventSurfaceType.WEB)
.setId("1")
- .setEventId(2L)
+ .setEventId(new UnsignedLong(2L))
.setPriority(3L)
.setEventTime(5L)
.setExpiryTime(5L)
- .setDedupKeys(LongStream.range(0, 2).boxed().collect(Collectors.toList()))
+ .setDedupKeys(LongStream.range(0, 2)
+ .boxed()
+ .map(UnsignedLong::new)
+ .collect(Collectors.toList()))
.setStatus(Source.Status.ACTIVE)
.setSourceType(Source.SourceType.EVENT)
.setRegistrant(Uri.parse("android-app://com.example.abc"))
- .setAggregateFilterData(aggregateFilterData.toString())
+ .setFilterData(filterData.toString())
.setAggregateSource(aggregateSource.toString())
.setAggregateContributions(50001)
.setDebugKey(DEBUG_KEY_1)
@@ -134,8 +140,8 @@ public class SourceTest {
SourceFixture.getValidSourceBuilder().setId("1").build(),
SourceFixture.getValidSourceBuilder().setId("2").build());
assertNotEquals(
- SourceFixture.getValidSourceBuilder().setEventId(1).build(),
- SourceFixture.getValidSourceBuilder().setEventId(2).build());
+ SourceFixture.getValidSourceBuilder().setEventId(new UnsignedLong(1L)).build(),
+ SourceFixture.getValidSourceBuilder().setEventId(new UnsignedLong(2L)).build());
assertNotEquals(
SourceFixture.getValidSourceBuilder()
.setAppDestination(Uri.parse("android-app://1.com"))
@@ -188,11 +194,11 @@ public class SourceTest {
.setStatus(Source.Status.IGNORED).build());
assertNotEquals(
SourceFixture.getValidSourceBuilder()
- .setDedupKeys(LongStream.range(0, 2).boxed()
+ .setDedupKeys(LongStream.range(0, 2).boxed().map(UnsignedLong::new)
.collect(Collectors.toList()))
.build(),
SourceFixture.getValidSourceBuilder()
- .setDedupKeys(LongStream.range(1, 3).boxed()
+ .setDedupKeys(LongStream.range(1, 3).boxed().map(UnsignedLong::new)
.collect(Collectors.toList()))
.build());
assertNotEquals(
@@ -255,7 +261,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -273,7 +279,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -294,7 +300,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -312,7 +318,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -330,7 +336,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -351,7 +357,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -372,7 +378,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -390,7 +396,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -411,7 +417,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -591,8 +597,8 @@ public class SourceTest {
final AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(
- new AggregateFilterData.Builder()
+ .setFilterData(
+ new FilterData.Builder()
.setAttributionFilterMap(filterMap)
.build())
.build();
@@ -604,14 +610,14 @@ public class SourceTest {
assertNotNull(source.getAggregatableAttributionSource());
assertNotNull(source.getAggregatableAttributionSource().getAggregatableSource());
- assertNotNull(source.getAggregatableAttributionSource().getAggregateFilterData());
+ assertNotNull(source.getAggregatableAttributionSource().getFilterData());
assertEquals(
aggregatableSource,
source.getAggregatableAttributionSource().getAggregatableSource());
assertEquals(
filterMap,
source.getAggregatableAttributionSource()
- .getAggregateFilterData()
+ .getFilterData()
.getAttributionFilterMap());
}
@@ -627,7 +633,7 @@ public class SourceTest {
.setSourceType(Source.SourceType.NAVIGATION)
.build();
assertEquals(
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY,
+ PrivacyParams.getNavigationTriggerDataCardinality(),
navigationSource.getTriggerDataCardinality());
}
@@ -792,7 +798,7 @@ public class SourceTest {
.setWebDestination(null)
.setExpiryTime(expiry)
.build()),
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY);
+ PrivacyParams.getNavigationTriggerDataCardinality());
// Single (Web) destination, EVENT type
verifyAlgorithmicFakeReportGeneration(
@@ -814,7 +820,7 @@ public class SourceTest {
.setAppDestination(null)
.setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
.build()),
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY);
+ PrivacyParams.getNavigationTriggerDataCardinality());
// Both destinations set, EVENT type
verifyAlgorithmicFakeReportGeneration(
@@ -838,7 +844,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
.setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
.build()),
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY);
+ PrivacyParams.getNavigationTriggerDataCardinality());
// App destination with cooldown window
verifyAlgorithmicFakeReportGeneration(
@@ -1258,16 +1264,52 @@ public class SourceTest {
}
@Test
+ public void testParseFilterData_nonEmpty() throws JSONException {
+ JSONObject filterDataJson = new JSONObject();
+ filterDataJson.put("conversion", new JSONArray(Collections.singletonList("electronics")));
+ filterDataJson.put("product", new JSONArray(Arrays.asList("1234", "2345")));
+ Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.NAVIGATION)
+ .setFilterData(filterDataJson.toString())
+ .build();
+ FilterData filterData = source.parseFilterData();
+ assertEquals(filterData.getAttributionFilterMap().size(), 3);
+ assertEquals(Collections.singletonList("electronics"),
+ filterData.getAttributionFilterMap().get("conversion"));
+ assertEquals(Arrays.asList("1234", "2345"),
+ filterData.getAttributionFilterMap().get("product"));
+ assertEquals(Collections.singletonList("navigation"),
+ filterData.getAttributionFilterMap().get("source_type"));
+ }
+
+ @Test
+ public void testParseFilterData_nullFilterData() throws JSONException {
+ Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.EVENT)
+ .build();
+ FilterData filterData = source.parseFilterData();
+ assertEquals(filterData.getAttributionFilterMap().size(), 1);
+ assertEquals(Collections.singletonList("event"),
+ filterData.getAttributionFilterMap().get("source_type"));
+ }
+
+ @Test
+ public void testParseFilterData_emptyFilterData() throws JSONException {
+ Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.EVENT)
+ .setFilterData("")
+ .build();
+ FilterData filterData = source.parseFilterData();
+ assertEquals(filterData.getAttributionFilterMap().size(), 1);
+ assertEquals(Collections.singletonList("event"),
+ filterData.getAttributionFilterMap().get("source_type"));
+ }
+
+ @Test
public void testParseAggregateSource() throws JSONException {
- JSONArray aggregatableSource = new JSONArray();
- JSONObject jsonObject1 = new JSONObject();
- jsonObject1.put("id", "campaignCounts");
- jsonObject1.put("key_piece", "0x159");
- JSONObject jsonObject2 = new JSONObject();
- jsonObject2.put("id", "geoValue");
- jsonObject2.put("key_piece", "0x5");
- aggregatableSource.put(jsonObject1);
- aggregatableSource.put(jsonObject2);
+ JSONObject aggregatableSource = new JSONObject();
+ aggregatableSource.put("campaignCounts", "0x159");
+ aggregatableSource.put("geoValue", "0x5");
JSONObject filterData = new JSONObject();
filterData.put("conversion_subdomain",
@@ -1275,8 +1317,9 @@ public class SourceTest {
filterData.put("product", new JSONArray(Arrays.asList("1234", "2345")));
Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.NAVIGATION)
.setAggregateSource(aggregatableSource.toString())
- .setAggregateFilterData(filterData.toString()).build();
+ .setFilterData(filterData.toString()).build();
Optional<AggregatableAttributionSource> aggregatableAttributionSource =
source.parseAggregateSource();
assertTrue(aggregatableAttributionSource.isPresent());
@@ -1285,7 +1328,7 @@ public class SourceTest {
assertEquals(
aggregateSource.getAggregatableSource().get("campaignCounts").longValue(), 345L);
assertEquals(aggregateSource.getAggregatableSource().get("geoValue").longValue(), 5L);
- assertEquals(aggregateSource.getAggregateFilterData().getAttributionFilterMap().size(), 2);
+ assertEquals(aggregateSource.getFilterData().getAttributionFilterMap().size(), 3);
}
private void verifyAlgorithmicFakeReportGeneration(Source source, int expectedCardinality) {
@@ -1304,7 +1347,8 @@ public class SourceTest {
assertTrue(
source.getExpiryTime() + TimeUnit.HOURS.toMillis(1)
>= report.getReportingTime());
- assertTrue(report.getTriggerData() < expectedCardinality);
+ Long triggerData = report.getTriggerData().getValue();
+ assertTrue(0 <= triggerData && triggerData < expectedCardinality);
}
} else if (source.getAttributionMode() == Source.AttributionMode.NEVER) {
neverCount++;
@@ -1331,7 +1375,7 @@ public class SourceTest {
.map(
reportState ->
new Source.FakeReport(
- reportState[0],
+ new UnsignedLong(Long.valueOf(reportState[0])),
source.getReportingTimeForNoising(reportState[1]),
reportState[2] == 0
? source.getAppDestination()
@@ -1340,7 +1384,7 @@ public class SourceTest {
}
private void assertInvalidSourceArguments(
- Long sourceEventId,
+ UnsignedLong sourceEventId,
Uri publisher,
Uri appDestination,
Uri webDestination,
@@ -1352,10 +1396,10 @@ public class SourceTest {
Source.SourceType sourceType,
Long installAttributionWindow,
Long installCooldownWindow,
- @Nullable Long debugKey,
+ @Nullable UnsignedLong debugKey,
@Source.AttributionMode int attributionMode,
@Nullable String aggregateSource,
- @Nullable String aggregateFilterData) {
+ @Nullable String filterData) {
assertThrows(
IllegalArgumentException.class,
() ->
@@ -1374,7 +1418,7 @@ public class SourceTest {
.setInstallCooldownWindow(installCooldownWindow)
.setAttributionMode(attributionMode)
.setAggregateSource(aggregateSource)
- .setAggregateFilterData(aggregateFilterData)
+ .setFilterData(filterData)
.setDebugKey(debugKey)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java
index 07974cfb43..288989b7a3 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java
@@ -24,12 +24,15 @@ import static org.mockito.Mockito.spy;
import android.annotation.IntDef;
import android.test.mock.MockContentResolver;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
import com.android.adservices.service.Flags;
import com.android.adservices.service.measurement.attribution.AttributionJobHandlerWrapper;
import com.android.adservices.service.measurement.inputverification.ClickVerifier;
-import com.android.adservices.service.measurement.registration.SourceFetcher;
-import com.android.adservices.service.measurement.registration.TriggerFetcher;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.mockito.stubbing.Answer;
@@ -57,35 +60,49 @@ class TestObjectProvider {
}
static MeasurementImpl getMeasurementImpl(
- @Type int type,
DatastoreManager datastoreManager,
- SourceFetcher sourceFetcher,
- TriggerFetcher triggerFetcher,
ClickVerifier clickVerifier,
- Flags flags) {
+ Flags flags,
+ MeasurementDataDeleter measurementDataDeleter,
+ EnrollmentDao enrollmentDao) {
+ return spy(
+ new MeasurementImpl(
+ null,
+ datastoreManager,
+ clickVerifier,
+ measurementDataDeleter,
+ enrollmentDao));
+ }
+
+ static AsyncRegistrationQueueRunner getAsyncRegistrationQueueRunner(
+ @Type int type,
+ DatastoreManager datastoreManager,
+ AsyncSourceFetcher asyncSourceFetcher,
+ AsyncTriggerFetcher asyncTriggerFetcher,
+ EnrollmentDao enrollmentDao) {
if (type == Type.DENOISED) {
- MeasurementImpl measurementImpl =
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
spy(
- new MeasurementImpl(
- null,
+ new AsyncRegistrationQueueRunner(
new MockContentResolver(),
- datastoreManager,
- sourceFetcher,
- triggerFetcher,
- clickVerifier));
+ asyncSourceFetcher,
+ asyncTriggerFetcher,
+ enrollmentDao,
+ datastoreManager));
// Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurementImpl).generateFakeEventReports(any());
- return measurementImpl;
+ doReturn(Collections.emptyList())
+ .when(asyncRegistrationQueueRunner)
+ .generateFakeEventReports(any());
+ return asyncRegistrationQueueRunner;
} else if (type == Type.NOISY) {
- MeasurementImpl measurementImpl =
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
spy(
- new MeasurementImpl(
- null,
+ new AsyncRegistrationQueueRunner(
new MockContentResolver(),
- datastoreManager,
- sourceFetcher,
- triggerFetcher,
- clickVerifier));
+ asyncSourceFetcher,
+ asyncTriggerFetcher,
+ enrollmentDao,
+ datastoreManager));
// Create impression noise with 100% probability
Answer<?> answerSourceEventReports =
invocation -> {
@@ -93,9 +110,9 @@ class TestObjectProvider {
source.setAttributionMode(Source.AttributionMode.FALSELY);
return Collections.singletonList(
new EventReport.Builder()
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setReportTime(source.getExpiryTime() + ONE_HOUR_IN_MILLIS)
- .setTriggerData(0)
+ .setTriggerData(new UnsignedLong(0L))
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
.setTriggerTime(0)
@@ -106,17 +123,16 @@ class TestObjectProvider {
.build());
};
doAnswer(answerSourceEventReports)
- .when(measurementImpl)
+ .when(asyncRegistrationQueueRunner)
.generateFakeEventReports(any());
- return measurementImpl;
+ return asyncRegistrationQueueRunner;
}
- return new MeasurementImpl(
- null,
+ return new AsyncRegistrationQueueRunner(
new MockContentResolver(),
- datastoreManager,
- sourceFetcher,
- triggerFetcher,
- clickVerifier);
+ asyncSourceFetcher,
+ asyncTriggerFetcher,
+ enrollmentDao,
+ datastoreManager);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java
index c563d9a421..d3250af480 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java
@@ -19,14 +19,15 @@ package com.android.adservices.service.measurement;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import android.net.Uri;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionTrigger;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.aggregation.AggregateTriggerData;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -65,7 +66,17 @@ public class TriggerTest {
+ "}"
+ "]\n";
- private static final Long DEBUG_KEY = 2367372L;
+ private static final UnsignedLong DEBUG_KEY = new UnsignedLong(2367372L);
+ private static final Uri APP_DESTINATION = Uri.parse("android-app://com.android.app");
+ private static final Uri APP_DESTINATION_WITH_PATH =
+ Uri.parse("android-app://com.android.app/with/path");
+ private static final Uri WEB_DESTINATION = Uri.parse("https://example.com");
+ private static final Uri WEB_DESTINATION_WITH_PATH = Uri.parse("https://example.com/with/path");
+ private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
+ Uri.parse("https://subdomain.example.com");
+ private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN_PATH_QUERY_FRAGMENT =
+ Uri.parse("https://subdomain.example.com/with/path?query=0#fragment");
+ private static final Uri WEB_DESTINATION_INVALID = Uri.parse("https://example.notatld");
@Test
public void testEqualsPass() throws JSONException {
@@ -96,6 +107,7 @@ public class TriggerTest {
.setAggregateTriggerData(aggregateTriggerDatas.toString())
.setAggregateValues(values.toString())
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING)
.setDebugKey(DEBUG_KEY)
.setAggregatableAttributionTrigger(
TriggerFixture.getValidTrigger()
@@ -113,6 +125,7 @@ public class TriggerTest {
.setAggregateTriggerData(aggregateTriggerDatas.toString())
.setAggregateValues(values.toString())
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING)
.setDebugKey(DEBUG_KEY)
.setAggregatableAttributionTrigger(
TriggerFixture.getValidTrigger()
@@ -187,6 +200,11 @@ public class TriggerTest {
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING).build(),
TriggerFixture.getValidTriggerBuilder()
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING_X).build());
+ assertNotEquals(
+ TriggerFixture.getValidTriggerBuilder()
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING).build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING_X).build());
}
@Test
@@ -222,6 +240,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
assertInvalidTriggerArguments(
Uri.parse("com.destination"),
@@ -232,6 +251,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
}
@@ -246,6 +266,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
}
@@ -260,6 +281,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
assertInvalidTriggerArguments(
TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION,
@@ -270,6 +292,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
}
@@ -343,6 +366,73 @@ public class TriggerTest {
}
@Test
+ public void testGetAttributionDestinationBaseUri_appDestination() throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(APP_DESTINATION)
+ .setDestinationType(EventSurfaceType.APP)
+ .build();
+ assertEquals(APP_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsAppDestination() throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(APP_DESTINATION_WITH_PATH)
+ .setDestinationType(EventSurfaceType.APP)
+ .build();
+ assertEquals(APP_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_webDestination() throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsWebDestinationWithSubdomain()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_WITH_SUBDOMAIN)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsWebDestinationWithPath()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_WITH_PATH)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsWebDestinationWithSubdomainPathQueryFrag()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_WITH_SUBDOMAIN_PATH_QUERY_FRAGMENT)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_invalidWebDestination()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_INVALID)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertNull(trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
public void parseEventTriggers() throws JSONException {
// setup
JSONObject filters1 =
@@ -392,25 +482,25 @@ public class TriggerTest {
EventTrigger eventTrigger1 =
new EventTrigger.Builder()
.setTriggerPriority(2L)
- .setTriggerData(2L)
- .setDedupKey(2L)
+ .setTriggerData(new UnsignedLong(2L))
+ .setDedupKey(new UnsignedLong(2L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(filters1)
+ new FilterData.Builder()
+ .buildFilterData(filters1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(notFilters1)
+ new FilterData.Builder()
+ .buildFilterData(notFilters1)
.build())
.build();
EventTrigger eventTrigger2 =
new EventTrigger.Builder()
.setTriggerPriority(3L)
- .setTriggerData(3L)
- .setDedupKey(3L)
+ .setTriggerData(new UnsignedLong(3L))
+ .setDedupKey(new UnsignedLong(3L))
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(notFilters2)
+ new FilterData.Builder()
+ .buildFilterData(notFilters2)
.build())
.build();
@@ -430,7 +520,8 @@ public class TriggerTest {
String aggregateTriggerData,
String aggregateValues,
String filters,
- Long debugKey) {
+ String notFilters,
+ UnsignedLong debugKey) {
assertThrows(
IllegalArgumentException.class,
() ->
@@ -443,6 +534,7 @@ public class TriggerTest {
.setAggregateTriggerData(aggregateTriggerData)
.setAggregateValues(aggregateValues)
.setFilters(filters)
+ .setNotFilters(notFilters)
.setDebugKey(debugKey)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java
index 553cb029bb..02a7724e3b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java
@@ -68,13 +68,14 @@ public class ManifestBasedAdtechAccessResolverTest {
mMockEnrollmentDao = mock(EnrollmentDao.class);
doReturn(EnrollmentFixture.getValidEnrollment())
.when(mMockEnrollmentDao)
- .getEnrollmentDataFromMeasurementUrl(eq(ENROLLED_AD_TECH_URL.toString()));
+ .getEnrollmentDataFromMeasurementUrl(eq(ENROLLED_AD_TECH_URL));
mMockitoSession =
ExtendedMockito.mockitoSession()
.mockStatic(AppManifestConfigHelper.class)
.strictness(Strictness.LENIENT)
.initMocks(this)
.startMocking();
+ when(mFlags.isEnrollmentBlocklisted(any())).thenReturn(false);
}
@After
@@ -193,6 +194,21 @@ public class ManifestBasedAdtechAccessResolverTest {
}
@Test
+ public void isNotAllowed_enrollmentInBlocklist() {
+ mClassUnderTest =
+ new ManifestBasedAdtechAccessResolver(
+ mMockEnrollmentDao, mFlags, PACKAGE, ENROLLED_AD_TECH_URL);
+ when(mFlags.isDisableMeasurementEnrollmentCheck()).thenReturn(false);
+ when(AppManifestConfigHelper.isAllowedAttributionAccess(any(), any(), any()))
+ .thenReturn(true);
+
+ String enrollmentId = EnrollmentFixture.getValidEnrollment().getEnrollmentId();
+ when(mFlags.isEnrollmentBlocklisted(enrollmentId)).thenReturn(true);
+
+ assertFalse(mClassUnderTest.isAllowed(CONTEXT));
+ }
+
+ @Test
public void getErrorMessage() {
mClassUnderTest =
new ManifestBasedAdtechAccessResolver(
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java
index c0a7cb654b..34af05bb30 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java
@@ -53,7 +53,7 @@ public class UserConsentAccessResolverTest {
@Test
public void isAllowed_consented_success() {
// Setup
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
// Execution
assertTrue(mClassUnderTest.isAllowed(mContext));
@@ -62,7 +62,7 @@ public class UserConsentAccessResolverTest {
@Test
public void isAllowed_notConsented_success() {
// Setup
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent();
// Execution
assertFalse(mClassUnderTest.isAllowed(mContext));
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java
new file mode 100644
index 0000000000..19667858d5
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java
@@ -0,0 +1,44 @@
+/*
+ * 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.measurement.actions;
+
+import java.util.Objects;
+
+public final class AggregateReportingJob implements Action {
+ public final long mTimestamp;
+
+ public AggregateReportingJob(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ public long getComparable() {
+ return mTimestamp;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AggregateReportingJob)) return false;
+ AggregateReportingJob that = (AggregateReportingJob) o;
+ return mTimestamp == that.mTimestamp;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTimestamp);
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/ReportingJob.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/EventReportingJob.java
index 23f1e6b1c0..12b1551ea8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/ReportingJob.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/EventReportingJob.java
@@ -18,13 +18,14 @@ package com.android.adservices.service.measurement.actions;
import java.util.Objects;
-public final class ReportingJob implements Action {
+public final class EventReportingJob implements Action {
public final long mTimestamp;
- public ReportingJob(long timestamp) {
+ public EventReportingJob(long timestamp) {
mTimestamp = timestamp;
}
+ @Override
public long getComparable() {
return mTimestamp;
}
@@ -32,8 +33,8 @@ public final class ReportingJob implements Action {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof ReportingJob)) return false;
- ReportingJob that = (ReportingJob) o;
+ if (!(o instanceof EventReportingJob)) return false;
+ EventReportingJob that = (EventReportingJob) o;
return mTimestamp == that.mTimestamp;
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java
index 180d46e2a8..7de2213228 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java
@@ -33,6 +33,7 @@ public final class InstallApp implements Action {
mTimestamp = obj.getLong(TestFormatJsonMapping.INSTALLS_TIMESTAMP_KEY);
}
+ @Override
public long getComparable() {
return mTimestamp;
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java
index 4c17b47f4a..c81a9571d3 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java
@@ -36,21 +36,22 @@ public final class RegisterSource implements Action {
public final RegistrationRequest mRegistrationRequest;
public final Map<String, List<Map<String, List<String>>>> mUriToResponseHeadersMap;
public final long mTimestamp;
+ // Used in interop tests
+ public final String mPublisher;
public RegisterSource(JSONObject obj) throws JSONException {
JSONObject regParamsJson = obj.getJSONObject(
TestFormatJsonMapping.REGISTRATION_REQUEST_KEY);
AttributionSource attributionSource = getAttributionSource(
- regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY));
+ regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY,
+ TestFormatJsonMapping.ATTRIBUTION_SOURCE_DEFAULT));
+
+ mPublisher = regParamsJson.optString(TestFormatJsonMapping.SOURCE_TOP_ORIGIN_URI_KEY);
mRegistrationRequest =
new RegistrationRequest.Builder()
.setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setTopOriginUri(
- Uri.parse(
- regParamsJson.getString(
- TestFormatJsonMapping.SOURCE_TOP_ORIGIN_URI_KEY)))
.setRegistrationUri(
Uri.parse(
regParamsJson.getString(
@@ -61,6 +62,9 @@ public final class RegisterSource implements Action {
.equals(TestFormatJsonMapping.SOURCE_VIEW_TYPE)
? null
: getInputEvent())
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.setPackageName(attributionSource.getPackageName())
.build();
mUriToResponseHeadersMap = getUriToResponseHeadersMap(obj);
@@ -71,4 +75,8 @@ public final class RegisterSource implements Action {
public long getComparable() {
return mTimestamp;
}
+
+ public String getPublisher() {
+ return mPublisher;
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java
index dd9f562deb..cdba2ac300 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java
@@ -35,25 +35,29 @@ public final class RegisterTrigger implements Action {
public final RegistrationRequest mRegistrationRequest;
public final Map<String, List<Map<String, List<String>>>> mUriToResponseHeadersMap;
public final long mTimestamp;
+ // Used in interop tests
+ public final String mDestination;
public RegisterTrigger(JSONObject obj) throws JSONException {
JSONObject regParamsJson = obj.getJSONObject(
TestFormatJsonMapping.REGISTRATION_REQUEST_KEY);
AttributionSource attributionSource = getAttributionSource(
- regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY));
+ regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY,
+ TestFormatJsonMapping.ATTRIBUTION_SOURCE_DEFAULT));
+
+ mDestination = regParamsJson.optString(TestFormatJsonMapping.TRIGGER_TOP_ORIGIN_URI_KEY);
mRegistrationRequest =
new RegistrationRequest.Builder()
.setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
- .setTopOriginUri(
- Uri.parse(
- regParamsJson.getString(
- TestFormatJsonMapping.TRIGGER_TOP_ORIGIN_URI_KEY)))
.setRegistrationUri(
Uri.parse(
regParamsJson.getString(
TestFormatJsonMapping.REGISTRATION_URI_KEY)))
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.setPackageName(attributionSource.getPackageName())
.build();
@@ -61,7 +65,12 @@ public final class RegisterTrigger implements Action {
mTimestamp = obj.getLong(TestFormatJsonMapping.TIMESTAMP_KEY);
}
+ @Override
public long getComparable() {
return mTimestamp;
}
+
+ public String getDestination() {
+ return mDestination;
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java
index f65778126d..ac6c7660a1 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java
@@ -80,6 +80,9 @@ public final class RegisterWebSource implements Action {
mRegistrationRequest =
new WebSourceRegistrationRequestInternal.Builder(
registrationRequest, attributionSource.getPackageName(), 2000L)
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.build();
mUriToResponseHeadersMap = getUriToResponseHeadersMap(obj);
@@ -102,8 +105,7 @@ public final class RegisterWebSource implements Action {
Uri.parse(
sourceParams.getString(
TestFormatJsonMapping.REGISTRATION_URI_KEY)))
- .setDebugKeyAllowed(
- sourceParams.optBoolean(TestFormatJsonMapping.DEBUG_KEY, false))
+ .setDebugKeyAllowed(true)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java
index 2b86d30552..a52397486c 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java
@@ -61,6 +61,9 @@ public final class RegisterWebTrigger implements Action {
mRegistrationRequest =
new WebTriggerRegistrationRequestInternal.Builder(
registrationRequest, attributionSource.getPackageName())
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.build();
mUriToResponseHeadersMap = getUriToResponseHeadersMap(obj);
@@ -83,9 +86,7 @@ public final class RegisterWebTrigger implements Action {
Uri.parse(
triggerParams.getString(
TestFormatJsonMapping.REGISTRATION_URI_KEY)))
- .setDebugKeyAllowed(
- triggerParams.optBoolean(
- TestFormatJsonMapping.DEBUG_KEY, false))
+ .setDebugKeyAllowed(true)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java
index e73ad82d7b..ed11d75f86 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java
@@ -33,6 +33,7 @@ public final class UninstallApp implements Action {
mTimestamp = obj.getLong(TestFormatJsonMapping.INSTALLS_TIMESTAMP_KEY);
}
+ @Override
public long getComparable() {
return mTimestamp;
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java
index 3599e1d054..e45de13395 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java
@@ -20,10 +20,11 @@ import static org.mockito.Mockito.when;
import android.net.Uri;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import org.json.JSONException;
import org.junit.Assert;
@@ -74,7 +75,8 @@ public class AggregateEncryptionKeyManagerIntegrationTest extends AbstractDbInte
@Override
public void runActionToTest() {
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
AggregateEncryptionKeyManager aggregateEncryptionKeyManager =
new AggregateEncryptionKeyManager(datastoreManager, mFetcher, mClock,
MEASUREMENT_AGGREGATE_ENCRYPTION_KEY_COORDINATOR_URL);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java
index baeab5ff74..f55d7d28eb 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.aggregation;
+import com.android.adservices.service.measurement.FilterData;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -48,12 +50,12 @@ public final class AggregatePayloadGeneratorTest {
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
@@ -67,9 +69,9 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(1024L))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build())
- .setNotFilter(new AggregateFilterData.Builder()
+ .setNotFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataNotFilter1).build()).build());
// Apply this key_piece to "geoValue".
triggerDataList.add(
@@ -107,18 +109,18 @@ public final class AggregatePayloadGeneratorTest {
Map<String, BigInteger> aggregatableSource = new HashMap<>();
aggregatableSource.put("campaignCounts", BigInteger.valueOf(345L));
aggregatableSource.put("geoValue", BigInteger.valueOf(5L));
- aggregatableSource.put("thirdSource", BigInteger.valueOf(100L));
+ aggregatableSource.put("thirdSource", BigInteger.valueOf(101L));
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put("conversion_subdomain",
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
// Apply this key_piece to "campaignCounts".
@@ -131,9 +133,9 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(1024L))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build())
- .setNotFilter(new AggregateFilterData.Builder()
+ .setNotFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataNotFilter1).build())
.build());
// Apply this key_piece to "geoValue".
@@ -158,13 +160,16 @@ public final class AggregatePayloadGeneratorTest {
assertTrue(aggregateHistogramContributions.isPresent());
List<AggregateHistogramContribution> contributions = aggregateHistogramContributions.get();
- assertEquals(contributions.size(), 2);
+ assertEquals(contributions.size(), 3);
assertTrue(contributions.contains(
new AggregateHistogramContribution.Builder()
.setKey(BigInteger.valueOf(1369L)).setValue(32768).build()));
assertTrue(contributions.contains(
new AggregateHistogramContribution.Builder()
.setKey(BigInteger.valueOf(2693L)).setValue(1664).build()));
+ assertTrue(contributions.contains(
+ new AggregateHistogramContribution.Builder()
+ .setKey(BigInteger.valueOf(101L)).setValue(100).build()));
}
@Test
@@ -178,12 +183,12 @@ public final class AggregatePayloadGeneratorTest {
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
// Apply this key_piece to "campaignCounts".
@@ -196,9 +201,9 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(1024L))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build())
- .setNotFilter(new AggregateFilterData.Builder()
+ .setNotFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataNotFilter1).build())
.build());
// Apply this key_piece to "geoValue".
@@ -220,7 +225,7 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(200L))
.setSourceKeys(new HashSet<>(Arrays.asList("campaignCounts", "geoValue")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter2).build())
.build());
@@ -256,12 +261,12 @@ public final class AggregatePayloadGeneratorTest {
sourceFilterMap.put("conversion_subdomain",
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
@@ -273,7 +278,7 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(2L).shiftLeft(63))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build()).build());
Map<String, Integer> values = new HashMap<>();
@@ -305,12 +310,12 @@ public final class AggregatePayloadGeneratorTest {
sourceFilterMap.put("conversion_subdomain",
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
@@ -322,7 +327,7 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(2L).shiftLeft(63).add(BigInteger.valueOf(4L)))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build()).build());
Map<String, Integer> values = new HashMap<>();
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java
index 5dc25302c2..4cf1de7fc8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java
@@ -25,16 +25,21 @@ import android.net.Uri;
import androidx.test.filters.SmallTest;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.junit.Test;
import java.util.Set;
+import java.util.UUID;
/** Unit tests for {@link AggregateReport} */
@SmallTest
public final class AggregateReportTest {
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
+ private static final String SOURCE_ID = UUID.randomUUID().toString();
+ private static final String TRIGGER_ID = UUID.randomUUID().toString();
private AggregateReport createAttributionReport() {
return new AggregateReport.Builder()
@@ -47,9 +52,12 @@ public final class AggregateReportTest {
.setDebugCleartextPayload(" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
@@ -64,8 +72,11 @@ public final class AggregateReportTest {
.setDebugCleartextPayload(" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
@@ -80,8 +91,11 @@ public final class AggregateReportTest {
.setDebugCleartextPayload(" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
.setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
@@ -99,13 +113,18 @@ public final class AggregateReportTest {
attributionReport.getDebugCleartextPayload());
assertNotNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.PENDING,
+ attributionReport.getDebugReportStatus());
assertEquals("1452", attributionReport.getApiVersion());
assertEquals(SOURCE_DEBUG_KEY, attributionReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, attributionReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, attributionReport.getSourceId());
+ assertEquals(TRIGGER_ID, attributionReport.getTriggerId());
}
@Test
- public void testCreationSingleSourceDebugKey() throws Exception {
+ public void testCreationSingleSourceDebugKey() {
AggregateReport attributionReport = createAttributionReportSingleSourceDebugKey();
assertEquals("1", attributionReport.getId());
assertEquals(Uri.parse("android-app://com.example.abc"), attributionReport.getPublisher());
@@ -119,13 +138,18 @@ public final class AggregateReportTest {
attributionReport.getDebugCleartextPayload());
assertNotNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.PENDING,
+ attributionReport.getDebugReportStatus());
assertEquals("1452", attributionReport.getApiVersion());
assertEquals(SOURCE_DEBUG_KEY, attributionReport.getSourceDebugKey());
assertNull(attributionReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, attributionReport.getSourceId());
+ assertEquals(TRIGGER_ID, attributionReport.getTriggerId());
}
@Test
- public void testCreationSingleTriggerDebugKey() throws Exception {
+ public void testCreationSingleTriggerDebugKey() {
AggregateReport attributionReport = createAttributionReportSingleTriggerDebugKey();
assertEquals("1", attributionReport.getId());
assertEquals(Uri.parse("android-app://com.example.abc"), attributionReport.getPublisher());
@@ -139,9 +163,14 @@ public final class AggregateReportTest {
attributionReport.getDebugCleartextPayload());
assertNotNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.PENDING,
+ attributionReport.getDebugReportStatus());
assertEquals("1452", attributionReport.getApiVersion());
assertNull(attributionReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, attributionReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, attributionReport.getSourceId());
+ assertEquals(TRIGGER_ID, attributionReport.getTriggerId());
}
@Test
@@ -157,9 +186,13 @@ public final class AggregateReportTest {
assertNull(attributionReport.getDebugCleartextPayload());
assertNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.NONE, attributionReport.getDebugReportStatus());
assertNull(attributionReport.getApiVersion());
assertNull(attributionReport.getSourceDebugKey());
assertNull(attributionReport.getTriggerDebugKey());
+ assertNull(attributionReport.getSourceId());
+ assertNull(attributionReport.getTriggerId());
}
@Test
@@ -188,7 +221,10 @@ public final class AggregateReportTest {
" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
Set<AggregateReport> attributionReportSet1 = Set.of(attributionReport1);
Set<AggregateReport> attributionReportSet2 = Set.of(attributionReport2);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java
index 4d4c920c8a..731f1554f8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.aggregation;
+import com.android.adservices.service.measurement.FilterData;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -45,8 +47,8 @@ public final class AggregateTriggerDataTest {
assertEquals(attributionTriggerData.getKey().longValue(), 5L);
assertEquals(attributionTriggerData.getSourceKeys().size(), 3);
assertTrue(attributionTriggerData.getFilter().isPresent());
- AggregateFilterData filterData = attributionTriggerData.getFilter().get();
- AggregateFilterData nonFilteredData = attributionTriggerData.getNotFilter().get();
+ FilterData filterData = attributionTriggerData.getFilter().get();
+ FilterData nonFilteredData = attributionTriggerData.getNotFilter().get();
assertEquals(2, filterData.getAttributionFilterMap().get("ctid").size());
assertEquals(1, nonFilteredData.getAttributionFilterMap().get("nctid").size());
}
@@ -78,15 +80,15 @@ public final class AggregateTriggerDataTest {
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("ctid", Arrays.asList("1"));
- AggregateFilterData filterData =
- new AggregateFilterData.Builder()
+ FilterData filterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
Map<String, List<String>> attributionNonFilterMap = new HashMap<>();
attributionNonFilterMap.put("other", Arrays.asList("1"));
- AggregateFilterData nonFilterData =
- new AggregateFilterData.Builder()
+ FilterData nonFilterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionNonFilterMap)
.build();
@@ -110,15 +112,15 @@ public final class AggregateTriggerDataTest {
private AggregateTriggerData createExample() {
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("ctid", Arrays.asList("1", "2"));
- AggregateFilterData filterData =
- new AggregateFilterData.Builder()
+ FilterData filterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
Map<String, List<String>> attributionNonFilterMap = new HashMap<>();
attributionNonFilterMap.put("nctid", Arrays.asList("3"));
- AggregateFilterData nonFilterData =
- new AggregateFilterData.Builder()
+ FilterData nonFilterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionNonFilterMap)
.build();
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java
index 8948dad655..936bb18df1 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java
@@ -16,10 +16,11 @@
package com.android.adservices.service.measurement.attribution;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import org.json.JSONException;
import org.junit.Assert;
@@ -51,7 +52,8 @@ public class AttributionJobHandlerIntegrationTest extends AbstractDbIntegrationT
@Override
public void runActionToTest() {
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
Assert.assertTrue("Attribution failed.",
(new AttributionJobHandler(datastoreManager))
.performPendingAttributions());
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
index 95b8388675..0edcefdc81 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.attribution;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -43,11 +45,14 @@ import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.SourceFixture;
import com.android.adservices.service.measurement.Trigger;
import com.android.adservices.service.measurement.TriggerFixture;
+import com.android.adservices.service.measurement.aggregation.AggregateAttributionData;
+import com.android.adservices.service.measurement.aggregation.AggregateHistogramContribution;
+import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,6 +60,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -70,6 +76,8 @@ public class AttributionJobHandlerTest {
private static final Context sContext = ApplicationProvider.getApplicationContext();
private static final Uri APP_DESTINATION = Uri.parse("android-app://com.example.app");
private static final Uri PUBLISHER = Uri.parse("android-app://publisher.app");
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(111111L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(222222L);
private static final String EVENT_TRIGGERS =
"[\n"
+ "{\n"
@@ -128,7 +136,7 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
verify(mMeasurementDao).getTrigger(trigger.getId());
- verify(mMeasurementDao, never()).updateTriggerStatus(any());
+ verify(mMeasurementDao, never()).updateTriggerStatus(any(), anyInt());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -143,9 +151,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -171,7 +179,7 @@ public class AttributionJobHandlerTest {
+ "]\n")
.build();
Source source = SourceFixture.getValidSourceBuilder()
- .setDedupKeys(Arrays.asList(1L, 2L))
+ .setDedupKeys(Arrays.asList(new UnsignedLong(1L), new UnsignedLong(2L)))
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
.build();
List<Source> matchingSourceList = new ArrayList<>();
@@ -183,9 +191,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -207,9 +215,9 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
verify(mMeasurementDao).getAttributionsPerRateLimitWindow(source, trigger);
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -231,11 +239,12 @@ public class AttributionJobHandlerTest {
any(), any(), any(), anyLong(), anyLong())).thenReturn(10);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- verify(mMeasurementDao).countDistinctEnrollmentsPerPublisherXDestinationInAttribution(
- any(), any(), any(), anyLong(), anyLong());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .countDistinctEnrollmentsPerPublisherXDestinationInAttribution(
+ any(), any(), any(), anyLong(), anyLong());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -269,9 +278,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -321,9 +330,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -345,12 +354,15 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -375,16 +387,20 @@ public class AttributionJobHandlerTest {
.setStatus(Trigger.Status.PENDING)
.setEventTriggers(eventTriggers)
.build();
- Source source1 = SourceFixture.getValidSourceBuilder()
- .setPriority(100L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(1L)
- .build();
- Source source2 = SourceFixture.getValidSourceBuilder()
- .setPriority(200L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(2L)
- .build();
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setPriority(100L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(1L)
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source2")
+ .setPriority(200L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(2L)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -396,14 +412,18 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
trigger.setStatus(Trigger.Status.ATTRIBUTED);
- verify(mMeasurementDao).updateSourceStatus(matchingSourceList, Source.Status.IGNORED);
- Assert.assertEquals(1, matchingSourceList.size());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateSourceStatus(eq(List.of(source1.getId())), eq(Source.Status.IGNORED));
+ assertEquals(1, matchingSourceList.size());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -466,9 +486,10 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
verify(mMeasurementDao).deleteEventReport(eventReport1);
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -500,7 +521,7 @@ public class AttributionJobHandlerTest {
verify(mMeasurementDao).getTrigger(anyString());
verify(mMeasurementDao).getMatchingActiveSources(any());
verify(mMeasurementDao).getAttributionsPerRateLimitWindow(any(), any());
- verify(mMeasurementDao, never()).updateTriggerStatus(any());
+ verify(mMeasurementDao, never()).updateTriggerStatus(any(), anyInt());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction).rollback();
@@ -554,22 +575,16 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getMatchingActiveSources(trigger2)).thenReturn(matchingSourceList2);
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
- Assert.assertTrue(attributionService.performPendingAttributions());
+ assertTrue(attributionService.performPendingAttributions());
// Verify trigger status updates.
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao, times(2)).updateTriggerStatus(triggerArg.capture());
- List<Trigger> statusArgs = triggerArg.getAllValues();
- for (int i = 0; i < statusArgs.size(); i++) {
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, statusArgs.get(i).getStatus());
- Assert.assertEquals(triggers.get(i).getId(), statusArgs.get(i).getId());
- }
+ verify(mMeasurementDao, times(2)).updateTriggerStatus(any(), eq(Trigger.Status.ATTRIBUTED));
// Verify source dedup key updates.
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao, times(2))
.updateSourceDedupKeys(sourceArg.capture());
List<Source> dedupArgs = sourceArg.getAllValues();
for (int i = 0; i < dedupArgs.size(); i++) {
- Assert.assertEquals(
+ assertEquals(
dedupArgs.get(i).getDedupKeys(),
Collections.singletonList(
triggers.get(i).parseEventTriggers().get(0).getDedupKey()));
@@ -580,7 +595,7 @@ public class AttributionJobHandlerTest {
.insertEventReport(reportArg.capture());
List<EventReport> newReportArgs = reportArg.getAllValues();
for (int i = 0; i < newReportArgs.size(); i++) {
- Assert.assertEquals(
+ assertEquals(
newReportArgs.get(i).getTriggerDedupKey(),
triggers.get(i).parseEventTriggers().get(0).getDedupKey());
}
@@ -591,6 +606,7 @@ public class AttributionJobHandlerTest {
long eventTime = System.currentTimeMillis();
Trigger trigger =
TriggerFixture.getValidTriggerBuilder()
+ .setId("trigger1")
.setStatus(Trigger.Status.PENDING)
.setTriggerTime(eventTime + TimeUnit.DAYS.toMillis(5))
.setEventTriggers(
@@ -603,20 +619,24 @@ public class AttributionJobHandlerTest {
+ "]\n")
.build();
// Lower priority and older priority source.
- Source source1 = SourceFixture.getValidSourceBuilder()
- .setEventId(1)
- .setPriority(100L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setInstallAttributed(true)
- .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(10))
- .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
- .build();
- Source source2 = SourceFixture.getValidSourceBuilder()
- .setEventId(2)
- .setPriority(200L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(eventTime)
- .build();
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setEventId(new UnsignedLong(1L))
+ .setPriority(100L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setInstallAttributed(true)
+ .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(10))
+ .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source2")
+ .setEventId(new UnsignedLong(2L))
+ .setPriority(200L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(eventTime)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -628,24 +648,30 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
trigger.setStatus(Trigger.Status.ATTRIBUTED);
- verify(mMeasurementDao).updateSourceStatus(matchingSourceList, Source.Status.IGNORED);
- Assert.assertEquals(1, matchingSourceList.size());
- Assert.assertEquals(source2.getEventId(), matchingSourceList.get(0).getEventId());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateSourceStatus(eq(List.of(source2.getId())), eq(Source.Status.IGNORED));
+ assertEquals(1, matchingSourceList.size());
+ assertEquals(source2.getEventId(), matchingSourceList.get(0).getEventId());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(source1.getEventId(), sourceArg.getValue().getEventId());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(source1.getEventId(), sourceArg.getValue().getEventId());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
}
@Test
public void shouldNotAttributeToOldInstallAttributedSource() throws DatastoreException {
+ // Setup
long eventTime = System.currentTimeMillis();
Trigger trigger =
TriggerFixture.getValidTriggerBuilder()
+ .setId("trigger1")
.setStatus(Trigger.Status.PENDING)
.setTriggerTime(eventTime + TimeUnit.DAYS.toMillis(10))
.setEventTriggers(
@@ -658,20 +684,24 @@ public class AttributionJobHandlerTest {
+ "]\n")
.build();
// Lower Priority. Install cooldown Window passed.
- Source source1 = SourceFixture.getValidSourceBuilder()
- .setEventId(1)
- .setPriority(100L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setInstallAttributed(true)
- .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(3))
- .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
- .build();
- Source source2 = SourceFixture.getValidSourceBuilder()
- .setEventId(2)
- .setPriority(200L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(eventTime)
- .build();
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setEventId(new UnsignedLong(1L))
+ .setPriority(100L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setInstallAttributed(true)
+ .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(3))
+ .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source2")
+ .setEventId(new UnsignedLong(2L))
+ .setPriority(200L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(eventTime)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -681,18 +711,25 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+ // Execution
attributionService.performPendingAttributions();
trigger.setStatus(Trigger.Status.ATTRIBUTED);
- verify(mMeasurementDao).updateSourceStatus(matchingSourceList, Source.Status.IGNORED);
- Assert.assertEquals(1, matchingSourceList.size());
- Assert.assertEquals(source1.getEventId(), matchingSourceList.get(0).getEventId());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateSourceStatus(eq(List.of(source1.getId())), eq(Source.Status.IGNORED));
+ assertEquals(1, matchingSourceList.size());
+ assertEquals(source1.getEventId(), matchingSourceList.get(0).getEventId());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(source2.getEventId(), sourceArg.getValue().getEventId());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(source2.getEventId(), sourceArg.getValue().getEventId());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
}
@@ -724,9 +761,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
@@ -761,9 +798,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
@@ -799,12 +836,41 @@ public class AttributionJobHandlerTest {
.setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
.build();
- Source source = SourceFixture.getValidSourceBuilder()
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateSource("[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData("{\"product\":[\"1234\",\"2345\"]}")
- .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setId("sourceId1")
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData("{\"product\":[\"1234\",\"2345\"]}")
+ .build();
+ AggregateReport expectedAggregateReport =
+ new AggregateReport.Builder()
+ .setApiVersion("0.1")
+ .setAttributionDestination(trigger.getAttributionDestination())
+ .setDebugCleartextPayload(
+ "{\"operation\":\"histogram\","
+ + "\"data\":[{\"bucket\":2693,\"value\":1644},{\"bucket\":1369,"
+ + "\"value\":32768}]}")
+ .setEnrollmentId(source.getEnrollmentId())
+ .setPublisher(source.getRegistrant())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
+ .setAggregateAttributionData(
+ new AggregateAttributionData.Builder()
+ .setContributions(
+ Arrays.asList(
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("2693"))
+ .setValue(1644)
+ .build(),
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("1369"))
+ .setValue(32768)
+ .build()))
+ .build())
+ .build();
+
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -816,8 +882,12 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceAggregateContributions(sourceArg.capture());
- verify(mMeasurementDao).insertAggregateReport(any());
- Assert.assertEquals(sourceArg.getValue().getAggregateContributions(), 32768 + 1644);
+ ArgumentCaptor<AggregateReport> aggregateReportCaptor =
+ ArgumentCaptor.forClass(AggregateReport.class);
+ verify(mMeasurementDao).insertAggregateReport(aggregateReportCaptor.capture());
+
+ assertAggregateReportsEqual(expectedAggregateReport, aggregateReportCaptor.getValue());
+ assertEquals(sourceArg.getValue().getAggregateContributions(), 32768 + 1644);
}
@Test
@@ -842,13 +912,14 @@ public class AttributionJobHandlerTest {
.setEventTriggers(EVENT_TRIGGERS)
.build();
- Source source = SourceFixture.getValidSourceBuilder()
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateSource("[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData("{\"product\":[\"1234\",\"2345\"]}")
- .setAggregateContributions(65536 - 32768 - 1644 + 1)
- .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData("{\"product\":[\"1234\",\"2345\"]}")
+ .setAggregateContributions(65536 - 32768 - 1644 + 1)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -887,7 +958,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1_x\", \"value_2_x\"],\n"
+ " \"key_2\": [\"value_1_x\", \"value_2_x\"]\n"
@@ -907,9 +978,63 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
+ verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_triggerNotFiltersWithCommonKeysIntersect_ignoreTrigger()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setNotFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2_x\"],\n"
+ + " \"key_2\": [\"value_1_x\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
@@ -917,6 +1042,65 @@ public class AttributionJobHandlerTest {
}
@Test
+ public void performAttributions_triggerNotFiltersWithCommonKeysDontIntersect_attributeTrigger()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setNotFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11_x\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22_x\"]\n"
+ + "}\n")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
public void performAttributions_triggerSourceFiltersWithCommonKeysIntersect_attributeTrigger()
throws DatastoreException {
// Setup
@@ -941,7 +1125,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
@@ -961,12 +1145,198 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(1L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_commonKeysIntersect_attributeTrigger_debugApi()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(SOURCE_DEBUG_KEY)
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ assertEquals(sourceArg.getValue().getDebugKey(), SOURCE_DEBUG_KEY);
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_commonKeysIntersect_attributeTrigger_debugApi_sourceKey()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22\"]\n"
+ + "}\n")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(SOURCE_DEBUG_KEY)
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ assertEquals(sourceArg.getValue().getDebugKey(), SOURCE_DEBUG_KEY);
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_commonKeysIntersect_attributeTrigger_debugApi_triggerKey()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -997,7 +1367,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1x\": [\"value_11_x\", \"value_12_x\"],\n"
+ " \"key_2x\": [\"value_21_x\", \"value_22_x\"]\n"
@@ -1017,12 +1387,15 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(1L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -1060,7 +1433,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ " \"key_2\": [\"value_1\", \"value_2\"]\n"
@@ -1070,10 +1443,10 @@ public class AttributionJobHandlerTest {
EventReport expectedEventReport =
new EventReport.Builder()
.setTriggerPriority(3L)
- .setTriggerDedupKey(3L)
- .setTriggerData(1L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
+ .setTriggerData(new UnsignedLong(1L))
.setTriggerTime(234324L)
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setStatus(EventReport.Status.PENDING)
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
@@ -1082,6 +1455,8 @@ public class AttributionJobHandlerTest {
trigger.getTriggerTime(), trigger.getDestinationType()))
.setSourceType(source.getSourceType())
.setRandomizedTriggerRate(source.getRandomAttributionProbability())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
@@ -1097,9 +1472,10 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).updateSourceDedupKeys(source);
verify(mMeasurementDao).insertEventReport(eq(expectedEventReport));
verify(mTransaction, times(2)).begin();
@@ -1138,7 +1514,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ " \"key_2\": [\"value_1\", \"value_2\"]\n"
@@ -1148,10 +1524,10 @@ public class AttributionJobHandlerTest {
EventReport expectedEventReport =
new EventReport.Builder()
.setTriggerPriority(3L)
- .setTriggerDedupKey(3L)
- .setTriggerData(1L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
+ .setTriggerData(new UnsignedLong(1L))
.setTriggerTime(234324L)
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setStatus(EventReport.Status.PENDING)
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
@@ -1160,6 +1536,8 @@ public class AttributionJobHandlerTest {
trigger.getTriggerTime(), trigger.getDestinationType()))
.setSourceType(source.getSourceType())
.setRandomizedTriggerRate(source.getRandomAttributionProbability())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
@@ -1175,9 +1553,10 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).updateSourceDedupKeys(source);
verify(mMeasurementDao).insertEventReport(eq(expectedEventReport));
verify(mTransaction, times(2)).begin();
@@ -1218,7 +1597,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ " \"key_2\": [\"value_1\", \"value_2\"]\n"
@@ -1229,10 +1608,10 @@ public class AttributionJobHandlerTest {
EventReport expectedEventReport =
new EventReport.Builder()
.setTriggerPriority(3L)
- .setTriggerDedupKey(3L)
- .setTriggerData(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
+ .setTriggerData(new UnsignedLong(3L))
.setTriggerTime(234324L)
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setStatus(EventReport.Status.PENDING)
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
@@ -1241,6 +1620,8 @@ public class AttributionJobHandlerTest {
trigger.getTriggerTime(), trigger.getDestinationType()))
.setSourceType(Source.SourceType.NAVIGATION)
.setRandomizedTriggerRate(source.getRandomAttributionProbability())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
@@ -1256,9 +1637,10 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).updateSourceDedupKeys(source);
verify(mMeasurementDao).insertEventReport(eq(expectedEventReport));
verify(mTransaction, times(2)).begin();
@@ -1299,15 +1681,14 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1_y\", \"value_2_y\"],\n"
+ " \"key_2\": [\"value_1_y\", \"value_2_y\"]\n"
+ "}\n")
.setAggregateSource(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
"{\"product\":[\"1234\",\"2345\"], \"key_1\": "
+ "[\"value_1_y\", \"value_2_y\"]}")
.setId("sourceId")
@@ -1326,11 +1707,283 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mMeasurementDao).insertAggregateReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_aggregateReportsExceedsLimit_insertsOnlyEventReport()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1024;
+ int numEventReportPerDestination = 1023;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ verify(mMeasurementDao, never()).insertAggregateReport(any());
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_eventReportsExceedsLimit_insertsOnlyAggregateReport()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1023;
+ int numEventReportPerDestination = 1024;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ verify(mMeasurementDao).insertAggregateReport(any());
verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_aggregateAndEventReportsExceedsLimit_noReportInsertion()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1024;
+ int numEventReportPerDestination = 1024;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
+ verify(mMeasurementDao, never()).insertAggregateReport(any());
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_aggregateAndEventReportsDoNotExceedsLimit_ReportInsertion()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1023;
+ int numEventReportPerDestination = 1023;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).insertAggregateReport(any());
+ verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -1357,4 +2010,24 @@ public class AttributionJobHandlerTest {
filterData.put("product", new JSONArray(Arrays.asList("1234", "2345")));
return filterData;
}
+
+ private void assertAggregateReportsEqual(
+ AggregateReport expectedReport, AggregateReport actualReport) {
+ // Avoids checking report time because there is randomization
+ assertEquals(expectedReport.getApiVersion(), actualReport.getApiVersion());
+ assertEquals(
+ expectedReport.getAttributionDestination(),
+ actualReport.getAttributionDestination());
+ assertEquals(
+ expectedReport.getDebugCleartextPayload(), actualReport.getDebugCleartextPayload());
+ assertEquals(expectedReport.getEnrollmentId(), actualReport.getEnrollmentId());
+ assertEquals(expectedReport.getPublisher(), actualReport.getPublisher());
+ assertEquals(expectedReport.getSourceId(), actualReport.getSourceId());
+ assertEquals(expectedReport.getTriggerId(), actualReport.getTriggerId());
+ assertEquals(
+ expectedReport.getAggregateAttributionData(),
+ actualReport.getAggregateAttributionData());
+ assertEquals(expectedReport.getSourceDebugKey(), actualReport.getSourceDebugKey());
+ assertEquals(expectedReport.getTriggerDebugKey(), actualReport.getTriggerDebugKey());
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java
index c1ced6759a..d32739f30c 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.attribution;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_ATTRIBUTION_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,17 +36,17 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -56,11 +57,6 @@ import java.util.Optional;
* Unit test for {@link AttributionJobService
*/
public class AttributionJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final long WAIT_IN_MILLIS = 50L;
private DatastoreManager mMockDatastoreManager;
@@ -77,10 +73,11 @@ public class AttributionJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -99,11 +96,11 @@ public class AttributionJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
ExtendedMockito.doNothing()
.when(
() ->
@@ -128,11 +125,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -159,11 +156,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -190,11 +187,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -220,11 +217,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -246,11 +243,27 @@ public class AttributionJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ AttributionJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertFalse(captor.getValue().isPersisted());
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
- .spyStatic(DatastoreManagerFactory.class)
.spyStatic(AttributionJobService.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -282,10 +295,8 @@ public class AttributionJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_attribution_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobAttributionKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java
new file mode 100644
index 0000000000..2ddb62286a
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java
@@ -0,0 +1,2635 @@
+/*
+ * 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.measurement.registration;
+
+import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+import static com.android.adservices.service.measurement.PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_REDIRECTS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.adservices.measurement.WebSourceParams;
+import android.adservices.measurement.WebSourceRegistrationRequest;
+import android.content.Context;
+import android.net.Uri;
+import android.view.InputEvent;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.net.ssl.HttpsURLConnection;
+/** Unit tests for {@link SourceFetcher} */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+public final class AsyncSourceFetcherTest {
+ private static final String ANDROID_APP_SCHEME = "android-app";
+ private static final String ANDROID_APP_SCHEME_URI_PREFIX = ANDROID_APP_SCHEME + "://";
+ private static final String DEFAULT_REGISTRATION = "https://foo.com";
+ private static final String ENROLLMENT_ID = "enrollment-id";
+ private static final EnrollmentData ENROLLMENT =
+ new EnrollmentData.Builder().setEnrollmentId("enrollment-id").build();
+ private static final String DEFAULT_TOP_ORIGIN =
+ "https://com.android.adservices.servicecoretest";
+ ;
+ private static final String DEFAULT_DESTINATION = "android-app://com.myapps";
+ private static final String DEFAULT_DESTINATION_WITHOUT_SCHEME = "com.myapps";
+ private static final long DEFAULT_PRIORITY = 123;
+ private static final long DEFAULT_EXPIRY = 456789;
+ private static final UnsignedLong DEFAULT_EVENT_ID = new UnsignedLong(987654321L);
+ private static final UnsignedLong EVENT_ID_1 = new UnsignedLong(987654321L);
+ private static final UnsignedLong DEBUG_KEY = new UnsignedLong(823523783L);
+ private static final String LIST_TYPE_REDIRECT_URI = "https://bar.com";
+ private static final String LOCATION_TYPE_REDIRECT_URI = "https://example.com";
+ private static final String ALT_DESTINATION = "android-app://com.yourapps";
+ private static final long ALT_PRIORITY = 321;
+ private static final long ALT_EVENT_ID = 123456789;
+ private static final long ALT_EXPIRY = 456790;
+ private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
+ private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
+ private static final Uri OS_DESTINATION = Uri.parse("android-app://com.os-destination");
+ private static final String LONG_FILTER_STRING = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
+ private static final Uri OS_DESTINATION_WITH_PATH =
+ Uri.parse("android-app://com.os-destination/my/path");
+ private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
+ private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
+ Uri.parse("https://subdomain.web-destination.com");
+ private static final WebSourceParams SOURCE_REGISTRATION_1 =
+ new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+ private static final WebSourceParams SOURCE_REGISTRATION_2 =
+ new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ AsyncSourceFetcher mFetcher;
+
+ @Mock HttpsURLConnection mUrlConnection;
+ @Mock EnrollmentDao mEnrollmentDao;
+ @Mock Flags mFlags;
+ @Mock AdServicesLogger mLogger;
+
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setup() {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+
+ mFetcher = spy(new AsyncSourceFetcher(mEnrollmentDao, mFlags, mLogger));
+ // For convenience, return the same enrollment-ID since we're using many arbitrary
+ // registration URIs and not yet enforcing uniqueness of enrollment.
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
+ }
+
+ @After
+ public void cleanup() throws InterruptedException {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testBasicSourceRequest() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(DEBUG_KEY, result.getDebugKey());
+ verify(mLogger)
+ .logMeasurementRegistrationsResponseSize(
+ eq(
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
+ 190)
+ .setAdTechDomain(null)
+ .build()));
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_failsWhenNotEnrolled() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mFetcher, never()).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequestWithoutAdIdPermission() throws Exception {
+ RegistrationRequest request = buildRequestWithoutAdIdPermission(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequestWithPostInstallAttributes() throws Exception {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"install_attribution_window\": \"272800\",\n"
+ + " \"post_install_exclusivity_window\": \"987654\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(TimeUnit.SECONDS.toMillis(272800), result.getInstallAttributionWindow());
+ assertEquals(TimeUnit.SECONDS.toMillis(987654L), result.getInstallCooldownWindow());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequestWithPostInstallAttributesReceivedAsNull() throws Exception {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"install_attribution_window\": null,\n"
+ + " \"post_install_exclusivity_window\": null\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ // fallback to default value - 30 days
+ assertEquals(TimeUnit.SECONDS.toMillis(2592000L), result.getInstallAttributionWindow());
+ // fallback to default value - 0 days
+ assertEquals(0L, result.getInstallCooldownWindow());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequestWithInstallAttributesOutofBounds() throws IOException {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ // Min value of attribution is 1 day or 86400
+ // seconds
+ + " \"install_attribution_window\": \"86300\",\n"
+ // Max value of cooldown is 30 days or 2592000
+ // seconds
+ + " \"post_install_exclusivity_window\":"
+ + " \"9876543210\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ // Adjusted to minimum allowed value
+ assertEquals(TimeUnit.SECONDS.toMillis(86400), result.getInstallAttributionWindow());
+ // Adjusted to maximum allowed value
+ assertEquals(TimeUnit.SECONDS.toMillis((2592000L)), result.getInstallCooldownWindow());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadSourceUrl() {
+ RegistrationRequest request = buildRequest(/* registrationUri = */ "bad-schema://foo.com");
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testBadSourceConnection() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doThrow(new IOException("Bad internet things"))
+ .when(mFetcher)
+ .openUrl(new URL(DEFAULT_REGISTRATION));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testBadSourceJson_missingSourceEventId() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadSourceJson_missingHeader() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields()).thenReturn(Collections.emptyMap());
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadSourceJson_missingDestination() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\"")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestMinimumFields() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_negative() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\"-35\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\",\"source_event_id\":\""
+ + "18446744073709551616\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\"8l2\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\",\"source_event_id\":\""
+ + "18446744073709551615\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(0, result.getPriority());
+ assertEquals(new UnsignedLong(-1L), result.getEventId());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_negative() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"-18\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertNull(result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"18446744073709551616\"}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertNull(result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"987fs\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertNull(result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"18446744073709551615\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(new UnsignedLong(-1L), result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestMinimumFieldsAndRestNull() throws Exception {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \"android-app://com.myapps\",\n"
+ + "\"source_event_id\": \"123\",\n"
+ + "\"priority\": null,\n"
+ + "\"expiry\": null\n"
+ + "}\n")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(new UnsignedLong(123L), result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testBasicSourceRequestWithExpiryLessThan2Days() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": 1"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testBasicSourceRequestWithExpiryMoreThan30Days() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": 2678400"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testNotOverHttps() throws Exception {
+ RegistrationRequest request = buildRequest("http://foo.com");
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mFetcher, never()).openUrl(any());
+ }
+
+ @Test
+ public void test500_ignoreFailure() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(500);
+ Map<String, List<String>> headersSecondRequest = new HashMap<>();
+ headersSecondRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + ALT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": "
+ + ALT_EXPIRY
+ + ""
+ + "}\n"));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersSecondRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testFailedParsingButValidRedirect_returnFailure() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersFirstRequest = new HashMap<>();
+ headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{}"));
+ headersFirstRequest.put("Attribution-Reporting-Redirect", List.of("https://bar.com"));
+ Map<String, List<String>> headersSecondRequest = new HashMap<>();
+ headersSecondRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + ALT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": "
+ + ALT_EXPIRY
+ + ""
+ + "}\n"));
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(headersFirstRequest)
+ .thenReturn(headersSecondRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+ @Test
+ public void testRedirectDifferentDestination_keepAllReturnSuccess() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersFirstRequest = new HashMap<>();
+ headersFirstRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + "\"expiry\": "
+ + DEFAULT_EXPIRY
+ + ""
+ + "}\n"));
+ headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ Map<String, List<String>> headersSecondRequest = new HashMap<>();
+ headersSecondRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + ALT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + ALT_EVENT_ID
+ + "\",\n"
+ + "\"priority\": \""
+ + ALT_PRIORITY
+ + "\",\n"
+ + "\"expiry\": "
+ + ALT_EXPIRY
+ + ""
+ + "}\n"));
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(headersFirstRequest)
+ .thenReturn(headersSecondRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ assertEquals(LIST_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ // Tests for redirect types
+
+ @Test
+ public void testRedirectType_bothRedirectHeaderTypes_choosesListType() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'none' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LIST_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectHeaderType_choosesLocationType() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_maxCount_noRedirectReturned()
+ throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ appSourceRegistrationRequest(
+ request,
+ AsyncRegistration.RedirectType.DAISY_CHAIN,
+ MAX_REDIRECTS_PER_REGISTRATION),
+ asyncFetchStatus,
+ asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirect but no uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_count1_redirectReturned() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1),
+ asyncFetchStatus,
+ asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_ignoresListType() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1),
+ asyncFetchStatus,
+ asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ // End tests for redirect types
+
+ @Test
+ public void testBasicSourceRequestWithFilterData() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ "{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}",
+ result.getFilterData());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequest_filterData_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String filterData = " \"filter_data\": " + filters + "\n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \""
+ + LONG_FILTER_STRING
+ + "\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String filterData = " \"filter_data\": " + filters + " \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":[\"1234\",\""
+ + LONG_FILTER_STRING
+ + "\"], \"ctid\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testMissingHeaderButWithRedirect() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(Map.of(
+ "Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI)))
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestWithAggregateSource() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\"campaignCounts\" :"
+ + " \"0x159\", \"geoValue\" : \"0x5\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(
+ new JSONObject("{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .toString(),
+ result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestWithAggregateSource_rejectsTooManyKeys() throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ for (int i = 0; i < 51; i++) {
+ tooManyKeys.append(String.format("\"campaign-%1$s\": \"0x15%1$s\"", i));
+ }
+ tooManyKeys.append("}");
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\":"
+ + " \"987654321\",\"aggregation_keys\": "
+ + tooManyKeys)));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testSourceRequestWithAggregateSource_tooManyKeys() throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ for (int i = 0; i < MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1; i++) {
+ tooManyKeys.append(String.format("\"campaign-%1$s\": \"0x15%1$s\"", i));
+ }
+ tooManyKeys.append("}");
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\":"
+ + " \"987654321\",\"aggregation_keys\": "
+ + tooManyKeys)));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testSourceRequestWithAggregateSource_keyIsNotAnObject() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": [\"campaignCounts\","
+ + " \"0x159\", \"geoValue\", \"0x5\"]\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequestWithAggregateSource_invalidKeyId() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\""
+ + LONG_AGGREGATE_KEY_ID
+ + "\": \"0x159\","
+ + "\"geoValue\": \"0x5\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequestWithAggregateSource_invalidKeyPiece_missingPrefix()
+ throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\"campaignCounts\" :"
+ + " \"0159\", \"geoValue\" : \"0x5\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testSourceRequestWithAggregateSource_invalidKeyPiece_tooLong() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\"campaignCounts\":"
+ + " \"0x159\", \"geoValue\": \""
+ + LONG_AGGREGATE_KEY_PIECE
+ + "\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_basic_success() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Arrays.asList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertEquals(EVENT_ID_1, result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSourcesSuccessWithoutAdIdPermission() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, false), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertEquals(EVENT_ID_1, result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ assertNull(result.getFilterData());
+ assertNull(result.getAggregateSource());
+ }
+ @Test
+ public void fetchWebSources_oneSuccessAndOneFailure_resultsIntoOneSourceFetched()
+ throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Arrays.asList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ // Its validation will fail due to destination mismatch
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ /* wrong destination */
+ + "android-app://com.wrongapp"
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withExtendedHeaders_success() throws IOException, JSONException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String aggregateSource = "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}";
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + OS_DESTINATION
+ + "\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"filter_data\": "
+ + filterData
+ + ", "
+ + " \"aggregation_keys\": "
+ + aggregateSource
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertEquals(filterData, result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertEquals(new JSONObject(aggregateSource).toString(), result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withRedirects_ignoresRedirects() throws IOException, JSONException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String aggregateSource = "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}";
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"filter_data\": "
+ + filterData
+ + " , "
+ + " \"aggregation_keys\": "
+ + aggregateSource
+ + "}"),
+ "Attribution-Reporting-Redirect",
+ List.of(LIST_TYPE_REDIRECT_URI),
+ "Location",
+ List.of(LOCATION_TYPE_REDIRECT_URI)));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertEquals(filterData, result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertEquals(new JSONObject(aggregateSource).toString(), result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_appDestinationDoNotMatch_failsDropsSource() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\":"
+ + " android-app://wrong.os-destination,\n"
+ + " \"web_destination\": "
+ + WEB_DESTINATION
+ + ",\n"
+ + " \"filter_data\": "
+ + filterData
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_webDestinationDoNotMatch_failsDropsSource() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\": "
+ + OS_DESTINATION
+ + ",\n"
+ + " \"web_destination\": "
+ + " https://wrong-web-destination.com,\n"
+ + " \"filter_data\": "
+ + filterData
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_osAndWebDestinationMatch_recordSourceSuccess() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\": \""
+ + OS_DESTINATION
+ + "\",\n"
+ + "\"web_destination\": \""
+ + WEB_DESTINATION
+ + "\""
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebSources_extractsTopPrivateDomain() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION_WITH_SUBDOMAIN);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\": \""
+ + OS_DESTINATION
+ + "\",\n"
+ + "\"web_destination\": \""
+ + WEB_DESTINATION_WITH_SUBDOMAIN
+ + "\""
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_extractsDestinationBaseUri() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION_WITH_PATH,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + OS_DESTINATION_WITH_PATH
+ + "\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"web_destination\": \""
+ + WEB_DESTINATION
+ + "\""
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_missingDestinations_dropsSource() throws Exception {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withDestinationUriNotHavingScheme_attachesAppScheme()
+ throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(EVENT_ID_1, result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withDestinationUriHavingHttpsScheme_dropsSource()
+ throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ // Invalid (https) URI for app destination
+ + WEB_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void basicSourceRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
+ throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ assertTrue(fetch.isPresent());
+ verify(mLogger)
+ .logMeasurementRegistrationsResponseSize(
+ eq(
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
+ 115)
+ .setAdTechDomain(DEFAULT_REGISTRATION)
+ .build()));
+ }
+ private RegistrationRequest buildRequest(String registrationUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse(registrationUri))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(true)
+ .build();
+ }
+ private RegistrationRequest buildRequestWithoutAdIdPermission(String registrationUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse(registrationUri))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(false)
+ .build();
+ }
+
+ public static AsyncRegistration appSourceRegistrationRequest(
+ RegistrationRequest registrationRequest) {
+ return appSourceRegistrationRequest(
+ registrationRequest, AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ public static AsyncRegistration appSourceRegistrationRequest(
+ RegistrationRequest registrationRequest,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount) {
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ registrationRequest.getRegistrationUri(),
+ null,
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + sContext.getPackageName()),
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + sContext.getPackageName()),
+ registrationRequest.getRegistrationType() == RegistrationRequest.REGISTER_SOURCE
+ ? AsyncRegistration.RegistrationType.APP_SOURCE
+ : AsyncRegistration.RegistrationType.APP_TRIGGER,
+ getSourceType(registrationRequest.getInputEvent()),
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ redirectType,
+ redirectCount,
+ registrationRequest.isAdIdPermissionGranted());
+ }
+
+ private static AsyncRegistration webSourceRegistrationRequest(
+ WebSourceRegistrationRequest webSourceRegistrationRequest,
+ boolean adIdPermissionGranted) {
+ if (webSourceRegistrationRequest.getSourceParams().size() > 0) {
+ WebSourceParams webSourceParams = webSourceRegistrationRequest.getSourceParams().get(0);
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ webSourceRegistrationRequest
+ .getSourceParams()
+ .get(0)
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ webSourceParams
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ webSourceParams.getRegistrationUri(),
+ webSourceRegistrationRequest.getWebDestination(),
+ webSourceRegistrationRequest.getAppDestination(),
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + sContext.getPackageName()),
+ null,
+ webSourceRegistrationRequest.getTopOriginUri(),
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ Source.SourceType.NAVIGATION,
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ AsyncRegistration.RedirectType.NONE,
+ 0,
+ adIdPermissionGranted);
+ }
+ return null;
+ }
+
+ private static AsyncRegistration createAsyncRegistration(
+ String iD,
+ String enrollmentId,
+ Uri registrationUri,
+ Uri webDestination,
+ Uri osDestination,
+ Uri registrant,
+ Uri verifiedDestination,
+ Uri topOrigin,
+ AsyncRegistration.RegistrationType registrationType,
+ Source.SourceType sourceType,
+ long mRequestTime,
+ long mRetryCount,
+ long mLastProcessingTime,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount,
+ boolean debugKeyAllowed) {
+ return new AsyncRegistration.Builder()
+ .setId(iD)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrationUri(registrationUri)
+ .setWebDestination(webDestination)
+ .setOsDestination(osDestination)
+ .setRegistrant(registrant)
+ .setVerifiedDestination(verifiedDestination)
+ .setTopOrigin(topOrigin)
+ .setType(registrationType.ordinal())
+ .setSourceType(
+ registrationType == AsyncRegistration.RegistrationType.APP_SOURCE
+ || registrationType
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? sourceType
+ : null)
+ .setRequestTime(mRequestTime)
+ .setRetryCount(mRetryCount)
+ .setLastProcessingTime(mLastProcessingTime)
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(debugKeyAllowed)
+ .build();
+ }
+
+ private WebSourceRegistrationRequest buildWebSourceRegistrationRequest(
+ List<WebSourceParams> sourceParamsList,
+ String topOrigin,
+ Uri appDestination,
+ Uri webDestination) {
+ WebSourceRegistrationRequest.Builder webSourceRegistrationRequestBuilder =
+ new WebSourceRegistrationRequest.Builder(sourceParamsList, Uri.parse(topOrigin))
+ .setAppDestination(appDestination);
+ if (webDestination != null) {
+ webSourceRegistrationRequestBuilder.setWebDestination(webDestination);
+ }
+ return webSourceRegistrationRequestBuilder.build();
+ }
+
+ static Source.SourceType getSourceType(InputEvent inputEvent) {
+ return inputEvent == null ? Source.SourceType.EVENT : Source.SourceType.NAVIGATION;
+ }
+
+ static Map<String, List<String>> getDefaultHeaders() {
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + "\"expiry\": "
+ + DEFAULT_EXPIRY
+ + ""
+ + "}\n"));
+ return headers;
+ }
+
+ private static void assertDefaultSourceRegistration(Source result) {
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java
new file mode 100644
index 0000000000..e52eae5696
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java
@@ -0,0 +1,2797 @@
+/*
+ * 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.measurement.registration;
+
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATABLE_TRIGGER_DATA;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_EVENT_TRIGGER_DATA;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_REDIRECTS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.adservices.measurement.WebTriggerParams;
+import android.adservices.measurement.WebTriggerRegistrationRequest;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.Trigger;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.net.ssl.HttpsURLConnection;
+/** Unit tests for {@link AsyncTriggerFetcher} */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+public final class AsyncTriggerFetcherTest {
+ private static final String ANDROID_APP_SCHEME = "android-app";
+ private static final String ANDROID_APP_SCHEME_URI_PREFIX = ANDROID_APP_SCHEME + "://";
+ private static final String TRIGGER_URI = "https://foo.com";
+ private static final String ENROLLMENT_ID = "enrollment-id";
+ private static final EnrollmentData ENROLLMENT =
+ new EnrollmentData.Builder().setEnrollmentId("enrollment-id").build();
+ private static final String TOP_ORIGIN = "https://baz.com";
+ private static final long TRIGGER_DATA = 7;
+ private static final long PRIORITY = 1;
+ private static final String LONG_FILTER_STRING = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
+ private static final long DEDUP_KEY = 100;
+ private static final UnsignedLong DEBUG_KEY = new UnsignedLong(34787843L);
+ private static final String DEFAULT_REDIRECT = "https://bar.com";
+ private static final String EVENT_TRIGGERS_1 =
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \""
+ + TRIGGER_DATA
+ + "\",\n"
+ + " \"priority\": \""
+ + PRIORITY
+ + "\",\n"
+ + " \"deduplication_key\": \""
+ + DEDUP_KEY
+ + "\",\n"
+ + " \"filters\": {\n"
+ + " \"source_type\": [\"navigation\"],\n"
+ + " \"key_1\": [\"value_1\"] \n"
+ + " }\n"
+ + "}"
+ + "]\n";
+ private static final String LIST_TYPE_REDIRECT_URI = "https://bar.com";
+ private static final String LOCATION_TYPE_REDIRECT_URI = "https://example.com";
+ private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
+ private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
+ private static final WebTriggerParams TRIGGER_REGISTRATION_1 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+ private static final WebTriggerParams TRIGGER_REGISTRATION_2 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ AsyncTriggerFetcher mFetcher;
+
+ @Mock HttpsURLConnection mUrlConnection;
+ @Mock HttpsURLConnection mUrlConnection1;
+ @Mock EnrollmentDao mEnrollmentDao;
+ @Mock Flags mFlags;
+ @Mock AdServicesLogger mLogger;
+
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setup() {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+ mFetcher = spy(new AsyncTriggerFetcher(mEnrollmentDao, mFlags, mLogger));
+ // For convenience, return the same enrollment-ID since we're using many arbitrary
+ // registration URIs and not yet enforcing uniqueness of enrollment.
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
+ }
+
+ @After
+ public void cleanup() throws InterruptedException {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testBasicTriggerRequest() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ MeasurementRegistrationResponseStats expectedStats =
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
+ 221)
+ .setAdTechDomain(null)
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
+ }
+
+ // Tests for redirect types
+
+ @Test
+ public void testRedirectType_bothRedirectHeaderTypes_choosesListType() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'none' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LIST_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectHeaderType_choosesLocationType() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_maxCount_noRedirectReturned()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request,
+ AsyncRegistration.RedirectType.DAISY_CHAIN, MAX_REDIRECTS_PER_REGISTRATION);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirect but no uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_count1_redirectReturned() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_ignoresListType() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ // End tests for redirect types
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_tooManyEntries() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder tooManyEntries = new StringBuilder("[");
+ for (int i = 0; i < MAX_ATTRIBUTION_EVENT_TRIGGER_DATA + 1; i++) {
+ tooManyEntries.append("{\"trigger_data\": \"2\",\"priority\": \"101\"}");
+ }
+ tooManyEntries.append("]");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + tooManyEntries + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"-2\",\"priority\":\"101\"}]";
+ String expectedResult = "[{\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"18446744073709551616\"," + "\"priority\":\"101\"}]";
+ String expectedResult = "[{\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"101z\",\"priority\":\"101\"}]";
+ String expectedResult = "[{\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"18446744073709551615\"," + "\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(eventTriggerData, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_priority_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"2\",\"priority\":\"-101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(eventTriggerData, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_priority_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":" + "\"18446744073709551615\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_priority_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"2\",\"priority\":\"a101\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"-34\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\",\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"18446744073709551616\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\",\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"145l\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\",\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_uses64thBit()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"18446744073709551615\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(eventTriggerData, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters = new StringBuilder("{");
+ notFilters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ notFilters.append("}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ notFilters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ notFilters.append("]}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters = new StringBuilder("{");
+ notFilters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ notFilters.append("}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ notFilters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ notFilters.append("]}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicTriggerRequest_failsWhenNotEnrolled() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mFetcher, never()).openUrl(any());
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithDebugKey() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertEquals(DEBUG_KEY, result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"-376\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"18446744073709551616\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"65g43\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"18446744073709551615\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertEquals(new UnsignedLong(-1L), result.getDebugKey());
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithoutAdIdPermission() throws Exception {
+ RegistrationRequest request = buildRequestWithoutAdIdPermission(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadTriggerUrl() throws Exception {
+ RegistrationRequest request = buildRequest("bad-schema://foo.com");
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testBadTriggerConnection() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doThrow(new IOException("Bad internet things"))
+ .when(mFetcher)
+ .openUrl(new URL(TRIGGER_URI));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, never()).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadRequestReturnFailure() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(400);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestMinimumFields() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\": " + "[{}]" + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("[{}]", result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testNotOverHttps() throws Exception {
+ RegistrationRequest request = buildRequest("http://foo.com");
+ // Non-https should fail.
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testFirst200Next500_ignoreFailureReturnSuccess() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200).thenReturn(500);
+ Map<String, List<String>> headersFirstRequest = new HashMap<>();
+ headersFirstRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}"));
+ headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testMissingHeaderButWithRedirect() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(Map.of("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT)))
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateTriggerData() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(
+ new JSONArray(aggregatable_trigger_data).toString(),
+ result.getAggregateTriggerData());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateTriggerData_rejectsTooManyDataKeys()
+ throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("[");
+ for (int i = 0; i < 51; i++) {
+ tooManyKeys.append(
+ String.format(
+ "{\"key_piece\": \"0x15%1$s\",\"source_keys\":[\"campaign-%1$s\"]}",
+ i));
+ }
+ tooManyKeys.append("]");
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + tooManyKeys
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values = "{\"campaignCounts\":32768,\"geoValue\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONObject(aggregatable_values).toString(), result.getAggregateValues());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateTriggerData_rejectsTooManyValueKeys()
+ throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ int i = 0;
+ for (; i < 50; i++) {
+ tooManyKeys.append(String.format("\"key-%s\": 12345,", i));
+ }
+ tooManyKeys.append(String.format("\"key-%s\": 12345}", i));
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"aggregatable_values\": " + tooManyKeys + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchTrigger_withReportingFilters_success() throws IOException, JSONException {
+ // Setup
+ String filters =
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}";
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\": " + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONObject(filters).toString(), result.getFilters());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebTriggers_basic_success() throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Arrays.asList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
+ doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection1.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection1).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebTriggers_withExtendedHeaders_success() throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String aggregatableValues = "{\"campaignCounts\":32768,\"geoValue\":1644}";
+ String filters =
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}";
+ String aggregatableTriggerData =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"filters\": "
+ + filters
+ + ", \"aggregatable_values\": "
+ + aggregatableValues
+ + ", \"aggregatable_trigger_data\": "
+ + aggregatableTriggerData
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertEquals(new JSONArray(aggregatableTriggerData).toString(),
+ result.getAggregateTriggerData());
+ assertEquals(new JSONObject(aggregatableValues).toString(), result.getAggregateValues());
+ assertEquals(new JSONObject(filters).toString(), result.getFilters());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebTriggers_withRedirects_ignoresRedirects()
+ throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\": " + EVENT_TRIGGERS_1 + "}"),
+ "Attribution-Reporting-Redirect",
+ List.of(LIST_TYPE_REDIRECT_URI),
+ "Location",
+ List.of(LOCATION_TYPE_REDIRECT_URI)));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_tooManyEntries() throws Exception {
+ StringBuilder tooManyEntries = new StringBuilder("[");
+ for (int i = 0; i < MAX_AGGREGATABLE_TRIGGER_DATA + 1; i++) {
+ tooManyEntries.append(
+ String.format(
+ "{\"key_piece\": \"0x15%1$s\",\"source_keys\":[\"campaign-%1$s\"]}",
+ i));
+ }
+ tooManyEntries.append("]");
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + tooManyEntries
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_missingPrefix()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_tooLong()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\""
+ + LONG_AGGREGATE_KEY_PIECE
+ + "\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_sourceKeys_notAnArray()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":{\"campaignCounts\": true},"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_sourceKeys_tooManyKeys()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder tooManyKeys = new StringBuilder("[");
+ tooManyKeys.append(
+ IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
+ .mapToObj(i -> "aggregate-key-" + i)
+ .collect(Collectors.joining(",")));
+ tooManyKeys.append("]");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\": "
+ + tooManyKeys
+ + ","
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_sourceKeys_invalidKeyId()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\", \""
+ + LONG_AGGREGATE_KEY_ID
+ + "\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_tooManyFilters()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_tooManyValues()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyFilters()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_keyTooLong()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyValues()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_valueTooLong()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testBasicTriggerRequestWithAggregatableValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values = "{\"campaignCounts\":32768,\"geoValue\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONObject(aggregatable_values).toString(), result.getAggregateValues());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testTriggerRequestWithAggregatableValues_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values = "{\"campaignCounts\":32768\"geoValue\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregatableValues_tooManyKeys() throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ tooManyKeys.append(
+ IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
+ .mapToObj(i -> String.format("\"key-%s\": 12345,", i))
+ .collect(Collectors.joining(",")));
+ tooManyKeys.append("}");
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"aggregatable_values\": " + tooManyKeys + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregatableValues_invalidKeyId() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values =
+ "{\"campaignCounts\":32768, \"" + LONG_AGGREGATE_KEY_ID + "\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebTriggerSuccessWithoutAdIdPermission() throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Arrays.asList(TRIGGER_REGISTRATION_1, TRIGGER_REGISTRATION_2), TOP_ORIGIN);
+ doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection1.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, false), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(TOP_ORIGIN, result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection1).setRequestMethod("POST");
+ }
+ @Test
+ public void basicTriggerRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ MeasurementRegistrationResponseStats expectedStats =
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
+ 221)
+ .setAdTechDomain(TRIGGER_URI)
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
+ }
+ private RegistrationRequest buildRequest(String triggerUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
+ .setRegistrationUri(Uri.parse(triggerUri))
+ .setPackageName(CONTEXT.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(true)
+ .build();
+ }
+ private RegistrationRequest buildRequestWithoutAdIdPermission(String triggerUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
+ .setRegistrationUri(Uri.parse(triggerUri))
+ .setPackageName(CONTEXT.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(false)
+ .build();
+ }
+ private WebTriggerRegistrationRequest buildWebTriggerRegistrationRequest(
+ List<WebTriggerParams> triggerParams, String topOrigin) {
+ return new WebTriggerRegistrationRequest.Builder(triggerParams, Uri.parse(topOrigin))
+ .build();
+ }
+
+ public static AsyncRegistration appTriggerRegistrationRequest(
+ RegistrationRequest registrationRequest) {
+ return appTriggerRegistrationRequest(registrationRequest,
+ AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ public static AsyncRegistration appTriggerRegistrationRequest(
+ RegistrationRequest registrationRequest,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount) {
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ registrationRequest.getRegistrationUri(),
+ null,
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + CONTEXT.getPackageName()),
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + CONTEXT.getPackageName()),
+ registrationRequest.getRegistrationType() == RegistrationRequest.REGISTER_SOURCE
+ ? AsyncRegistration.RegistrationType.APP_SOURCE
+ : AsyncRegistration.RegistrationType.APP_TRIGGER,
+ null,
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ redirectType,
+ redirectCount,
+ registrationRequest.isAdIdPermissionGranted());
+ }
+
+ private static AsyncRegistration webTriggerRegistrationRequest(
+ WebTriggerRegistrationRequest webTriggerRegistrationRequest,
+ boolean adIdPermissionGranted) {
+ if (webTriggerRegistrationRequest.getTriggerParams().size() > 0) {
+ WebTriggerParams webTriggerParams =
+ webTriggerRegistrationRequest.getTriggerParams().get(0);
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ webTriggerRegistrationRequest
+ .getTriggerParams()
+ .get(0)
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ webTriggerParams
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ webTriggerParams.getRegistrationUri(),
+ null,
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + CONTEXT.getPackageName()),
+ null,
+ webTriggerRegistrationRequest.getDestination(),
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ null,
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ AsyncRegistration.RedirectType.NONE,
+ 0,
+ adIdPermissionGranted);
+ }
+ return null;
+ }
+ private static AsyncRegistration createAsyncRegistration(
+ String iD,
+ String enrollmentId,
+ Uri registrationUri,
+ Uri webDestination,
+ Uri osDestination,
+ Uri registrant,
+ Uri verifiedDestination,
+ Uri topOrigin,
+ AsyncRegistration.RegistrationType registrationType,
+ Source.SourceType sourceType,
+ long mRequestTime,
+ long mRetryCount,
+ long mLastProcessingTime,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount,
+ boolean debugKeyAllowed) {
+ return new AsyncRegistration.Builder()
+ .setId(iD)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrationUri(registrationUri)
+ .setWebDestination(webDestination)
+ .setOsDestination(osDestination)
+ .setRegistrant(registrant)
+ .setVerifiedDestination(verifiedDestination)
+ .setTopOrigin(topOrigin)
+ .setType(registrationType.ordinal())
+ .setSourceType(sourceType)
+ .setRequestTime(mRequestTime)
+ .setRetryCount(mRetryCount)
+ .setLastProcessingTime(mLastProcessingTime)
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(debugKeyAllowed)
+ .build();
+ }
+
+ private static Map<String, List<String>> getDefaultHeaders() {
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put("Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}"));
+ return headers;
+ }
+
+ private static void assertSourceRegistration(AsyncRegistration asyncRegistration,
+ Trigger result) throws JSONException {
+ assertEquals(
+ asyncRegistration.getRegistrant().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java
index 4a76028b79..07af7b36a5 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java
@@ -32,6 +32,8 @@ import android.net.Uri;
import androidx.test.filters.SmallTest;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
@@ -82,27 +84,49 @@ public final class FetcherUtilTest {
}
@Test
- public void testParseRedirectsNothingInitial() {
- List<Uri> redirs = FetcherUtil.parseRedirects(Map.of());
- assertEquals(0, redirs.size());
+ public void parseRedirects_noRedirectHeaders_returnsEmpty() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of(), AsyncRegistration.RedirectType.ANY);
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
}
@Test
- public void testParseRedirectsARR() {
- List<Uri> redirs =
- FetcherUtil.parseRedirects(
- Map.of("Attribution-Reporting-Redirect", List.of("foo.com", "bar.com")));
- assertEquals(2, redirs.size());
- assertEquals(Uri.parse("foo.com"), redirs.get(0));
- assertEquals(Uri.parse("bar.com"), redirs.get(1));
+ public void parseRedirects_bothHeaderTypes_choosesListType() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of(
+ "Attribution-Reporting-Redirect", List.of("foo.test", "bar.test"),
+ "Location", List.of("baz.test")),
+ AsyncRegistration.RedirectType.ANY);
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ List<Uri> redirects = asyncRedirect.getRedirects();
+ assertEquals(2, redirects.size());
+ assertEquals(Uri.parse("foo.test"), redirects.get(0));
+ assertEquals(Uri.parse("bar.test"), redirects.get(1));
}
@Test
- public void testParseRedirectsSingleElementARR() {
- List<Uri> redirs =
- FetcherUtil.parseRedirects(
- Map.of("Attribution-Reporting-Redirect", List.of("foo.com")));
- assertEquals(1, redirs.size());
+ public void parseRedirects_locationHeaderOnly_choosesLocationType() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of("Location", List.of("foo.test")),
+ AsyncRegistration.RedirectType.ANY);
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ List<Uri> redirects = asyncRedirect.getRedirects();
+ assertEquals(1, redirects.size());
+ assertEquals(Uri.parse("foo.test"), redirects.get(0));
+ }
+
+ @Test
+ public void parseRedirects_bothHeaderTypes_providedLocationType_choosesLocationType() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of(
+ "Attribution-Reporting-Redirect", List.of("foo.test", "bar.test"),
+ "Location", List.of("baz.test")),
+ AsyncRegistration.RedirectType.DAISY_CHAIN);
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ List<Uri> redirects = asyncRedirect.getRedirects();
+ assertEquals(1, redirects.size());
+ assertEquals(Uri.parse("baz.test"), redirects.get(0));
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java
deleted file mode 100644
index fbc3c935d5..0000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java
+++ /dev/null
@@ -1,1937 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
-import static com.android.adservices.service.measurement.PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.adservices.measurement.RegistrationRequest;
-import android.adservices.measurement.WebSourceParams;
-import android.adservices.measurement.WebSourceRegistrationRequest;
-import android.content.Context;
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.service.Flags;
-import com.android.adservices.service.enrollment.EnrollmentData;
-import com.android.adservices.service.stats.AdServicesLogger;
-import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import javax.net.ssl.HttpsURLConnection;
-
-/** Unit tests for {@link SourceFetcher} */
-@RunWith(MockitoJUnitRunner.class)
-@SmallTest
-public final class SourceFetcherTest {
- private static final String DEFAULT_REGISTRATION = "https://foo.com";
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
- .setEnrollmentId("enrollment-id")
- .build();
- private static final String DEFAULT_TOP_ORIGIN = "https://baz.com";
- private static final String DEFAULT_DESTINATION = "android-app://com.myapps";
- private static final String DEFAULT_DESTINATION_WITHOUT_SCHEME = "com.myapps";
- private static final long DEFAULT_PRIORITY = 123;
- private static final long DEFAULT_EXPIRY = 456789;
- private static final long DEFAULT_EVENT_ID = 987654321;
- private static final long EVENT_ID_1 = 987654321;
- private static final long EVENT_ID_2 = 987654322;
- private static final Long DEBUG_KEY = 823523783L;
- private static final String ALT_REGISTRATION = "https://bar.com";
- private static final String ALT_DESTINATION = "android-app://com.yourapps";
- private static final long ALT_PRIORITY = 321;
- private static final long ALT_EVENT_ID = 123456789;
- private static final long ALT_EXPIRY = 456790;
- private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
- private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
- private static final Uri OS_DESTINATION = Uri.parse("android-app://com.os-destination");
- private static final Uri OS_DESTINATION_WITH_PATH =
- Uri.parse("android-app://com.os-destination/my/path");
- private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
- private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
- Uri.parse("https://subdomain.web-destination.com");
- private static final String LONG_FILTER_STRING = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
- private static final WebSourceParams SOURCE_REGISTRATION_1 =
- new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
- private static final WebSourceParams SOURCE_REGISTRATION_2 =
- new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
-
- private static final Context sContext =
- InstrumentationRegistry.getInstrumentation().getContext();
-
- SourceFetcher mFetcher;
- @Mock HttpsURLConnection mUrlConnection;
-
- @Mock HttpsURLConnection mUrlConnection1;
- @Mock HttpsURLConnection mUrlConnection2;
- @Mock EnrollmentDao mEnrollmentDao;
- @Mock Flags mFlags;
- @Mock AdServicesLogger mLogger;
-
- @Before
- public void setup() {
- mFetcher = spy(new SourceFetcher(mEnrollmentDao, mFlags, mLogger));
- // For convenience, return the same enrollment-ID since we're using many arbitrary
- // registration URIs and not yet enforcing uniqueness of enrollment.
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
- }
-
- @Test
- public void testBasicSourceRequest() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \""
- + DEFAULT_PRIORITY
- + "\",\n"
- + " \"expiry\": \""
- + DEFAULT_EXPIRY
- + "\",\n"
- + " \"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_PRIORITY, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(DEBUG_KEY, result.get(0).getDebugKey());
- verify(mLogger)
- .logMeasurementRegistrationsResponseSize(
- eq(
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
- 190)
- .setAdTechDomain(null)
- .build()));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequest_failsWhenNotEnrolled() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \""
- + DEFAULT_PRIORITY
- + "\",\n"
- + " \"expiry\": \""
- + DEFAULT_EXPIRY
- + "\",\n"
- + " \"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mFetcher, never()).openUrl(any());
- }
-
- @Test
- public void testBasicSourceRequestWithoutAdIdPermission() throws Exception {
- RegistrationRequest request =
- buildRequestWithoutAdIdPermission(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \""
- + DEFAULT_PRIORITY
- + "\",\n"
- + " \"expiry\": \""
- + DEFAULT_EXPIRY
- + "\",\n"
- + " \"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_PRIORITY, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertNull(result.get(0).getDebugKey());
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithPostInstallAttributes() throws Exception {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"install_attribution_window\": \"272800\",\n"
- + " \"post_install_exclusivity_window\": \"987654\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- assertEquals(272800, result.get(0).getInstallAttributionWindow());
- assertEquals(987654L, result.get(0).getInstallCooldownWindow());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithPostInstallAttributesReceivedAsNull() throws Exception {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"install_attribution_window\": null,\n"
- + " \"post_install_exclusivity_window\": null\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- // fallback to default value - 30 days
- assertEquals(2592000L, result.get(0).getInstallAttributionWindow());
- // fallback to default value - 0 days
- assertEquals(0L, result.get(0).getInstallCooldownWindow());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithInstallAttributesOutofBounds() throws IOException {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- // Min value of attribution is 2 days or 172800 seconds
- + " \"install_attribution_window\": \"172700\",\n"
- // Max value of cooldown is 30 days or 2592000 seconds
- + " \"post_install_exclusivity_window\": \"9876543210\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- // Adjusted to minimum allowed value
- assertEquals(172800, result.get(0).getInstallAttributionWindow());
- // Adjusted to maximum allowed value
- assertEquals(2592000L, result.get(0).getInstallCooldownWindow());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBadSourceUrl() {
- RegistrationRequest request = buildRequest(
- /* registrationUri = */ "bad-schema://foo.com", DEFAULT_TOP_ORIGIN);
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testBadSourceConnection() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doThrow(new IOException("Bad internet things")).when(mFetcher).openUrl(
- new URL(DEFAULT_REGISTRATION)
- );
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testBadSourceJson_missingSourceEventId() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\"")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBadSourceJson_missingHeader() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields()).thenReturn(Collections.emptyMap());
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBadSourceJson_missingDestination() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\"")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicSourceRequestMinimumFields() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(
- MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequestMinimumFieldsAndRestNull() throws Exception {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"android-app://com.myapps\",\n"
- + "\"source_event_id\": \"123\",\n"
- + "\"priority\": null,\n"
- + "\"expiry\": null\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS,
- result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequestWithExpiryLessThan2Days() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"expiry\": 1"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(
- MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequestWithExpiryMoreThan30Days() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"expiry\": 2678400"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(
- MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testNotOverHttps() throws Exception {
- RegistrationRequest request = buildRequest("http://foo.com", DEFAULT_TOP_ORIGIN);
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mFetcher, never()).openUrl(any());
- }
-
- @Test
- public void testFirst200Next500_ignoreFailureReturnSuccess() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200).thenReturn(500);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"expiry\": " + DEFAULT_EXPIRY + ""
- + "}\n"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of("https://bar.com"));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- verify(mUrlConnection, times(2)).setRequestMethod("POST");
- }
-
- @Test
- public void testFailedParsingButValidRedirect_returnFailure() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{}"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of("https://bar.com"));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testRedirectDifferentDestination_keepAllReturnSuccess() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"priority\": \"" + DEFAULT_PRIORITY + "\",\n"
- + "\"expiry\": " + DEFAULT_EXPIRY + ""
- + "}\n"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(ALT_REGISTRATION));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + ALT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"priority\": \"" + ALT_PRIORITY + "\",\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(DEFAULT_PRIORITY, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(1).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(1).getEnrollmentId());
- assertEquals(ALT_DESTINATION, result.get(1).getAppDestination().toString());
- assertEquals(ALT_EVENT_ID, result.get(1).getSourceEventId());
- assertEquals(ALT_PRIORITY, result.get(1).getSourcePriority());
- assertEquals(ALT_EXPIRY, result.get(1).getExpiry());
- verify(mUrlConnection, times(2)).setRequestMethod("POST");
- }
-
- @Test
- public void testRedirectSameDestination_returnAllSuccessfully() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"priority\": 999,\n"
- + "\"expiry\": " + DEFAULT_EXPIRY + ""
- + "}\n"));
- headersFirstRequest.put("Attribution-Reporting-Redirect",
- List.of(ALT_REGISTRATION, ALT_REGISTRATION));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"priority\": 888,\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- Map<String, List<String>> headersThirdRequest = new HashMap<>();
- headersThirdRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": 777,\n"
- + "\"priority\": 777,\n"
- + "\"expiry\": 456791"
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest, headersThirdRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(3, result.size());
- result.sort((o1, o2) -> (int) (o2.getSourcePriority() - o1.getSourcePriority()));
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(999, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(1).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(1).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(1).getAppDestination().toString());
- assertEquals(ALT_EVENT_ID, result.get(1).getSourceEventId());
- assertEquals(888, result.get(1).getSourcePriority());
- assertEquals(ALT_EXPIRY, result.get(1).getExpiry());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(2).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(2).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(2).getAppDestination().toString());
- assertEquals(777, result.get(2).getSourceEventId());
- assertEquals(777, result.get(2).getSourcePriority());
- assertEquals(456791, result.get(2).getExpiry());
- verify(mUrlConnection, times(3)).setRequestMethod("POST");
- }
-
- @Test
- public void testRedirectSameDestinationWithDelay_returnAllSuccessfully() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest =
- buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 1, DEFAULT_EXPIRY);
- headersFirstRequest.put("Attribution-Reporting-Redirect",
- List.of("https://bar2.com",
- "https://bar3.com",
- "https://bar4.com",
- "https://bar5.com",
- "https://bar6.com"));
-
- Map<String, List<String>> headersSecondRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 2, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersThirdRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 3, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersFourthRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 4, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersFifthRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 5, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersSixthRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 6, DEFAULT_EXPIRY);
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest)
- .thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersSecondRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersThirdRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersFourthRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersFifthRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersSixthRequest;
- });
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(6, result.size());
- long expected = 1;
- for (long priority :
- result.stream()
- .map(SourceRegistration::getSourcePriority)
- .sorted()
- .collect(Collectors.toList())) {
- Assert.assertEquals(expected++, priority);
- }
- }
-
- @Test
- public void testBasicSourceRequestWithAggregateFilterData() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- assertEquals("{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}",
- result.get(0).getAggregateFilterData());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_tooManyFilters() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{");
- filters.append(IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
- .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
- .collect(Collectors.joining(",")));
- filters.append("}");
- String filterData = " \"filter_data\": " + filters + "\n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_keyTooLong() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING
- + "\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_tooManyValues() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{"
- + "\"filter-string-1\": [\"filter-value-1\"],"
- + "\"filter-string-2\": [");
- filters.append(IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
- .mapToObj(i -> "\"filter-value-" + i + "\"")
- .collect(Collectors.joining(",")));
- filters.append("]}");
- String filterData = " \"filter_data\": " + filters + " \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_valueTooLong() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":[\"1234\",\"" + LONG_FILTER_STRING
- + "\"], \"ctid\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testMissingHeaderButWithRedirect() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Redirect", List.of(ALT_REGISTRATION)))
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + " \"priority\": \"" + DEFAULT_PRIORITY + "\",\n"
- + " \"expiry\": \"" + DEFAULT_EXPIRY + "\",\n"
- + " \"source_event_id\": \"" + DEFAULT_EVENT_ID + "\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicSourceRequestWithAggregateSource() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(
- new JSONArray(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},{\"id\""
- + " : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .toString(),
- result.get(0).getAggregateSource());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_tooManyKeys() throws Exception {
- StringBuilder tooManyKeys = new StringBuilder("[");
- for (int i = 0; i < MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1; i++) {
- tooManyKeys.append(String.format(
- "{\"id\": \"campaign-%1$s\", \"key_piece\": \"0x15%1$s\"}", i));
- }
- tooManyKeys.append("]");
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\":"
- + " \"987654321\",'aggregation_keys': "
- + tooManyKeys)));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_keyIsNotAnObject() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},[\"id\", \"geoValue\", \"key_piece\" ,"
- + " \"0x5\"]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidKeyId() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"" + LONG_AGGREGATE_KEY_ID + "\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidKeyPiece_missingPrefix()
- throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidKeyPiece_tooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"" + LONG_AGGREGATE_KEY_PIECE + "\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_basic_success() throws IOException {
- // Setup
- SourceRegistration expectedResult1 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_1)
- .setSourcePriority(0)
- .setDebugKey(DEBUG_KEY)
- .build();
- SourceRegistration expectedResult2 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_2)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_2
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSourcesSuccessWithoutAdIdPermission() throws IOException {
- // Setup
- SourceRegistration expectedResult1 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_1)
- .setSourcePriority(0)
- .build();
- SourceRegistration expectedResult2 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_2)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_2
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, false);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_oneSuccessAndOneFailure_resultsIntoOneSourceFetched()
- throws IOException {
- // Setup
- SourceRegistration expectedResult2 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_2)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
- // Its validation will fail due to destination mismatch
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- /* wrong destination */
- + "android-app://com.wrongapp"
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_2
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedResult2, result.iterator().next());
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withExtendedHeaders_success() throws IOException, JSONException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String aggregateSource =
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]";
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + OS_DESTINATION
- + "\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"filter_data\": "
- + filterData
- + ", "
- + " \"aggregation_keys\": "
- + aggregateSource
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setAggregateFilterData(filterData)
- .setAggregateSource(new JSONArray(aggregateSource).toString())
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withRedirects_ignoresRedirects() throws IOException, JSONException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String aggregateSource =
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]";
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"filter_data\": "
- + filterData
- + " , "
- + " \"aggregation_keys\": "
- + aggregateSource
- + "}"),
- "Attribution-Reporting-Redirect",
- List.of(ALT_REGISTRATION)));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setAggregateFilterData(filterData)
- .setAggregateSource(new JSONArray(aggregateSource).toString())
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_appDestinationDoNotMatch_failsDropsSource() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\":"
- + " android-app://wrong.os-destination,\n"
- + " \"web_destination\": "
- + WEB_DESTINATION
- + ",\n"
- + " \"filter_data\": "
- + filterData
- + "}")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
-
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_webDestinationDoNotMatch_failsDropsSource() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\": "
- + OS_DESTINATION
- + ",\n"
- + " \"web_destination\": "
- + " https://wrong-web-destination.com,\n"
- + " \"filter_data\": "
- + filterData
- + "}")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
-
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_osAndWebDestinationMatch_recordSourceSuccess() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\": \""
- + OS_DESTINATION
- + "\",\n"
- + "\"web_destination\": \""
- + WEB_DESTINATION
- + "\""
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setWebDestination(WEB_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_extractsTopPrivateDomain() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION_WITH_SUBDOMAIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\": \""
- + OS_DESTINATION
- + "\",\n"
- + "\"web_destination\": \""
- + WEB_DESTINATION_WITH_SUBDOMAIN
- + "\""
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setWebDestination(WEB_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_extractsDestinationBaseUri() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION_WITH_PATH,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + OS_DESTINATION_WITH_PATH
- + "\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"web_destination\": \""
- + WEB_DESTINATION
- + "\""
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setWebDestination(WEB_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_missingDestinations_dropsSource() throws Exception {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withDestinationUriNotHavingScheme_attachesAppScheme()
- throws IOException {
- // Setup
- SourceRegistration expectedResult =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_1)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(
- new HashSet<>(Collections.singletonList(expectedResult)), new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withDestinationUriHavingHttpsScheme_dropsSource()
- throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- // Invalid (https) URI for app destination
- + WEB_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
- verify(mUrlConnection1).setRequestMethod("POST");
- }
-
- @Test
- public void basicSourceRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
- throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- verify(mLogger)
- .logMeasurementRegistrationsResponseSize(
- eq(
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
- 115)
- .setAdTechDomain(DEFAULT_REGISTRATION)
- .build()));
- }
-
- private RegistrationRequest buildRequest(String registrationUri, String topOrigin) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse(registrationUri))
- .setTopOriginUri(Uri.parse(topOrigin))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(true)
- .build();
- }
-
- private RegistrationRequest buildRequestWithoutAdIdPermission(
- String registrationUri, String topOrigin) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse(registrationUri))
- .setTopOriginUri(Uri.parse(topOrigin))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(false)
- .build();
- }
-
- private WebSourceRegistrationRequest buildWebSourceRegistrationRequest(
- List<WebSourceParams> sourceParamsList,
- String topOrigin,
- Uri appDestination,
- Uri webDestination) {
- WebSourceRegistrationRequest.Builder webSourceRegistrationRequestBuilder =
- new WebSourceRegistrationRequest.Builder(sourceParamsList, Uri.parse(topOrigin))
- .setAppDestination(appDestination);
-
- if (webDestination != null) {
- webSourceRegistrationRequestBuilder.setWebDestination(webDestination);
- }
-
- return webSourceRegistrationRequestBuilder.build();
- }
-
- private Map<String, List<String>> buildRegisterSourceDefaultHeader(
- String destination, long eventId, long priority, long expiry) {
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + destination + "\",\n"
- + "\"source_event_id\": \"" + eventId + "\",\n"
- + "\"priority\": \"" + priority + "\",\n"
- + "\"expiry\": " + expiry + ""
- + "}\n"));
- return headersFirstRequest;
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java
deleted file mode 100644
index e944fef627..0000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
-
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-
-/**
- * Unit tests for {@link SourceRegistration}
- */
-@SmallTest
-public final class SourceRegistrationTest {
- private static final Uri TOP_ORIGIN = Uri.parse("https://foo.com");
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final Long DEBUG_KEY = 2376843L;
-
- private SourceRegistration createExampleResponse() {
-
- return new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setAppDestination(Uri.parse("android-app://baz.com"))
- .setSourceEventId(1234567)
- .setExpiry(2345678)
- .setSourcePriority(345678)
- .setAggregateSource(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData("{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}")
- .setDebugKey(DEBUG_KEY)
- .build();
- }
-
- void verifyExampleResponse(SourceRegistration response) {
- assertEquals("https://foo.com", response.getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals("android-app://baz.com", response.getAppDestination().toString());
- assertEquals(1234567, response.getSourceEventId());
- assertEquals(2345678, response.getExpiry());
- assertEquals(345678, response.getSourcePriority());
- assertEquals(DEBUG_KEY, response.getDebugKey());
- assertEquals(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]",
- response.getAggregateSource());
- assertEquals("{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}",
- response.getAggregateFilterData());
- }
-
- @Test
- public void testCreation() throws Exception {
- verifyExampleResponse(createExampleResponse());
- }
-
- @Test
- public void sourceRegistration_onlyAppDestination_success() {
- Uri destination = Uri.parse("android-app://baz.com");
- SourceRegistration response =
- new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setAppDestination(destination)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals(destination, response.getAppDestination());
- assertNull(response.getWebDestination());
- assertEquals(0, response.getSourceEventId());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, response.getExpiry());
- assertEquals(0, response.getSourcePriority());
- assertNull(response.getAggregateSource());
- assertNull(response.getAggregateFilterData());
- }
-
- @Test
- public void sourceRegistration_onlyWebDestination_success() {
- Uri destination = Uri.parse("https://baz.com");
- SourceRegistration response =
- new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setWebDestination(destination)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals(destination, response.getWebDestination());
- assertNull(response.getAppDestination());
- assertEquals(0, response.getSourceEventId());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, response.getExpiry());
- assertEquals(0, response.getSourcePriority());
- assertNull(response.getAggregateSource());
- assertNull(response.getAggregateFilterData());
- }
-
- @Test
- public void sourceRegistration_bothOsAndWebDestination_success() {
- Uri webDestination = Uri.parse("https://baz.com");
- Uri destination = Uri.parse("android-app://baz.com");
- SourceRegistration response =
- new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setAppDestination(destination)
- .setWebDestination(webDestination)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals(webDestination, response.getWebDestination());
- assertEquals(destination, response.getAppDestination());
- assertEquals(0, response.getSourceEventId());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, response.getExpiry());
- assertEquals(0, response.getSourcePriority());
- assertNull(response.getAggregateSource());
- assertNull(response.getAggregateFilterData());
- }
-
- @Test
- public void sourceRegistration_bothDestinationsNull_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () ->
- new SourceRegistration.Builder()
- .setAppDestination(null)
- .setWebDestination(null)
- .build());
- }
-
- @Test
- public void equals_success() {
- assertEquals(createExampleResponse(), createExampleResponse());
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java
deleted file mode 100644
index e7ae8f09b5..0000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java
+++ /dev/null
@@ -1,1231 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATABLE_TRIGGER_DATA;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_EVENT_TRIGGER_DATA;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.adservices.measurement.RegistrationRequest;
-import android.adservices.measurement.WebTriggerParams;
-import android.adservices.measurement.WebTriggerRegistrationRequest;
-import android.content.Context;
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.service.Flags;
-import com.android.adservices.service.enrollment.EnrollmentData;
-import com.android.adservices.service.stats.AdServicesLogger;
-import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import javax.net.ssl.HttpsURLConnection;
-
-/** Unit tests for {@link TriggerFetcher} */
-@RunWith(MockitoJUnitRunner.class)
-@SmallTest
-public final class TriggerFetcherTest {
- private static final String TRIGGER_URI = "https://foo.com";
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
- .setEnrollmentId("enrollment-id")
- .build();
- private static final String TOP_ORIGIN = "https://baz.com";
- private static final long TRIGGER_DATA = 7;
- private static final long PRIORITY = 1;
- private static final String LONG_FILTER_STRING = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
- private static final long DEDUP_KEY = 100;
- private static final Long DEBUG_KEY = 34787843L;
-
- private static final String DEFAULT_REDIRECT = "https://bar.com";
-
- private static final String EVENT_TRIGGERS_1 =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \""
- + TRIGGER_DATA
- + "\",\n"
- + " \"priority\": \""
- + PRIORITY
- + "\",\n"
- + " \"deduplication_key\": \""
- + DEDUP_KEY
- + "\",\n"
- + " \"filters\": {\n"
- + " \"source_type\": [\"navigation\"],\n"
- + " \"key_1\": [\"value_1\"] \n"
- + " }\n"
- + "}"
- + "]\n";
- private static final String EVENT_TRIGGERS_2 =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \""
- + 11
- + "\",\n"
- + " \"priority\": \""
- + 21
- + "\",\n"
- + " \"deduplication_key\": \""
- + 31
- + "\"}"
- + "]\n";
-
- private static final String ALT_REGISTRATION = "https://bar.com";
- private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
- private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
- private static final WebTriggerParams TRIGGER_REGISTRATION_1 =
- new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
- private static final WebTriggerParams TRIGGER_REGISTRATION_2 =
- new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
- private static final Context CONTEXT =
- InstrumentationRegistry.getInstrumentation().getContext();
-
- TriggerFetcher mFetcher;
- @Mock HttpsURLConnection mUrlConnection;
- @Mock HttpsURLConnection mUrlConnection1;
- @Mock HttpsURLConnection mUrlConnection2;
- @Mock EnrollmentDao mEnrollmentDao;
- @Mock Flags mFlags;
- @Mock AdServicesLogger mLogger;
-
- @Before
- public void setup() {
- mFetcher = spy(new TriggerFetcher(mEnrollmentDao, mFlags, mLogger));
- // For convenience, return the same enrollment-ID since we're using many arbitrary
- // registration URIs and not yet enforcing uniqueness of enrollment.
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
- }
-
- @Test
- public void testBasicTriggerRequest() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- MeasurementRegistrationResponseStats expectedStats =
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
- 221)
- .setAdTechDomain(null)
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
- }
-
- @Test
- public void testTriggerRequest_eventTriggerData_tooManyEntries() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder tooManyEntries = new StringBuilder("[");
- for (int i = 0; i < MAX_ATTRIBUTION_EVENT_TRIGGER_DATA + 1; i++) {
- tooManyEntries.append("{\"trigger_data\": \"2\",\"priority\": \"101\"}");
- }
- tooManyEntries.append("]");
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + tooManyEntries + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequest_failsWhenNotEnrolled() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mFetcher, never()).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestWithDebugKey() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- assertEquals(DEBUG_KEY, result.get(0).getDebugKey());
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicTriggerRequestWithoutAdIdPermission() throws Exception {
- RegistrationRequest request = buildRequestWithoutAdIdPermission(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- assertNull(result.get(0).getDebugKey());
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBadTriggerUrl() throws Exception {
- RegistrationRequest request =
- buildRequest("bad-schema://foo.com", TOP_ORIGIN);
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testBadTriggerConnection() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doThrow(new IOException("Bad internet things"))
- .when(mFetcher).openUrl(new URL(TRIGGER_URI));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, never()).setRequestMethod("POST");
- }
-
- @Test
- public void testBadRequestReturnFailure() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(400);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestMinimumFields() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + "[{}]" + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("[{}]", result.get(0).getEventTriggers());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testNotOverHttps() throws Exception {
- RegistrationRequest request = buildRequest("http://foo.com", TOP_ORIGIN);
- // Non-https should fail.
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testFirst200Next500_ignoreFailureReturnSuccess() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200).thenReturn(500);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest);
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- verify(mUrlConnection, times(2)).setRequestMethod("POST");
- }
-
- @Test
- public void testMissingHeaderButWithRedirect() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT)))
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestWithAggregateTriggerData() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(
- new JSONArray(aggregatable_trigger_data).toString(),
- result.get(0).getAggregateTriggerData());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_tooManyEntries() throws Exception {
- StringBuilder tooManyEntries = new StringBuilder("[");
- for (int i = 0; i < MAX_AGGREGATABLE_TRIGGER_DATA + 1; i++) {
- tooManyEntries.append(String.format(
- "{\"key_piece\": \"0x15%1$s\",\"source_keys\":[\"campaign-%1$s\"]}", i));
- }
- tooManyEntries.append("]");
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + tooManyEntries
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_missingPrefix()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_tooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"" + LONG_AGGREGATE_KEY_PIECE
- + "\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_sourceKeys_notAnArray()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":{\"campaignCounts\": true},"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_sourceKeys_tooManyKeys()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder tooManyKeys = new StringBuilder("[");
- tooManyKeys.append(IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
- .mapToObj(i -> "aggregate-key-" + i)
- .collect(Collectors.joining(",")));
- tooManyKeys.append("]");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\": " + tooManyKeys + ","
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_sourceKeys_invalidKeyId()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\", \""
- + LONG_AGGREGATE_KEY_ID + "\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_tooManyFilters()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{");
- filters.append(IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
- .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
- .collect(Collectors.joining(",")));
- filters.append("}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_keyTooLong() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_tooManyValues()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{"
- + "\"filter-string-1\": [\"filter-value-1\"],"
- + "\"filter-string-2\": [");
- filters.append(IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
- .mapToObj(i -> "\"filter-value-" + i + "\"")
- .collect(Collectors.joining(",")));
- filters.append("]}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_valueTooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyFilters()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{");
- filters.append(IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
- .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
- .collect(Collectors.joining(",")));
- filters.append("}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_keyTooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyValues()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{"
- + "\"filter-string-1\": [\"filter-value-1\"],"
- + "\"filter-string-2\": [");
- filters.append(IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
- .mapToObj(i -> "\"filter-value-" + i + "\"")
- .collect(Collectors.joining(",")));
- filters.append("]}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_valueTooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestWithAggregatableValues() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_values = "{\"campaignCounts\":32768,\"geoValue\":1644}";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_values': "
- + aggregatable_values
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(
- new JSONObject(aggregatable_values).toString(), result.get(0).getAggregateValues());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testTriggerRequestWithAggregatableValues_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_values = "{\"campaignCounts\":32768\"geoValue\":1644}";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_values': "
- + aggregatable_values
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregatableValues_tooManyKeys() throws Exception {
- StringBuilder tooManyKeys = new StringBuilder("{");
- tooManyKeys.append(IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
- .mapToObj(i -> String.format("\"key-%s\": 12345,", i))
- .collect(Collectors.joining(",")));
- tooManyKeys.append("}");
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'aggregatable_values': " + tooManyKeys + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregatableValues_invalidKeyId() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_values = "{\"campaignCounts\":32768, \"" + LONG_AGGREGATE_KEY_ID
- + "\":1644}";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_values': "
- + aggregatable_values
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchTrigger_withReportingFilters_success() throws IOException, JSONException {
- // Setup
- String filters =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}";
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'filters': " + filters + "}")));
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONObject(filters).toString(), result.get(0).getFilters());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggers_basic_success() throws IOException, JSONException {
- // Setup
- TriggerRegistration expectedResult1 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .setDebugKey(DEBUG_KEY)
- .build();
- TriggerRegistration expectedResult2 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_2).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Arrays.asList(TRIGGER_REGISTRATION_1, TRIGGER_REGISTRATION_2), TOP_ORIGIN);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
- when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + EVENT_TRIGGERS_2 + "}")));
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggerSuccessWithoutAdIdPermission() throws IOException, JSONException {
- // Setup
- TriggerRegistration expectedResult1 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
- TriggerRegistration expectedResult2 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_2).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Arrays.asList(TRIGGER_REGISTRATION_1, TRIGGER_REGISTRATION_2), TOP_ORIGIN);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
- when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + EVENT_TRIGGERS_2 + "}")));
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, false);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggers_withExtendedHeaders_success() throws IOException, JSONException {
- // Setup
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String aggregatableValues = "{\"campaignCounts\":32768,\"geoValue\":1644}";
- String filters =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}";
- String aggregatableTriggerData =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'filters': "
- + filters
- + ", 'aggregatable_values': "
- + aggregatableValues
- + ", 'aggregatable_trigger_data': "
- + aggregatableTriggerData
- + "}")));
- TriggerRegistration expectedResult =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .setFilters(new JSONObject(filters).toString())
- .setAggregateTriggerData(new JSONArray(aggregatableTriggerData).toString())
- .setAggregateValues(new JSONObject(aggregatableValues).toString())
- .build();
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedResult, result.get(0));
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggers_withRedirects_ignoresRedirects()
- throws IOException, JSONException {
- // Setup
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + EVENT_TRIGGERS_1 + "}"),
- "Attribution-Reporting-Redirect",
- List.of(ALT_REGISTRATION)));
- TriggerRegistration expectedResult =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedResult, result.get(0));
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void basicTriggerRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- MeasurementRegistrationResponseStats expectedStats =
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
- 221)
- .setAdTechDomain(TRIGGER_URI)
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
- }
-
- private RegistrationRequest buildRequest(String triggerUri, String topOriginUri) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
- .setRegistrationUri(Uri.parse(triggerUri))
- .setTopOriginUri(Uri.parse(topOriginUri))
- .setPackageName(CONTEXT.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(true)
- .build();
- }
-
- private RegistrationRequest buildRequestWithoutAdIdPermission(
- String triggerUri, String topOriginUri) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
- .setRegistrationUri(Uri.parse(triggerUri))
- .setTopOriginUri(Uri.parse(topOriginUri))
- .setPackageName(CONTEXT.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(false)
- .build();
- }
-
- private WebTriggerRegistrationRequest buildWebTriggerRegistrationRequest(
- List<WebTriggerParams> triggerParams, String topOrigin) {
- return new WebTriggerRegistrationRequest.Builder(triggerParams, Uri.parse(topOrigin))
- .build();
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java
deleted file mode 100644
index d42f96fc6d..0000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-
-/**
- * Unit tests for {@link TriggerRegistration}
- */
-@SmallTest
-public final class TriggerRegistrationTest {
- private static final Uri TOP_ORIGIN = Uri.parse("https://foo.com");
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final String TOP_LEVEL_FILTERS_JSON_STRING =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}\n";
- private static final String EVENT_TRIGGERS =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \"1\",\n"
- + " \"priority\": \"345678\",\n"
- + " \"deduplication_key\": \"2345678\",\n"
- + " \"filters\": {\n"
- + " \"source_type\": [\"navigation\"],\n"
- + " \"key_1\": [\"value_1\"] \n"
- + " }\n"
- + "}"
- + "]\n";
-
- private static final Long DEBUG_KEY = 23478951L;
-
- private static final String AGGREGATE_TRIGGER_DATA =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
-
- private TriggerRegistration createExampleResponse() {
- return new TriggerRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setEventTriggers(EVENT_TRIGGERS)
- .setAggregateTriggerData(AGGREGATE_TRIGGER_DATA)
- .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
- .setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
- .setDebugKey(DEBUG_KEY)
- .build();
- }
-
- void verifyExampleResponse(TriggerRegistration triggerRegistration) {
- assertEquals("https://foo.com", triggerRegistration.getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, triggerRegistration.getEnrollmentId());
- assertEquals(EVENT_TRIGGERS, triggerRegistration.getEventTriggers());
- assertEquals(AGGREGATE_TRIGGER_DATA, triggerRegistration.getAggregateTriggerData());
- assertEquals(
- "{\"campaignCounts\":32768,\"geoValue\":1644}",
- triggerRegistration.getAggregateValues());
- assertEquals(TOP_LEVEL_FILTERS_JSON_STRING, triggerRegistration.getFilters());
- assertEquals(DEBUG_KEY, triggerRegistration.getDebugKey());
- }
-
- @Test
- public void testCreation() throws Exception {
- verifyExampleResponse(createExampleResponse());
- }
-
- @Test
- public void testDefaults() throws Exception {
- TriggerRegistration response =
- new TriggerRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertNull(response.getEventTriggers());
- assertNull(response.getAggregateTriggerData());
- assertNull(response.getAggregateValues());
- assertNull(response.getFilters());
- }
-
- @Test
- public void equals_success() {
- assertEquals(createExampleResponse(), createExampleResponse());
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java
index 8757ab94d1..781224d349 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_AGGREGATE_FALLBACK_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,33 +36,31 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link AggregateFallbackReportingJobService
*/
public class AggregateFallbackReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
- private static final long WAIT_IN_MILLIS = 50L;
+ private static final long WAIT_IN_MILLIS = 1_000L;
private DatastoreManager mMockDatastoreManager;
private JobScheduler mMockJobScheduler;
@@ -77,10 +76,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -97,10 +97,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ disableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -117,11 +118,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -149,11 +150,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -181,11 +182,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -213,11 +214,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -240,11 +241,37 @@ public class AggregateFallbackReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(
+ () ->
+ AggregateFallbackReportingJobService.schedule(
+ any(), any()));
+ AggregateFallbackReportingJobService.schedule(
+ mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
- .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(AdServicesConfig.class)
.spyStatic(AggregateFallbackReportingJobService.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -256,6 +283,14 @@ public class AggregateFallbackReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementAggregateMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementAggregateFallbackReportingJobPeriodMs);
+ ExtendedMockito.doReturn("http://example.com")
+ .when(AdServicesConfig::getMeasurementAggregateEncryptionKeyCoordinatorUrl);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing()
@@ -277,10 +312,10 @@ public class AggregateFallbackReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_aggregate_fallback_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobAggregateFallbackReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java
index 3d79b2e089..d285a741ae 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java
@@ -22,8 +22,10 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.android.adservices.HpkeJni;
+import com.android.adservices.service.measurement.aggregation.AggregateCryptoConverter;
import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -50,8 +52,8 @@ public class AggregateReportBodyTest {
private static final String SCHEDULED_REPORT_TIME = "1246174158155";
private static final String VERSION = "12";
private static final String REPORT_ID = "A1";
- private static final Long SOURCE_DEBUG_KEY = 27628792L;
- private static final Long TRIGGER_DEBUG_KEY = 23443234L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(27628792L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(23443234L);
private static final String REPORTING_ORIGIN = "https://adtech.domain";
private static final String DEBUG_CLEARTEXT_PAYLOAD = "{\"operation\":\"histogram\","
+ "\"data\":[{\"bucket\":1369,\"value\":32768},{\"bucket\":3461,"
@@ -123,10 +125,8 @@ public class AggregateReportBodyTest {
assertEquals(REPORTING_ORIGIN, sharedInfoJson.get("reporting_origin"));
assertEquals(ATTRIBUTION_DESTINATION, sharedInfoJson.get("attribution_destination"));
assertEquals(SOURCE_REGISTRATION_TIME, sharedInfoJson.get("source_registration_time"));
- assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY), aggregateJson.get("source_debug_key"));
- assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY), aggregateJson.get("trigger_debug_key"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), aggregateJson.get("source_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), aggregateJson.get("trigger_debug_key"));
}
@Test
@@ -158,8 +158,7 @@ public class AggregateReportBodyTest {
assertEquals(REPORTING_ORIGIN, sharedInfoJson.get("reporting_origin"));
assertEquals(ATTRIBUTION_DESTINATION, sharedInfoJson.get("attribution_destination"));
assertEquals(SOURCE_REGISTRATION_TIME, sharedInfoJson.get("source_registration_time"));
- assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY), aggregateJson.get("source_debug_key"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), aggregateJson.get("source_debug_key"));
assertNull(aggregateJson.opt("trigger_debug_key"));
}
@@ -177,8 +176,7 @@ public class AggregateReportBodyTest {
assertEquals(ATTRIBUTION_DESTINATION, sharedInfoJson.get("attribution_destination"));
assertEquals(SOURCE_REGISTRATION_TIME, sharedInfoJson.get("source_registration_time"));
assertNull(aggregateJson.opt("source_debug_key"));
- assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY), aggregateJson.get("trigger_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), aggregateJson.get("trigger_debug_key"));
}
@Test
@@ -196,13 +194,50 @@ public class AggregateReportBodyTest {
assertEncryptedPayload(aggregateServicePayloads);
}
- private void assertEncodedDebugPayload(JSONObject aggregateServicePayloads) throws Exception {
- final String encodedPayloadBase64 =
- (String) aggregateServicePayloads.get("debug_cleartext_payload");
- assertNotNull(encodedPayloadBase64);
+ @Test
+ public void testAggregationServicePayloadsJsonSerializationWithDebugKey() throws Exception {
+ AggregateReportBody aggregateReport =
+ createAggregateReportBodyExampleWithSingleTriggerDebugKey();
+
+ AggregateEncryptionKey key = AggregateCryptoFixture.getKey();
+ JSONArray aggregationServicePayloadsJson =
+ aggregateReport.aggregationServicePayloadsToJson(/* sharedInfo = */ null, key);
+
+ JSONObject aggregateServicePayloads = aggregationServicePayloadsJson.getJSONObject(0);
- final byte[] cborEncodedPayload = Base64.getDecoder().decode(encodedPayloadBase64);
- assertCborEncoded(cborEncodedPayload);
+ assertEquals(key.getKeyId(), aggregateServicePayloads.get("key_id"));
+ assertEquals(
+ AggregateCryptoConverter.encode(DEBUG_CLEARTEXT_PAYLOAD),
+ aggregateServicePayloads.opt("debug_cleartext_payload"));
+ assertEncodedDebugPayload(aggregateServicePayloads);
+ assertEncryptedPayload(aggregateServicePayloads);
+ }
+
+ @Test
+ public void testAggregationServicePayloadsJsonSerializationWithoutDebugKey() throws Exception {
+ AggregateReportBody aggregateReport = createAggregateReportBodyExampleWithNullDebugKeys();
+
+ AggregateEncryptionKey key = AggregateCryptoFixture.getKey();
+ JSONArray aggregationServicePayloadsJson =
+ aggregateReport.aggregationServicePayloadsToJson(/* sharedInfo = */ null, key);
+
+ JSONObject aggregateServicePayloads = aggregationServicePayloadsJson.getJSONObject(0);
+
+ assertEquals(key.getKeyId(), aggregateServicePayloads.get("key_id"));
+ assertNull(aggregateServicePayloads.opt("debug_cleartext_payload"));
+ assertEncodedDebugPayload(aggregateServicePayloads);
+ assertEncryptedPayload(aggregateServicePayloads);
+ }
+
+ private void assertEncodedDebugPayload(JSONObject aggregateServicePayloads) throws Exception {
+ if (!aggregateServicePayloads.isNull("debug_cleartext_payload")) {
+ final String encodedPayloadBase64 =
+ (String) aggregateServicePayloads.get("debug_cleartext_payload");
+ assertNotNull(encodedPayloadBase64);
+
+ final byte[] cborEncodedPayload = Base64.getDecoder().decode(encodedPayloadBase64);
+ assertCborEncoded(cborEncodedPayload);
+ }
}
private void assertEncryptedPayload(JSONObject aggregateServicePayloads) throws Exception {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java
index 9ff8b9b544..5d0a1372b9 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java
@@ -23,6 +23,8 @@ import android.net.Uri;
import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.google.common.truth.Truth;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Rule;
@@ -79,7 +81,7 @@ public class AggregateReportSenderTest {
createAggregateReportBodyExample1().toJson(AggregateCryptoFixture.getKey());
Uri reportingOrigin = Uri.parse(REPORTING_ORIGIN);
- AggregateReportSender aggregateReportSender = new AggregateReportSender();
+ AggregateReportSender aggregateReportSender = new AggregateReportSender(false);
AggregateReportSender spyAggregateReportSender = Mockito.spy(aggregateReportSender);
Mockito.doReturn(httpUrlConnection).when(spyAggregateReportSender)
@@ -98,8 +100,16 @@ public class AggregateReportSenderTest {
URL spyUrl = Mockito.spy(new URL("https://foo"));
Mockito.doReturn(mockConnection).when(spyUrl).openConnection();
- AggregateReportSender aggregateReportSender = new AggregateReportSender();
+ AggregateReportSender aggregateReportSender = new AggregateReportSender(false);
HttpURLConnection connection = aggregateReportSender.createHttpUrlConnection(spyUrl);
assertEquals(mockConnection, connection);
}
+
+ @Test
+ public void testDebugReportUriPath() {
+ Truth.assertThat(new AggregateReportSender(false).getReportUriPath())
+ .isEqualTo(AggregateReportSender.AGGREGATE_ATTRIBUTION_REPORT_URI_PATH);
+ Truth.assertThat(new AggregateReportSender(true).getReportUriPath())
+ .isEqualTo(AggregateReportSender.DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java
index 09e4a71b5e..f0d3f8220a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java
@@ -19,11 +19,12 @@ package com.android.adservices.service.measurement.reporting;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
@@ -93,7 +94,8 @@ public class AggregateReportingJobHandlerIntegrationTest extends AbstractDbInteg
});
Mockito.doReturn(isEnrolled ? ENROLLMENT : null)
.when(mEnrollmentDao).getEnrollmentData(Mockito.any());
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
AggregateReportingJobHandler spyReportingService =
Mockito.spy(new AggregateReportingJobHandler(
mEnrollmentDao, datastoreManager, mockKeyManager));
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java
index 01a9da6dab..d77bc6d2f4 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java
@@ -42,6 +42,7 @@ import com.android.adservices.service.measurement.aggregation.AggregateCryptoFix
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKeyManager;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
import org.json.JSONObject;
@@ -72,8 +73,8 @@ public class AggregateReportingJobHandlerTest {
private static final String CLEARTEXT_PAYLOAD =
"{\"operation\":\"histogram\",\"data\":[{\"bucket\":1,\"value\":2}]}";
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
protected static final Context sContext = ApplicationProvider.getApplicationContext();
DatastoreManager mDatastoreManager;
@@ -86,6 +87,7 @@ public class AggregateReportingJobHandlerTest {
AggregateReportingJobHandler mAggregateReportingJobHandler;
AggregateReportingJobHandler mSpyAggregateReportingJobHandler;
+ AggregateReportingJobHandler mSpyDebugAggregateReportingJobHandler;
class FakeDatasoreManager extends DatastoreManager {
@@ -118,6 +120,11 @@ public class AggregateReportingJobHandlerTest {
mAggregateReportingJobHandler = new AggregateReportingJobHandler(
mEnrollmentDao, mDatastoreManager, mockKeyManager);
mSpyAggregateReportingJobHandler = Mockito.spy(mAggregateReportingJobHandler);
+ mSpyDebugAggregateReportingJobHandler =
+ Mockito.spy(
+ new AggregateReportingJobHandler(
+ mEnrollmentDao, mDatastoreManager, mockKeyManager)
+ .setDebugReport(true));
}
@Test
@@ -147,13 +154,57 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void testSendReportForPendingDebugReportSuccess()
+ throws DatastoreException, IOException, JSONException {
+ AggregateReport aggregateReport =
+ new AggregateReport.Builder()
+ .setId("aggregateReportId")
+ .setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
+ .setEnrollmentId(ENROLLMENT_ID)
+ .setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ JSONObject aggregateReportBody =
+ new AggregateReportBody.Builder()
+ .setReportId(aggregateReport.getId())
+ .setDebugCleartextPayload(CLEARTEXT_PAYLOAD)
+ .build()
+ .toJson(AggregateCryptoFixture.getKey());
+
+ when(mMeasurementDao.getAggregateReport(aggregateReport.getId()))
+ .thenReturn(aggregateReport);
+ doReturn(HttpURLConnection.HTTP_OK)
+ .when(mSpyDebugAggregateReportingJobHandler)
+ .makeHttpPostRequest(Mockito.any(), Mockito.any());
+ doReturn(aggregateReportBody)
+ .when(mSpyDebugAggregateReportingJobHandler)
+ .createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
+
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateDebugReportDelivered(aggregateReport.getId());
+ Assert.assertEquals(
+ AdServicesStatusUtils.STATUS_SUCCESS,
+ mSpyDebugAggregateReportingJobHandler.performReport(
+ aggregateReport.getId(), AggregateCryptoFixture.getKey()));
+
+ verify(mMeasurementDao, times(1)).markAggregateDebugReportDelivered(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -184,13 +235,16 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -221,13 +275,16 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -259,13 +316,16 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -300,7 +360,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -322,7 +382,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -379,7 +439,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performScheduledPendingReportsInWindow(
1000, 1100));
- verify(mMeasurementDao, times(2)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(2)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(5)).begin();
verify(mTransaction, times(5)).end();
}
@@ -424,7 +484,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performScheduledPendingReportsInWindow(
1000, 1100));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
}
@Test
@@ -445,7 +505,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java
index 9e17489156..d092a50f18 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,33 +36,31 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link AggregateReportingJobService
*/
public class AggregateReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
- private static final long WAIT_IN_MILLIS = 50L;
+ private static final long WAIT_IN_MILLIS = 1_000L;
private DatastoreManager mMockDatastoreManager;
private JobScheduler mMockJobScheduler;
@@ -77,10 +76,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -97,10 +97,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ disableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -117,11 +118,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -148,11 +149,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -179,11 +180,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -210,11 +211,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -236,11 +237,34 @@ public class AggregateReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(() -> AggregateReportingJobService.schedule(any(), any()));
+ AggregateReportingJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
- .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(AdServicesConfig.class)
.spyStatic(AggregateReportingJobService.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -252,6 +276,12 @@ public class AggregateReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementAggregateMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn("http://example.com")
+ .when(AdServicesConfig::getMeasurementAggregateEncryptionKeyCoordinatorUrl);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing()
@@ -273,10 +303,10 @@ public class AggregateReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_aggregate_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobAggregateReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java
new file mode 100644
index 0000000000..c18f231b17
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.measurement.reporting;
+
+import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DEBUG_REPORT_JOB_ID;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.Context;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Unit test for {@link DebugReportingJobService
+ */
+public class DebugReportingJobServiceTest {
+
+ private static final long WAIT_IN_MILLIS = 1000L;
+
+ private DatastoreManager mMockDatastoreManager;
+ private JobScheduler mMockJobScheduler;
+
+ private DebugReportingJobService mSpyService;
+
+ @Before
+ public void setUp() {
+ mSpyService = spy(new DebugReportingJobService());
+ mMockDatastoreManager = mock(DatastoreManager.class);
+ mMockJobScheduler = mock(JobScheduler.class);
+ }
+
+ @Test
+ public void onStartJob_killSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertFalse(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockDatastoreManager, never()).runInTransactionWithResult(any());
+ verify(mSpyService, times(1)).jobFinished(any(), eq(false));
+ verify(mMockJobScheduler, times(1)).cancel(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void onStartJob_killSwitchOff() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ ExtendedMockito.doNothing()
+ .when(
+ () ->
+ DebugReportingJobService.scheduleIfNeeded(
+ any(), anyBoolean()));
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertTrue(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockDatastoreManager, times(2)).runInTransactionWithResult(any());
+ verify(mSpyService, times(1)).jobFinished(any(), anyBoolean());
+ verify(mMockJobScheduler, never()).cancel(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, never())
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ true);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ doReturn(/* noJobInfo = */ null)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(mockContext, false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(DebugReportingJobService.class)
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ try {
+ // Setup mock everything in job
+ mMockDatastoreManager = mock(DatastoreManager.class);
+ doReturn(Optional.empty())
+ .when(mMockDatastoreManager)
+ .runInTransactionWithResult(any());
+ doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
+ doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
+ ExtendedMockito.doReturn(mMockDatastoreManager)
+ .when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
+ ExtendedMockito.doNothing().when(() -> DebugReportingJobService.schedule(any(), any()));
+
+ // Execute
+ execute.run();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private void enableKillSwitch() {
+ toggleKillSwitch(true);
+ }
+
+ private void disableKillSwitch() {
+ toggleKillSwitch(false);
+ }
+
+ private void toggleKillSwitch(boolean value) {
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobDebugReportingKillSwitch();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java
index 0fb5730cc3..84f2bbfa74 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,33 +36,31 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link EventFallbackReportingJobService
*/
public class EventFallbackReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
- private static final long WAIT_IN_MILLIS = 50L;
+ private static final long WAIT_IN_MILLIS = 1_000L;
private DatastoreManager mMockDatastoreManager;
private JobScheduler mMockJobScheduler;
@@ -77,10 +76,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -97,11 +97,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
ExtendedMockito.doNothing()
.when(
() ->
@@ -124,11 +124,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -155,11 +155,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -186,11 +186,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -218,11 +218,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -245,11 +245,34 @@ public class EventFallbackReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(() -> EventFallbackReportingJobService.schedule(any(), any()));
+ EventFallbackReportingJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
.spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
.spyStatic(EventFallbackReportingJobService.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -261,6 +284,12 @@ public class EventFallbackReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementEventMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementEventFallbackReportingJobPeriodMs);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing()
@@ -282,10 +311,10 @@ public class EventFallbackReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_event_fallback_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobEventFallbackReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
index cd590525d1..c12ee6ea6b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
@@ -19,6 +19,8 @@ package com.android.adservices.service.measurement.reporting;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
@@ -26,94 +28,72 @@ import org.junit.Test;
public class EventReportPayloadTest {
private static final String ATTRIBUTION_DESTINATION = "https://toasters.example";
- private static final String SOURCE_EVENT_ID = "12345";
- private static final String TRIGGER_DATA = "2";
+ private static final UnsignedLong SOURCE_EVENT_ID = new UnsignedLong(12345L);
+ private static final UnsignedLong TRIGGER_DATA = new UnsignedLong(2L);
private static final String REPORT_ID = "678";
private static final String SOURCE_TYPE = "event";
private static final double RANDOMIZED_TRIGGER_RATE = 0.0024;
- private static final Long SOURCE_DEBUG_KEY = 3894783L;
- private static final Long TRIGGER_DEBUG_KEY = 2387222L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(3894783L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(2387222L);
- private EventReportPayload createEventReportPayloadExample1() {
+ private static EventReportPayload createEventReportPayload(UnsignedLong triggerData,
+ UnsignedLong sourceDebugKey, UnsignedLong triggerDebugKey) {
return new EventReportPayload.Builder()
.setAttributionDestination(ATTRIBUTION_DESTINATION)
.setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
+ .setTriggerData(triggerData)
.setReportId(REPORT_ID)
.setSourceType(SOURCE_TYPE)
.setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setSourceDebugKey(SOURCE_DEBUG_KEY)
- .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
- .build();
- }
-
- private EventReportPayload createEventReportPayloadWithNullDebugKeys() {
- return new EventReportPayload.Builder()
- .setAttributionDestination(ATTRIBUTION_DESTINATION)
- .setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
- .setReportId(REPORT_ID)
- .setSourceType(SOURCE_TYPE)
- .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setSourceDebugKey(null)
- .setTriggerDebugKey(null)
- .build();
- }
-
- private EventReportPayload createEventReportPayloadWithSingleTriggerDebugKeys() {
- return new EventReportPayload.Builder()
- .setAttributionDestination(ATTRIBUTION_DESTINATION)
- .setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
- .setReportId(REPORT_ID)
- .setSourceType(SOURCE_TYPE)
- .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
- .build();
- }
-
- private EventReportPayload createEventReportPayloadWithSingleSourceDebugKeys() {
- return new EventReportPayload.Builder()
- .setAttributionDestination(ATTRIBUTION_DESTINATION)
- .setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
- .setReportId(REPORT_ID)
- .setSourceType(SOURCE_TYPE)
- .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setSourceDebugKey(sourceDebugKey)
+ .setTriggerDebugKey(triggerDebugKey)
.build();
}
@Test
public void testEventPayloadJsonSerialization() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadExample1();
+ EventReportPayload eventReport =
+ createEventReportPayload(TRIGGER_DATA, SOURCE_DEBUG_KEY, TRIGGER_DEBUG_KEY);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(ATTRIBUTION_DESTINATION,
eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(RANDOMIZED_TRIGGER_RATE,
eventPayloadReportJson.get("randomized_trigger_rate"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), eventPayloadReportJson.get("source_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), eventPayloadReportJson.get("trigger_debug_key"));
+ }
+
+ @Test
+ public void testEventPayloadJsonSerializationWithNullDebugKeys() throws JSONException {
+ EventReportPayload eventReport = createEventReportPayload(TRIGGER_DATA, null, null);
+ JSONObject eventPayloadReportJson = eventReport.toJson();
+
assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY),
- eventPayloadReportJson.get("source_debug_key"));
+ ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
+ assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
+ assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY),
- eventPayloadReportJson.get("trigger_debug_key"));
+ RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
+ assertNull(eventPayloadReportJson.opt("source_debug_key"));
+ assertNull(eventPayloadReportJson.opt("trigger_debug_key"));
}
@Test
- public void testEventPayloadJsonSerializationWithNullDebugKeys() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadWithNullDebugKeys();
+ public void testEventPayloadJsonSerializationWithNullTriggerData() throws JSONException {
+ EventReportPayload eventReport = createEventReportPayload(null, null, null);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(
ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(new UnsignedLong(0L).toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
@@ -124,39 +104,66 @@ public class EventReportPayloadTest {
@Test
public void testEventPayloadJsonSerializationWithSingleTriggerDebugKeys() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadWithSingleTriggerDebugKeys();
+ EventReportPayload eventReport =
+ createEventReportPayload(TRIGGER_DATA, null, TRIGGER_DEBUG_KEY);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(
ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
assertNull(eventPayloadReportJson.opt("source_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), eventPayloadReportJson.get("trigger_debug_key"));
+ }
+
+ @Test
+ public void testEventPayloadJsonSerialization_debugKeysSourceEventIdAndTriggerDataUse64thBit()
+ throws JSONException {
+ String unsigned64BitIntString = "18446744073709551615";
+ UnsignedLong signed64BitInt = new UnsignedLong(-1L);
+ EventReportPayload eventReport = new EventReportPayload.Builder()
+ .setAttributionDestination(ATTRIBUTION_DESTINATION)
+ .setSourceEventId(signed64BitInt)
+ .setTriggerData(signed64BitInt)
+ .setReportId(REPORT_ID)
+ .setSourceType(SOURCE_TYPE)
+ .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
+ .setSourceDebugKey(signed64BitInt)
+ .setTriggerDebugKey(signed64BitInt)
+ .build();
+ JSONObject eventPayloadReportJson = eventReport.toJson();
+
assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY),
- eventPayloadReportJson.get("trigger_debug_key"));
+ ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.get("source_event_id"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
+ assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
+ assertEquals(
+ RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.opt("source_debug_key"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.get("trigger_debug_key"));
}
@Test
public void testEventPayloadJsonSerializationWithSingleSourceDebugKeys() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadWithSingleSourceDebugKeys();
+ EventReportPayload eventReport =
+ createEventReportPayload(TRIGGER_DATA, SOURCE_DEBUG_KEY, null);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(
ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
assertNull(eventPayloadReportJson.opt("trigger_debug_key"));
- assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY),
- eventPayloadReportJson.get("source_debug_key"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), eventPayloadReportJson.get("source_debug_key"));
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java
index 6d96c9a95c..0fd9d1828d 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java
@@ -16,10 +16,14 @@
package com.android.adservices.service.measurement.reporting;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import android.net.Uri;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
@@ -33,8 +37,8 @@ import java.net.HttpURLConnection;
public class EventReportSenderTest {
private static final String ATTRIBUTION_DESTINATION = "https://toasters.example";
- private static final String SOURCE_EVENT_ID = "12345";
- private static final String TRIGGER_DATA = "2";
+ private static final UnsignedLong SOURCE_EVENT_ID = new UnsignedLong(12345L);
+ private static final UnsignedLong TRIGGER_DATA = new UnsignedLong(2L);
private static final String REPORT_ID = "678";
private static final String SOURCE_TYPE = "event";
private static final double RANDOMIZED_TRIGGER_RATE = 0.0024;
@@ -68,7 +72,7 @@ public class EventReportSenderTest {
Uri reportingOrigin = Uri.parse("https://ad-tech.example");
JSONObject eventReportJson = createEventReportPayloadExample1().toJson();
- EventReportSender eventReportSender = new EventReportSender();
+ EventReportSender eventReportSender = new EventReportSender(false);
EventReportSender spyEventReportSender = Mockito.spy(eventReportSender);
Mockito.doReturn(httpUrlConnection).when(spyEventReportSender)
@@ -80,4 +84,12 @@ public class EventReportSenderTest {
assertEquals(outputStream.toString(), eventReportJson.toString());
assertEquals(responseCode, 200);
}
+
+ @Test
+ public void testDebugReportUriPath() {
+ assertThat(new EventReportSender(false).getReportUriPath())
+ .isEqualTo(EventReportSender.EVENT_ATTRIBUTION_REPORT_URI_PATH);
+ assertThat(new EventReportSender(true).getReportUriPath())
+ .isEqualTo(EventReportSender.DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java
index 76075c2913..18d25f0fe0 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java
@@ -16,11 +16,12 @@
package com.android.adservices.service.measurement.reporting;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import com.android.adservices.service.enrollment.EnrollmentData;
import org.json.JSONException;
@@ -39,6 +40,7 @@ import java.util.Objects;
/** Integration tests for {@link EventReportingJobHandler} */
@RunWith(Parameterized.class)
public class EventReportingJobHandlerIntegrationTest extends AbstractDbIntegrationTest {
+
private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
.setAttributionReportingUrl(List.of("https://ad-tech.com"))
.build();
@@ -74,7 +76,8 @@ public class EventReportingJobHandlerIntegrationTest extends AbstractDbIntegrati
Mockito.doReturn(isEnrolled ? ENROLLMENT : null)
.when(mEnrollmentDao).getEnrollmentData(Mockito.any());
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
EventReportingJobHandler spyReportingService =
Mockito.spy(new EventReportingJobHandler(mEnrollmentDao, datastoreManager));
try {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java
index 8f8fecdaa4..e4a3bc95bf 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.adservices.service.measurement.reporting;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -36,6 +37,8 @@ import com.android.adservices.data.measurement.IMeasurementDao;
import com.android.adservices.data.measurement.ITransaction;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.EventReport;
+import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
import org.json.JSONObject;
@@ -54,8 +57,8 @@ import java.util.List;
/** Unit test for {@link EventReportingJobHandler} */
@RunWith(MockitoJUnitRunner.class)
public class EventReportingJobHandlerTest {
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
.setAttributionReportingUrl(List.of("https://ad-tech.com"))
@@ -72,6 +75,7 @@ public class EventReportingJobHandlerTest {
EventReportingJobHandler mEventReportingJobHandler;
EventReportingJobHandler mSpyEventReportingJobHandler;
+ EventReportingJobHandler mSpyDebugEventReportingJobHandler;
class FakeDatasoreManager extends DatastoreManager {
@Override
@@ -91,6 +95,10 @@ public class EventReportingJobHandlerTest {
when(mEnrollmentDao.getEnrollmentData(any())).thenReturn(ENROLLMENT);
mEventReportingJobHandler = new EventReportingJobHandler(mEnrollmentDao, mDatastoreManager);
mSpyEventReportingJobHandler = Mockito.spy(mEventReportingJobHandler);
+ mSpyDebugEventReportingJobHandler =
+ Mockito.spy(
+ new EventReportingJobHandler(mEnrollmentDao, mDatastoreManager)
+ .setDebugReport(true));
}
@Test
@@ -99,12 +107,17 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -114,13 +127,53 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void testSendReportForPendingDebugReportSuccess()
+ throws DatastoreException, IOException, JSONException {
+ EventReport eventReport =
+ new EventReport.Builder()
+ .setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
+ .setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
+ .setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ JSONObject eventReportPayload =
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
+
+ when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
+ doReturn(HttpURLConnection.HTTP_OK)
+ .when(mSpyDebugEventReportingJobHandler)
+ .makeHttpPostRequest(Mockito.any(), Mockito.any());
+ doReturn(eventReportPayload)
+ .when(mSpyDebugEventReportingJobHandler)
+ .createReportJsonPayload(Mockito.any());
+
+ doNothing().when(mMeasurementDao).markEventDebugReportDelivered(eventReport.getId());
+
+ Assert.assertEquals(
+ AdServicesStatusUtils.STATUS_SUCCESS,
+ mSpyDebugEventReportingJobHandler.performReport(eventReport.getId()));
+
+ verify(mMeasurementDao, times(1)).markEventDebugReportDelivered(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -131,11 +184,16 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -145,13 +203,15 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -162,11 +222,16 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -176,13 +241,15 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -193,12 +260,17 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setSourceDebugKey(null)
.setTriggerDebugKey(null)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -208,12 +280,14 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -224,10 +298,15 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_BAD_REQUEST)
@@ -241,7 +320,7 @@ public class EventReportingJobHandlerTest {
AdServicesStatusUtils.STATUS_IO_ERROR,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, never()).markEventReportDelivered(any());
+ verify(mMeasurementDao, never()).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -259,7 +338,7 @@ public class EventReportingJobHandlerTest {
AdServicesStatusUtils.STATUS_INVALID_ARGUMENT,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, never()).markEventReportDelivered(any());
+ verify(mMeasurementDao, never()).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -270,19 +349,29 @@ public class EventReportingJobHandlerTest {
EventReport eventReport1 =
new EventReport.Builder()
.setId("eventReport1")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setReportTime(1000L)
.build();
JSONObject eventReportPayload1 =
- new EventReportPayload.Builder().setReportId(eventReport1.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport1.getId())
+ .setSourceEventId(eventReport1.getSourceEventId())
+ .build()
+ .toJson();
EventReport eventReport2 =
new EventReport.Builder()
.setId("eventReport2")
+ .setSourceEventId(new UnsignedLong(12345L))
.setStatus(EventReport.Status.PENDING)
.setReportTime(1100L)
.build();
JSONObject eventReportPayload2 =
- new EventReportPayload.Builder().setReportId(eventReport2.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport2.getId())
+ .setSourceEventId(eventReport2.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getPendingEventReportIdsInWindow(1000, 1100))
.thenReturn(List.of(eventReport1.getId(), eventReport2.getId()));
@@ -301,7 +390,7 @@ public class EventReportingJobHandlerTest {
Assert.assertTrue(
mSpyEventReportingJobHandler.performScheduledPendingReportsInWindow(1000, 1100));
- verify(mMeasurementDao, times(2)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(2)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(5)).begin();
verify(mTransaction, times(5)).end();
}
@@ -320,7 +409,7 @@ public class EventReportingJobHandlerTest {
AdServicesStatusUtils.STATUS_INTERNAL_ERROR,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, never()).markEventReportDelivered(any());
+ verify(mMeasurementDao, never()).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java
index bfae948a64..4cee88d060 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,32 +36,30 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link EventReportingJobService
*/
public class EventReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final long WAIT_IN_MILLIS = 50L;
private DatastoreManager mMockDatastoreManager;
@@ -77,10 +76,11 @@ public class EventReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -97,11 +97,11 @@ public class EventReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
ExtendedMockito.doNothing()
.when(
() ->
@@ -124,11 +124,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -155,11 +155,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -186,11 +186,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -217,11 +217,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -243,11 +243,34 @@ public class EventReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(() -> EventReportingJobService.schedule(any(), any()));
+ EventReportingJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
.spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
.spyStatic(EventReportingJobService.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -259,6 +282,10 @@ public class EventReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementEventMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing().when(() -> EventReportingJobService.schedule(any(), any()));
@@ -279,10 +306,8 @@ public class EventReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_event_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobEventReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java
index ffe8b06f02..72e21704ed 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java
@@ -52,7 +52,7 @@ public final class EnrollmentTest {
@Test
public void testMaybeGetEnrollmentId_enrollmentDataNull() {
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI.toString())))
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI)))
.thenReturn(null);
assertEquals(
Optional.empty(),
@@ -61,7 +61,7 @@ public final class EnrollmentTest {
@Test
public void testMaybeGetEnrollmentId_enrollmentDataNonNull() {
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI.toString())))
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI)))
.thenReturn(ENROLLMENT);
assertEquals(
Optional.of(ENROLLMENT.getEnrollmentId()),
@@ -87,7 +87,7 @@ public final class EnrollmentTest {
@Test
public void testMaybeGetReportingOrigin_attributionReportingUrlEmpty() {
EnrollmentData enrollment = new EnrollmentData.Builder().build();
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(ENROLLMENT_ID)))
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(Uri.parse(ENROLLMENT_ID))))
.thenReturn(enrollment);
assertEquals(
Optional.empty(),
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java
index cf07d13611..a5e7094a56 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java
@@ -19,7 +19,7 @@ package com.android.adservices.service.measurement.util;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
+import com.android.adservices.service.measurement.FilterData;
import org.junit.Test;
@@ -31,83 +31,170 @@ import java.util.Map;
public class FilterTest {
@Test
- public void testIsFilterMatchReturnTrue() {
+ public void testIsFilterMatch_nonEmptyValues_returnTrue() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
triggerFilterMap.put("product", Arrays.asList("1234", "2345"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
}
@Test
- public void testIsFilterMatchReturnFalse() {
+ public void testIsFilterMatch_nonEmptyValues_returnFalse() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
- triggerFilterMap.put("product", Arrays.asList("1", "2")); // doesn't match.
+ // Doesn't match
+ triggerFilterMap.put("product", Arrays.asList("1", "2"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
}
@Test
- public void testIsNotFilterMatchReturnTrue() {
+ public void testIsFilterMatch_withEmptyValues_returnTrue() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Collections.emptyList());
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+
+ assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
+ }
+
+ @Test
+ public void testIsFilterMatch_withEmptyValues_returnFalse() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Arrays.asList("1234", "234"));
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ // Doesn't match
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+
+ assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
+ }
+
+ @Test
+ public void testIsFilterMatch_withNegation_nonEmptyValues_returnTrue() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put("conversion_subdomain", Collections.singletonList("electronics"));
- triggerFilterMap.put("product", Arrays.asList("1", "2")); // doesn't match.
+ // Doesn't match
+ triggerFilterMap.put("product", Arrays.asList("1", "2"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
}
@Test
- public void testIsNotFilterMatchReturnFalse() {
+ public void testIsFilterMatch_withNegation_nonEmptyValues_returnFalse() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
triggerFilterMap.put("product", Arrays.asList("1234", "2345"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+
+ assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
+ }
+
+ @Test
+ public void testIsFilterMatch_withNegation_withEmptyValues_returnTrue() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Arrays.asList("1234", "234"));
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put("conversion_subdomain", Collections.singletonList("electronics"));
+ // Matches when negated
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
+ }
+
+ @Test
+ public void testIsFilterMatch_withNegation_withEmptyValues_returnFalse() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Collections.emptyList());
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ // Doesn't match when negated
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java
new file mode 100644
index 0000000000..170b006802
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.measurement.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class UnsignedLongTest {
+ private static final UnsignedLong ULONG_LONG = new UnsignedLong(2L);
+ private static final UnsignedLong ULONG_STR = new UnsignedLong("2");
+ private static final UnsignedLong LARGE = new UnsignedLong("9223372036854775808");
+ private static final UnsignedLong LARGER = new UnsignedLong("9223372036854775809");
+
+ @Test
+ public void testEqualsPass() {
+ assertEquals(ULONG_STR, ULONG_STR);
+ assertEquals(ULONG_STR, ULONG_LONG);
+ assertEquals(ULONG_LONG, ULONG_LONG);
+ }
+
+ @Test
+ public void testEqualsFail() {
+ assertNotEquals(ULONG_LONG, new UnsignedLong(3L));
+ assertNotEquals(ULONG_STR, new UnsignedLong(3L));
+ }
+
+ @Test
+ public void testCompareTo_equal() {
+ assertTrue(ULONG_LONG.compareTo(ULONG_STR) == 0);
+ assertTrue(LARGE.compareTo(new UnsignedLong("9223372036854775808")) == 0);
+ }
+
+ @Test
+ public void testCompareTo_smaller() {
+ assertTrue(ULONG_LONG.compareTo(new UnsignedLong(3L)) < 0);
+ assertTrue(ULONG_LONG.compareTo(LARGE) < 0);
+ assertTrue(LARGE.compareTo(LARGER) < 0);
+ }
+
+ @Test
+ public void testCompareTo_larger() {
+ assertTrue(ULONG_LONG.compareTo(new UnsignedLong(1L)) > 0);
+ assertTrue(LARGE.compareTo(ULONG_LONG) > 0);
+ assertTrue(LARGER.compareTo(LARGE) > 0);
+ }
+
+ @Test
+ public void testHashCode_equals() {
+ assertEquals(ULONG_STR.hashCode(), ULONG_STR.hashCode());
+ assertEquals(ULONG_STR.hashCode(), ULONG_LONG.hashCode());
+ assertEquals(ULONG_LONG.hashCode(), ULONG_LONG.hashCode());
+ }
+
+ @Test
+ public void testHashCode_notEquals() {
+ assertNotEquals(ULONG_LONG.hashCode(), new UnsignedLong(3L).hashCode());
+ assertNotEquals(ULONG_STR.hashCode(), new UnsignedLong(3L).hashCode());
+ }
+
+ @Test
+ public void testValidateConstructorArgument_long() {
+ Long arg = null;
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new UnsignedLong(arg));
+ }
+
+ @Test
+ public void testValidateConstructorArgument_string() {
+ String arg = null;
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new UnsignedLong(arg));
+ }
+
+ @Test
+ public void testMod() {
+ // uint64 18446744073709501613 = long -50003
+ // 18446744073709501613 % 8 = 5
+ assertEquals(new UnsignedLong(5L), new UnsignedLong(-50003L).mod(8));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("18446744073709551615", new UnsignedLong(-1L).toString());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java
index 2b2bc00b7f..e16c1d8c3e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java
@@ -36,13 +36,15 @@ public class WebTest {
private static final String HTTPS_SCHEME = "https";
private static final String HTTP_SCHEME = "http";
private static final String INVALID_URL = "invalid url";
+ private static final String PATH = "path";
+ private static final String SECOND_PATH = "second_path";
@Test
public void testTopPrivateDomainAndScheme_ValidPublicDomainAndHttpsScheme() {
String inputUrl = String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN);
Uri expectedUri = Uri.parse(inputUrl);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -52,7 +54,7 @@ public class WebTest {
String inputUrl = String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PRIVATE_DOMAIN);
Uri expectedUri = Uri.parse(inputUrl);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -63,7 +65,7 @@ public class WebTest {
HTTPS_SCHEME, SUBDOMAIN, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN);
Uri expectedUri = Uri.parse(String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN));
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -74,7 +76,7 @@ public class WebTest {
HTTPS_SCHEME, SUBDOMAIN, TOP_PRIVATE_DOMAIN, VALID_PRIVATE_DOMAIN);
Uri expectedUri = Uri.parse(String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PRIVATE_DOMAIN));
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -84,7 +86,7 @@ public class WebTest {
String inputUrl = String.format("%s://%s.%s",
HTTP_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN);
Uri expectedUri = Uri.parse(inputUrl);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -92,13 +94,68 @@ public class WebTest {
@Test
public void testTopPrivateDomainAndScheme_InvalidTldAndHttpsScheme() {
String inputUrl = String.format("%s://%s.%s", HTTP_SCHEME, TOP_PRIVATE_DOMAIN, INVALID_TLD);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertFalse(output.isPresent());
}
@Test
public void testTopPrivateDomainAndScheme_InvalidUrl() {
- Optional output = Web.topPrivateDomainAndScheme(INVALID_URL);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(INVALID_URL);
+ assertFalse(output.isPresent());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForDomainAndPath_ReturnsDomainAndPath() {
+ String inputUrl =
+ String.format(
+ "%s://%s.%s/%s",
+ HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH);
+ Uri expectedUri = Uri.parse(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainSchemeAndPath(Uri.parse(inputUrl));
+ assertTrue(output.isPresent());
+ assertEquals(expectedUri, output.get());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForSubdomain_DoesNotReturnSubdomain() {
+ String inputUrl =
+ String.format(
+ "%s://%s.%s.%s/%s",
+ HTTPS_SCHEME, SUBDOMAIN, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH);
+ Uri uri = Uri.parse(inputUrl);
+ String expectedUri =
+ String.format(
+ "%s://%s.%s/%s",
+ HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH);
+ Optional<Uri> output = Web.topPrivateDomainSchemeAndPath(uri);
+ assertTrue(output.isPresent());
+ assertEquals(expectedUri, output.get().toString());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForMultiplePaths_ReturnsMultipleTokens() {
+ String inputUrl =
+ String.format(
+ "%s://%s.%s.%s/%s/%s",
+ HTTPS_SCHEME,
+ SUBDOMAIN,
+ TOP_PRIVATE_DOMAIN,
+ VALID_PUBLIC_DOMAIN,
+ PATH,
+ SECOND_PATH);
+ Uri uri = Uri.parse(inputUrl);
+ String expectedUri =
+ String.format(
+ "%s://%s.%s/%s/%s",
+ HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH, SECOND_PATH);
+ Optional<Uri> output = Web.topPrivateDomainSchemeAndPath(uri);
+ assertTrue(output.isPresent());
+ assertEquals(expectedUri, output.get().toString());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForInvalidUri_ReturnsEmptyOptional() {
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(INVALID_URL);
assertFalse(output.isPresent());
}
}