| /* |
| * 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.data.measurement; |
| |
| import static com.android.adservices.data.measurement.MeasurementTables.ALL_MSMT_TABLES; |
| import static com.android.adservices.data.measurement.MeasurementTables.AsyncRegistrationContract; |
| import static com.android.adservices.data.measurement.MeasurementTables.AttributionContract; |
| import static com.android.adservices.data.measurement.MeasurementTables.EventReportContract; |
| import static com.android.adservices.data.measurement.MeasurementTables.MSMT_TABLE_PREFIX; |
| import static com.android.adservices.data.measurement.MeasurementTables.SourceContract; |
| import static com.android.adservices.data.measurement.MeasurementTables.TriggerContract; |
| import static com.android.adservices.data.measurement.MeasurementTables.XnaIgnoredSourcesContract; |
| import static com.android.adservices.service.Flags.MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS; |
| import static com.android.adservices.service.Flags.MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW; |
| import static com.android.adservices.service.Flags.MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS; |
| import static com.android.adservices.service.measurement.SourceFixture.ValidSourceParams.SHARED_AGGREGATE_KEYS; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| 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 static org.junit.Assert.fail; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.spy; |
| |
| import android.adservices.measurement.DeletionRequest; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.database.DatabaseUtils; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.net.Uri; |
| import android.util.Pair; |
| |
| import androidx.test.core.app.ApplicationProvider; |
| |
| import com.android.adservices.common.WebUtil; |
| import com.android.adservices.data.measurement.MeasurementTables.DebugReportContract; |
| import com.android.adservices.service.Flags; |
| import com.android.adservices.service.FlagsFactory; |
| import com.android.adservices.service.measurement.AsyncRegistrationFixture; |
| import com.android.adservices.service.measurement.AsyncRegistrationFixture.ValidAsyncRegistrationParams; |
| import com.android.adservices.service.measurement.AttributedTrigger; |
| import com.android.adservices.service.measurement.Attribution; |
| import com.android.adservices.service.measurement.EventReport; |
| import com.android.adservices.service.measurement.EventReportFixture; |
| import com.android.adservices.service.measurement.EventSurfaceType; |
| import com.android.adservices.service.measurement.KeyValueData; |
| import com.android.adservices.service.measurement.KeyValueData.DataType; |
| 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.TriggerSpecs; |
| import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey; |
| import com.android.adservices.service.measurement.aggregation.AggregateReport; |
| import com.android.adservices.service.measurement.aggregation.AggregateReportFixture; |
| import com.android.adservices.service.measurement.noising.SourceNoiseHandler; |
| import com.android.adservices.service.measurement.registration.AsyncRegistration; |
| import com.android.adservices.service.measurement.reporting.DebugReport; |
| import com.android.adservices.service.measurement.reporting.EventReportWindowCalcDelegate; |
| import com.android.adservices.service.measurement.util.UnsignedLong; |
| import com.android.adservices.shared.errorlogging.AdServicesErrorLogger; |
| import com.android.dx.mockito.inline.extended.ExtendedMockito; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMultiset; |
| |
| import org.json.JSONException; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentMatchers; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoSession; |
| import org.mockito.junit.MockitoJUnitRunner; |
| import org.mockito.quality.Strictness; |
| |
| import java.time.Instant; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| import java.util.stream.Stream; |
| |
| @RunWith(MockitoJUnitRunner.class) |
| public class MeasurementDaoTest { |
| protected static final Context sContext = ApplicationProvider.getApplicationContext(); |
| private static final Uri APP_TWO_SOURCES = Uri.parse("android-app://com.example1.two-sources"); |
| private static final Uri APP_ONE_SOURCE = Uri.parse("android-app://com.example2.one-source"); |
| private static final String DEFAULT_ENROLLMENT_ID = "enrollment-id"; |
| private static final Uri APP_TWO_PUBLISHER = |
| Uri.parse("android-app://com.publisher2.two-sources"); |
| private static final Uri APP_ONE_PUBLISHER = |
| Uri.parse("android-app://com.publisher1.one-source"); |
| private static final Uri APP_NO_PUBLISHER = |
| Uri.parse("android-app://com.publisher3.no-sources"); |
| private static final Uri APP_BROWSER = Uri.parse("android-app://com.example1.browser"); |
| private static final Uri WEB_ONE_DESTINATION = WebUtil.validUri("https://www.example1.test"); |
| private static final Uri WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN = |
| WebUtil.validUri("https://store.example1.test"); |
| private static final Uri WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2 = |
| WebUtil.validUri("https://foo.example1.test"); |
| private static final Uri WEB_TWO_DESTINATION = WebUtil.validUri("https://www.example2.test"); |
| private static final Uri WEB_TWO_DESTINATION_WITH_PATH = |
| WebUtil.validUri("https://www.example2.test/ad/foo"); |
| private static final Uri APP_ONE_DESTINATION = |
| Uri.parse("android-app://com.example1.one-trigger"); |
| private static final Uri APP_TWO_DESTINATION = |
| Uri.parse("android-app://com.example1.two-triggers"); |
| private static final Uri APP_THREE_DESTINATION = |
| Uri.parse("android-app://com.example1.three-triggers"); |
| private static final Uri APP_THREE_DESTINATION_PATH1 = |
| Uri.parse("android-app://com.example1.three-triggers/path1"); |
| private static final Uri APP_THREE_DESTINATION_PATH2 = |
| Uri.parse("android-app://com.example1.three-triggers/path2"); |
| private static final Uri APP_NO_TRIGGERS = Uri.parse("android-app://com.example1.no-triggers"); |
| private static final Uri INSTALLED_PACKAGE = Uri.parse("android-app://com.example.installed"); |
| private static final Uri WEB_PUBLISHER_ONE = WebUtil.validUri("https://not.example.test"); |
| private static final Uri WEB_PUBLISHER_TWO = WebUtil.validUri("https://notexample.test"); |
| // Differs from WEB_PUBLISHER_ONE by scheme. |
| private static final Uri WEB_PUBLISHER_THREE = WebUtil.validUri("http://not.example.test"); |
| private static final Uri APP_DESTINATION = Uri.parse("android-app://com.destination.example"); |
| private static final Uri REGISTRATION_ORIGIN = |
| WebUtil.validUri("https://subdomain.example.test"); |
| |
| private static final Uri REGISTRANT = Uri.parse("android-app://com.example.abc"); |
| private static final Uri INSTALLED_REGISTRANT = Uri.parse("android-app://installed-registrant"); |
| private static final Uri NOT_INSTALLED_REGISTRANT = |
| Uri.parse("android-app://not-installed-registrant"); |
| |
| private static final long INSERTION_TIME = 1617297798; |
| |
| // Fake ID count for initializing triggers. |
| private int mValueId = 1; |
| private MockitoSession mStaticMockSession; |
| private Flags mFlags; |
| private DatastoreManager mDatastoreManager; |
| public static final Uri REGISTRATION_ORIGIN_2 = |
| WebUtil.validUri("https://subdomain_2.example.test"); |
| |
| @Before |
| public void before() { |
| mStaticMockSession = |
| ExtendedMockito.mockitoSession() |
| .spyStatic(FlagsFactory.class) |
| .spyStatic(MeasurementDbHelper.class) |
| .strictness(Strictness.WARN) |
| .startMocking(); |
| ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags); |
| mFlags = FlagsFactory.getFlagsForTest(); |
| mDatastoreManager = |
| new SQLDatastoreManager( |
| MeasurementDbHelper.getInstance(sContext), |
| Mockito.mock(AdServicesErrorLogger.class)); |
| } |
| |
| @After |
| public void cleanup() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| for (String table : ALL_MSMT_TABLES) { |
| db.delete(table, null, null); |
| } |
| |
| mStaticMockSession.finishMocking(); |
| } |
| |
| @Test |
| public void testInsertSource() { |
| Source validSource = |
| SourceFixture.getValidSourceBuilder() |
| .setEventReportWindows("{'start_time': 1, 'end_times': ['3600', '7200']}") |
| .setStatus(Source.Status.MARKED_TO_DELETE) |
| .build(); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource)); |
| |
| String sourceId = getFirstSourceIdFromDatastore(); |
| Source source = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> measurementDao.getSource(sourceId)) |
| .get(); |
| |
| assertNotNull(source); |
| assertNotNull(source.getId()); |
| assertNull(source.getAppDestinations()); |
| assertNull(source.getWebDestinations()); |
| assertEquals(validSource.getEnrollmentId(), source.getEnrollmentId()); |
| assertEquals(validSource.getRegistrant(), source.getRegistrant()); |
| assertEquals(validSource.getEventTime(), source.getEventTime()); |
| assertEquals(validSource.getExpiryTime(), source.getExpiryTime()); |
| assertEquals(validSource.getStatus(), source.getStatus()); |
| assertEquals(validSource.getEventReportWindow(), source.getEventReportWindow()); |
| assertEquals( |
| validSource.getAggregatableReportWindow(), source.getAggregatableReportWindow()); |
| assertEquals(validSource.getPriority(), source.getPriority()); |
| assertEquals(validSource.getSourceType(), source.getSourceType()); |
| assertEquals( |
| validSource.getInstallAttributionWindow(), source.getInstallAttributionWindow()); |
| assertEquals(validSource.getInstallCooldownWindow(), source.getInstallCooldownWindow()); |
| assertEquals(validSource.getAttributionMode(), source.getAttributionMode()); |
| assertEquals(validSource.getAggregateSource(), source.getAggregateSource()); |
| assertEquals(validSource.getFilterDataString(), source.getFilterDataString()); |
| assertEquals(validSource.getSharedFilterDataKeys(), source.getSharedFilterDataKeys()); |
| assertEquals(validSource.getAggregateContributions(), source.getAggregateContributions()); |
| assertEquals(validSource.isDebugReporting(), source.isDebugReporting()); |
| assertEquals(validSource.getSharedAggregationKeys(), source.getSharedAggregationKeys()); |
| assertEquals(validSource.getRegistrationId(), source.getRegistrationId()); |
| assertEquals(validSource.getInstallTime(), source.getInstallTime()); |
| assertEquals(validSource.getPlatformAdId(), source.getPlatformAdId()); |
| assertEquals(validSource.getDebugAdId(), source.getDebugAdId()); |
| assertEquals(validSource.getRegistrationOrigin(), source.getRegistrationOrigin()); |
| assertEquals( |
| validSource.getCoarseEventReportDestinations(), |
| source.getCoarseEventReportDestinations()); |
| assertEquals(validSource.getEventReportWindows(), source.getEventReportWindows()); |
| assertEquals(SourceFixture.ValidSourceParams.SHARED_DEBUG_KEY, source.getSharedDebugKey()); |
| |
| // Assert destinations were inserted into the source destination table. |
| |
| Pair<List<Uri>, List<Uri>> destinations = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceDestinations(source.getId())) |
| .get(); |
| assertTrue( |
| ImmutableMultiset.copyOf(validSource.getAppDestinations()) |
| .equals(ImmutableMultiset.copyOf(destinations.first))); |
| assertTrue( |
| ImmutableMultiset.copyOf(validSource.getWebDestinations()) |
| .equals(ImmutableMultiset.copyOf(destinations.second))); |
| } |
| |
| @Test |
| public void testInsertSource_flexibleEventReport_equal() throws JSONException { |
| Source validSource = SourceFixture.getValidSourceWithFlexEventReport(); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource)); |
| |
| String sourceId = getFirstSourceIdFromDatastore(); |
| Source source = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> measurementDao.getSource(sourceId)) |
| .get(); |
| source.buildTriggerSpecs(); |
| |
| assertNotNull(source); |
| assertNotNull(source.getId()); |
| assertNull(source.getAppDestinations()); |
| assertNull(source.getWebDestinations()); |
| assertEquals(validSource.getEnrollmentId(), source.getEnrollmentId()); |
| assertEquals(validSource.getRegistrant(), source.getRegistrant()); |
| assertEquals(validSource.getEventTime(), source.getEventTime()); |
| assertEquals(validSource.getExpiryTime(), source.getExpiryTime()); |
| assertEquals(validSource.getEventReportWindow(), source.getEventReportWindow()); |
| assertEquals( |
| validSource.getAggregatableReportWindow(), source.getAggregatableReportWindow()); |
| assertEquals(validSource.getPriority(), source.getPriority()); |
| assertEquals(validSource.getSourceType(), source.getSourceType()); |
| assertEquals( |
| validSource.getInstallAttributionWindow(), source.getInstallAttributionWindow()); |
| assertEquals(validSource.getInstallCooldownWindow(), source.getInstallCooldownWindow()); |
| assertEquals(validSource.getAttributionMode(), source.getAttributionMode()); |
| assertEquals(validSource.getAggregateSource(), source.getAggregateSource()); |
| assertEquals(validSource.getFilterDataString(), source.getFilterDataString()); |
| assertEquals(validSource.getAggregateContributions(), source.getAggregateContributions()); |
| assertEquals(validSource.isDebugReporting(), source.isDebugReporting()); |
| assertEquals(validSource.getSharedAggregationKeys(), source.getSharedAggregationKeys()); |
| assertEquals(validSource.getRegistrationId(), source.getRegistrationId()); |
| assertEquals(validSource.getInstallTime(), source.getInstallTime()); |
| assertEquals(validSource.getPlatformAdId(), source.getPlatformAdId()); |
| assertEquals(validSource.getDebugAdId(), source.getDebugAdId()); |
| assertEquals(validSource.getRegistrationOrigin(), source.getRegistrationOrigin()); |
| assertEquals( |
| validSource.getTriggerSpecs().getMaxReports(), |
| source.getMaxEventLevelReports().intValue()); |
| assertEquals( |
| validSource.getTriggerSpecs().encodeToJson(), |
| source.getTriggerSpecsString()); |
| assertNull(source.getEventAttributionStatus()); |
| assertEquals( |
| validSource.getTriggerSpecs().encodePrivacyParametersToJSONString(), |
| source.getPrivacyParameters()); |
| assertEquals(validSource.getTriggerSpecs(), source.getTriggerSpecs()); |
| |
| // Assert destinations were inserted into the source destination table. |
| Pair<List<Uri>, List<Uri>> destinations = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceDestinations(source.getId())) |
| .get(); |
| assertTrue( |
| ImmutableMultiset.copyOf(validSource.getAppDestinations()) |
| .equals(ImmutableMultiset.copyOf(destinations.first))); |
| assertTrue( |
| ImmutableMultiset.copyOf(validSource.getWebDestinations()) |
| .equals(ImmutableMultiset.copyOf(destinations.second))); |
| } |
| |
| @Test |
| public void testInsertSource_reachedDbSizeLimitOnEdgeCase_doNotInsert() { |
| insertSourceReachingDbSizeLimit(/* dbSize= */ 100L, /* dbSizeMaxLimit= */ 100L); |
| } |
| |
| @Test |
| public void testInsertSource_reachedDbSizeLimitUpperEdgeCase_doNotInsert() { |
| insertSourceReachingDbSizeLimit(/* dbSize= */ 101L, /* dbSizeMaxLimit= */ 100L); |
| } |
| |
| private void insertSourceReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit) { |
| final Source validSource = SourceFixture.getValidSource(); |
| |
| // Mocking that the DB file has a size of 100 bytes |
| final MeasurementDbHelper spyMeasurementDbHelper = |
| spy(MeasurementDbHelper.getInstance(sContext)); |
| ExtendedMockito.doReturn(spyMeasurementDbHelper) |
| .when(() -> MeasurementDbHelper.getInstance(ArgumentMatchers.any())); |
| ExtendedMockito.doReturn(dbSize).when(spyMeasurementDbHelper).getDbFileSize(); |
| |
| // Mocking that the flags return a max limit size of 100 bytes |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(dbSizeMaxLimit).when(mockFlags).getMeasurementDbSizeLimit(); |
| |
| mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource)); |
| |
| try (Cursor sourceCursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query(SourceContract.TABLE, null, null, null, null, null, null)) { |
| assertFalse(sourceCursor.moveToNext()); |
| } |
| } |
| |
| @Test |
| public void testInsertTrigger() { |
| Trigger validTrigger = TriggerFixture.getValidTrigger(); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger)); |
| |
| try (Cursor triggerCursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query(TriggerContract.TABLE, null, null, null, null, null, null)) { |
| assertTrue(triggerCursor.moveToNext()); |
| Trigger trigger = SqliteObjectMapper.constructTriggerFromCursor(triggerCursor); |
| assertNotNull(trigger); |
| assertNotNull(trigger.getId()); |
| assertEquals( |
| validTrigger.getAttributionDestination(), trigger.getAttributionDestination()); |
| assertEquals(validTrigger.getDestinationType(), trigger.getDestinationType()); |
| assertEquals(validTrigger.getEnrollmentId(), trigger.getEnrollmentId()); |
| assertEquals(validTrigger.getRegistrant(), trigger.getRegistrant()); |
| assertEquals(validTrigger.getTriggerTime(), trigger.getTriggerTime()); |
| assertEquals(validTrigger.getEventTriggers(), trigger.getEventTriggers()); |
| assertEquals(validTrigger.getAttributionConfig(), trigger.getAttributionConfig()); |
| assertEquals(validTrigger.getAdtechKeyMapping(), trigger.getAdtechKeyMapping()); |
| assertEquals(validTrigger.getPlatformAdId(), trigger.getPlatformAdId()); |
| assertEquals(validTrigger.getDebugAdId(), trigger.getDebugAdId()); |
| assertEquals(validTrigger.getRegistrationOrigin(), trigger.getRegistrationOrigin()); |
| assertEquals( |
| validTrigger.getAggregationCoordinatorOrigin(), |
| trigger.getAggregationCoordinatorOrigin()); |
| } |
| } |
| |
| @Test |
| public void testInsertTrigger_reachedDbSizeLimitOnEdgeCase_doNotInsert() { |
| insertTriggerReachingDbSizeLimit(/* dbSize= */ 100L, /* dbSizeMaxLimit= */ 100L); |
| } |
| |
| @Test |
| public void testInsertTrigger_reachedDbSizeLimitUpperEdgeCase_doNotInsert() { |
| insertTriggerReachingDbSizeLimit(/* dbSize= */ 101L, /* dbSizeMaxLimit= */ 100L); |
| } |
| |
| private void insertTriggerReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit) { |
| final Trigger validTrigger = TriggerFixture.getValidTrigger(); |
| |
| // Mocking that the DB file has a size of 100 bytes |
| final MeasurementDbHelper spyMeasurementDbHelper = |
| spy(MeasurementDbHelper.getInstance(sContext)); |
| ExtendedMockito.doReturn(spyMeasurementDbHelper) |
| .when(() -> MeasurementDbHelper.getInstance(ArgumentMatchers.any())); |
| ExtendedMockito.doReturn(dbSize).when(spyMeasurementDbHelper).getDbFileSize(); |
| |
| // Mocking that the flags return a max limit size of 100 bytes |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(dbSizeMaxLimit).when(mockFlags).getMeasurementDbSizeLimit(); |
| |
| mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger)); |
| |
| try (Cursor sourceCursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query(TriggerContract.TABLE, null, null, null, null, null, null)) { |
| assertFalse(sourceCursor.moveToNext()); |
| } |
| } |
| |
| @Test |
| public void testGetNumSourcesPerPublisher_publisherTypeApp() { |
| setupSourceAndTriggerData(); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| 2, |
| measurementDao.getNumSourcesPerPublisher( |
| APP_TWO_PUBLISHER, EventSurfaceType.APP)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| 1, |
| measurementDao.getNumSourcesPerPublisher( |
| APP_ONE_PUBLISHER, EventSurfaceType.APP)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| 0, |
| measurementDao.getNumSourcesPerPublisher( |
| APP_NO_PUBLISHER, EventSurfaceType.APP)); |
| }); |
| } |
| |
| @Test |
| public void testGetNumSourcesPerPublisher_publisherTypeWeb() { |
| setupSourceDataForPublisherTypeWeb(); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| 1, |
| measurementDao.getNumSourcesPerPublisher( |
| WEB_PUBLISHER_ONE, EventSurfaceType.WEB)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| 2, |
| measurementDao.getNumSourcesPerPublisher( |
| WEB_PUBLISHER_TWO, EventSurfaceType.WEB)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| 1, |
| measurementDao.getNumSourcesPerPublisher( |
| WEB_PUBLISHER_THREE, EventSurfaceType.WEB)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginPerPublisherXDestinationInAttribution_atWindow() { |
| Uri sourceSite = Uri.parse("android-app://publisher.app"); |
| Uri appDestination = Uri.parse("android-app://destination.app"); |
| String registrant = "android-app://registrant.app"; |
| List<Attribution> attributionsWithAppDestinations = |
| getAttributionsWithDifferentReportingOrigins( |
| 4, appDestination, 5000000001L, sourceSite, registrant); |
| for (Attribution attribution : attributionsWithAppDestinations) { |
| insertAttribution(attribution); |
| } |
| Uri excludedRegistrationOrigin = WebUtil.validUri("https://subdomain0.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestInAttribution( |
| sourceSite, |
| appDestination, |
| excludedRegistrationOrigin, |
| 5000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginsPerPublisherXDestInAttribution_beyondWindow() { |
| Uri sourceSite = Uri.parse("android-app://publisher.app"); |
| Uri appDestination = Uri.parse("android-app://destination.app"); |
| String registrant = "android-app://registrant.app"; |
| List<Attribution> attributionsWithAppDestinations = |
| getAttributionsWithDifferentReportingOrigins( |
| 4, appDestination, 5000000000L, sourceSite, registrant); |
| for (Attribution attribution : attributionsWithAppDestinations) { |
| insertAttribution(attribution); |
| } |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain.example0.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestInAttribution( |
| sourceSite, |
| appDestination, |
| excludedReportingOrigin, |
| 5000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testInsertDebugReport() { |
| DebugReport debugReport = createDebugReport(); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport)); |
| |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query(DebugReportContract.TABLE, null, null, null, null, null, null)) { |
| assertTrue(cursor.moveToNext()); |
| DebugReport report = SqliteObjectMapper.constructDebugReportFromCursor(cursor); |
| assertNotNull(report); |
| assertNotNull(report.getId()); |
| assertEquals(debugReport.getType(), report.getType()); |
| assertEquals(debugReport.getBody().toString(), report.getBody().toString()); |
| assertEquals(debugReport.getEnrollmentId(), report.getEnrollmentId()); |
| assertEquals(debugReport.getRegistrationOrigin(), report.getRegistrationOrigin()); |
| assertEquals(debugReport.getReferenceId(), report.getReferenceId()); |
| assertEquals(debugReport.getInsertionTime(), report.getInsertionTime()); |
| assertEquals(debugReport.getRegistrant(), report.getRegistrant()); |
| } |
| } |
| |
| @Test |
| public void singleAppTrigger_triggersPerDestination_returnsOne() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| triggerList.add(createAppTrigger(APP_ONE_DESTINATION, APP_ONE_DESTINATION)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| APP_ONE_DESTINATION, EventSurfaceType.APP)) |
| .isEqualTo(1)); |
| } |
| |
| @Test |
| public void multipleAppTriggers_similarUris_triggersPerDestination() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| triggerList.add(createAppTrigger(APP_TWO_DESTINATION, APP_TWO_DESTINATION)); |
| triggerList.add(createAppTrigger(APP_TWO_DESTINATION, APP_TWO_DESTINATION)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| APP_TWO_DESTINATION, EventSurfaceType.APP)) |
| .isEqualTo(2)); |
| } |
| |
| @Test |
| public void noAppTriggers_triggersPerDestination_returnsNone() { |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| APP_NO_TRIGGERS, EventSurfaceType.APP)) |
| .isEqualTo(0)); |
| } |
| |
| @Test |
| public void multipleAppTriggers_differentPaths_returnsAllMatching() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION)); |
| triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION_PATH1)); |
| triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION_PATH2)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| APP_THREE_DESTINATION, EventSurfaceType.APP)) |
| .isEqualTo(3); |
| // Try the same thing, but use the app uri with path to find number of triggers. |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| APP_THREE_DESTINATION_PATH1, EventSurfaceType.APP)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| APP_THREE_DESTINATION_PATH2, EventSurfaceType.APP)) |
| .isEqualTo(3); |
| Uri unseenAppThreePath = |
| Uri.parse("android-app://com.example1.three-triggers/path3"); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| unseenAppThreePath, EventSurfaceType.APP)) |
| .isEqualTo(3); |
| }); |
| } |
| |
| @Test |
| public void singleWebTrigger_triggersPerDestination_returnsOne() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| triggerList.add(createWebTrigger(WEB_ONE_DESTINATION)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WEB_ONE_DESTINATION, EventSurfaceType.WEB)) |
| .isEqualTo(1); |
| }); |
| } |
| |
| @Test |
| public void webTriggerMultipleSubDomains_triggersPerDestination_returnsAllMatching() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| triggerList.add(createWebTrigger(WEB_ONE_DESTINATION)); |
| triggerList.add(createWebTrigger(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN)); |
| triggerList.add(createWebTrigger(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WEB_ONE_DESTINATION, EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN, |
| EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2, |
| EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WebUtil.validUri("https://new-subdomain.example1.test"), |
| EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WebUtil.validUri("https://example1.test"), |
| EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| }); |
| } |
| |
| @Test |
| public void webTriggerWithoutSubdomains_triggersPerDestination_returnsAllMatching() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| Uri webDestinationWithoutSubdomain = WebUtil.validUri("https://example1.test"); |
| Uri webDestinationWithoutSubdomainPath1 = WebUtil.validUri("https://example1.test/path1"); |
| Uri webDestinationWithoutSubdomainPath2 = WebUtil.validUri("https://example1.test/path2"); |
| Uri webDestinationWithoutSubdomainPath3 = WebUtil.validUri("https://example1.test/path3"); |
| triggerList.add(createWebTrigger(webDestinationWithoutSubdomain)); |
| triggerList.add(createWebTrigger(webDestinationWithoutSubdomainPath1)); |
| triggerList.add(createWebTrigger(webDestinationWithoutSubdomainPath2)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| webDestinationWithoutSubdomain, EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| webDestinationWithoutSubdomainPath1, |
| EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| webDestinationWithoutSubdomainPath2, |
| EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| webDestinationWithoutSubdomainPath3, |
| EventSurfaceType.WEB)) |
| .isEqualTo(3); |
| }); |
| } |
| |
| @Test |
| public void webTriggerDifferentPaths_triggersPerDestination_returnsAllMatching() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| triggerList.add(createWebTrigger(WEB_TWO_DESTINATION)); |
| triggerList.add(createWebTrigger(WEB_TWO_DESTINATION_WITH_PATH)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WEB_TWO_DESTINATION, EventSurfaceType.WEB)) |
| .isEqualTo(2); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| WEB_TWO_DESTINATION_WITH_PATH, EventSurfaceType.WEB)) |
| .isEqualTo(2); |
| }); |
| } |
| |
| @Test |
| public void noMathingWebTriggers_triggersPerDestination_returnsZero() { |
| List<Trigger> triggerList = new ArrayList<>(); |
| triggerList.add(createWebTrigger(WEB_ONE_DESTINATION)); |
| addTriggersToDatabase(triggerList); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| Uri differentScheme = WebUtil.validUri("http://www.example1.test"); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| differentScheme, EventSurfaceType.WEB)) |
| .isEqualTo(0); |
| |
| Uri notMatchingUrl2 = WebUtil.validUri("https://www.not-example1.test"); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| notMatchingUrl2, EventSurfaceType.WEB)) |
| .isEqualTo(0); |
| |
| Uri notMatchingUrl = WebUtil.validUri("https://www.not-example-1.test"); |
| assertThat( |
| measurementDao.getNumTriggersPerDestination( |
| notMatchingUrl, EventSurfaceType.WEB)) |
| .isEqualTo(0); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_appDest() { |
| Uri sourceSite = Uri.parse("android-app://publisher.app"); |
| Uri webDestination = WebUtil.validUri("https://web-destination.test"); |
| Uri appDestination = Uri.parse("android-app://destination.app"); |
| String registrant = "android-app://registrant.app"; |
| List<Attribution> attributionsWithAppDestinations1 = |
| getAttributionsWithDifferentReportingOrigins( |
| 4, appDestination, 5000000000L, sourceSite, registrant); |
| List<Attribution> attributionsWithAppDestinations2 = |
| getAttributionsWithDifferentReportingOrigins( |
| 2, appDestination, 5000000000L, sourceSite, registrant); |
| List<Attribution> attributionsWithWebDestinations = |
| getAttributionsWithDifferentReportingOrigins( |
| 2, webDestination, 5500000000L, sourceSite, registrant); |
| List<Attribution> attributionsOutOfWindow = |
| getAttributionsWithDifferentReportingOrigins( |
| 10, appDestination, 50000000000L, sourceSite, registrant); |
| for (Attribution attribution : attributionsWithAppDestinations1) { |
| insertAttribution(attribution); |
| } |
| for (Attribution attribution : attributionsWithAppDestinations2) { |
| insertAttribution(attribution); |
| } |
| for (Attribution attribution : attributionsWithWebDestinations) { |
| insertAttribution(attribution); |
| } |
| for (Attribution attribution : attributionsOutOfWindow) { |
| insertAttribution(attribution); |
| } |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain0.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestInAttribution( |
| sourceSite, |
| appDestination, |
| excludedReportingOrigin, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_webDest() { |
| Uri sourceSite = Uri.parse("android-app://publisher.app"); |
| Uri webDestination = WebUtil.validUri("https://web-destination.test"); |
| Uri appDestination = Uri.parse("android-app://destination.app"); |
| String registrant = "android-app://registrant.app"; |
| List<Attribution> attributionsWithAppDestinations = |
| getAttributionsWithDifferentReportingOrigins( |
| 2, appDestination, 5000000000L, sourceSite, registrant); |
| List<Attribution> attributionsWithWebDestinations1 = |
| getAttributionsWithDifferentReportingOrigins( |
| 4, webDestination, 5000000000L, sourceSite, registrant); |
| List<Attribution> attributionsWithWebDestinations2 = |
| getAttributionsWithDifferentReportingOrigins( |
| 2, webDestination, 5500000000L, sourceSite, registrant); |
| List<Attribution> attributionsOutOfWindow = |
| getAttributionsWithDifferentReportingOrigins( |
| 10, webDestination, 50000000000L, sourceSite, registrant); |
| for (Attribution attribution : attributionsWithAppDestinations) { |
| insertAttribution(attribution); |
| } |
| for (Attribution attribution : attributionsWithWebDestinations1) { |
| insertAttribution(attribution); |
| } |
| for (Attribution attribution : attributionsWithWebDestinations2) { |
| insertAttribution(attribution); |
| } |
| for (Attribution attribution : attributionsOutOfWindow) { |
| insertAttribution(attribution); |
| } |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain3.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestInAttribution( |
| sourceSite, |
| webDestination, |
| excludedReportingOrigin, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_atWindow() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000001L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.APP, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_expiredSource() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000001L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> expiredSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 6, |
| true, |
| true, |
| 4500000001L, |
| 6000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : expiredSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.APP, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_beyondWindow() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.APP, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_appPublisher() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| true, |
| false, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| false, |
| true, |
| 5500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentDestinations( |
| 10, |
| true, |
| true, |
| 50000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentDestinations( |
| 6, |
| true, |
| true, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.IGNORED); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(9), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.APP, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| // (Testing countDistinctDestinationsPerPublisherInActiveSource) |
| @Test |
| public void testCountDistinctDestinations_appPublisher_differentEnrollment() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| true, |
| false, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| false, |
| true, |
| 5500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentDestinations( |
| 10, |
| true, |
| true, |
| 50000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentDestinations( |
| 6, |
| true, |
| true, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.IGNORED); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.APP, |
| "unmatched-enrollment-id", |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.APP, |
| "unmatched-enrollment-id", |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.APP, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_webPublisher_exactMatch() { |
| Uri publisher = WebUtil.validUri("https://publisher.test"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| true, |
| false, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| false, |
| true, |
| 5500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentDestinations( |
| 10, |
| true, |
| true, |
| 50000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentDestinations( |
| 6, |
| true, |
| true, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.IGNORED); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(9), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_webPublisher_doesNotMatchDomainAsSuffix() { |
| Uri publisher = WebUtil.validUri("https://publisher.test"); |
| Uri publisherAsSuffix = WebUtil.validUri("https://prefix-publisher.test"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 8, |
| true, |
| true, |
| 4500000000L, |
| publisherAsSuffix, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.IGNORED); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| true, |
| false, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| false, |
| true, |
| 5500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentDestinations( |
| 10, |
| true, |
| true, |
| 50000000000L, |
| publisherAsSuffix, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.MARKED_TO_DELETE); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(4), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(4), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(4), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_webPublisher_doesNotMatchDifferentScheme() { |
| Uri publisher = WebUtil.validUri("https://publisher.test"); |
| Uri publisherWithDifferentScheme = WebUtil.validUri("http://publisher.test"); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 4500000000L, |
| publisherWithDifferentScheme, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| true, |
| false, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| false, |
| true, |
| 5500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentDestinations( |
| 10, |
| true, |
| true, |
| 50000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentDestinations( |
| 6, |
| true, |
| true, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.IGNORED); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of(WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(9), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctDestinations_webPublisher_multipleDestinations() { |
| Uri publisher = WebUtil.validUri("https://publisher.test"); |
| // One source with multiple destinations |
| Source activeSourceWithAppAndWebDestinations = |
| getSourceWithDifferentDestinations( |
| 3, |
| true, |
| true, |
| 4500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentDestinations( |
| 2, |
| true, |
| false, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentDestinations( |
| 1, |
| false, |
| true, |
| 5500000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentDestinations( |
| 10, |
| true, |
| true, |
| 50000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentDestinations( |
| 4, |
| true, |
| true, |
| 5000000000L, |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.IGNORED); |
| insertSource(activeSourceWithAppAndWebDestinations); |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| List<Uri> excludedDestinations = |
| List.of( |
| WebUtil.validUri("https://web-destination-1.test"), |
| WebUtil.validUri("https://web-destination-2.test")); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(2), |
| measurementDao |
| .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(8), |
| measurementDao |
| .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( |
| publisher, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 6000000000L)); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(2), |
| measurementDao |
| .countDistinctDestinationsPerPublisherPerRateLimitWindow( |
| publisher, |
| EventSurfaceType.WEB, |
| excludedDestinations, |
| EventSurfaceType.WEB, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| // Tests countSourcesPerPublisherXEnrollmentExcludingRegistrationOriginSinceTime |
| @Test |
| public void testCountSourcesExclRegOrigin_forSameOrigin_returnsZero() { |
| // Positive case. For same registration origin we always pass the 1 origin |
| // per site limit and return 0 |
| Uri appPublisher = Uri.parse("android-app://publisher.app"); |
| List<Source> sourcesMoreThanOneDayOld = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2), |
| appPublisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| |
| List<Source> sourcesRecent = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2), |
| appPublisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| |
| for (Source source : sourcesMoreThanOneDayOld) { |
| insertSource(source); |
| } |
| for (Source source : sourcesRecent) { |
| insertSource(source); |
| } |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao.countSourcesPerPublisherXEnrollmentExcludingRegOrigin( |
| REGISTRATION_ORIGIN, |
| appPublisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| System.currentTimeMillis(), |
| MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW)); |
| }); |
| } |
| |
| @Test |
| public void testCountSourcesExclRegOrigin_forDifferentAppPublisher_returnsZero() { |
| // Positive case. For different publisher we always pass the 1 origin |
| // per site limit and return 0 |
| Uri appPublisher = Uri.parse("android-app://publisher.app"); |
| Uri appPublisher2 = Uri.parse("android-app://publisher2.app"); |
| List<Source> sources = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2), |
| appPublisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| for (Source source : sources) { |
| insertSource(source); |
| } |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao.countSourcesPerPublisherXEnrollmentExcludingRegOrigin( |
| REGISTRATION_ORIGIN_2, |
| appPublisher2, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| System.currentTimeMillis(), |
| MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW)); |
| }); |
| } |
| |
| @Test |
| public void testCountSourcesExclRegOrigin_forDifferentWebPublisher_returnsZero() { |
| // Positive case. For different publisher we always pass the 1 origin |
| // per site limit and return 0 |
| Uri publisher = WebUtil.validUri("https://publisher.test"); |
| Uri publisher2 = WebUtil.validUri("https://publisher2.test"); |
| List<Source> sources = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2), |
| publisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| for (Source source : sources) { |
| insertSource(source); |
| } |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao.countSourcesPerPublisherXEnrollmentExcludingRegOrigin( |
| REGISTRATION_ORIGIN_2, |
| publisher2, |
| EventSurfaceType.WEB, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| System.currentTimeMillis(), |
| MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW)); |
| }); |
| } |
| |
| @Test |
| public void testCountSourcesExclRegOrigin_forDifferentEnrollment_returnsZero() { |
| // Positive case. For different enrollment (aka reporting site) |
| // we always pass the 1 origin per site limit and return 0 |
| String differentEnrollment = "new-enrollment"; |
| Uri differentSite = WebUtil.validUri("https://subdomain.different-site.test"); |
| Uri appPublisher = Uri.parse("android-app://publisher.app"); |
| List<Source> sources = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2), |
| appPublisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| for (Source source : sources) { |
| insertSource(source); |
| } |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao.countSourcesPerPublisherXEnrollmentExcludingRegOrigin( |
| differentSite, |
| appPublisher, |
| EventSurfaceType.APP, |
| differentEnrollment, |
| System.currentTimeMillis(), |
| MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW)); |
| }); |
| } |
| |
| @Test |
| public void testCountSourcesExclRegOrigin_forDifferentOriginMoreThanTimeWindow_returnsZero() { |
| // Positive case. For different origin with same enrollment |
| // more than time window of 1 day we always pass the 1 origin per site |
| // limit and return 0 |
| Uri appPublisher = Uri.parse("android-app://publisher.app"); |
| List<Source> sources = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2), |
| appPublisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| for (Source source : sources) { |
| insertSource(source); |
| } |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao.countSourcesPerPublisherXEnrollmentExcludingRegOrigin( |
| REGISTRATION_ORIGIN_2, |
| appPublisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| System.currentTimeMillis(), |
| MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW)); |
| }); |
| } |
| |
| // Tests countSourcesPerPublisherXEnrollmentExcludingRegistrationOriginSinceTime |
| @Test |
| public void testCountSources_forDifferentOriginWithinTimeWindow_returnsNumOfSources() { |
| // Negative case. For different origin with same enrollment |
| // we always fail the 1 origin per site limit and return 1 |
| Uri appPublisher = Uri.parse("android-app://publisher.app"); |
| List<Source> sources = |
| getSourcesWithDifferentDestinations( |
| 5, |
| true, |
| true, |
| System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2), |
| appPublisher, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| Source.Status.ACTIVE, |
| REGISTRATION_ORIGIN); |
| for (Source source : sources) { |
| insertSource(source); |
| } |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(5), |
| measurementDao.countSourcesPerPublisherXEnrollmentExcludingRegOrigin( |
| REGISTRATION_ORIGIN_2, |
| appPublisher, |
| EventSurfaceType.APP, |
| SourceFixture.ValidSourceParams.ENROLLMENT_ID, |
| System.currentTimeMillis(), |
| MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctRegistrationOriginPerPublisherXDestinationInSource_atWindow() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test")); |
| List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app")); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, |
| appDestinations, |
| webDestinations, |
| 4500000001L, |
| publisher, |
| Source.Status.ACTIVE); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| Uri excludedRegistrationOrigin = WebUtil.validUri("https://subdomain1.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(1), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestinationInSource( |
| publisher, |
| EventSurfaceType.APP, |
| appDestinations, |
| excludedRegistrationOrigin, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_beyondWindow() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test")); |
| List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app")); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, |
| appDestinations, |
| webDestinations, |
| 4500000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(0), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestinationInSource( |
| publisher, |
| EventSurfaceType.APP, |
| appDestinations, |
| excludedReportingOrigin, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_expiredSource() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test")); |
| List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app")); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, |
| appDestinations, |
| webDestinations, |
| 4500000001L, |
| publisher, |
| Source.Status.ACTIVE); |
| List<Source> expiredSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 4, |
| appDestinations, |
| webDestinations, |
| 4500000001L, |
| 6000000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : expiredSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestinationInSource( |
| publisher, |
| EventSurfaceType.APP, |
| appDestinations, |
| excludedReportingOrigin, |
| 4500000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_appDestination() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test")); |
| List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app")); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, |
| appDestinations, |
| webDestinations, |
| 4500000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, null, webDestinations, 5500000000L, publisher, Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentRegistrationOrigins( |
| 10, |
| appDestinations, |
| webDestinations, |
| 50000000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentRegistrationOrigins( |
| 3, |
| appDestinations, |
| webDestinations, |
| 5000000000L, |
| publisher, |
| Source.Status.IGNORED); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| String excludedEnrollmentId = "enrollment-id-1"; |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(2), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestinationInSource( |
| publisher, |
| EventSurfaceType.APP, |
| appDestinations, |
| excludedReportingOrigin, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_webDestination() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test")); |
| List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app")); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, |
| appDestinations, |
| webDestinations, |
| 4500000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, null, webDestinations, 5500000000L, publisher, Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentRegistrationOrigins( |
| 10, |
| appDestinations, |
| webDestinations, |
| 50000000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentRegistrationOrigins( |
| 3, |
| appDestinations, |
| webDestinations, |
| 5000000000L, |
| publisher, |
| Source.Status.IGNORED); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| String excludedEnrollmentId = "enrollment-id-22"; |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain22.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(3), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestinationInSource( |
| publisher, |
| EventSurfaceType.WEB, |
| webDestinations, |
| excludedReportingOrigin, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| // countDistinctEnrollmentsPerPublisherXDestinationInSource |
| @Test |
| public void countDistinctReportingOriginsPerPublisher_webDestination_multipleDestinations() { |
| Uri publisher = Uri.parse("android-app://publisher.app"); |
| List<Uri> webDestinations1 = List.of(WebUtil.validUri("https://web-destination-1.test")); |
| List<Uri> webDestinations2 = |
| List.of( |
| WebUtil.validUri("https://web-destination-1.test"), |
| WebUtil.validUri("https://web-destination-2.test")); |
| List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app")); |
| List<Source> activeSourcesWithAppAndWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 3, |
| appDestinations, |
| webDestinations1, |
| 4500000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| List<Source> activeSourcesWithAppDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE); |
| List<Source> activeSourcesWithWebDestinations = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, null, webDestinations2, 5500000000L, publisher, Source.Status.ACTIVE); |
| List<Source> activeSourcesOutOfWindow = |
| getSourcesWithDifferentRegistrationOrigins( |
| 10, |
| appDestinations, |
| webDestinations2, |
| 50000000000L, |
| publisher, |
| Source.Status.ACTIVE); |
| List<Source> ignoredSources = |
| getSourcesWithDifferentRegistrationOrigins( |
| 2, |
| appDestinations, |
| webDestinations1, |
| 5000000000L, |
| publisher, |
| Source.Status.IGNORED); |
| for (Source source : activeSourcesWithAppAndWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithAppDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesWithWebDestinations) { |
| insertSource(source); |
| } |
| for (Source source : activeSourcesOutOfWindow) { |
| insertSource(source); |
| } |
| for (Source source : ignoredSources) { |
| insertSource(source); |
| } |
| String excludedEnrollmentId = "enrollment-id-1"; |
| Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test"); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertEquals( |
| Integer.valueOf(2), |
| measurementDao |
| .countDistinctReportingOriginsPerPublisherXDestinationInSource( |
| publisher, |
| EventSurfaceType.WEB, |
| webDestinations2, |
| excludedReportingOrigin, |
| 4000000000L, |
| 6000000000L)); |
| }); |
| } |
| |
| @Test |
| public void testInstallAttribution_selectHighestPriority() { |
| long currentTimestamp = System.currentTimeMillis(); |
| |
| insertSource( |
| createSourceForIATest( |
| "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA1"); |
| insertSource( |
| createSourceForIATest("IA2", currentTimestamp, 50, -1, false, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA2"); |
| // Should select id IA1 because it has higher priority |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, currentTimestamp); |
| })); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertTrue(getInstallAttributionStatus("IA1", db)); |
| assertFalse(getInstallAttributionStatus("IA2", db)); |
| removeSources(Arrays.asList("IA1", "IA2"), db); |
| } |
| |
| @Test |
| public void testInstallAttribution_selectLatest() { |
| long currentTimestamp = System.currentTimeMillis(); |
| insertSource( |
| createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA1"); |
| insertSource( |
| createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA2"); |
| // Should select id=IA2 as it is latest |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, currentTimestamp); |
| })); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertFalse(getInstallAttributionStatus("IA1", db)); |
| assertTrue(getInstallAttributionStatus("IA2", db)); |
| |
| removeSources(Arrays.asList("IA1", "IA2"), db); |
| } |
| |
| @Test |
| public void testInstallAttribution_ignoreNewerSources() { |
| long currentTimestamp = System.currentTimeMillis(); |
| insertSource( |
| createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA1"); |
| insertSource( |
| createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA2"); |
| // Should select id=IA1 as it is the only valid choice. |
| // id=IA2 is newer than the evenTimestamp of install event. |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, |
| currentTimestamp - TimeUnit.DAYS.toMillis(7)); |
| })); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertTrue(getInstallAttributionStatus("IA1", db)); |
| assertFalse(getInstallAttributionStatus("IA2", db)); |
| removeSources(Arrays.asList("IA1", "IA2"), db); |
| } |
| |
| @Test |
| public void testInstallAttribution_noValidSource() { |
| long currentTimestamp = System.currentTimeMillis(); |
| insertSource( |
| createSourceForIATest("IA1", currentTimestamp, 10, 10, true, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA1"); |
| insertSource( |
| createSourceForIATest("IA2", currentTimestamp, 10, 11, true, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA2"); |
| // Should not update any sources. |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, currentTimestamp))); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertFalse(getInstallAttributionStatus("IA1", db)); |
| assertFalse(getInstallAttributionStatus("IA2", db)); |
| removeSources(Arrays.asList("IA1", "IA2"), db); |
| } |
| |
| @Test |
| public void installAttribution_install_installTimeEqualsEventTime() { |
| long currentTimestamp = System.currentTimeMillis(); |
| insertSource( |
| createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID) |
| .build(), |
| "IA1"); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, |
| currentTimestamp - TimeUnit.DAYS.toMillis(7)); |
| })); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertEquals( |
| currentTimestamp - TimeUnit.DAYS.toMillis(7), |
| getInstallAttributionInstallTime("IA1", db).longValue()); |
| removeSources(Arrays.asList("IA1"), db); |
| } |
| |
| @Test |
| public void doInstallAttribution_noValidSourceStatus_IgnoresSources() { |
| long currentTimestamp = System.currentTimeMillis(); |
| Source source = |
| createSourceForIATest( |
| "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID) |
| .build(); |
| |
| // Execution |
| // Active source should get install attributed |
| source.setStatus(Source.Status.ACTIVE); |
| insertSource(source, source.getId()); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, currentTimestamp))); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertTrue(getInstallAttributionStatus("IA1", db)); |
| removeSources(Collections.singletonList("IA1"), db); |
| |
| // Active source should not get install attributed |
| source.setStatus(Source.Status.IGNORED); |
| insertSource(source, source.getId()); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, currentTimestamp))); |
| assertFalse(getInstallAttributionStatus("IA1", db)); |
| removeSources(Collections.singletonList("IA1"), db); |
| |
| // MARKED_TO_DELETE source should not get install attributed |
| source.setStatus(Source.Status.MARKED_TO_DELETE); |
| insertSource(source, source.getId()); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, currentTimestamp))); |
| assertFalse(getInstallAttributionStatus("IA1", db)); |
| removeSources(Collections.singletonList("IA1"), db); |
| } |
| |
| @Test |
| public void |
| doInstallAttribution_withSourcesAcrossEnrollments_marksOneInstallFromEachRegOrigin() { |
| long currentTimestamp = System.currentTimeMillis(); |
| |
| // Enrollment1: Choose IA2 because that's newer and still occurred before install |
| insertSource( |
| createSourceForIATest( |
| "IA1", |
| currentTimestamp, |
| -1, |
| 10, |
| false, |
| DEFAULT_ENROLLMENT_ID + "_1") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example1.test")) |
| .build(), |
| "IA1"); |
| insertSource( |
| createSourceForIATest( |
| "IA2", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_1") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example1.test")) |
| .build(), |
| "IA2"); |
| |
| // Enrollment2: Choose IA4 because IA3's install attribution window has expired |
| insertSource( |
| createSourceForIATest( |
| "IA3", currentTimestamp, -1, 10, true, DEFAULT_ENROLLMENT_ID + "_2") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example2.test")) |
| .build(), |
| "IA3"); |
| insertSource( |
| createSourceForIATest( |
| "IA4", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_2") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example2.test")) |
| .build(), |
| "IA4"); |
| |
| // Enrollment3: Choose IA5 because IA6 was registered after install event |
| insertSource( |
| createSourceForIATest( |
| "IA5", |
| currentTimestamp, |
| -1, |
| 10, |
| false, |
| DEFAULT_ENROLLMENT_ID + "_3") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example3.test")) |
| .build(), |
| "IA5"); |
| insertSource( |
| createSourceForIATest( |
| "IA6", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID + "_3") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example3.test")) |
| .build(), |
| "IA6"); |
| |
| // Enrollment4: Choose IA8 due to higher priority |
| insertSource( |
| createSourceForIATest( |
| "IA7", currentTimestamp, 5, 10, false, DEFAULT_ENROLLMENT_ID + "_4") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example4.test")) |
| .build(), |
| "IA7"); |
| insertSource( |
| createSourceForIATest( |
| "IA8", |
| currentTimestamp, |
| 10, |
| 10, |
| false, |
| DEFAULT_ENROLLMENT_ID + "_4") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example4.test")) |
| .build(), |
| "IA8"); |
| |
| // Enrollment5: Choose none because both sources are ineligible |
| // Expired install attribution window |
| insertSource( |
| createSourceForIATest( |
| "IA9", currentTimestamp, 5, 31, true, DEFAULT_ENROLLMENT_ID + "_5") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example5.test")) |
| .build(), |
| "IA9"); |
| // Registered after install attribution |
| insertSource( |
| createSourceForIATest( |
| "IA10", |
| currentTimestamp, |
| 10, |
| 3, |
| false, |
| DEFAULT_ENROLLMENT_ID + "_5") |
| .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example5.test")) |
| .build(), |
| "IA10"); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| measurementDao.doInstallAttribution( |
| INSTALLED_PACKAGE, |
| currentTimestamp - TimeUnit.DAYS.toMillis(7)); |
| })); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertTrue(getInstallAttributionStatus("IA2", db)); |
| assertTrue(getInstallAttributionStatus("IA4", db)); |
| assertTrue(getInstallAttributionStatus("IA5", db)); |
| assertTrue(getInstallAttributionStatus("IA8", db)); |
| |
| assertFalse(getInstallAttributionStatus("IA1", db)); |
| assertFalse(getInstallAttributionStatus("IA3", db)); |
| assertFalse(getInstallAttributionStatus("IA6", db)); |
| assertFalse(getInstallAttributionStatus("IA7", db)); |
| assertFalse(getInstallAttributionStatus("IA9", db)); |
| assertFalse(getInstallAttributionStatus("IA10", db)); |
| |
| removeSources( |
| Arrays.asList( |
| "IA1", "IA2", "IA3", "IA4", "IA5", "IA6", "IA7", "IA8", "IA8", "IA10"), |
| db); |
| } |
| |
| @Test |
| public void deleteSources_providedIds_deletesMatchingSourcesAndRelatedData() |
| throws JSONException { |
| // Setup - Creates the following - |
| // source - S1, S2, S3, S4 |
| // trigger - T1, T2, T3, T4 |
| // event reports - E11, E12, E21, E22, E23, E33, E44 |
| // aggregate reports - AR11, AR12, AR21, AR34 |
| // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44 |
| prepareDataForSourceAndTriggerDeletion(); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| measurementDao.deleteSources(List.of("S1", "S2")); |
| |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getSource("S1"); |
| }); |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getSource("S2"); |
| }); |
| |
| assertNotNull(measurementDao.getSource("S3")); |
| assertNotNull(measurementDao.getSource("S4")); |
| assertNotNull(measurementDao.getTrigger("T1")); |
| assertNotNull(measurementDao.getTrigger("T2")); |
| assertNotNull(measurementDao.getTrigger("T3")); |
| assertNotNull(measurementDao.getTrigger("T4")); |
| |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getEventReport("E11"); |
| }); |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getEventReport("E12"); |
| }); |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getEventReport("E21"); |
| }); |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getEventReport("E22"); |
| }); |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getEventReport("E23"); |
| }); |
| assertNotNull(measurementDao.getEventReport("E33")); |
| assertNotNull(measurementDao.getEventReport("E44")); |
| |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getAggregateReport("AR11"); |
| }); |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getAggregateReport("AR12"); |
| }); |
| assertThrows( |
| DatastoreException.class, |
| () -> { |
| measurementDao.getAggregateReport("AR21"); |
| }); |
| assertNotNull(measurementDao.getAggregateReport("AR34")); |
| }); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE)); |
| } |
| |
| @Test |
| public void deleteSource_providedId_deletesMatchingXnaIgnoredSource() { |
| // Setup |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| Source s1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setId("S1") |
| .setEnrollmentId("1") |
| .build(); |
| |
| ContentValues sourceValues = new ContentValues(); |
| sourceValues.put(SourceContract.ID, s1.getId()); |
| sourceValues.put(SourceContract.EVENT_ID, s1.getEventId().getValue()); |
| |
| ContentValues xnaIgnoredSourceValues = new ContentValues(); |
| xnaIgnoredSourceValues.put(XnaIgnoredSourcesContract.SOURCE_ID, s1.getId()); |
| xnaIgnoredSourceValues.put(XnaIgnoredSourcesContract.ENROLLMENT_ID, s1.getEnrollmentId()); |
| |
| // Execution |
| db.insert(SourceContract.TABLE, null, sourceValues); |
| db.insert(XnaIgnoredSourcesContract.TABLE, null, xnaIgnoredSourceValues); |
| |
| // Assertion |
| assertEquals(1, DatabaseUtils.queryNumEntries(db, SourceContract.TABLE)); |
| assertEquals(1, DatabaseUtils.queryNumEntries(db, XnaIgnoredSourcesContract.TABLE)); |
| |
| // Execution |
| removeSources(Collections.singletonList(s1.getId()), db); |
| |
| // Assertion |
| assertEquals(0, DatabaseUtils.queryNumEntries(db, SourceContract.TABLE)); |
| assertEquals(0, DatabaseUtils.queryNumEntries(db, XnaIgnoredSourcesContract.TABLE)); |
| } |
| |
| @Test |
| public void deleteTriggers_providedIds_deletesMatchingTriggersAndRelatedData() |
| throws JSONException { |
| // Setup - Creates the following - |
| // source - S1, S2, S3, S4 |
| // trigger - T1, T2, T3, T4 |
| // event reports - E11, E12, E21, E22, E23, E33, E44 |
| // aggregate reports - AR11, AR12, AR21, AR34 |
| // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44 |
| prepareDataForSourceAndTriggerDeletion(); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| measurementDao.deleteTriggers(List.of("T1", "T2")); |
| |
| assertNotNull(measurementDao.getSource("S1")); |
| assertNotNull(measurementDao.getSource("S2")); |
| assertNotNull(measurementDao.getSource("S3")); |
| assertNotNull(measurementDao.getSource("S4")); |
| assertThrows(DatastoreException.class, () -> measurementDao.getTrigger("T1")); |
| assertThrows(DatastoreException.class, () -> measurementDao.getTrigger("T2")); |
| assertNotNull(measurementDao.getTrigger("T3")); |
| assertNotNull(measurementDao.getTrigger("T4")); |
| |
| assertThrows( |
| DatastoreException.class, () -> measurementDao.getEventReport("E11")); |
| assertThrows( |
| DatastoreException.class, () -> measurementDao.getEventReport("E12")); |
| assertThrows( |
| DatastoreException.class, () -> measurementDao.getEventReport("E21")); |
| assertThrows( |
| DatastoreException.class, () -> measurementDao.getEventReport("E22")); |
| assertNotNull(measurementDao.getEventReport("E23")); |
| assertNotNull(measurementDao.getEventReport("E33")); |
| assertNotNull(measurementDao.getEventReport("E44")); |
| |
| assertThrows( |
| DatastoreException.class, |
| () -> measurementDao.getAggregateReport("AR11")); |
| assertThrows( |
| DatastoreException.class, |
| () -> measurementDao.getAggregateReport("AR12")); |
| assertThrows( |
| DatastoreException.class, |
| () -> measurementDao.getAggregateReport("AR21")); |
| |
| assertNotNull(measurementDao.getAggregateReport("AR34")); |
| }); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE)); |
| } |
| |
| private void prepareDataForSourceAndTriggerDeletion() throws JSONException { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Source s1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setId("S1") |
| .build(); // deleted |
| Source s2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setId("S2") |
| .build(); // deleted |
| Source s3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(3L)) |
| .setId("S3") |
| .build(); |
| Source s4 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(4L)) |
| .setId("S4") |
| .build(); |
| Trigger t1 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setId("T1") |
| .build(); |
| Trigger t2 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setId("T2") |
| .build(); |
| Trigger t3 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setId("T3") |
| .build(); |
| Trigger t4 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setId("T4") |
| .build(); |
| EventReport e11 = createEventReportForSourceAndTrigger("E11", s1, t1); |
| EventReport e12 = createEventReportForSourceAndTrigger("E12", s1, t2); |
| EventReport e21 = createEventReportForSourceAndTrigger("E21", s2, t1); |
| EventReport e22 = createEventReportForSourceAndTrigger("E22", s2, t2); |
| EventReport e23 = createEventReportForSourceAndTrigger("E23", s2, t3); |
| EventReport e33 = createEventReportForSourceAndTrigger("E33", s3, t3); |
| EventReport e44 = createEventReportForSourceAndTrigger("E44", s4, t4); |
| AggregateReport ar11 = createAggregateReportForSourceAndTrigger("AR11", s1, t1); |
| AggregateReport ar12 = createAggregateReportForSourceAndTrigger("AR12", s1, t2); |
| AggregateReport ar21 = createAggregateReportForSourceAndTrigger("AR21", s2, t1); |
| AggregateReport ar34 = createAggregateReportForSourceAndTrigger("AR34", s3, t4); |
| Attribution att11 = |
| createAttributionWithSourceAndTriggerIds( |
| "ATT11", s1.getId(), t1.getId()); // deleted |
| Attribution att12 = |
| createAttributionWithSourceAndTriggerIds( |
| "ATT12", s1.getId(), t2.getId()); // deleted |
| Attribution att21 = |
| createAttributionWithSourceAndTriggerIds( |
| "ATT21", s2.getId(), t1.getId()); // deleted |
| Attribution att22 = |
| createAttributionWithSourceAndTriggerIds( |
| "ATT22", s2.getId(), t2.getId()); // deleted |
| Attribution att33 = |
| createAttributionWithSourceAndTriggerIds("ATT33", s3.getId(), t3.getId()); |
| Attribution att44 = |
| createAttributionWithSourceAndTriggerIds("ATT44", s4.getId(), t4.getId()); |
| |
| insertSource(s1, s1.getId()); |
| insertSource(s2, s2.getId()); |
| insertSource(s3, s3.getId()); |
| insertSource(s4, s4.getId()); |
| |
| AbstractDbIntegrationTest.insertToDb(t1, db); |
| AbstractDbIntegrationTest.insertToDb(t2, db); |
| AbstractDbIntegrationTest.insertToDb(t3, db); |
| AbstractDbIntegrationTest.insertToDb(t4, db); |
| |
| AbstractDbIntegrationTest.insertToDb(e11, db); |
| AbstractDbIntegrationTest.insertToDb(e12, db); |
| AbstractDbIntegrationTest.insertToDb(e21, db); |
| AbstractDbIntegrationTest.insertToDb(e22, db); |
| AbstractDbIntegrationTest.insertToDb(e23, db); |
| AbstractDbIntegrationTest.insertToDb(e33, db); |
| AbstractDbIntegrationTest.insertToDb(e44, db); |
| |
| AbstractDbIntegrationTest.insertToDb(ar11, db); |
| AbstractDbIntegrationTest.insertToDb(ar12, db); |
| AbstractDbIntegrationTest.insertToDb(ar21, db); |
| AbstractDbIntegrationTest.insertToDb(ar34, db); |
| |
| AbstractDbIntegrationTest.insertToDb(att11, db); |
| AbstractDbIntegrationTest.insertToDb(att12, db); |
| AbstractDbIntegrationTest.insertToDb(att21, db); |
| AbstractDbIntegrationTest.insertToDb(att22, db); |
| AbstractDbIntegrationTest.insertToDb(att33, db); |
| AbstractDbIntegrationTest.insertToDb(att44, db); |
| } |
| |
| @Test |
| public void testUndoInstallAttribution_noMarkedSource() { |
| long currentTimestamp = System.currentTimeMillis(); |
| Source source = |
| createSourceForIATest("IA1", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID) |
| .build(); |
| source.setInstallAttributed(true); |
| insertSource(source, source.getId()); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.undoInstallAttribution(INSTALLED_PACKAGE))); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| // Should set installAttributed = false for id=IA1 |
| assertFalse(getInstallAttributionStatus("IA1", db)); |
| } |
| |
| @Test |
| public void undoInstallAttribution_uninstall_nullInstallTime() { |
| long currentTimestamp = System.currentTimeMillis(); |
| Source source = |
| createSourceForIATest("IA1", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID) |
| .build(); |
| source.setInstallAttributed(true); |
| insertSource(source, source.getId()); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.undoInstallAttribution(INSTALLED_PACKAGE))); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| // Should set installTime = null for id=IA1 |
| assertNull(getInstallAttributionInstallTime("IA1", db)); |
| } |
| |
| @Test |
| public void getSourceDestinations_returnsExpected() { |
| // Insert two sources with some intersection of destinations |
| // and assert all destination queries work. |
| |
| // First source |
| List<Uri> webDestinations1 = |
| List.of( |
| Uri.parse("https://first-place.test"), |
| Uri.parse("https://second-place.test"), |
| Uri.parse("https://third-place.test")); |
| List<Uri> appDestinations1 = List.of(Uri.parse("android-app://test.first-place")); |
| Source source1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("1") |
| .setAppDestinations(appDestinations1) |
| .setWebDestinations(webDestinations1) |
| .build(); |
| insertSource(source1, source1.getId()); |
| |
| // Second source |
| List<Uri> webDestinations2 = |
| List.of( |
| Uri.parse("https://not-first-place.test"), |
| Uri.parse("https://not-second-place.test"), |
| Uri.parse("https://third-place.test")); |
| List<Uri> appDestinations2 = List.of(Uri.parse("android-app://test.not-first-place")); |
| Source source2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("2") |
| .setAppDestinations(appDestinations2) |
| .setWebDestinations(webDestinations2) |
| .build(); |
| insertSource(source2, source2.getId()); |
| |
| Pair<List<Uri>, List<Uri>> result1 = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceDestinations(source1.getId())) |
| .get(); |
| // Assert first app destinations |
| assertTrue( |
| ImmutableMultiset.copyOf(source1.getAppDestinations()) |
| .equals(ImmutableMultiset.copyOf(result1.first))); |
| // Assert first web destinations |
| assertTrue( |
| ImmutableMultiset.copyOf(source1.getWebDestinations()) |
| .equals(ImmutableMultiset.copyOf(result1.second))); |
| |
| Pair<List<Uri>, List<Uri>> result2 = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceDestinations(source2.getId())) |
| .get(); |
| // Assert second app destinations |
| assertTrue( |
| ImmutableMultiset.copyOf(source2.getAppDestinations()) |
| .equals(ImmutableMultiset.copyOf(result2.first))); |
| // Assert second web destinations |
| assertTrue( |
| ImmutableMultiset.copyOf(source2.getWebDestinations()) |
| .equals(ImmutableMultiset.copyOf(result2.second))); |
| } |
| |
| @Test |
| public void getNumAggregateReportsPerSource_returnsExpected() { |
| List<Source> sources = |
| Arrays.asList( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setId("source1") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setId("source2") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(3L)) |
| .setId("source3") |
| .build()); |
| List<AggregateReport> reports = |
| Arrays.asList( |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://destination-1.test"), 1, "source1"), |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://destination-1.test"), 2, "source1"), |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://destination-2.test"), 3, "source2")); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| sources.forEach(source -> insertSource(source, source.getId())); |
| Consumer<AggregateReport> aggregateReportConsumer = |
| aggregateReport -> { |
| ContentValues values = new ContentValues(); |
| values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId()); |
| values.put( |
| MeasurementTables.AggregateReport.SOURCE_ID, |
| aggregateReport.getSourceId()); |
| values.put( |
| MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION, |
| aggregateReport.getAttributionDestination().toString()); |
| db.insert(MeasurementTables.AggregateReport.TABLE, null, values); |
| }; |
| reports.forEach(aggregateReportConsumer); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| assertThat(measurementDao.getNumAggregateReportsPerSource("source1")) |
| .isEqualTo(2); |
| assertThat(measurementDao.getNumAggregateReportsPerSource("source2")) |
| .isEqualTo(1); |
| assertThat(measurementDao.getNumAggregateReportsPerSource("source3")) |
| .isEqualTo(0); |
| }); |
| } |
| |
| @Test |
| public void getNumAggregateReportsPerDestination_returnsExpected() { |
| List<AggregateReport> reportsWithPlainDestination = |
| Arrays.asList( |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://destination-1.test"), 1)); |
| List<AggregateReport> reportsWithPlainAndSubDomainDestination = |
| Arrays.asList( |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://destination-2.test"), 2), |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://subdomain.destination-2.test"), 3)); |
| List<AggregateReport> reportsWithPlainAndPathDestination = |
| Arrays.asList( |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://subdomain.destination-3.test"), 4), |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://subdomain.destination-3.test/abcd"), 5)); |
| List<AggregateReport> reportsWithAll3Types = |
| Arrays.asList( |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://destination-4.test"), 6), |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://subdomain.destination-4.test"), 7), |
| generateMockAggregateReport( |
| WebUtil.validUrl("https://subdomain.destination-4.test/abcd"), 8)); |
| List<AggregateReport> reportsWithAndroidAppDestination = |
| Arrays.asList(generateMockAggregateReport("android-app://destination-5.app", 9)); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| Stream.of( |
| reportsWithPlainDestination, |
| reportsWithPlainAndSubDomainDestination, |
| reportsWithPlainAndPathDestination, |
| reportsWithAll3Types, |
| reportsWithAndroidAppDestination) |
| .flatMap(Collection::stream) |
| .forEach( |
| aggregateReport -> { |
| ContentValues values = new ContentValues(); |
| values.put( |
| MeasurementTables.AggregateReport.ID, aggregateReport.getId()); |
| values.put( |
| MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION, |
| aggregateReport.getAttributionDestination().toString()); |
| values.put( |
| MeasurementTables.AggregateReport.IS_FAKE_REPORT, |
| aggregateReport.isFakeReport()); |
| db.insert(MeasurementTables.AggregateReport.TABLE, null, values); |
| }); |
| |
| List<String> attributionDestinations1 = createWebDestinationVariants(1); |
| List<String> attributionDestinations2 = createWebDestinationVariants(2); |
| List<String> attributionDestinations3 = createWebDestinationVariants(3); |
| List<String> attributionDestinations4 = createWebDestinationVariants(4); |
| List<String> attributionDestinations5 = createAppDestinationVariants(5); |
| |
| // expected query return values for attribution destination variants |
| List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0); |
| List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0); |
| List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0); |
| List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0); |
| List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0); |
| assertAggregateReportCount( |
| attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts); |
| assertAggregateReportCount( |
| attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts); |
| assertAggregateReportCount( |
| attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts); |
| assertAggregateReportCount( |
| attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts); |
| assertAggregateReportCount( |
| attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts); |
| } |
| |
| @Test |
| public void getAggregateReportById_fakeReport() { |
| AggregateReport ar11 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("11") |
| .setIsFakeReport(true) |
| .build(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AbstractDbIntegrationTest.insertToDb(ar11, db); |
| |
| Optional<AggregateReport> resOpt = |
| mDatastoreManager.runInTransactionWithResult((dao) -> dao.getAggregateReport("11")); |
| assertTrue(resOpt.isPresent()); |
| AggregateReport res = resOpt.get(); |
| assertTrue(res.isFakeReport()); |
| } |
| |
| @Test |
| public void getNumEventReportsPerDestination_returnsExpected() { |
| List<EventReport> reportsWithPlainDestination = |
| Arrays.asList( |
| generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1)); |
| List<EventReport> reportsWithPlainAndSubDomainDestination = |
| Arrays.asList( |
| generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2), |
| generateMockEventReport( |
| WebUtil.validUrl("https://subdomain.destination-2.test"), 3)); |
| List<EventReport> reportsWithPlainAndPathDestination = |
| Arrays.asList( |
| generateMockEventReport( |
| WebUtil.validUrl("https://subdomain.destination-3.test"), 4), |
| generateMockEventReport( |
| WebUtil.validUrl("https://subdomain.destination-3.test/abcd"), 5)); |
| List<EventReport> reportsWithAll3Types = |
| Arrays.asList( |
| generateMockEventReport(WebUtil.validUrl("https://destination-4.test"), 6), |
| generateMockEventReport( |
| WebUtil.validUrl("https://subdomain.destination-4.test"), 7), |
| generateMockEventReport( |
| WebUtil.validUrl("https://subdomain.destination-4.test/abcd"), 8)); |
| List<EventReport> reportsWithAndroidAppDestination = |
| Arrays.asList(generateMockEventReport("android-app://destination-5.app", 9)); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| Stream.of( |
| reportsWithPlainDestination, |
| reportsWithPlainAndSubDomainDestination, |
| reportsWithPlainAndPathDestination, |
| reportsWithAll3Types, |
| reportsWithAndroidAppDestination) |
| .flatMap(Collection::stream) |
| .forEach( |
| eventReport -> { |
| ContentValues values = new ContentValues(); |
| values.put( |
| MeasurementTables.EventReportContract.ID, eventReport.getId()); |
| values.put( |
| MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION, |
| eventReport.getAttributionDestinations().get(0).toString()); |
| db.insert(MeasurementTables.EventReportContract.TABLE, null, values); |
| }); |
| |
| List<String> attributionDestinations1 = createWebDestinationVariants(1); |
| List<String> attributionDestinations2 = createWebDestinationVariants(2); |
| List<String> attributionDestinations3 = createWebDestinationVariants(3); |
| List<String> attributionDestinations4 = createWebDestinationVariants(4); |
| List<String> attributionDestinations5 = createAppDestinationVariants(5); |
| |
| // expected query return values for attribution destination variants |
| List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0); |
| List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0); |
| List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0); |
| List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0); |
| List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0); |
| assertEventReportCount( |
| attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts); |
| assertEventReportCount( |
| attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts); |
| assertEventReportCount( |
| attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts); |
| assertEventReportCount( |
| attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts); |
| assertEventReportCount( |
| attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts); |
| } |
| |
| @Test |
| public void testGetSourceEventReports() { |
| List<Source> sourceList = |
| Arrays.asList( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("1") |
| .setEventId(new UnsignedLong(3L)) |
| .setEnrollmentId("1") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("2") |
| .setEventId(new UnsignedLong(4L)) |
| .setEnrollmentId("1") |
| .build(), |
| // Should always be ignored |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("3") |
| .setEventId(new UnsignedLong(4L)) |
| .setEnrollmentId("2") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("15") |
| .setEventId(new UnsignedLong(15L)) |
| .setEnrollmentId("2") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("16") |
| .setEventId(new UnsignedLong(16L)) |
| .setEnrollmentId("2") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("20") |
| .setEventId(new UnsignedLong(20L)) |
| .setEnrollmentId("2") |
| .build()); |
| |
| List<Trigger> triggers = |
| Arrays.asList( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("101") |
| .setEnrollmentId("2") |
| .build(), |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("102") |
| .setEnrollmentId("2") |
| .build(), |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("201") |
| .setEnrollmentId("2") |
| .build(), |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("202") |
| .setEnrollmentId("2") |
| .build(), |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("1001") |
| .setEnrollmentId("2") |
| .build()); |
| |
| // Should match with source 1 |
| List<EventReport> reportList1 = new ArrayList<>(); |
| reportList1.add( |
| new EventReport.Builder() |
| .setId("1") |
| .setSourceEventId(new UnsignedLong(3L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(sourceList.get(0).getAppDestinations()) |
| .setSourceType(sourceList.get(0).getSourceType()) |
| .setSourceId("1") |
| .setTriggerId("101") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList1.add( |
| new EventReport.Builder() |
| .setId("7") |
| .setSourceEventId(new UnsignedLong(3L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceType(sourceList.get(0).getSourceType()) |
| .setSourceId("1") |
| .setTriggerId("102") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| // Should match with source 2 |
| List<EventReport> reportList2 = new ArrayList<>(); |
| reportList2.add( |
| new EventReport.Builder() |
| .setId("3") |
| .setSourceEventId(new UnsignedLong(4L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(sourceList.get(1).getAppDestinations()) |
| .setSourceType(sourceList.get(1).getSourceType()) |
| .setSourceId("2") |
| .setTriggerId("201") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList2.add( |
| new EventReport.Builder() |
| .setId("8") |
| .setSourceEventId(new UnsignedLong(4L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(sourceList.get(1).getAppDestinations()) |
| .setSourceType(sourceList.get(1).getSourceType()) |
| .setSourceId("2") |
| .setTriggerId("202") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| List<EventReport> reportList3 = new ArrayList<>(); |
| // Should not match with any source |
| reportList3.add( |
| new EventReport.Builder() |
| .setId("2") |
| .setSourceEventId(new UnsignedLong(5L)) |
| .setEnrollmentId("1") |
| .setSourceType(Source.SourceType.EVENT) |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceId("15") |
| .setTriggerId("1001") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList3.add( |
| new EventReport.Builder() |
| .setId("4") |
| .setSourceEventId(new UnsignedLong(6L)) |
| .setEnrollmentId("1") |
| .setSourceType(Source.SourceType.EVENT) |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceId("16") |
| .setTriggerId("1001") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setTriggerValue(100L) |
| .build()); |
| reportList3.add( |
| new EventReport.Builder() |
| .setId("5") |
| .setSourceEventId(new UnsignedLong(1L)) |
| .setEnrollmentId("1") |
| .setSourceType(Source.SourceType.EVENT) |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceId("15") |
| .setTriggerId("1001") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setTriggerValue(120L) |
| .build()); |
| reportList3.add( |
| new EventReport.Builder() |
| .setId("6") |
| .setSourceEventId(new UnsignedLong(2L)) |
| .setEnrollmentId("1") |
| .setSourceType(Source.SourceType.EVENT) |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceId("20") |
| .setTriggerId("1001") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setTriggerValue(200L) |
| .build()); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| sourceList.forEach(source -> insertSource(source, source.getId())); |
| triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db)); |
| |
| Stream.of(reportList1, reportList2, reportList3) |
| .flatMap(Collection::stream) |
| .forEach( |
| (eventReport -> { |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.insertEventReport(eventReport)); |
| })); |
| |
| assertEquals( |
| reportList1, |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceEventReports(sourceList.get(0))) |
| .orElseThrow()); |
| |
| assertEquals( |
| reportList2, |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceEventReports(sourceList.get(1))) |
| .orElseThrow()); |
| } |
| |
| @Test |
| public void getSourceEventReports_sourcesWithSameEventId_haveSeparateEventReportsMatch() { |
| List<Source> sourceList = |
| Arrays.asList( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("1") |
| .setEventId(new UnsignedLong(1L)) |
| .setEnrollmentId("1") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("2") |
| .setEventId(new UnsignedLong(1L)) |
| .setEnrollmentId("1") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("3") |
| .setEventId(new UnsignedLong(2L)) |
| .setEnrollmentId("2") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("4") |
| .setEventId(new UnsignedLong(2L)) |
| .setEnrollmentId("2") |
| .build()); |
| |
| List<Trigger> triggers = |
| Arrays.asList( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("101") |
| .setEnrollmentId("2") |
| .build(), |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("102") |
| .setEnrollmentId("2") |
| .build()); |
| |
| // Should match with source 1 |
| List<EventReport> reportList1 = new ArrayList<>(); |
| reportList1.add( |
| new EventReport.Builder() |
| .setId("1") |
| .setSourceEventId(new UnsignedLong(1L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(sourceList.get(0).getAppDestinations()) |
| .setSourceType(sourceList.get(0).getSourceType()) |
| .setSourceId("1") |
| .setTriggerId("101") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList1.add( |
| new EventReport.Builder() |
| .setId("2") |
| .setSourceEventId(new UnsignedLong(1L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceType(sourceList.get(0).getSourceType()) |
| .setSourceId("1") |
| .setTriggerId("102") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| // Should match with source 2 |
| List<EventReport> reportList2 = new ArrayList<>(); |
| reportList2.add( |
| new EventReport.Builder() |
| .setId("3") |
| .setSourceEventId(new UnsignedLong(2L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(sourceList.get(1).getAppDestinations()) |
| .setSourceType(sourceList.get(1).getSourceType()) |
| .setSourceId("2") |
| .setTriggerId("101") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList2.add( |
| new EventReport.Builder() |
| .setId("4") |
| .setSourceEventId(new UnsignedLong(2L)) |
| .setEnrollmentId("1") |
| .setAttributionDestinations(sourceList.get(1).getAppDestinations()) |
| .setSourceType(sourceList.get(1).getSourceType()) |
| .setSourceId("2") |
| .setTriggerId("102") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| // Match with source3 |
| List<EventReport> reportList3 = new ArrayList<>(); |
| reportList3.add( |
| new EventReport.Builder() |
| .setId("5") |
| .setSourceEventId(new UnsignedLong(2L)) |
| .setEnrollmentId("2") |
| .setSourceType(Source.SourceType.EVENT) |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceId("3") |
| .setTriggerId("101") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList3.add( |
| new EventReport.Builder() |
| .setId("6") |
| .setSourceEventId(new UnsignedLong(2L)) |
| .setEnrollmentId("2") |
| .setSourceType(Source.SourceType.EVENT) |
| .setAttributionDestinations(List.of(APP_DESTINATION)) |
| .setSourceId("3") |
| .setTriggerId("102") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| sourceList.forEach(source -> insertSource(source, source.getId())); |
| triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db)); |
| |
| Stream.of(reportList1, reportList2, reportList3) |
| .flatMap(Collection::stream) |
| .forEach( |
| (eventReport -> { |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.insertEventReport(eventReport)); |
| })); |
| |
| assertEquals( |
| reportList1, |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceEventReports(sourceList.get(0))) |
| .orElseThrow()); |
| |
| assertEquals( |
| reportList2, |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceEventReports(sourceList.get(1))) |
| .orElseThrow()); |
| |
| assertEquals( |
| reportList3, |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getSourceEventReports(sourceList.get(2))) |
| .orElseThrow()); |
| } |
| |
| @Test |
| public void testUpdateSourceStatus() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| |
| List<Source> sourceList = new ArrayList<>(); |
| sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("1").build()); |
| sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("2").build()); |
| sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("3").build()); |
| sourceList.forEach( |
| source -> { |
| ContentValues values = new ContentValues(); |
| values.put(SourceContract.ID, source.getId()); |
| values.put(SourceContract.STATUS, 1); |
| db.insert(SourceContract.TABLE, null, values); |
| }); |
| |
| // Multiple Elements |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.updateSourceStatus( |
| List.of("1", "2", "3"), Source.Status.IGNORED))); |
| |
| // Single Element |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.updateSourceStatus( |
| List.of("1", "2"), Source.Status.IGNORED))); |
| } |
| |
| @Test |
| public void updateSourceAttributedTriggers_baseline_equal() throws JSONException { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| |
| List<Source> sourceList = new ArrayList<>(); |
| sourceList.add( |
| SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum() |
| .setId("1") |
| .build()); |
| sourceList.add( |
| SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum() |
| .setId("2") |
| .build()); |
| sourceList.add( |
| SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum() |
| .setId("3") |
| .build()); |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| for (Source source : sourceList) { |
| dao.insertSource(source); |
| } |
| }); |
| List<EventReport> eventReportList = new ArrayList<>(); |
| eventReportList.add( |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerData(new UnsignedLong(1L)) |
| .setTriggerPriority(3L) |
| .setTriggerValue(5L) |
| .setReportTime(10000L) |
| .build()); |
| Source originalSource = sourceList.get(0); |
| insertAttributedTrigger(originalSource.getTriggerSpecs(), eventReportList.get(0)); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| Source newSource = measurementDao.getSource(originalSource.getId()); |
| assertNotEquals(originalSource, newSource); |
| assertEquals( |
| 0, newSource.getTriggerSpecs().getAttributedTriggers().size()); |
| }); |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.updateSourceAttributedTriggers( |
| originalSource.getId(), |
| originalSource.attributedTriggersToJsonFlexApi())); |
| |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| Source newSource = measurementDao.getSource(originalSource.getId()); |
| assertEquals(originalSource, newSource); |
| }); |
| } |
| |
| @Test |
| public void testGetMatchingActiveSources() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| String enrollmentId = "enrollment-id"; |
| Uri appDestination = Uri.parse("android-app://com.example.abc"); |
| Uri webDestination = WebUtil.validUri("https://example.test"); |
| Uri webDestinationWithSubdomain = WebUtil.validUri("https://xyz.example.test"); |
| Source sApp1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("1") |
| .setEventTime(10) |
| .setExpiryTime(20) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sApp2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("2") |
| .setEventTime(10) |
| .setExpiryTime(50) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sApp3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("3") |
| .setEventTime(20) |
| .setExpiryTime(50) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sApp4 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("4") |
| .setEventTime(30) |
| .setExpiryTime(50) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sWeb5 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("5") |
| .setEventTime(10) |
| .setExpiryTime(20) |
| .setWebDestinations(List.of(webDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sWeb6 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("6") |
| .setEventTime(10) |
| .setExpiryTime(50) |
| .setWebDestinations(List.of(webDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sAppWeb7 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("7") |
| .setEventTime(10) |
| .setExpiryTime(20) |
| .setAppDestinations(List.of(appDestination)) |
| .setWebDestinations(List.of(webDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| |
| List<Source> sources = Arrays.asList(sApp1, sApp2, sApp3, sApp4, sWeb5, sWeb6, sAppWeb7); |
| sources.forEach(source -> insertInDb(db, source)); |
| |
| Function<Trigger, List<Source>> runFunc = |
| trigger -> { |
| List<Source> result = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getMatchingActiveSources( |
| trigger)) |
| .orElseThrow(); |
| result.sort(Comparator.comparing(Source::getId)); |
| return result; |
| }; |
| |
| // Trigger Time > sApp1's eventTime and < sApp1's expiryTime |
| // Trigger Time > sApp2's eventTime and < sApp2's expiryTime |
| // Trigger Time < sApp3's eventTime |
| // Trigger Time < sApp4's eventTime |
| // sApp5 and sApp6 don't have app destination |
| // Trigger Time > sAppWeb7's eventTime and < sAppWeb7's expiryTime |
| // Expected: Match with sApp1, sApp2, sAppWeb7 |
| Trigger trigger1MatchSource1And2 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(12) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| List<Source> result1 = runFunc.apply(trigger1MatchSource1And2); |
| assertEquals(3, result1.size()); |
| assertEquals(sApp1.getId(), result1.get(0).getId()); |
| assertEquals(sApp2.getId(), result1.get(1).getId()); |
| assertEquals(sAppWeb7.getId(), result1.get(2).getId()); |
| |
| // Trigger Time > sApp1's eventTime and = sApp1's expiryTime |
| // Trigger Time > sApp2's eventTime and < sApp2's expiryTime |
| // Trigger Time = sApp3's eventTime |
| // Trigger Time < sApp4's eventTime |
| // sApp5 and sApp6 don't have app destination |
| // Trigger Time > sAppWeb7's eventTime and = sAppWeb7's expiryTime |
| // Expected: Match with sApp2, sApp3 |
| Trigger trigger2MatchSource127 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(20) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| |
| List<Source> result2 = runFunc.apply(trigger2MatchSource127); |
| assertEquals(2, result2.size()); |
| assertEquals(sApp2.getId(), result2.get(0).getId()); |
| assertEquals(sApp3.getId(), result2.get(1).getId()); |
| |
| // Trigger Time > sApp1's expiryTime |
| // Trigger Time > sApp2's eventTime and < sApp2's expiryTime |
| // Trigger Time > sApp3's eventTime and < sApp3's expiryTime |
| // Trigger Time < sApp4's eventTime |
| // sApp5 and sApp6 don't have app destination |
| // Trigger Time > sAppWeb7's expiryTime |
| // Expected: Match with sApp2, sApp3 |
| Trigger trigger3MatchSource237 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(21) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| |
| List<Source> result3 = runFunc.apply(trigger3MatchSource237); |
| assertEquals(2, result3.size()); |
| assertEquals(sApp2.getId(), result3.get(0).getId()); |
| assertEquals(sApp3.getId(), result3.get(1).getId()); |
| |
| // Trigger Time > sApp1's expiryTime |
| // Trigger Time > sApp2's eventTime and < sApp2's expiryTime |
| // Trigger Time > sApp3's eventTime and < sApp3's expiryTime |
| // Trigger Time > sApp4's eventTime and < sApp4's expiryTime |
| // sApp5 and sApp6 don't have app destination |
| // Trigger Time > sAppWeb7's expiryTime |
| // Expected: Match with sApp2, sApp3 and sApp4 |
| Trigger trigger4MatchSource1And2And3 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(31) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| |
| List<Source> result4 = runFunc.apply(trigger4MatchSource1And2And3); |
| assertEquals(3, result4.size()); |
| assertEquals(sApp2.getId(), result4.get(0).getId()); |
| assertEquals(sApp3.getId(), result4.get(1).getId()); |
| assertEquals(sApp4.getId(), result4.get(2).getId()); |
| |
| // sApp1, sApp2, sApp3, sApp4 don't have web destination |
| // Trigger Time > sWeb5's eventTime and < sApp5's expiryTime |
| // Trigger Time > sWeb6's eventTime and < sApp6's expiryTime |
| // Trigger Time > sAppWeb7's eventTime and < sAppWeb7's expiryTime |
| // Expected: Match with sApp5, sApp6, sAppWeb7 |
| Trigger trigger5MatchSource567 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(12) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(webDestination) |
| .setDestinationType(EventSurfaceType.WEB) |
| .build(); |
| List<Source> result5 = runFunc.apply(trigger5MatchSource567); |
| assertEquals(3, result1.size()); |
| assertEquals(sWeb5.getId(), result5.get(0).getId()); |
| assertEquals(sWeb6.getId(), result5.get(1).getId()); |
| assertEquals(sAppWeb7.getId(), result5.get(2).getId()); |
| |
| // sApp1, sApp2, sApp3, sApp4 don't have web destination |
| // Trigger Time > sWeb5's expiryTime |
| // Trigger Time > sWeb6's eventTime and < sApp6's expiryTime |
| // Trigger Time > sWeb7's expiryTime |
| // Expected: Match with sApp6 only |
| Trigger trigger6MatchSource67 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(21) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(webDestinationWithSubdomain) |
| .setDestinationType(EventSurfaceType.WEB) |
| .build(); |
| |
| List<Source> result6 = runFunc.apply(trigger6MatchSource67); |
| assertEquals(1, result6.size()); |
| assertEquals(sWeb6.getId(), result6.get(0).getId()); |
| |
| // Trigger with different subdomain than source |
| // Expected: No Match found |
| Trigger triggerDifferentRegistrationOrigin = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(12) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .setRegistrationOrigin( |
| WebUtil.validUri("https://subdomain-different.example.test")) |
| .build(); |
| |
| List<Source> result7 = runFunc.apply(triggerDifferentRegistrationOrigin); |
| assertTrue(result7.isEmpty()); |
| |
| // Trigger with different domain than source |
| // Expected: No Match found |
| Trigger triggerDifferentDomainOrigin = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(12) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .setRegistrationOrigin( |
| WebUtil.validUri("https://subdomain.example-different.test")) |
| .build(); |
| |
| List<Source> result8 = runFunc.apply(triggerDifferentDomainOrigin); |
| assertTrue(result8.isEmpty()); |
| |
| // Trigger with different port than source |
| // Expected: No Match found |
| Trigger triggerDifferentPort = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(12) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .setRegistrationOrigin( |
| WebUtil.validUri("https://subdomain.example.test:8083")) |
| .build(); |
| |
| List<Source> result9 = runFunc.apply(triggerDifferentPort); |
| assertTrue(result9.isEmpty()); |
| |
| // Enrollment id for trigger and source not same |
| // Registration Origin for trigger and source same |
| // Expected: Match with sApp1, sApp2, sAppWeb7 |
| Trigger triggerDifferentEnrollmentSameRegistration = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(12) |
| .setEnrollmentId("different-enrollment-id") |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| List<Source> result10 = runFunc.apply(triggerDifferentEnrollmentSameRegistration); |
| assertEquals(3, result10.size()); |
| assertEquals(sApp1.getId(), result10.get(0).getId()); |
| assertEquals(sApp2.getId(), result10.get(1).getId()); |
| assertEquals(sAppWeb7.getId(), result10.get(2).getId()); |
| } |
| |
| @Test |
| public void testGetMatchingActiveSources_multipleDestinations() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| String enrollmentId = "enrollment-id"; |
| Uri webDestination1 = WebUtil.validUri("https://example.test"); |
| Uri webDestination1WithSubdomain = WebUtil.validUri("https://xyz.example.test"); |
| Uri webDestination2 = WebUtil.validUri("https://example2.test"); |
| Source sWeb1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("1") |
| .setEventTime(10) |
| .setExpiryTime(20) |
| .setWebDestinations(List.of(webDestination1)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sWeb2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("2") |
| .setEventTime(10) |
| .setExpiryTime(50) |
| .setWebDestinations(List.of(webDestination1, webDestination2)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sAppWeb3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("3") |
| .setEventTime(10) |
| .setExpiryTime(20) |
| .setWebDestinations(List.of(webDestination1)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| |
| List<Source> sources = Arrays.asList(sWeb1, sWeb2, sAppWeb3); |
| sources.forEach(source -> insertInDb(db, source)); |
| |
| Function<Trigger, List<Source>> getMatchingSources = |
| trigger -> { |
| List<Source> result = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.getMatchingActiveSources( |
| trigger)) |
| .orElseThrow(); |
| result.sort(Comparator.comparing(Source::getId)); |
| return result; |
| }; |
| |
| Trigger triggerMatchSourceWeb2 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(21) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(webDestination1WithSubdomain) |
| .setDestinationType(EventSurfaceType.WEB) |
| .build(); |
| |
| List<Source> result = getMatchingSources.apply(triggerMatchSourceWeb2); |
| assertEquals(1, result.size()); |
| assertEquals(sWeb2.getId(), result.get(0).getId()); |
| } |
| |
| @Test |
| public void testGetMatchingActiveDelayedSources() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| String enrollmentId = "enrollment-id"; |
| Uri appDestination = Uri.parse("android-app://com.example.abc"); |
| Uri webDestination = WebUtil.validUri("https://example.test"); |
| Source sApp1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("1") |
| .setEventTime(10) |
| .setExpiryTime(20) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sApp2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("2") |
| .setEventTime(140) |
| .setExpiryTime(200) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sApp3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("3") |
| .setEventTime(20) |
| .setExpiryTime(50) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sApp4 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("4") |
| .setEventTime(16) |
| .setExpiryTime(50) |
| .setAppDestinations(List.of(appDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sWeb5 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("5") |
| .setEventTime(13) |
| .setExpiryTime(20) |
| .setWebDestinations(List.of(webDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sWeb6 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("6") |
| .setEventTime(14) |
| .setExpiryTime(50) |
| .setWebDestinations(List.of(webDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sAppWeb7 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("7") |
| .setEventTime(10) |
| .setExpiryTime(20) |
| .setAppDestinations(List.of(appDestination)) |
| .setWebDestinations(List.of(webDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| Source sAppWeb8 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("8") |
| .setEventTime(15) |
| .setExpiryTime(25) |
| .setAppDestinations(List.of(appDestination)) |
| .setWebDestinations(List.of(webDestination)) |
| .setEnrollmentId(enrollmentId) |
| .build(); |
| |
| List<Source> sources = |
| Arrays.asList(sApp1, sApp2, sApp3, sApp4, sWeb5, sWeb6, sAppWeb7, sAppWeb8); |
| sources.forEach(source -> insertInDb(db, source)); |
| |
| Function<Trigger, Optional<Source>> runFunc = |
| trigger -> { |
| Optional<Source> result = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao |
| .getNearestDelayedMatchingActiveSource( |
| trigger)) |
| .orElseThrow(); |
| return result; |
| }; |
| |
| // sApp1's eventTime <= Trigger Time |
| // Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW > sApp2's eventTime |
| // Trigger Time < sApp3's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW |
| // Trigger Time < sApp4's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW |
| // sWeb5 and sWeb6 don't have app destination |
| // sAppWeb7's eventTime <= Trigger Time |
| // Trigger Time < sAppWeb8's eventTime <= Trigger Time + |
| // MAX_DELAYED_SOURCE_REGISTRATION_WINDOW |
| // Expected: Match with sAppWeb8 |
| Trigger trigger1MatchSource8 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(12) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| Optional<Source> result1 = runFunc.apply(trigger1MatchSource8); |
| assertEquals(sAppWeb8.getId(), result1.get().getId()); |
| |
| // sApp1's eventTime <= Trigger Time |
| // Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW > sApp2's eventTime |
| // Trigger Time < sApp3's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW |
| // Trigger Time < sApp4's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW |
| // sWeb5 and sWeb6 don't have app destination |
| // sAppWeb7's eventTime <= Trigger Time |
| // sAppWeb8's eventTime <= Trigger Time |
| // Expected: Match with sApp4 |
| Trigger trigger2MatchSource4 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(15) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| Optional<Source> result2 = runFunc.apply(trigger2MatchSource4); |
| assertEquals(sApp4.getId(), result2.get().getId()); |
| |
| // sApp1's eventTime <= Trigger Time |
| // sApp2's eventTime <= Trigger Time |
| // sApp3's eventTime <= Trigger Time |
| // sApp4's eventTime <= Trigger Time |
| // sWeb5 and sWeb6 don't have app destination |
| // sAppWeb7's eventTime <= Trigger Time |
| // sAppWeb8's eventTime <= Trigger Time |
| // Expected: no match |
| Trigger trigger3NoMatchingSource = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(150) |
| .setEnrollmentId(enrollmentId) |
| .setAttributionDestination(appDestination) |
| .setDestinationType(EventSurfaceType.APP) |
| .build(); |
| Optional<Source> result3 = runFunc.apply(trigger3NoMatchingSource); |
| assertFalse(result3.isPresent()); |
| } |
| |
| @Test |
| public void testInsertAggregateEncryptionKey() { |
| String keyId = "38b1d571-f924-4dc0-abe1-e2bac9b6a6be"; |
| String publicKey = "/amqBgfDOvHAIuatDyoHxhfHaMoYA4BDxZxwtWBRQhc="; |
| long expiry = 1653620135831L; |
| Uri aggregationOrigin = WebUtil.validUri("https://a.test"); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> |
| dao.insertAggregateEncryptionKey( |
| new AggregateEncryptionKey.Builder() |
| .setKeyId(keyId) |
| .setPublicKey(publicKey) |
| .setExpiry(expiry) |
| .setAggregationCoordinatorOrigin(aggregationOrigin) |
| .build())); |
| |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query( |
| MeasurementTables.AggregateEncryptionKey.TABLE, |
| null, |
| null, |
| null, |
| null, |
| null, |
| null)) { |
| assertTrue(cursor.moveToNext()); |
| AggregateEncryptionKey aggregateEncryptionKey = |
| SqliteObjectMapper.constructAggregateEncryptionKeyFromCursor(cursor); |
| assertNotNull(aggregateEncryptionKey); |
| assertNotNull(aggregateEncryptionKey.getId()); |
| assertEquals(keyId, aggregateEncryptionKey.getKeyId()); |
| assertEquals(publicKey, aggregateEncryptionKey.getPublicKey()); |
| assertEquals(expiry, aggregateEncryptionKey.getExpiry()); |
| assertEquals( |
| aggregationOrigin, aggregateEncryptionKey.getAggregationCoordinatorOrigin()); |
| } |
| } |
| |
| @Test |
| public void testInsertAggregateReport() { |
| AggregateReport validAggregateReport = AggregateReportFixture.getValidAggregateReport(); |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.insertAggregateReport(validAggregateReport)); |
| |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query( |
| MeasurementTables.AggregateReport.TABLE, |
| null, |
| null, |
| null, |
| null, |
| null, |
| null)) { |
| assertTrue(cursor.moveToNext()); |
| AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor); |
| assertNotNull(aggregateReport); |
| assertNotNull(aggregateReport.getId()); |
| assertTrue(Objects.equals(validAggregateReport, aggregateReport)); |
| } |
| } |
| |
| @Test |
| public void testDeleteAllMeasurementDataWithEmptyList() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| Source source = SourceFixture.getMinimalValidSourceBuilder().setId("S1").build(); |
| ContentValues sourceValue = new ContentValues(); |
| sourceValue.put("_id", source.getId()); |
| db.insert(SourceContract.TABLE, null, sourceValue); |
| |
| Trigger trigger = TriggerFixture.getValidTriggerBuilder().setId("T1").build(); |
| ContentValues triggerValue = new ContentValues(); |
| triggerValue.put("_id", trigger.getId()); |
| db.insert(TriggerContract.TABLE, null, triggerValue); |
| |
| EventReport eventReport = new EventReport.Builder().setId("E1").build(); |
| ContentValues eventReportValue = new ContentValues(); |
| eventReportValue.put("_id", eventReport.getId()); |
| db.insert(EventReportContract.TABLE, null, eventReportValue); |
| |
| AggregateReport aggregateReport = new AggregateReport.Builder().setId("A1").build(); |
| ContentValues aggregateReportValue = new ContentValues(); |
| aggregateReportValue.put("_id", aggregateReport.getId()); |
| db.insert(MeasurementTables.AggregateReport.TABLE, null, aggregateReportValue); |
| |
| ContentValues rateLimitValue = new ContentValues(); |
| rateLimitValue.put(AttributionContract.ID, "ARL1"); |
| rateLimitValue.put(AttributionContract.SOURCE_SITE, "sourceSite"); |
| rateLimitValue.put(AttributionContract.SOURCE_ORIGIN, "sourceOrigin"); |
| rateLimitValue.put(AttributionContract.DESTINATION_SITE, "destinationSite"); |
| rateLimitValue.put(AttributionContract.TRIGGER_TIME, 5L); |
| rateLimitValue.put(AttributionContract.REGISTRANT, "registrant"); |
| rateLimitValue.put(AttributionContract.ENROLLMENT_ID, "enrollmentId"); |
| |
| db.insert(AttributionContract.TABLE, null, rateLimitValue); |
| |
| AggregateEncryptionKey key = |
| new AggregateEncryptionKey.Builder() |
| .setId("K1") |
| .setKeyId("keyId") |
| .setPublicKey("publicKey") |
| .setExpiry(1) |
| .setAggregationCoordinatorOrigin(Uri.parse("https://1.test")) |
| .build(); |
| ContentValues keyValues = new ContentValues(); |
| keyValues.put("_id", key.getId()); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.deleteAllMeasurementData(Collections.emptyList())); |
| |
| for (String table : ALL_MSMT_TABLES) { |
| assertThat( |
| db.query( |
| /* table */ table, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderedBy */ null) |
| .getCount()) |
| .isEqualTo(0); |
| } |
| } |
| |
| @Test |
| public void testDeleteAllMeasurementDataWithNonEmptyList() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| Source source = SourceFixture.getMinimalValidSourceBuilder().setId("S1").build(); |
| ContentValues sourceValue = new ContentValues(); |
| sourceValue.put("_id", source.getId()); |
| db.insert(SourceContract.TABLE, null, sourceValue); |
| |
| Trigger trigger = TriggerFixture.getValidTriggerBuilder().setId("T1").build(); |
| ContentValues triggerValue = new ContentValues(); |
| triggerValue.put("_id", trigger.getId()); |
| db.insert(TriggerContract.TABLE, null, triggerValue); |
| |
| EventReport eventReport = new EventReport.Builder().setId("E1").build(); |
| ContentValues eventReportValue = new ContentValues(); |
| eventReportValue.put("_id", eventReport.getId()); |
| db.insert(EventReportContract.TABLE, null, eventReportValue); |
| |
| AggregateReport aggregateReport = new AggregateReport.Builder().setId("A1").build(); |
| ContentValues aggregateReportValue = new ContentValues(); |
| aggregateReportValue.put("_id", aggregateReport.getId()); |
| db.insert(MeasurementTables.AggregateReport.TABLE, null, aggregateReportValue); |
| |
| ContentValues rateLimitValue = new ContentValues(); |
| rateLimitValue.put(AttributionContract.ID, "ARL1"); |
| rateLimitValue.put(AttributionContract.SOURCE_SITE, "sourceSite"); |
| rateLimitValue.put(AttributionContract.SOURCE_ORIGIN, "sourceOrigin"); |
| rateLimitValue.put(AttributionContract.DESTINATION_SITE, "destinationSite"); |
| rateLimitValue.put(AttributionContract.TRIGGER_TIME, 5L); |
| rateLimitValue.put(AttributionContract.REGISTRANT, "registrant"); |
| rateLimitValue.put(AttributionContract.ENROLLMENT_ID, "enrollmentId"); |
| db.insert(AttributionContract.TABLE, null, rateLimitValue); |
| |
| AggregateEncryptionKey key = |
| new AggregateEncryptionKey.Builder() |
| .setId("K1") |
| .setKeyId("keyId") |
| .setPublicKey("publicKey") |
| .setExpiry(1) |
| .setAggregationCoordinatorOrigin(Uri.parse("https://1.test")) |
| .build(); |
| ContentValues keyValues = new ContentValues(); |
| keyValues.put("_id", key.getId()); |
| |
| List<String> excludedTables = List.of(SourceContract.TABLE); |
| |
| mDatastoreManager.runInTransaction((dao) -> dao.deleteAllMeasurementData(excludedTables)); |
| |
| for (String table : ALL_MSMT_TABLES) { |
| if (!excludedTables.contains(table)) { |
| assertThat( |
| db.query( |
| /* table */ table, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderedBy */ null) |
| .getCount()) |
| .isEqualTo(0); |
| } else { |
| assertThat( |
| db.query( |
| /* table */ table, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderedBy */ null) |
| .getCount()) |
| .isNotEqualTo(0); |
| } |
| } |
| } |
| |
| /** Test that the variable ALL_MSMT_TABLES actually has all the measurement related tables. */ |
| @Test |
| public void testAllMsmtTables() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Cursor cursor = |
| db.query( |
| "sqlite_master", |
| /* columns */ null, |
| /* selection */ "type = ? AND name like ?", |
| /* selectionArgs*/ new String[] {"table", MSMT_TABLE_PREFIX + "%"}, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| |
| List<String> tableNames = new ArrayList<>(); |
| while (cursor.moveToNext()) { |
| String tableName = cursor.getString(cursor.getColumnIndex("name")); |
| tableNames.add(tableName); |
| } |
| assertThat(tableNames.size()).isEqualTo(ALL_MSMT_TABLES.length); |
| for (String tableName : tableNames) { |
| assertThat(ALL_MSMT_TABLES).asList().contains(tableName); |
| } |
| } |
| |
| @Test |
| public void insertAttributionRateLimit() { |
| // Setup |
| Source source = SourceFixture.getValidSource(); |
| Trigger trigger = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1)) |
| .build(); |
| Attribution attribution = |
| new Attribution.Builder() |
| .setScope(Attribution.Scope.AGGREGATE) |
| .setEnrollmentId(source.getEnrollmentId()) |
| .setDestinationOrigin(source.getWebDestinations().get(0).toString()) |
| .setDestinationSite(source.getAppDestinations().get(0).toString()) |
| .setSourceOrigin(source.getPublisher().toString()) |
| .setSourceSite(source.getPublisher().toString()) |
| .setRegistrant(source.getRegistrant().toString()) |
| .setTriggerTime(trigger.getTriggerTime()) |
| .setRegistrationOrigin(trigger.getRegistrationOrigin()) |
| .build(); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.insertAttribution(attribution); |
| }); |
| |
| // Assertion |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query(AttributionContract.TABLE, null, null, null, null, null, null)) { |
| assertTrue(cursor.moveToNext()); |
| assertEquals( |
| attribution.getScope(), |
| cursor.getInt(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.SCOPE))); |
| assertEquals( |
| attribution.getEnrollmentId(), |
| cursor.getString(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.ENROLLMENT_ID))); |
| assertEquals( |
| attribution.getDestinationOrigin(), |
| cursor.getString(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.DESTINATION_ORIGIN))); |
| assertEquals( |
| attribution.getDestinationSite(), |
| cursor.getString(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.DESTINATION_SITE))); |
| assertEquals( |
| attribution.getSourceOrigin(), |
| cursor.getString(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.SOURCE_ORIGIN))); |
| assertEquals( |
| attribution.getSourceSite(), |
| cursor.getString(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.SOURCE_SITE))); |
| assertEquals( |
| attribution.getRegistrant(), |
| cursor.getString(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.REGISTRANT))); |
| assertEquals( |
| attribution.getTriggerTime(), |
| cursor.getLong(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.TRIGGER_TIME))); |
| assertEquals( |
| attribution.getRegistrationOrigin(), |
| Uri.parse(cursor.getString(cursor.getColumnIndex( |
| MeasurementTables.AttributionContract.REGISTRATION_ORIGIN)))); |
| } |
| } |
| |
| @Test |
| public void testGetAttributionsPerRateLimitWindow_atTimeWindow() { |
| // Setup |
| Source source = SourceFixture.getValidSource(); |
| Trigger trigger = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1)) |
| .build(); |
| Attribution attribution = |
| new Attribution.Builder() |
| .setEnrollmentId(source.getEnrollmentId()) |
| .setDestinationOrigin(source.getWebDestinations().get(0).toString()) |
| .setDestinationSite(source.getAppDestinations().get(0).toString()) |
| .setSourceOrigin(source.getPublisher().toString()) |
| .setSourceSite(source.getPublisher().toString()) |
| .setRegistrant(source.getRegistrant().toString()) |
| .setTriggerTime( |
| trigger.getTriggerTime() |
| - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS |
| + 1) |
| .setRegistrationOrigin(trigger.getRegistrationOrigin()) |
| .build(); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.insertAttribution(attribution); |
| }); |
| |
| // Assertion |
| AtomicLong attributionsCount = new AtomicLong(); |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| attributionsCount.set(dao.getAttributionsPerRateLimitWindow(source, trigger)); |
| }); |
| |
| assertEquals(1L, attributionsCount.get()); |
| } |
| |
| @Test |
| public void getAttributionsPerRateLimitWindow_atTimeWindowScoped_countsAttribution() { |
| // Setup |
| Source source = SourceFixture.getValidSource(); |
| Trigger trigger = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1)) |
| .build(); |
| |
| Attribution eventAttribution = |
| getAttributionBuilder(source, trigger) |
| .setScope(Attribution.Scope.EVENT) |
| .build(); |
| |
| Attribution aggregateAttribution = |
| getAttributionBuilder(source, trigger) |
| .setScope(Attribution.Scope.AGGREGATE) |
| .build(); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.insertAttribution(eventAttribution); |
| dao.insertAttribution(aggregateAttribution); |
| }); |
| |
| // Assertion |
| AtomicLong eventAttributionsCount = new AtomicLong(); |
| AtomicLong aggregateAttributionsCount = new AtomicLong(); |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| eventAttributionsCount.set( |
| dao.getAttributionsPerRateLimitWindow( |
| Attribution.Scope.EVENT, source, trigger)); |
| aggregateAttributionsCount.set( |
| dao.getAttributionsPerRateLimitWindow( |
| Attribution.Scope.AGGREGATE, source, trigger)); |
| }); |
| |
| assertEquals(1L, eventAttributionsCount.get()); |
| assertEquals(1L, aggregateAttributionsCount.get()); |
| } |
| |
| @Test |
| public void getAttributionsPerRateLimitWindow_beyondTimeWindowScoped_countsAttribution() { |
| // Setup |
| Source source = SourceFixture.getValidSource(); |
| Trigger trigger = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1)) |
| .build(); |
| |
| Attribution eventAttribution = |
| getAttributionBuilder(source, trigger) |
| .setTriggerTime( |
| trigger.getTriggerTime() |
| - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS) |
| .setScope(Attribution.Scope.EVENT) |
| .build(); |
| |
| Attribution aggregateAttribution = |
| getAttributionBuilder(source, trigger) |
| .setTriggerTime( |
| trigger.getTriggerTime() |
| - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS) |
| .setScope(Attribution.Scope.AGGREGATE) |
| .build(); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.insertAttribution(eventAttribution); |
| dao.insertAttribution(aggregateAttribution); |
| }); |
| |
| // Assertion |
| AtomicLong eventAttributionsCount = new AtomicLong(); |
| AtomicLong aggregateAttributionsCount = new AtomicLong(); |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| eventAttributionsCount.set( |
| dao.getAttributionsPerRateLimitWindow( |
| Attribution.Scope.EVENT, source, trigger)); |
| aggregateAttributionsCount.set( |
| dao.getAttributionsPerRateLimitWindow( |
| Attribution.Scope.AGGREGATE, source, trigger)); |
| }); |
| |
| assertEquals(0L, eventAttributionsCount.get()); |
| assertEquals(0L, aggregateAttributionsCount.get()); |
| } |
| |
| @Test |
| public void testGetAttributionsPerRateLimitWindow_beyondTimeWindow() { |
| // Setup |
| Source source = SourceFixture.getValidSource(); |
| Trigger trigger = |
| TriggerFixture.getValidTriggerBuilder() |
| .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1)) |
| .build(); |
| Attribution attribution = |
| new Attribution.Builder() |
| .setEnrollmentId(source.getEnrollmentId()) |
| .setDestinationOrigin(source.getWebDestinations().get(0).toString()) |
| .setDestinationSite(source.getAppDestinations().get(0).toString()) |
| .setSourceOrigin(source.getPublisher().toString()) |
| .setSourceSite(source.getPublisher().toString()) |
| .setRegistrant(source.getRegistrant().toString()) |
| .setTriggerTime( |
| trigger.getTriggerTime() |
| - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS) |
| .setRegistrationOrigin(trigger.getRegistrationOrigin()) |
| .build(); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.insertAttribution(attribution); |
| }); |
| |
| // Assertion |
| AtomicLong attributionsCount = new AtomicLong(); |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| attributionsCount.set(dao.getAttributionsPerRateLimitWindow(source, trigger)); |
| }); |
| |
| assertEquals(0L, attributionsCount.get()); |
| } |
| |
| @Test |
| public void testTransactionRollbackForRuntimeException() { |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.insertSource(SourceFixture.getValidSource()); |
| // build() call throws IllegalArgumentException |
| Trigger trigger = new Trigger.Builder().build(); |
| dao.insertTrigger(trigger); |
| })); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| // There should be no insertions |
| assertEquals( |
| 0, |
| db.query(MeasurementTables.SourceContract.TABLE, null, null, null, null, null, null) |
| .getCount()); |
| assertEquals( |
| 0, |
| db.query( |
| MeasurementTables.TriggerContract.TABLE, |
| null, |
| null, |
| null, |
| null, |
| null, |
| null) |
| .getCount()); |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForSources() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| List<Source> sourceList = new ArrayList<>(); |
| // Source registrant is still installed, record is not deleted. |
| sourceList.add( |
| new Source.Builder() |
| .setId("1") |
| .setEventId(new UnsignedLong(1L)) |
| .setAppDestinations( |
| List.of(Uri.parse("android-app://installed-app-destination"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setPublisher(INSTALLED_REGISTRANT) |
| .setStatus(Source.Status.ACTIVE) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| // Source registrant is not installed, record is deleted. |
| sourceList.add( |
| new Source.Builder() |
| .setId("2") |
| .setEventId(new UnsignedLong(2L)) |
| .setAppDestinations( |
| List.of(Uri.parse("android-app://installed-app-destination"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(NOT_INSTALLED_REGISTRANT) |
| .setPublisher(NOT_INSTALLED_REGISTRANT) |
| .setStatus(Source.Status.ACTIVE) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| // Source registrant is installed and status is active on not installed destination, record |
| // is not deleted. |
| sourceList.add( |
| new Source.Builder() |
| .setId("3") |
| .setEventId(new UnsignedLong(3L)) |
| .setAppDestinations( |
| List.of(Uri.parse("android-app://not-installed-app-destination"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setPublisher(INSTALLED_REGISTRANT) |
| .setStatus(Source.Status.ACTIVE) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| // Source registrant is installed and status is ignored on not installed destination, record |
| // is deleted. |
| sourceList.add( |
| new Source.Builder() |
| .setId("4") |
| .setEventId(new UnsignedLong(4L)) |
| .setAppDestinations( |
| List.of(Uri.parse("android-app://not-installed-app-destination"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setPublisher(INSTALLED_REGISTRANT) |
| .setStatus(Source.Status.IGNORED) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| // Source registrant is installed and status is ignored on installed destination, record is |
| // not deleted. |
| sourceList.add( |
| new Source.Builder() |
| .setId("5") |
| .setEventId(new UnsignedLong(5L)) |
| .setAppDestinations( |
| List.of(Uri.parse("android-app://installed-app-destination"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setPublisher(INSTALLED_REGISTRANT) |
| .setStatus(Source.Status.IGNORED) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| |
| sourceList.forEach( |
| source -> { |
| ContentValues values = new ContentValues(); |
| values.put(SourceContract.ID, source.getId()); |
| values.put(SourceContract.EVENT_ID, source.getEventId().toString()); |
| values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId()); |
| values.put(SourceContract.REGISTRANT, source.getRegistrant().toString()); |
| values.put(SourceContract.PUBLISHER, source.getPublisher().toString()); |
| values.put(SourceContract.STATUS, source.getStatus()); |
| values.put( |
| SourceContract.REGISTRATION_ORIGIN, |
| source.getRegistrationOrigin().toString()); |
| db.insert(SourceContract.TABLE, /* nullColumnHack */ null, values); |
| |
| maybeInsertSourceDestinations(db, source, source.getId()); |
| }); |
| |
| long count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null); |
| assertEquals(5, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(INSTALLED_REGISTRANT); |
| installedUriList.add(Uri.parse("android-app://installed-app-destination")); |
| |
| assertTrue( |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList)) |
| .get()); |
| |
| count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null); |
| assertEquals(3, count); |
| |
| Cursor cursor = |
| db.query( |
| SourceContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| while (cursor.moveToNext()) { |
| String id = |
| cursor.getString(cursor.getColumnIndex(MeasurementTables.SourceContract.ID)); |
| assertThat(Arrays.asList("1", "3", "5")).contains(id); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForTriggers() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<Trigger> triggerList = new ArrayList<>(); |
| // Trigger registrant is still installed, record will not be deleted. |
| triggerList.add( |
| new Trigger.Builder() |
| .setId("1") |
| .setAttributionDestination( |
| Uri.parse("android-app://attribution-destination")) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setRegistrationOrigin( |
| TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN) |
| .build()); |
| |
| // Trigger registrant is not installed, record will be deleted. |
| triggerList.add( |
| new Trigger.Builder() |
| .setId("2") |
| .setAttributionDestination( |
| Uri.parse("android-app://attribution-destination")) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(NOT_INSTALLED_REGISTRANT) |
| .setRegistrationOrigin( |
| TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN) |
| .build()); |
| |
| triggerList.forEach( |
| trigger -> { |
| ContentValues values = new ContentValues(); |
| values.put(TriggerContract.ID, trigger.getId()); |
| values.put( |
| TriggerContract.ATTRIBUTION_DESTINATION, |
| trigger.getAttributionDestination().toString()); |
| values.put(TriggerContract.ENROLLMENT_ID, trigger.getEnrollmentId()); |
| values.put(TriggerContract.REGISTRANT, trigger.getRegistrant().toString()); |
| values.put( |
| TriggerContract.REGISTRATION_ORIGIN, |
| trigger.getRegistrationOrigin().toString()); |
| db.insert(TriggerContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| long count = DatabaseUtils.queryNumEntries(db, TriggerContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(INSTALLED_REGISTRANT); |
| |
| assertTrue( |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList)) |
| .get()); |
| |
| count = DatabaseUtils.queryNumEntries(db, TriggerContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| Cursor cursor = |
| db.query( |
| TriggerContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| while (cursor.moveToNext()) { |
| Trigger trigger = SqliteObjectMapper.constructTriggerFromCursor(cursor); |
| assertEquals("1", trigger.getId()); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForEventReports() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<EventReport> eventReportList = new ArrayList<>(); |
| // Event report attribution destination is still installed, record will not be deleted. |
| eventReportList.add( |
| new EventReport.Builder() |
| .setId("1") |
| .setAttributionDestinations(List.of( |
| Uri.parse( |
| "android-app://installed-attribution-destination"))) |
| .build()); |
| // Event report attribution destination is not installed, record will be deleted. |
| eventReportList.add( |
| new EventReport.Builder() |
| .setId("2") |
| .setAttributionDestinations(List.of( |
| Uri.parse( |
| "android-app://not-installed-attribution-destination"))) |
| .build()); |
| eventReportList.forEach( |
| eventReport -> { |
| ContentValues values = new ContentValues(); |
| values.put(EventReportContract.ID, eventReport.getId()); |
| values.put( |
| EventReportContract.ATTRIBUTION_DESTINATION, |
| eventReport.getAttributionDestinations().get(0).toString()); |
| db.insert(EventReportContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(Uri.parse("android-app://installed-attribution-destination")); |
| |
| assertTrue( |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList)) |
| .get()); |
| |
| count = DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| Cursor cursor = |
| db.query( |
| EventReportContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| while (cursor.moveToNext()) { |
| EventReport eventReport = SqliteObjectMapper.constructEventReportFromCursor(cursor); |
| assertEquals("1", eventReport.getId()); |
| } |
| } |
| |
| @Test |
| public void constructEventReportFromCursor_missingTriggerSummaryBucket_noException() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<EventReport> eventReportList = new ArrayList<>(); |
| |
| // set a valid trigger summary bucket |
| eventReportList.add( |
| EventReportFixture.getBaseEventReportBuild() |
| .setId("2") |
| .setTriggerSummaryBucket("10,99") |
| .build()); |
| // set null for trigger summary bucket explicitly |
| String emptySummaryBucket = null; |
| eventReportList.add( |
| EventReportFixture.getBaseEventReportBuild() |
| .setId("3") |
| .setTriggerSummaryBucket(emptySummaryBucket) |
| .build()); |
| |
| eventReportList.forEach( |
| eventReport -> { |
| ContentValues values = new ContentValues(); |
| values.put( |
| MeasurementTables.EventReportContract.ID, UUID.randomUUID().toString()); |
| values.put( |
| MeasurementTables.EventReportContract.TRIGGER_SUMMARY_BUCKET, |
| eventReport.getStringEncodedTriggerSummaryBucket()); |
| db.insert(EventReportContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| List<EventReport> results = new ArrayList<>(); |
| Cursor cursor = |
| db.query( |
| EventReportContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| while (cursor.moveToNext()) { |
| EventReport eventReport = SqliteObjectMapper.constructEventReportFromCursor(cursor); |
| results.add(eventReport); |
| } |
| assertEquals( |
| eventReportList.get(0).getStringEncodedTriggerSummaryBucket(), |
| results.get(0).getStringEncodedTriggerSummaryBucket()); |
| assertEquals( |
| eventReportList.get(1).getStringEncodedTriggerSummaryBucket(), |
| results.get(1).getStringEncodedTriggerSummaryBucket()); |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForAggregateReports() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<AggregateReport> aggregateReportList = new ArrayList<>(); |
| // Aggregate report attribution destination and publisher is still installed, record will |
| // not be deleted. |
| aggregateReportList.add( |
| new AggregateReport.Builder() |
| .setId("1") |
| .setAttributionDestination( |
| Uri.parse("android-app://installed-attribution-destination")) |
| .setPublisher(Uri.parse("android-app://installed-publisher")) |
| .build()); |
| // Aggregate report attribution destination is not installed, record will be deleted. |
| aggregateReportList.add( |
| new AggregateReport.Builder() |
| .setId("2") |
| .setAttributionDestination( |
| Uri.parse("android-app://not-installed-attribution-destination")) |
| .setPublisher(Uri.parse("android-app://installed-publisher")) |
| .build()); |
| // Aggregate report publisher is not installed, record will be deleted. |
| aggregateReportList.add( |
| new AggregateReport.Builder() |
| .setId("3") |
| .setAttributionDestination( |
| Uri.parse("android-app://installed-attribution-destination")) |
| .setPublisher(Uri.parse("android-app://not-installed-publisher")) |
| .build()); |
| aggregateReportList.forEach( |
| aggregateReport -> { |
| ContentValues values = new ContentValues(); |
| values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId()); |
| values.put( |
| MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION, |
| aggregateReport.getAttributionDestination().toString()); |
| values.put( |
| MeasurementTables.AggregateReport.PUBLISHER, |
| aggregateReport.getPublisher().toString()); |
| db.insert( |
| MeasurementTables.AggregateReport.TABLE, /* nullColumnHack */ |
| null, |
| values); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries( |
| db, MeasurementTables.AggregateReport.TABLE, /* selection */ null); |
| assertEquals(3, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(Uri.parse("android-app://installed-attribution-destination")); |
| installedUriList.add(Uri.parse("android-app://installed-publisher")); |
| |
| assertTrue( |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList)) |
| .get()); |
| |
| count = |
| DatabaseUtils.queryNumEntries( |
| db, MeasurementTables.AggregateReport.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| Cursor cursor = |
| db.query( |
| MeasurementTables.AggregateReport.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| while (cursor.moveToNext()) { |
| AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor); |
| assertEquals("1", aggregateReport.getId()); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForAttributions() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<Attribution> attributionList = new ArrayList<>(); |
| // Attribution has source site and destination site still installed, record will not be |
| // deleted. |
| attributionList.add( |
| new Attribution.Builder() |
| .setId("1") |
| .setSourceSite("android-app://installed-source-site") |
| .setSourceOrigin("android-app://installed-source-site") |
| .setDestinationSite("android-app://installed-destination-site") |
| .setDestinationOrigin("android-app://installed-destination-site") |
| .setRegistrant("android-app://installed-source-site") |
| .setEnrollmentId("enrollment-id") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| // Attribution has source site not installed, record will be deleted. |
| attributionList.add( |
| new Attribution.Builder() |
| .setId("2") |
| .setSourceSite("android-app://not-installed-source-site") |
| .setSourceOrigin("android-app://not-installed-source-site") |
| .setDestinationSite("android-app://installed-destination-site") |
| .setDestinationOrigin("android-app://installed-destination-site") |
| .setRegistrant("android-app://installed-source-site") |
| .setEnrollmentId("enrollment-id") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| // Attribution has destination site not installed, record will be deleted. |
| attributionList.add( |
| new Attribution.Builder() |
| .setId("3") |
| .setSourceSite("android-app://installed-source-site") |
| .setSourceOrigin("android-app://installed-source-site") |
| .setDestinationSite("android-app://not-installed-destination-site") |
| .setDestinationOrigin("android-app://not-installed-destination-site") |
| .setRegistrant("android-app://installed-source-site") |
| .setEnrollmentId("enrollment-id") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| attributionList.forEach( |
| attribution -> { |
| ContentValues values = new ContentValues(); |
| values.put(AttributionContract.ID, attribution.getId()); |
| values.put(AttributionContract.SOURCE_SITE, attribution.getSourceSite()); |
| values.put(AttributionContract.SOURCE_ORIGIN, attribution.getSourceOrigin()); |
| values.put( |
| AttributionContract.DESTINATION_SITE, attribution.getDestinationSite()); |
| values.put( |
| AttributionContract.DESTINATION_ORIGIN, |
| attribution.getDestinationOrigin()); |
| values.put(AttributionContract.REGISTRANT, attribution.getRegistrant()); |
| values.put(AttributionContract.ENROLLMENT_ID, attribution.getEnrollmentId()); |
| db.insert(AttributionContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE, /* selection */ null); |
| assertEquals(3, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(Uri.parse("android-app://installed-source-site")); |
| installedUriList.add(Uri.parse("android-app://installed-destination-site")); |
| |
| assertTrue( |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList)) |
| .get()); |
| |
| count = DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| Cursor cursor = |
| db.query( |
| AttributionContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| while (cursor.moveToNext()) { |
| Attribution attribution = constructAttributionFromCursor(cursor); |
| assertEquals("1", attribution.getId()); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForEventReportsFromSources() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| List<Source> sourceList = new ArrayList<>(); |
| sourceList.add( |
| new Source.Builder() // deleted |
| .setId("1") |
| .setEventId(new UnsignedLong(1L)) |
| .setAppDestinations(List.of(Uri.parse("android-app://app-destination-1"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(Uri.parse("android-app://uninstalled-app")) |
| .setPublisher(Uri.parse("android-app://uninstalled-app")) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| sourceList.add( |
| new Source.Builder() |
| .setId("2") |
| .setEventId(new UnsignedLong(2L)) |
| .setAppDestinations(List.of(Uri.parse("android-app://app-destination-2"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(Uri.parse("android-app://installed-app")) |
| .setPublisher(Uri.parse("android-app://installed-app")) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| sourceList.forEach(source -> insertSource(source, source.getId())); |
| |
| Trigger trigger = |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("1") |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setRegistrant(Uri.parse("android-app://installed-app")) |
| .build(); |
| AbstractDbIntegrationTest.insertToDb(trigger, db); |
| |
| List<EventReport> reportList = new ArrayList<>(); |
| reportList.add( |
| new EventReport.Builder() |
| .setId("1") // deleted |
| .setSourceEventId(new UnsignedLong(1L)) |
| .setAttributionDestinations( |
| List.of(Uri.parse("android-app://app-destination-1"))) |
| .setEnrollmentId("enrollment-id") |
| .setTriggerData(new UnsignedLong(5L)) |
| .setSourceId(sourceList.get(0).getId()) |
| .setTriggerId(trigger.getId()) |
| .setSourceType(sourceList.get(0).getSourceType()) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList.add( |
| new EventReport.Builder() |
| .setId("2") |
| .setSourceEventId(new UnsignedLong(2L)) |
| .setAttributionDestinations( |
| List.of(Uri.parse("android-app://app-destination-2"))) |
| .setEnrollmentId("enrollment-id") |
| .setTriggerData(new UnsignedLong(5L)) |
| .setSourceId(sourceList.get(1).getId()) |
| .setTriggerId(trigger.getId()) |
| .setSourceType(sourceList.get(1).getSourceType()) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| reportList.forEach(report -> AbstractDbIntegrationTest.insertToDb(report, db)); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(Uri.parse("android-app://installed-app")); |
| installedUriList.add(Uri.parse("android-app://app-destination-1")); |
| installedUriList.add(Uri.parse("android-app://app-destination-2")); |
| |
| assertTrue( |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList)) |
| .get()); |
| |
| count = DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| Cursor cursor = |
| db.query( |
| EventReportContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| while (cursor.moveToNext()) { |
| EventReport eventReport = SqliteObjectMapper.constructEventReportFromCursor(cursor); |
| assertEquals("2", eventReport.getId()); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForLargeAppList() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<Source> sourceList = new ArrayList<>(); |
| |
| int limit = 5000; |
| sourceList.add( |
| new Source.Builder() |
| .setId("1") |
| .setEventId(new UnsignedLong(1L)) |
| .setAppDestinations(List.of(Uri.parse("android-app://app-destination-1"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(Uri.parse("android-app://installed-app" + limit)) |
| .setPublisher(Uri.parse("android-app://installed-app" + limit)) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| sourceList.add( |
| new Source.Builder() |
| .setId("2") |
| .setEventId(new UnsignedLong(1L)) |
| .setAppDestinations(List.of(Uri.parse("android-app://app-destination-1"))) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrant(Uri.parse("android-app://installed-app" + (limit + 1))) |
| .setPublisher(Uri.parse("android-app://installed-app" + (limit + 1))) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build()); |
| sourceList.forEach( |
| source -> { |
| ContentValues values = new ContentValues(); |
| values.put(SourceContract.ID, source.getId()); |
| values.put(SourceContract.EVENT_ID, source.getEventId().toString()); |
| values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId()); |
| values.put(SourceContract.REGISTRANT, source.getRegistrant().toString()); |
| values.put(SourceContract.PUBLISHER, source.getPublisher().toString()); |
| values.put( |
| SourceContract.REGISTRATION_ORIGIN, |
| source.getRegistrationOrigin().toString()); |
| db.insert(SourceContract.TABLE, /* nullColumnHack */ null, values); |
| |
| maybeInsertSourceDestinations(db, source, source.getId()); |
| }); |
| |
| long count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| for (int i = 0; i <= limit; i++) { |
| installedUriList.add(Uri.parse("android-app://installed-app" + i)); |
| } |
| |
| assertTrue( |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList)) |
| .get()); |
| |
| count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| } |
| |
| @Test |
| public void testDeleteAppRecordsNotPresentForAsyncRegistrations() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| List<AsyncRegistration> asyncRegistrationList = new ArrayList<>(); |
| |
| asyncRegistrationList.add(buildAsyncRegistration("1")); |
| |
| asyncRegistrationList.add(buildAsyncRegistration("2")); |
| |
| asyncRegistrationList.add(buildAsyncRegistrationWithNotRegistrant("3")); |
| |
| asyncRegistrationList.add(buildAsyncRegistration("4")); |
| |
| asyncRegistrationList.add(buildAsyncRegistrationWithNotRegistrant("5")); |
| |
| asyncRegistrationList.forEach( |
| asyncRegistration -> { |
| ContentValues values = new ContentValues(); |
| values.put(AsyncRegistrationContract.ID, asyncRegistration.getId()); |
| values.put( |
| AsyncRegistrationContract.REGISTRANT, |
| asyncRegistration.getRegistrant().toString()); |
| values.put( |
| AsyncRegistrationContract.TOP_ORIGIN, |
| asyncRegistration.getTopOrigin().toString()); |
| values.put( |
| AsyncRegistrationContract.AD_ID_PERMISSION, |
| asyncRegistration.getDebugKeyAllowed()); |
| values.put( |
| AsyncRegistrationContract.TYPE, asyncRegistration.getType().toString()); |
| values.put( |
| AsyncRegistrationContract.REGISTRATION_ID, |
| asyncRegistration.getRegistrationId()); |
| db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries( |
| db, AsyncRegistrationContract.TABLE, /* selection */ null); |
| assertEquals(5, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(INSTALLED_REGISTRANT); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList))); |
| |
| count = |
| DatabaseUtils.queryNumEntries( |
| db, AsyncRegistrationContract.TABLE, /* selection */ null); |
| assertEquals(3, count); |
| |
| Cursor cursor = |
| db.query( |
| AsyncRegistrationContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| |
| Set<String> ids = new HashSet<>(Arrays.asList("1", "2", "4")); |
| List<AsyncRegistration> asyncRegistrations = new ArrayList<>(); |
| while (cursor.moveToNext()) { |
| AsyncRegistration asyncRegistration = |
| SqliteObjectMapper.constructAsyncRegistration(cursor); |
| asyncRegistrations.add(asyncRegistration); |
| } |
| assertTrue(asyncRegistrations.size() == 3); |
| for (AsyncRegistration asyncRegistration : asyncRegistrations) { |
| assertTrue(ids.contains(asyncRegistration.getId())); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppRecordsForAsyncRegistrations() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| List<AsyncRegistration> asyncRegistrationList = new ArrayList<>(); |
| |
| asyncRegistrationList.add(buildAsyncRegistration("1")); |
| |
| asyncRegistrationList.add(buildAsyncRegistrationWithNotDestination("2")); |
| |
| asyncRegistrationList.add(buildAsyncRegistrationWithNotRegistrant("3")); |
| |
| asyncRegistrationList.add(buildAsyncRegistration("4")); |
| |
| asyncRegistrationList.add(buildAsyncRegistrationWithNotRegistrant("5")); |
| |
| asyncRegistrationList.forEach( |
| asyncRegistration -> { |
| ContentValues values = new ContentValues(); |
| values.put(AsyncRegistrationContract.ID, asyncRegistration.getId()); |
| values.put( |
| AsyncRegistrationContract.REGISTRANT, |
| asyncRegistration.getRegistrant().toString()); |
| values.put( |
| AsyncRegistrationContract.TOP_ORIGIN, |
| asyncRegistration.getTopOrigin().toString()); |
| values.put( |
| AsyncRegistrationContract.OS_DESTINATION, |
| asyncRegistration.getOsDestination().toString()); |
| values.put( |
| AsyncRegistrationContract.AD_ID_PERMISSION, |
| asyncRegistration.getDebugKeyAllowed()); |
| values.put( |
| AsyncRegistrationContract.TYPE, asyncRegistration.getType().toString()); |
| values.put( |
| AsyncRegistrationContract.REGISTRATION_ID, |
| asyncRegistration.getRegistrationId()); |
| db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries( |
| db, AsyncRegistrationContract.TABLE, /* selection */ null); |
| assertEquals(5, count); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> measurementDao.deleteAppRecords(INSTALLED_REGISTRANT))); |
| |
| count = |
| DatabaseUtils.queryNumEntries( |
| db, AsyncRegistrationContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| Cursor cursor = |
| db.query( |
| AsyncRegistrationContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| |
| Set<String> ids = new HashSet<>(Arrays.asList("3", "5")); |
| List<AsyncRegistration> asyncRegistrations = new ArrayList<>(); |
| while (cursor.moveToNext()) { |
| AsyncRegistration asyncRegistration = |
| SqliteObjectMapper.constructAsyncRegistration(cursor); |
| asyncRegistrations.add(asyncRegistration); |
| } |
| for (AsyncRegistration asyncRegistration : asyncRegistrations) { |
| assertTrue(ids.contains(asyncRegistration.getId())); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppRecordsForDebugReports() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| List<DebugReport> debugReportList = new ArrayList<>(); |
| |
| debugReportList.add(buildDebugReportWithInstalledRegistrant("1")); |
| debugReportList.add(buildDebugReportWithInstalledRegistrant("2")); |
| debugReportList.add(buildDebugReportWithNotInstalledRegistrant("3")); |
| debugReportList.add(buildDebugReportWithNotInstalledRegistrant("4")); |
| |
| debugReportList.forEach( |
| debugReport -> { |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.insertDebugReport(debugReport))); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(4, count); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> measurementDao.deleteAppRecords(INSTALLED_REGISTRANT))); |
| |
| count = DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| Cursor cursor = |
| db.query( |
| DebugReportContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| |
| Set<String> ids = new HashSet<>(Arrays.asList("3", "4")); |
| while (cursor.moveToNext()) { |
| DebugReport debugReport = SqliteObjectMapper.constructDebugReportFromCursor(cursor); |
| assertTrue(ids.contains(debugReport.getId())); |
| } |
| } |
| |
| @Test |
| public void testDeleteAppNotPresentRecordsForDebugReports() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| List<DebugReport> debugReportList = new ArrayList<>(); |
| |
| debugReportList.add(buildDebugReportWithInstalledRegistrant("1")); |
| debugReportList.add(buildDebugReportWithInstalledRegistrant("2")); |
| debugReportList.add(buildDebugReportWithNotInstalledRegistrant("3")); |
| debugReportList.add(buildDebugReportWithNotInstalledRegistrant("4")); |
| |
| debugReportList.forEach( |
| debugReport -> { |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.insertDebugReport(debugReport))); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(4, count); |
| |
| List<Uri> installedUriList = new ArrayList<>(); |
| installedUriList.add(INSTALLED_REGISTRANT); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.deleteAppRecordsNotPresent(installedUriList))); |
| |
| count = DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(2, count); |
| |
| Cursor cursor = |
| db.query( |
| DebugReportContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| |
| Set<String> ids = new HashSet<>(Arrays.asList("1", "2")); |
| while (cursor.moveToNext()) { |
| DebugReport debugReport = SqliteObjectMapper.constructDebugReportFromCursor(cursor); |
| assertTrue(ids.contains(debugReport.getId())); |
| } |
| } |
| |
| private static AsyncRegistration buildAsyncRegistration(String id) { |
| |
| return new AsyncRegistration.Builder() |
| .setId(id) |
| .setOsDestination(Uri.parse("android-app://installed-app-destination")) |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setTopOrigin(INSTALLED_REGISTRANT) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .build(); |
| } |
| |
| private static AsyncRegistration buildAsyncRegistrationWithNotDestination(String id) { |
| return new AsyncRegistration.Builder() |
| .setId(id) |
| .setOsDestination(Uri.parse("android-app://not-installed-app-destination")) |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setTopOrigin(INSTALLED_REGISTRANT) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(Long.MAX_VALUE) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .build(); |
| } |
| |
| private static AsyncRegistration buildAsyncRegistrationWithNotRegistrant(String id) { |
| return new AsyncRegistration.Builder() |
| .setId(id) |
| .setOsDestination(Uri.parse("android-app://installed-app-destination")) |
| .setRegistrant(NOT_INSTALLED_REGISTRANT) |
| .setTopOrigin(NOT_INSTALLED_REGISTRANT) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .build(); |
| } |
| |
| @Test |
| public void testDeleteDebugReport() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| DebugReport debugReport = createDebugReport(); |
| |
| ContentValues values = new ContentValues(); |
| values.put(DebugReportContract.ID, debugReport.getId()); |
| values.put(DebugReportContract.TYPE, debugReport.getType()); |
| values.put(DebugReportContract.BODY, debugReport.getBody().toString()); |
| values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId()); |
| values.put( |
| DebugReportContract.REGISTRATION_ORIGIN, |
| debugReport.getRegistrationOrigin().toString()); |
| db.insert(DebugReportContract.TABLE, null, values); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> measurementDao.deleteDebugReport(debugReport.getId()))); |
| |
| count = DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(0, count); |
| } |
| |
| @Test |
| public void testGetDebugReportIds() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| DebugReport debugReport = createDebugReport(); |
| |
| ContentValues values = new ContentValues(); |
| values.put(DebugReportContract.ID, debugReport.getId()); |
| values.put(DebugReportContract.TYPE, debugReport.getType()); |
| values.put(DebugReportContract.BODY, debugReport.getBody().toString()); |
| values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId()); |
| values.put( |
| DebugReportContract.REGISTRATION_ORIGIN, |
| debugReport.getRegistrationOrigin().toString()); |
| db.insert(DebugReportContract.TABLE, null, values); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| assertEquals( |
| List.of(debugReport.getId()), |
| measurementDao.getDebugReportIds()))); |
| } |
| |
| @Test |
| public void testGetDebugReportIdsWithRetryLimit() { |
| // Mocking that the flags return a Max Retry of 1 |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit(); |
| ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled(); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| DebugReport debugReport = createDebugReport(); |
| |
| ContentValues values = new ContentValues(); |
| values.put(DebugReportContract.ID, debugReport.getId()); |
| values.put(DebugReportContract.TYPE, debugReport.getType()); |
| values.put(DebugReportContract.BODY, debugReport.getBody().toString()); |
| values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId()); |
| values.put( |
| DebugReportContract.REGISTRATION_ORIGIN, |
| debugReport.getRegistrationOrigin().toString()); |
| db.insert(DebugReportContract.TABLE, null, values); |
| |
| long count = |
| DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| assertEquals( |
| List.of(debugReport.getId()), |
| measurementDao.getDebugReportIds()))); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> { |
| // Adds records to KeyValueData table for Retry Count. |
| measurementDao.incrementAndGetReportingRetryCount( |
| debugReport.getId(), DataType.DEBUG_REPORT_RETRY_COUNT); |
| assertTrue(measurementDao.getDebugReportIds().isEmpty()); |
| })); |
| } |
| |
| @Test |
| public void testDeleteExpiredRecordsForAsyncRegistrations() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| List<AsyncRegistration> asyncRegistrationList = new ArrayList<>(); |
| int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST; |
| |
| // Will be deleted by request time |
| asyncRegistrationList.add( |
| new AsyncRegistration.Builder() |
| .setId("1") |
| .setOsDestination(Uri.parse("android-app://installed-app-destination")) |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setTopOrigin(INSTALLED_REGISTRANT) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(1) |
| .setRetryCount(retryLimit - 1L) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .build()); |
| |
| // Will be deleted by either request time or retry limit |
| asyncRegistrationList.add( |
| new AsyncRegistration.Builder() |
| .setId("2") |
| .setOsDestination(Uri.parse("android-app://installed-app-destination")) |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setTopOrigin(INSTALLED_REGISTRANT) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(1) |
| .setRetryCount(retryLimit) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .build()); |
| |
| // Will not be deleted |
| asyncRegistrationList.add( |
| new AsyncRegistration.Builder() |
| .setId("3") |
| .setOsDestination(Uri.parse("android-app://not-installed-app-destination")) |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setTopOrigin(INSTALLED_REGISTRANT) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(Long.MAX_VALUE) |
| .setRetryCount(retryLimit - 1L) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .build()); |
| |
| // Will be deleted due to retry limit |
| asyncRegistrationList.add( |
| new AsyncRegistration.Builder() |
| .setId("4") |
| .setOsDestination(Uri.parse("android-app://not-installed-app-destination")) |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setTopOrigin(INSTALLED_REGISTRANT) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(Long.MAX_VALUE) |
| .setRetryCount(retryLimit) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .build()); |
| |
| asyncRegistrationList.forEach( |
| asyncRegistration -> { |
| ContentValues values = new ContentValues(); |
| values.put(AsyncRegistrationContract.ID, asyncRegistration.getId()); |
| values.put( |
| AsyncRegistrationContract.REGISTRANT, |
| asyncRegistration.getRegistrant().toString()); |
| values.put( |
| AsyncRegistrationContract.TOP_ORIGIN, |
| asyncRegistration.getTopOrigin().toString()); |
| values.put( |
| AsyncRegistrationContract.OS_DESTINATION, |
| asyncRegistration.getOsDestination().toString()); |
| values.put( |
| AsyncRegistrationContract.AD_ID_PERMISSION, |
| asyncRegistration.getDebugKeyAllowed()); |
| values.put( |
| AsyncRegistrationContract.TYPE, asyncRegistration.getType().toString()); |
| values.put( |
| AsyncRegistrationContract.REQUEST_TIME, |
| asyncRegistration.getRequestTime()); |
| values.put( |
| AsyncRegistrationContract.RETRY_COUNT, |
| asyncRegistration.getRetryCount()); |
| values.put( |
| AsyncRegistrationContract.REGISTRATION_ID, |
| asyncRegistration.getRegistrationId()); |
| db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| long count = |
| DatabaseUtils.queryNumEntries( |
| db, AsyncRegistrationContract.TABLE, /* selection */ null); |
| assertEquals(4, count); |
| |
| long earliestValidInsertion = System.currentTimeMillis() - 2; |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.deleteExpiredRecords( |
| earliestValidInsertion, retryLimit))); |
| |
| count = |
| DatabaseUtils.queryNumEntries( |
| db, AsyncRegistrationContract.TABLE, /* selection */ null); |
| assertEquals(1, count); |
| |
| Cursor cursor = |
| db.query( |
| AsyncRegistrationContract.TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderBy */ null); |
| |
| Set<String> ids = new HashSet<>(Arrays.asList("3")); |
| List<AsyncRegistration> asyncRegistrations = new ArrayList<>(); |
| while (cursor.moveToNext()) { |
| AsyncRegistration asyncRegistration = |
| SqliteObjectMapper.constructAsyncRegistration(cursor); |
| asyncRegistrations.add(asyncRegistration); |
| } |
| for (AsyncRegistration asyncRegistration : asyncRegistrations) { |
| assertTrue(ids.contains(asyncRegistration.getId())); |
| } |
| } |
| |
| @Test |
| public void deleteExpiredRecords_registrationRedirectCount() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<Pair<String, String>> regIdCounts = |
| List.of( |
| new Pair<>("reg1", "1"), |
| new Pair<>("reg2", "2"), |
| new Pair<>("reg3", "3"), |
| new Pair<>("reg4", "4")); |
| for (Pair<String, String> regIdCount : regIdCounts) { |
| ContentValues contentValues = new ContentValues(); |
| contentValues.put( |
| MeasurementTables.KeyValueDataContract.DATA_TYPE, |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString()); |
| contentValues.put(MeasurementTables.KeyValueDataContract.KEY, regIdCount.first); |
| contentValues.put(MeasurementTables.KeyValueDataContract.VALUE, regIdCount.second); |
| db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, contentValues); |
| } |
| AsyncRegistration asyncRegistration1 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setRegistrationId("reg1") |
| .setRequestTime(System.currentTimeMillis() + 60000) // Avoid deletion |
| .build(); |
| AsyncRegistration asyncRegistration2 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setRegistrationId("reg2") |
| .setRequestTime(System.currentTimeMillis() + 60000) // Avoid deletion |
| .build(); |
| List<AsyncRegistration> asyncRegistrations = |
| List.of(asyncRegistration1, asyncRegistration2); |
| asyncRegistrations.forEach( |
| asyncRegistration -> |
| mDatastoreManager.runInTransaction( |
| dao -> dao.insertAsyncRegistration(asyncRegistration))); |
| |
| long earliestValidInsertion = System.currentTimeMillis() - 60000; |
| int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST; |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.deleteExpiredRecords(earliestValidInsertion, retryLimit))); |
| |
| Cursor cursor = |
| db.query( |
| MeasurementTables.KeyValueDataContract.TABLE, |
| null, |
| null, |
| null, |
| null, |
| null, |
| MeasurementTables.KeyValueDataContract.KEY); |
| assertEquals(2, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals( |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(), |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.DATA_TYPE))); |
| assertEquals( |
| "reg1", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.KEY))); |
| assertEquals( |
| "1", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.VALUE))); |
| cursor.moveToNext(); |
| assertEquals( |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(), |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.DATA_TYPE))); |
| assertEquals( |
| "reg2", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.KEY))); |
| assertEquals( |
| "2", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.VALUE))); |
| cursor.close(); |
| } |
| |
| @Test |
| public void deleteExpiredRecords_skipDeliveredEventReportsOutsideWindow() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| ContentValues sourceValid = new ContentValues(); |
| sourceValid.put(SourceContract.ID, "s1"); |
| sourceValid.put(SourceContract.EVENT_TIME, System.currentTimeMillis()); |
| |
| ContentValues sourceExpired = new ContentValues(); |
| sourceExpired.put(SourceContract.ID, "s2"); |
| sourceExpired.put( |
| SourceContract.EVENT_TIME, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(20)); |
| |
| ContentValues triggerValid = new ContentValues(); |
| triggerValid.put(TriggerContract.ID, "t1"); |
| triggerValid.put(TriggerContract.TRIGGER_TIME, System.currentTimeMillis()); |
| |
| ContentValues triggerExpired = new ContentValues(); |
| triggerExpired.put(TriggerContract.ID, "t2"); |
| triggerExpired.put( |
| TriggerContract.TRIGGER_TIME, |
| System.currentTimeMillis() - TimeUnit.DAYS.toMillis(20)); |
| |
| db.insert(SourceContract.TABLE, null, sourceValid); |
| db.insert(SourceContract.TABLE, null, sourceExpired); |
| db.insert(TriggerContract.TABLE, null, triggerValid); |
| db.insert(TriggerContract.TABLE, null, triggerExpired); |
| |
| ContentValues eventReport_NotDelivered_WithinWindow = new ContentValues(); |
| eventReport_NotDelivered_WithinWindow.put(EventReportContract.ID, "e1"); |
| eventReport_NotDelivered_WithinWindow.put( |
| EventReportContract.REPORT_TIME, System.currentTimeMillis()); |
| eventReport_NotDelivered_WithinWindow.put( |
| EventReportContract.STATUS, EventReport.Status.PENDING); |
| eventReport_NotDelivered_WithinWindow.put( |
| EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID)); |
| eventReport_NotDelivered_WithinWindow.put( |
| EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID)); |
| db.insert(EventReportContract.TABLE, null, eventReport_NotDelivered_WithinWindow); |
| |
| ContentValues eventReport_Delivered_WithinWindow = new ContentValues(); |
| eventReport_Delivered_WithinWindow.put(EventReportContract.ID, "e2"); |
| eventReport_Delivered_WithinWindow.put( |
| EventReportContract.REPORT_TIME, System.currentTimeMillis()); |
| eventReport_Delivered_WithinWindow.put( |
| EventReportContract.STATUS, EventReport.Status.DELIVERED); |
| eventReport_Delivered_WithinWindow.put( |
| EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID)); |
| eventReport_Delivered_WithinWindow.put( |
| EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID)); |
| db.insert(EventReportContract.TABLE, null, eventReport_Delivered_WithinWindow); |
| |
| ContentValues eventReport_Delivered_OutsideWindow = new ContentValues(); |
| eventReport_Delivered_OutsideWindow.put(EventReportContract.ID, "e3"); |
| eventReport_Delivered_OutsideWindow.put( |
| EventReportContract.REPORT_TIME, |
| System.currentTimeMillis() - TimeUnit.DAYS.toMillis(20)); |
| eventReport_Delivered_OutsideWindow.put( |
| EventReportContract.STATUS, EventReport.Status.DELIVERED); |
| eventReport_Delivered_OutsideWindow.put( |
| EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID)); |
| eventReport_Delivered_OutsideWindow.put( |
| EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID)); |
| db.insert(EventReportContract.TABLE, null, eventReport_Delivered_OutsideWindow); |
| |
| ContentValues eventReport_NotDelivered_OutsideWindow = new ContentValues(); |
| eventReport_NotDelivered_OutsideWindow.put(EventReportContract.ID, "e4"); |
| eventReport_NotDelivered_OutsideWindow.put( |
| EventReportContract.REPORT_TIME, |
| System.currentTimeMillis() - TimeUnit.DAYS.toMillis(20)); |
| eventReport_NotDelivered_OutsideWindow.put( |
| EventReportContract.STATUS, EventReport.Status.PENDING); |
| eventReport_NotDelivered_OutsideWindow.put( |
| EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID)); |
| eventReport_NotDelivered_OutsideWindow.put( |
| EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID)); |
| db.insert(EventReportContract.TABLE, null, eventReport_NotDelivered_OutsideWindow); |
| |
| ContentValues eventReport_expiredSource = new ContentValues(); |
| eventReport_expiredSource.put(EventReportContract.ID, "e5"); |
| eventReport_expiredSource.put(EventReportContract.REPORT_TIME, System.currentTimeMillis()); |
| eventReport_expiredSource.put(EventReportContract.STATUS, EventReport.Status.PENDING); |
| eventReport_expiredSource.put( |
| EventReportContract.SOURCE_ID, sourceExpired.getAsString(SourceContract.ID)); |
| eventReport_expiredSource.put( |
| EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID)); |
| db.insert(EventReportContract.TABLE, null, eventReport_expiredSource); |
| |
| ContentValues eventReport_expiredTrigger = new ContentValues(); |
| eventReport_expiredTrigger.put(EventReportContract.ID, "e6"); |
| eventReport_expiredTrigger.put(EventReportContract.REPORT_TIME, System.currentTimeMillis()); |
| eventReport_expiredTrigger.put(EventReportContract.STATUS, EventReport.Status.PENDING); |
| eventReport_expiredTrigger.put( |
| EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID)); |
| eventReport_expiredTrigger.put( |
| EventReportContract.TRIGGER_ID, triggerExpired.getAsString(TriggerContract.ID)); |
| db.insert(EventReportContract.TABLE, null, eventReport_expiredTrigger); |
| |
| long earliestValidInsertion = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(10); |
| int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST; |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.deleteExpiredRecords(earliestValidInsertion, retryLimit)); |
| |
| List<ContentValues> deletedReports = |
| List.of(eventReport_expiredSource, eventReport_expiredTrigger); |
| |
| List<ContentValues> notDeletedReports = |
| List.of( |
| eventReport_Delivered_OutsideWindow, |
| eventReport_Delivered_WithinWindow, |
| eventReport_NotDelivered_OutsideWindow, |
| eventReport_NotDelivered_WithinWindow); |
| |
| assertEquals( |
| notDeletedReports.size(), |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + EventReportContract.ID |
| + ") FROM " |
| + EventReportContract.TABLE |
| + " WHERE " |
| + EventReportContract.ID |
| + " IN (" |
| + notDeletedReports.stream() |
| .map( |
| (eR) -> { |
| return DatabaseUtils.sqlEscapeString( |
| eR.getAsString(EventReportContract.ID)); |
| }) |
| .collect(Collectors.joining(",")) |
| + ")", |
| null)); |
| |
| assertEquals( |
| 0, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + EventReportContract.ID |
| + ") FROM " |
| + EventReportContract.TABLE |
| + " WHERE " |
| + EventReportContract.ID |
| + " IN (" |
| + deletedReports.stream() |
| .map( |
| (eR) -> { |
| return DatabaseUtils.sqlEscapeString( |
| eR.getAsString(EventReportContract.ID)); |
| }) |
| .collect(Collectors.joining(",")) |
| + ")", |
| null)); |
| } |
| |
| @Test |
| public void deleteExpiredRecords_VerboseDebugReportsWhileLimitingRetries() { |
| // Mocking that the flags return a Max Retry of 1 |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit(); |
| ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled(); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getReadableDatabase(); |
| |
| DebugReport debugReport1 = |
| new DebugReport.Builder() |
| .setId("reportId1") |
| .setType("trigger-event-deduplicated") |
| .setBody( |
| " {\n" |
| + " \"attribution_destination\":" |
| + " \"https://destination.example\",\n" |
| + " \"source_event_id\": \"45623\"\n" |
| + " }") |
| .setEnrollmentId("1") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build(); |
| |
| DebugReport debugReport2 = |
| new DebugReport.Builder() |
| .setId("reportId2") |
| .setType("trigger-event-deduplicated") |
| .setBody( |
| " {\n" |
| + " \"attribution_destination\":" |
| + " \"https://destination.example\",\n" |
| + " \"source_event_id\": \"45623\"\n" |
| + " }") |
| .setEnrollmentId("1") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build(); |
| |
| mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport1)); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport2)); |
| |
| mDatastoreManager.runInTransaction(dao -> dao.deleteExpiredRecords(0, 0)); |
| assertEquals( |
| 2, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + DebugReportContract.ID |
| + ") FROM " |
| + DebugReportContract.TABLE, |
| null)); |
| // Increment Attempt Record 1 |
| mDatastoreManager.runInTransaction( |
| (dao) -> |
| dao.incrementAndGetReportingRetryCount( |
| debugReport1.getId(), DataType.DEBUG_REPORT_RETRY_COUNT)); |
| // Delete Expired (Record 1) |
| mDatastoreManager.runInTransaction(dao -> dao.deleteExpiredRecords(0, 0)); |
| |
| // Assert Record 2 remains. |
| assertEquals( |
| 1, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + DebugReportContract.ID |
| + ") FROM " |
| + DebugReportContract.TABLE |
| + " WHERE " |
| + DebugReportContract.ID |
| + " = ?", |
| new String[] {debugReport2.getId()})); |
| |
| // Assert Record 1 Removed |
| assertEquals( |
| 0, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + DebugReportContract.ID |
| + ") FROM " |
| + DebugReportContract.TABLE |
| + " WHERE " |
| + DebugReportContract.ID |
| + " = ?", |
| new String[] {debugReport1.getId()})); |
| } |
| |
| @Test |
| public void deleteExpiredRecords_VerboseDebugReportsWhileNotLimitingRetries() { |
| // Mocking that the retry Limiting Disable, but has limit number, |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit(); |
| ExtendedMockito.doReturn(false).when(mockFlags).getMeasurementReportingRetryLimitEnabled(); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getReadableDatabase(); |
| |
| DebugReport debugReport1 = |
| new DebugReport.Builder() |
| .setId("reportId1") |
| .setType("trigger-event-deduplicated") |
| .setBody( |
| " {\n" |
| + " \"attribution_destination\":" |
| + " \"https://destination.example\",\n" |
| + " \"source_event_id\": \"45623\"\n" |
| + " }") |
| .setEnrollmentId("1") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setInsertionTime(System.currentTimeMillis() + 60000L) |
| .build(); |
| |
| DebugReport debugReport2 = |
| new DebugReport.Builder() |
| .setId("reportId2") |
| .setType("trigger-event-deduplicated") |
| .setBody( |
| " {\n" |
| + " \"attribution_destination\":" |
| + " \"https://destination.example\",\n" |
| + " \"source_event_id\": \"45623\"\n" |
| + " }") |
| .setEnrollmentId("1") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setInsertionTime(System.currentTimeMillis() - 60000L) |
| .build(); |
| // Insert |
| mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport1)); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport2)); |
| |
| // Increment Attempt |
| mDatastoreManager.runInTransaction( |
| (dao) -> |
| dao.incrementAndGetReportingRetryCount( |
| debugReport1.getId(), DataType.DEBUG_REPORT_RETRY_COUNT)); |
| // Delete Expired |
| long earliestValidInsertion = System.currentTimeMillis(); |
| |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| measurementDao -> |
| measurementDao.deleteExpiredRecords(earliestValidInsertion, 0))); |
| |
| // Assert Record 1 remains because not expired and Retry Limiting Off. |
| assertEquals( |
| 1, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + MeasurementTables.DebugReportContract.ID |
| + ") FROM " |
| + MeasurementTables.DebugReportContract.TABLE |
| + " WHERE " |
| + MeasurementTables.DebugReportContract.ID |
| + " = ?", |
| new String[] {debugReport1.getId()})); |
| |
| // Assert Record 2 Removed because expired. |
| assertEquals( |
| 0, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + MeasurementTables.DebugReportContract.ID |
| + ") FROM " |
| + MeasurementTables.DebugReportContract.TABLE |
| + " WHERE " |
| + MeasurementTables.DebugReportContract.ID |
| + " = ?", |
| new String[] {debugReport2.getId()})); |
| } |
| |
| @Test |
| public void deleteExpiredRecords_RetryKeyValueData() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| // Non-stale join record |
| DebugReport debugReport = createDebugReport(); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport)); |
| |
| // Should Remain |
| ContentValues nonStaleValues = new ContentValues(); |
| nonStaleValues.put( |
| MeasurementTables.KeyValueDataContract.DATA_TYPE, |
| DataType.DEBUG_REPORT_RETRY_COUNT.toString()); |
| nonStaleValues.put(MeasurementTables.KeyValueDataContract.KEY, debugReport.getId()); |
| nonStaleValues.put(MeasurementTables.KeyValueDataContract.VALUE, "1"); |
| db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, nonStaleValues); |
| |
| // Should Delete |
| ContentValues staleValues = new ContentValues(); |
| staleValues.put( |
| MeasurementTables.KeyValueDataContract.DATA_TYPE, |
| DataType.DEBUG_REPORT_RETRY_COUNT.toString()); |
| staleValues.put(MeasurementTables.KeyValueDataContract.KEY, "stale-key"); |
| staleValues.put(MeasurementTables.KeyValueDataContract.VALUE, "1"); |
| db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, staleValues); |
| |
| mDatastoreManager.runInTransaction(dao -> dao.deleteExpiredRecords(0, 0)); |
| |
| // Assert Non-Stale record remains. |
| assertEquals( |
| 1, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + MeasurementTables.KeyValueDataContract.KEY |
| + ") FROM " |
| + MeasurementTables.KeyValueDataContract.TABLE |
| + " WHERE " |
| + MeasurementTables.KeyValueDataContract.KEY |
| + " = ?", |
| new String[] { |
| nonStaleValues.getAsString(MeasurementTables.KeyValueDataContract.KEY) |
| })); |
| |
| // Assert Stale Record Removed |
| assertEquals( |
| 0, |
| DatabaseUtils.longForQuery( |
| db, |
| "SELECT COUNT(" |
| + MeasurementTables.KeyValueDataContract.KEY |
| + ") FROM " |
| + MeasurementTables.KeyValueDataContract.TABLE |
| + " WHERE " |
| + MeasurementTables.KeyValueDataContract.KEY |
| + " = ?", |
| new String[] { |
| staleValues.getAsString(MeasurementTables.KeyValueDataContract.KEY) |
| })); |
| } |
| |
| @Test |
| public void getRegistrationRedirectCount_keyMissing() { |
| Optional<KeyValueData> optKeyValueData = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| "missing_random_id", DataType.REGISTRATION_REDIRECT_COUNT)); |
| assertTrue(optKeyValueData.isPresent()); |
| KeyValueData keyValueData = optKeyValueData.get(); |
| assertEquals(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT, keyValueData.getDataType()); |
| assertEquals("missing_random_id", keyValueData.getKey()); |
| assertNull(keyValueData.getValue()); |
| assertEquals(1, keyValueData.getRegistrationRedirectCount()); |
| } |
| |
| @Test |
| public void getRegistrationRedirectCount_keyExists() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| ContentValues contentValues = new ContentValues(); |
| contentValues.put( |
| MeasurementTables.KeyValueDataContract.DATA_TYPE, |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString()); |
| contentValues.put(MeasurementTables.KeyValueDataContract.KEY, "random_id"); |
| contentValues.put(MeasurementTables.KeyValueDataContract.VALUE, "2"); |
| db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, contentValues); |
| Optional<KeyValueData> optKeyValueData = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| "random_id", |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT)); |
| assertTrue(optKeyValueData.isPresent()); |
| KeyValueData keyValueData = optKeyValueData.get(); |
| assertEquals(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT, keyValueData.getDataType()); |
| assertEquals("random_id", keyValueData.getKey()); |
| assertEquals("2", keyValueData.getValue()); |
| assertEquals(2, keyValueData.getRegistrationRedirectCount()); |
| } |
| |
| @Test |
| public void updateRegistrationRedirectCount_keyMissing() { |
| KeyValueData keyValueData = |
| new KeyValueData.Builder() |
| .setDataType(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT) |
| .setKey("key_1") |
| .setValue("4") |
| .build(); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertOrUpdateKeyValueData(keyValueData)); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Cursor cursor = |
| db.query( |
| MeasurementTables.KeyValueDataContract.TABLE, |
| null, |
| null, |
| null, |
| null, |
| null, |
| null); |
| assertEquals(1, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals( |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(), |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.DATA_TYPE))); |
| assertEquals( |
| "key_1", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.KEY))); |
| assertEquals( |
| "4", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.VALUE))); |
| cursor.close(); |
| } |
| |
| @Test |
| public void updateRegistrationRedirectCount_keyExists() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| ContentValues contentValues = new ContentValues(); |
| contentValues.put( |
| MeasurementTables.KeyValueDataContract.DATA_TYPE, |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString()); |
| contentValues.put(MeasurementTables.KeyValueDataContract.KEY, "key_1"); |
| contentValues.put(MeasurementTables.KeyValueDataContract.VALUE, "2"); |
| db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, contentValues); |
| |
| KeyValueData keyValueData = |
| new KeyValueData.Builder() |
| .setDataType(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT) |
| .setKey("key_1") |
| .setValue("4") |
| .build(); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertOrUpdateKeyValueData(keyValueData)); |
| |
| Cursor cursor = |
| db.query( |
| MeasurementTables.KeyValueDataContract.TABLE, |
| null, |
| null, |
| null, |
| null, |
| null, |
| null); |
| assertEquals(1, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals( |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(), |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.DATA_TYPE))); |
| assertEquals( |
| "key_1", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.KEY))); |
| assertEquals( |
| "4", |
| cursor.getString( |
| cursor.getColumnIndex(MeasurementTables.KeyValueDataContract.VALUE))); |
| cursor.close(); |
| } |
| |
| @Test |
| public void keyValueDataTable_PrimaryKeyConstraint() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| ContentValues contentValues1 = new ContentValues(); |
| contentValues1.put( |
| MeasurementTables.KeyValueDataContract.DATA_TYPE, |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString()); |
| contentValues1.put(MeasurementTables.KeyValueDataContract.KEY, "key_1"); |
| contentValues1.put(MeasurementTables.KeyValueDataContract.VALUE, "2"); |
| |
| assertNotEquals( |
| -1, db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, contentValues1)); |
| |
| // Should fail because we are using <DataType, Key> as primary key |
| assertEquals( |
| -1, db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, contentValues1)); |
| |
| ContentValues contentValues2 = new ContentValues(); |
| contentValues2.put( |
| MeasurementTables.KeyValueDataContract.DATA_TYPE, |
| KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString()); |
| contentValues2.put(MeasurementTables.KeyValueDataContract.KEY, "key_2"); |
| contentValues2.put(MeasurementTables.KeyValueDataContract.VALUE, "2"); |
| |
| assertNotEquals( |
| -1, db.insert(MeasurementTables.KeyValueDataContract.TABLE, null, contentValues2)); |
| } |
| |
| private static Source getSourceWithDifferentDestinations( |
| int numDestinations, |
| boolean hasAppDestinations, |
| boolean hasWebDestinations, |
| long eventTime, |
| Uri publisher, |
| String enrollmentId, |
| @Source.Status int sourceStatus) { |
| List<Uri> appDestinations = null; |
| List<Uri> webDestinations = null; |
| if (hasAppDestinations) { |
| appDestinations = new ArrayList<>(); |
| appDestinations.add(Uri.parse("android-app://com.app-destination")); |
| } |
| if (hasWebDestinations) { |
| webDestinations = new ArrayList<>(); |
| for (int i = 0; i < numDestinations; i++) { |
| webDestinations.add( |
| Uri.parse("https://web-destination-" + String.valueOf(i) + ".com")); |
| } |
| } |
| long expiryTime = |
| eventTime |
| + TimeUnit.SECONDS.toMillis( |
| MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS); |
| return new Source.Builder() |
| .setEventId(new UnsignedLong(0L)) |
| .setEventTime(eventTime) |
| .setExpiryTime(expiryTime) |
| .setPublisher(publisher) |
| .setAppDestinations(appDestinations) |
| .setWebDestinations(webDestinations) |
| .setEnrollmentId(enrollmentId) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setStatus(sourceStatus) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build(); |
| } |
| |
| private static List<Source> getSourcesWithDifferentDestinations( |
| int numSources, |
| boolean hasAppDestinations, |
| boolean hasWebDestinations, |
| long eventTime, |
| Uri publisher, |
| String enrollmentId, |
| @Source.Status int sourceStatus, |
| Uri registrationOrigin) { |
| long expiryTime = |
| eventTime |
| + TimeUnit.SECONDS.toMillis( |
| MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS); |
| return getSourcesWithDifferentDestinations( |
| numSources, |
| hasAppDestinations, |
| hasWebDestinations, |
| eventTime, |
| expiryTime, |
| publisher, |
| enrollmentId, |
| sourceStatus, |
| registrationOrigin); |
| } |
| |
| private static List<Source> getSourcesWithDifferentDestinations( |
| int numSources, |
| boolean hasAppDestinations, |
| boolean hasWebDestinations, |
| long eventTime, |
| Uri publisher, |
| String enrollmentId, |
| @Source.Status int sourceStatus) { |
| long expiryTime = |
| eventTime |
| + TimeUnit.SECONDS.toMillis( |
| MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS); |
| return getSourcesWithDifferentDestinations( |
| numSources, |
| hasAppDestinations, |
| hasWebDestinations, |
| eventTime, |
| expiryTime, |
| publisher, |
| enrollmentId, |
| sourceStatus, |
| REGISTRATION_ORIGIN); |
| } |
| |
| private static List<Source> getSourcesWithDifferentDestinations( |
| int numSources, |
| boolean hasAppDestinations, |
| boolean hasWebDestinations, |
| long eventTime, |
| long expiryTime, |
| Uri publisher, |
| String enrollmentId, |
| @Source.Status int sourceStatus, |
| Uri registrationOrigin) { |
| List<Source> sources = new ArrayList<>(); |
| for (int i = 0; i < numSources; i++) { |
| Source.Builder sourceBuilder = |
| new Source.Builder() |
| .setEventId(new UnsignedLong(0L)) |
| .setEventTime(eventTime) |
| .setExpiryTime(expiryTime) |
| .setPublisher(publisher) |
| .setEnrollmentId(enrollmentId) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setStatus(sourceStatus) |
| .setRegistrationOrigin(registrationOrigin); |
| if (hasAppDestinations) { |
| sourceBuilder.setAppDestinations( |
| List.of(Uri.parse("android-app://app-destination-" + String.valueOf(i)))); |
| } |
| if (hasWebDestinations) { |
| sourceBuilder.setWebDestinations( |
| List.of( |
| Uri.parse( |
| "https://web-destination-" + String.valueOf(i) + ".com"))); |
| } |
| sources.add(sourceBuilder.build()); |
| } |
| return sources; |
| } |
| |
| private static List<Source> getSourcesWithDifferentRegistrationOrigins( |
| int numSources, |
| List<Uri> appDestinations, |
| List<Uri> webDestinations, |
| long eventTime, |
| Uri publisher, |
| @Source.Status int sourceStatus) { |
| long expiryTime = |
| eventTime |
| + TimeUnit.SECONDS.toMillis( |
| MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS); |
| return getSourcesWithDifferentRegistrationOrigins( |
| numSources, |
| appDestinations, |
| webDestinations, |
| eventTime, |
| expiryTime, |
| publisher, |
| sourceStatus); |
| } |
| |
| private static List<Source> getSourcesWithDifferentRegistrationOrigins( |
| int numSources, |
| List<Uri> appDestinations, |
| List<Uri> webDestinations, |
| long eventTime, |
| long expiryTime, |
| Uri publisher, |
| @Source.Status int sourceStatus) { |
| List<Source> sources = new ArrayList<>(); |
| for (int i = 0; i < numSources; i++) { |
| Source.Builder sourceBuilder = |
| new Source.Builder() |
| .setEventId(new UnsignedLong(0L)) |
| .setEventTime(eventTime) |
| .setExpiryTime(expiryTime) |
| .setPublisher(publisher) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setStatus(sourceStatus) |
| .setAppDestinations(getNullableUriList(appDestinations)) |
| .setWebDestinations(getNullableUriList(webDestinations)) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrationOrigin( |
| WebUtil.validUri("https://subdomain" + i + ".example.test")); |
| sources.add(sourceBuilder.build()); |
| } |
| return sources; |
| } |
| |
| private static List<Attribution> getAttributionsWithDifferentReportingOrigins( |
| int numAttributions, |
| Uri destinationSite, |
| long triggerTime, |
| Uri sourceSite, |
| String registrant) { |
| List<Attribution> attributions = new ArrayList<>(); |
| for (int i = 0; i < numAttributions; i++) { |
| Attribution.Builder attributionBuilder = |
| new Attribution.Builder() |
| .setTriggerTime(triggerTime) |
| .setSourceSite(sourceSite.toString()) |
| .setSourceOrigin(sourceSite.toString()) |
| .setDestinationSite(destinationSite.toString()) |
| .setDestinationOrigin(destinationSite.toString()) |
| .setEnrollmentId("enrollment-id") |
| .setRegistrationOrigin( |
| WebUtil.validUri("https://subdomain" + i + ".example.test")) |
| .setRegistrant(registrant); |
| attributions.add(attributionBuilder.build()); |
| } |
| return attributions; |
| } |
| |
| private static void insertAttribution(Attribution attribution) { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| ContentValues values = new ContentValues(); |
| values.put(AttributionContract.ID, UUID.randomUUID().toString()); |
| values.put(AttributionContract.SOURCE_SITE, attribution.getSourceSite()); |
| values.put(AttributionContract.DESTINATION_SITE, attribution.getDestinationSite()); |
| values.put(AttributionContract.ENROLLMENT_ID, attribution.getEnrollmentId()); |
| values.put(AttributionContract.TRIGGER_TIME, attribution.getTriggerTime()); |
| values.put(AttributionContract.SOURCE_ID, attribution.getSourceId()); |
| values.put(AttributionContract.TRIGGER_ID, attribution.getTriggerId()); |
| values.put( |
| AttributionContract.REGISTRATION_ORIGIN, |
| attribution.getRegistrationOrigin().toString()); |
| long row = db.insert("msmt_attribution", null, values); |
| assertNotEquals("Attribution insertion failed", -1, row); |
| } |
| |
| private static Attribution createAttributionWithSourceAndTriggerIds( |
| String attributionId, String sourceId, String triggerId) { |
| return new Attribution.Builder() |
| .setId(attributionId) |
| .setTriggerTime(0L) |
| .setSourceSite("android-app://source.app") |
| .setSourceOrigin("android-app://source.app") |
| .setDestinationSite("android-app://destination.app") |
| .setDestinationOrigin("android-app://destination.app") |
| .setEnrollmentId("enrollment-id-") |
| .setRegistrant("android-app://registrant.app") |
| .setSourceId(sourceId) |
| .setTriggerId(triggerId) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .build(); |
| } |
| |
| private static void insertSource(Source source) { |
| insertSource(source, UUID.randomUUID().toString()); |
| } |
| |
| // This is needed because MeasurementDao::insertSource inserts a default value for status. |
| private static void insertSource(Source source, String sourceId) { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| ContentValues values = new ContentValues(); |
| values.put(SourceContract.ID, sourceId); |
| if (source.getEventId() != null) { |
| values.put(SourceContract.EVENT_ID, source.getEventId().getValue()); |
| } |
| values.put(SourceContract.PUBLISHER, source.getPublisher().toString()); |
| values.put(SourceContract.PUBLISHER_TYPE, source.getPublisherType()); |
| values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId()); |
| values.put(SourceContract.EVENT_TIME, source.getEventTime()); |
| values.put(SourceContract.EXPIRY_TIME, source.getExpiryTime()); |
| values.put(SourceContract.PRIORITY, source.getPriority()); |
| values.put(SourceContract.STATUS, source.getStatus()); |
| values.put(SourceContract.SOURCE_TYPE, source.getSourceType().toString()); |
| values.put(SourceContract.REGISTRANT, source.getRegistrant().toString()); |
| values.put(SourceContract.INSTALL_ATTRIBUTION_WINDOW, source.getInstallAttributionWindow()); |
| values.put(SourceContract.INSTALL_COOLDOWN_WINDOW, source.getInstallCooldownWindow()); |
| values.put(SourceContract.ATTRIBUTION_MODE, source.getAttributionMode()); |
| values.put(SourceContract.AGGREGATE_SOURCE, source.getAggregateSource()); |
| values.put(SourceContract.FILTER_DATA, source.getFilterDataString()); |
| values.put(SourceContract.SHARED_FILTER_DATA_KEYS, source.getSharedFilterDataKeys()); |
| values.put(SourceContract.AGGREGATE_CONTRIBUTIONS, source.getAggregateContributions()); |
| values.put(SourceContract.DEBUG_REPORTING, source.isDebugReporting()); |
| values.put(SourceContract.INSTALL_TIME, source.getInstallTime()); |
| values.put(SourceContract.REGISTRATION_ID, source.getRegistrationId()); |
| values.put(SourceContract.SHARED_AGGREGATION_KEYS, source.getSharedAggregationKeys()); |
| values.put(SourceContract.REGISTRATION_ORIGIN, source.getRegistrationOrigin().toString()); |
| long row = db.insert(SourceContract.TABLE, null, values); |
| assertNotEquals("Source insertion failed", -1, row); |
| |
| maybeInsertSourceDestinations(db, source, sourceId); |
| } |
| |
| private static String getNullableUriString(List<Uri> uriList) { |
| return Optional.ofNullable(uriList).map(uris -> uris.get(0).toString()).orElse(null); |
| } |
| |
| /** Test that the AsyncRegistration is inserted correctly. */ |
| @Test |
| public void testInsertAsyncRegistration() { |
| AsyncRegistration validAsyncRegistration = |
| AsyncRegistrationFixture.getValidAsyncRegistration(); |
| String validAsyncRegistrationId = validAsyncRegistration.getId(); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.insertAsyncRegistration(validAsyncRegistration)); |
| |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query( |
| MeasurementTables.AsyncRegistrationContract.TABLE, |
| null, |
| MeasurementTables.AsyncRegistrationContract.ID + " = ? ", |
| new String[] {validAsyncRegistrationId}, |
| null, |
| null, |
| null)) { |
| |
| assertTrue(cursor.moveToNext()); |
| AsyncRegistration asyncRegistration = |
| SqliteObjectMapper.constructAsyncRegistration(cursor); |
| assertNotNull(asyncRegistration); |
| assertNotNull(asyncRegistration.getId()); |
| assertEquals(asyncRegistration.getId(), validAsyncRegistration.getId()); |
| assertNotNull(asyncRegistration.getRegistrationUri()); |
| assertNotNull(asyncRegistration.getTopOrigin()); |
| assertEquals(asyncRegistration.getTopOrigin(), validAsyncRegistration.getTopOrigin()); |
| assertNotNull(asyncRegistration.getRegistrant()); |
| assertEquals(asyncRegistration.getRegistrant(), validAsyncRegistration.getRegistrant()); |
| assertNotNull(asyncRegistration.getSourceType()); |
| assertEquals(asyncRegistration.getSourceType(), validAsyncRegistration.getSourceType()); |
| assertNotNull(asyncRegistration.getDebugKeyAllowed()); |
| assertEquals( |
| asyncRegistration.getDebugKeyAllowed(), |
| validAsyncRegistration.getDebugKeyAllowed()); |
| assertNotNull(asyncRegistration.getRetryCount()); |
| assertEquals(asyncRegistration.getRetryCount(), validAsyncRegistration.getRetryCount()); |
| assertNotNull(asyncRegistration.getRequestTime()); |
| assertEquals( |
| asyncRegistration.getRequestTime(), validAsyncRegistration.getRequestTime()); |
| assertNotNull(asyncRegistration.getOsDestination()); |
| assertEquals( |
| asyncRegistration.getOsDestination(), |
| validAsyncRegistration.getOsDestination()); |
| assertNotNull(asyncRegistration.getRegistrationUri()); |
| assertEquals( |
| asyncRegistration.getRegistrationUri(), |
| validAsyncRegistration.getRegistrationUri()); |
| assertNotNull(asyncRegistration.getDebugKeyAllowed()); |
| assertEquals( |
| asyncRegistration.getDebugKeyAllowed(), |
| validAsyncRegistration.getDebugKeyAllowed()); |
| assertEquals( |
| asyncRegistration.getPlatformAdId(), validAsyncRegistration.getPlatformAdId()); |
| assertEquals(asyncRegistration.getPostBody(), validAsyncRegistration.getPostBody()); |
| } |
| } |
| |
| /** Test that records in AsyncRegistration queue are fetched properly. */ |
| @Test |
| public void testFetchNextQueuedAsyncRegistration_validRetryLimit() { |
| AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration(); |
| String asyncRegistrationId = asyncRegistration.getId(); |
| |
| mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration)); |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| AsyncRegistration fetchedAsyncRegistration = |
| dao.fetchNextQueuedAsyncRegistration((short) 1, new HashSet<>()); |
| assertNotNull(fetchedAsyncRegistration); |
| assertEquals(fetchedAsyncRegistration.getId(), asyncRegistrationId); |
| fetchedAsyncRegistration.incrementRetryCount(); |
| dao.updateRetryCount(fetchedAsyncRegistration); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| AsyncRegistration fetchedAsyncRegistration = |
| dao.fetchNextQueuedAsyncRegistration((short) 1, new HashSet<>()); |
| assertNull(fetchedAsyncRegistration); |
| }); |
| } |
| |
| /** Test that records in AsyncRegistration queue are fetched properly. */ |
| @Test |
| public void testFetchNextQueuedAsyncRegistration_excludeByOrigin() { |
| Uri origin1 = Uri.parse("https://adtech1.test"); |
| Uri origin2 = Uri.parse("https://adtech2.test"); |
| Uri regUri1 = origin1.buildUpon().appendPath("/hello").build(); |
| Uri regUri2 = origin2; |
| AsyncRegistration asyncRegistration1 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setRegistrationUri(regUri1) |
| .build(); |
| AsyncRegistration asyncRegistration2 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setRegistrationUri(regUri2) |
| .build(); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.insertAsyncRegistration(asyncRegistration1); |
| dao.insertAsyncRegistration(asyncRegistration2); |
| }); |
| // Should fetch none |
| Set<Uri> excludedOrigins1 = Set.of(origin1, origin2); |
| Optional<AsyncRegistration> optAsyncRegistration = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins1)); |
| assertTrue(optAsyncRegistration.isEmpty()); |
| |
| // Should fetch only origin1 |
| Set<Uri> excludedOrigins2 = Set.of(origin2); |
| optAsyncRegistration = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins2)); |
| assertTrue(optAsyncRegistration.isPresent()); |
| assertEquals(regUri1, optAsyncRegistration.get().getRegistrationUri()); |
| |
| // Should fetch only origin2 |
| Set<Uri> excludedOrigins3 = Set.of(origin1); |
| optAsyncRegistration = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins3)); |
| assertTrue(optAsyncRegistration.isPresent()); |
| assertEquals(regUri2, optAsyncRegistration.get().getRegistrationUri()); |
| } |
| |
| /** Test that AsyncRegistration is deleted correctly. */ |
| @Test |
| public void testDeleteAsyncRegistration() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration(); |
| String asyncRegistrationID = asyncRegistration.getId(); |
| |
| mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration)); |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query( |
| MeasurementTables.AsyncRegistrationContract.TABLE, |
| null, |
| MeasurementTables.AsyncRegistrationContract.ID + " = ? ", |
| new String[] {asyncRegistration.getId().toString()}, |
| null, |
| null, |
| null)) { |
| assertTrue(cursor.moveToNext()); |
| AsyncRegistration updateAsyncRegistration = |
| SqliteObjectMapper.constructAsyncRegistration(cursor); |
| assertNotNull(updateAsyncRegistration); |
| } |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.deleteAsyncRegistration(asyncRegistration.getId())); |
| |
| db.query( |
| /* table */ MeasurementTables.AsyncRegistrationContract.TABLE, |
| /* columns */ null, |
| /* selection */ MeasurementTables.AsyncRegistrationContract.ID + " = ? ", |
| /* selectionArgs */ new String[] {asyncRegistrationID.toString()}, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderedBy */ null); |
| |
| assertThat( |
| db.query( |
| /* table */ MeasurementTables.AsyncRegistrationContract |
| .TABLE, |
| /* columns */ null, |
| /* selection */ MeasurementTables.AsyncRegistrationContract |
| .ID |
| + " = ? ", |
| /* selectionArgs */ new String[] { |
| asyncRegistrationID.toString() |
| }, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderedBy */ null) |
| .getCount()) |
| .isEqualTo(0); |
| } |
| |
| @Test |
| public void testDeleteAsyncRegistration_missingRecord() { |
| mDatastoreManager.runInTransaction( |
| (dao) -> |
| assertThrows( |
| "Async Registration already deleted", |
| DatastoreException.class, |
| () -> dao.deleteAsyncRegistration("missingAsyncRegId"))); |
| } |
| |
| @Test |
| public void deleteAsyncRegistrations_success() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AsyncRegistration ar1 = |
| new AsyncRegistration.Builder() |
| .setId("1") |
| .setRegistrant(Uri.parse("android-app://installed-registrant1")) |
| .setTopOrigin(Uri.parse("android-app://installed-registrant1")) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(1) |
| .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID) |
| .build(); |
| |
| AsyncRegistration ar2 = |
| new AsyncRegistration.Builder() |
| .setId("2") |
| .setRegistrant(Uri.parse("android-app://installed-registrant2")) |
| .setTopOrigin(Uri.parse("android-app://installed-registrant2")) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(Long.MAX_VALUE) |
| .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID) |
| .build(); |
| |
| AsyncRegistration ar3 = |
| new AsyncRegistration.Builder() |
| .setId("3") |
| .setRegistrant(Uri.parse("android-app://installed-registrant3")) |
| .setTopOrigin(Uri.parse("android-app://installed-registrant3")) |
| .setAdIdPermission(false) |
| .setType(AsyncRegistration.RegistrationType.APP_SOURCE) |
| .setRequestTime(Long.MAX_VALUE) |
| .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID) |
| .build(); |
| |
| List<AsyncRegistration> asyncRegistrationList = List.of(ar1, ar2, ar3); |
| asyncRegistrationList.forEach( |
| asyncRegistration -> { |
| ContentValues values = new ContentValues(); |
| values.put(AsyncRegistrationContract.ID, asyncRegistration.getId()); |
| values.put( |
| AsyncRegistrationContract.REQUEST_TIME, |
| asyncRegistration.getRequestTime()); |
| values.put( |
| AsyncRegistrationContract.REGISTRANT, |
| asyncRegistration.getRegistrant().toString()); |
| values.put( |
| AsyncRegistrationContract.TOP_ORIGIN, |
| asyncRegistration.getTopOrigin().toString()); |
| values.put( |
| AsyncRegistrationContract.REGISTRATION_ID, |
| asyncRegistration.getRegistrationId()); |
| db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.deleteAsyncRegistrations(List.of("1", "3"))); |
| |
| assertThat( |
| db.query( |
| /* table */ MeasurementTables.AsyncRegistrationContract |
| .TABLE, |
| /* columns */ null, |
| /* selection */ null, |
| /* selectionArgs */ null, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderedBy */ null) |
| .getCount()) |
| .isEqualTo(1); |
| |
| assertThat( |
| db.query( |
| /* table */ MeasurementTables.AsyncRegistrationContract |
| .TABLE, |
| /* columns */ null, |
| /* selection */ MeasurementTables.AsyncRegistrationContract |
| .ID |
| + " = ? ", |
| /* selectionArgs */ new String[] {"2"}, |
| /* groupBy */ null, |
| /* having */ null, |
| /* orderedBy */ null) |
| .getCount()) |
| .isEqualTo(1); |
| } |
| |
| /** Test that retry count in AsyncRegistration is updated correctly. */ |
| @Test |
| public void testUpdateAsyncRegistrationRetryCount() { |
| AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration(); |
| String asyncRegistrationId = asyncRegistration.getId(); |
| long originalRetryCount = asyncRegistration.getRetryCount(); |
| |
| mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration)); |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| asyncRegistration.incrementRetryCount(); |
| dao.updateRetryCount(asyncRegistration); |
| }); |
| |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query( |
| MeasurementTables.AsyncRegistrationContract.TABLE, |
| null, |
| MeasurementTables.AsyncRegistrationContract.ID + " = ? ", |
| new String[] {asyncRegistrationId}, |
| null, |
| null, |
| null)) { |
| assertTrue(cursor.moveToNext()); |
| AsyncRegistration updateAsyncRegistration = |
| SqliteObjectMapper.constructAsyncRegistration(cursor); |
| assertNotNull(updateAsyncRegistration); |
| assertTrue(updateAsyncRegistration.getRetryCount() == originalRetryCount + 1); |
| } |
| } |
| |
| @Test |
| public void getSource_fetchesMatchingSourceFromDb() { |
| // Setup - insert 2 sources with different IDs |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| String sourceId1 = "source1"; |
| Source source1WithoutDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId1) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .build(); |
| Source source1WithDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId1) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .build(); |
| insertInDb(db, source1WithDestinations); |
| String sourceId2 = "source2"; |
| Source source2WithoutDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId2) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .build(); |
| Source source2WithDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId2) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .build(); |
| insertInDb(db, source2WithDestinations); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| assertEquals(source1WithoutDestinations, dao.getSource(sourceId1)); |
| assertEquals(source2WithoutDestinations, dao.getSource(sourceId2)); |
| }); |
| } |
| |
| @Test |
| public void getSourceRegistrant_fetchesMatchingSourceFromDb() { |
| // Setup - insert 2 sources with different IDs |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| String sourceId1 = "source1"; |
| String registrant1 = "android-app://registrant.app1"; |
| Source source1WithDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId1) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrant(Uri.parse(registrant1)) |
| .build(); |
| insertInDb(db, source1WithDestinations); |
| |
| String sourceId2 = "source2"; |
| String registrant2 = "android-app://registrant.app1"; |
| Source source2WithDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId2) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrant(Uri.parse(registrant2)) |
| .build(); |
| insertInDb(db, source2WithDestinations); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| assertEquals(registrant1, dao.getSourceRegistrant(sourceId1)); |
| assertEquals(registrant2, dao.getSourceRegistrant(sourceId2)); |
| }); |
| } |
| |
| @Test |
| public void getSource_nonExistingInDb_throwsException() { |
| // Setup - insert 2 sources with different IDs |
| mFlags = mock(Flags.class); |
| ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags); |
| doReturn(true).when(mFlags).getMeasurementEnableDatastoreManagerThrowDatastoreException(); |
| doReturn(1.0f).when(mFlags).getMeasurementThrowUnknownExceptionSamplingRate(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| String sourceId1 = "source1"; |
| Source source1WithDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId1) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .build(); |
| insertInDb(db, source1WithDestinations); |
| |
| // Execution |
| try { |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.getSource("random_source_id"); |
| }); |
| fail(); |
| } catch (IllegalStateException e) { |
| Throwable cause = e.getCause(); |
| assertEquals(DatastoreException.class, cause.getClass()); |
| assertEquals("Source retrieval failed. Id: random_source_id", cause.getMessage()); |
| } |
| } |
| |
| @Test |
| public void getSource_nonExistingInDbNoSampling_swallowException() { |
| // Setup - insert 2 sources with different IDs |
| mFlags = mock(Flags.class); |
| ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags); |
| doReturn(true).when(mFlags).getMeasurementEnableDatastoreManagerThrowDatastoreException(); |
| doReturn(0.0f).when(mFlags).getMeasurementThrowUnknownExceptionSamplingRate(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| String sourceId1 = "source1"; |
| Source source1WithDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId1) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .build(); |
| insertInDb(db, source1WithDestinations); |
| |
| // Execution |
| assertFalse( |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.getSource("random_source_id"); |
| })); |
| } |
| |
| @Test |
| public void getSource_nonExistingInDbThrowingDisabled_swallowException() { |
| // Setup - insert 2 sources with different IDs |
| mFlags = mock(Flags.class); |
| ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags); |
| doReturn(false).when(mFlags).getMeasurementEnableDatastoreManagerThrowDatastoreException(); |
| doReturn(1.0f).when(mFlags).getMeasurementThrowUnknownExceptionSamplingRate(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| String sourceId1 = "source1"; |
| Source source1WithDestinations = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId(sourceId1) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .build(); |
| insertInDb(db, source1WithDestinations); |
| |
| // Execution |
| assertFalse( |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.getSource("random_source_id"); |
| })); |
| } |
| |
| @Test |
| public void fetchMatchingAggregateReports_returnsMatchingReports() { |
| // setup - create reports for 3*3 combinations of source and trigger |
| List<Source> sources = |
| Arrays.asList( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setId("source1") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setId("source2") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(3L)) |
| .setId("source3") |
| .build()); |
| List<Trigger> triggers = |
| Arrays.asList( |
| TriggerFixture.getValidTriggerBuilder().setId("trigger1").build(), |
| TriggerFixture.getValidTriggerBuilder().setId("trigger2").build(), |
| TriggerFixture.getValidTriggerBuilder().setId("trigger3").build()); |
| List<AggregateReport> reports = |
| ImmutableList.of( |
| createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(0)), |
| createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(1)), |
| createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(2)), |
| createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(0)), |
| createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(1)), |
| createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(2)), |
| createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(0)), |
| createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(1)), |
| createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(2))); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| sources.forEach(source -> insertSource(source, source.getId())); |
| triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db)); |
| reports.forEach( |
| report -> |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.insertAggregateReport(report))); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| // Execution |
| List<AggregateReport> aggregateReports = |
| dao.fetchMatchingAggregateReports( |
| Arrays.asList(sources.get(1).getId(), "nonMatchingSource"), |
| Arrays.asList(triggers.get(2).getId(), "nonMatchingTrigger")); |
| assertEquals(5, aggregateReports.size()); |
| |
| aggregateReports = |
| dao.fetchMatchingAggregateReports( |
| Arrays.asList(sources.get(0).getId(), sources.get(1).getId()), |
| Collections.emptyList()); |
| assertEquals(6, aggregateReports.size()); |
| |
| aggregateReports = |
| dao.fetchMatchingAggregateReports( |
| Collections.emptyList(), |
| Arrays.asList( |
| triggers.get(0).getId(), triggers.get(2).getId())); |
| assertEquals(6, aggregateReports.size()); |
| |
| aggregateReports = |
| dao.fetchMatchingAggregateReports( |
| Arrays.asList( |
| sources.get(0).getId(), |
| sources.get(1).getId(), |
| sources.get(2).getId()), |
| Arrays.asList( |
| triggers.get(0).getId(), |
| triggers.get(1).getId(), |
| triggers.get(2).getId())); |
| assertEquals(9, aggregateReports.size()); |
| }); |
| } |
| |
| @Test |
| public void fetchMatchingEventReports_returnsMatchingReports() throws JSONException { |
| // setup - create reports for 3*3 combinations of source and trigger |
| List<Source> sources = |
| Arrays.asList( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setId("source1") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setId("source2") |
| .build(), |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(3L)) |
| .setId("source3") |
| .build()); |
| List<Trigger> triggers = |
| Arrays.asList( |
| TriggerFixture.getValidTriggerBuilder() |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setId("trigger1") |
| .build(), |
| TriggerFixture.getValidTriggerBuilder() |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setId("trigger2") |
| .build(), |
| TriggerFixture.getValidTriggerBuilder() |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setId("trigger3") |
| .build()); |
| List<EventReport> reports = |
| ImmutableList.of( |
| createEventReportForSourceAndTrigger(sources.get(0), triggers.get(0)), |
| createEventReportForSourceAndTrigger(sources.get(0), triggers.get(1)), |
| createEventReportForSourceAndTrigger(sources.get(0), triggers.get(2)), |
| createEventReportForSourceAndTrigger(sources.get(1), triggers.get(0)), |
| createEventReportForSourceAndTrigger(sources.get(1), triggers.get(1)), |
| createEventReportForSourceAndTrigger(sources.get(1), triggers.get(2)), |
| createEventReportForSourceAndTrigger(sources.get(2), triggers.get(0)), |
| createEventReportForSourceAndTrigger(sources.get(2), triggers.get(1)), |
| createEventReportForSourceAndTrigger(sources.get(2), triggers.get(2))); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| sources.forEach(source -> insertSource(source, source.getId())); |
| triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db)); |
| reports.forEach( |
| report -> |
| mDatastoreManager.runInTransaction((dao) -> dao.insertEventReport(report))); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| // Execution |
| List<EventReport> eventReports = |
| dao.fetchMatchingEventReports( |
| Arrays.asList(sources.get(1).getId(), "nonMatchingSource"), |
| Arrays.asList(triggers.get(2).getId(), "nonMatchingTrigger")); |
| assertEquals(5, eventReports.size()); |
| |
| eventReports = |
| dao.fetchMatchingEventReports( |
| Arrays.asList(sources.get(0).getId(), sources.get(1).getId()), |
| Collections.emptyList()); |
| assertEquals(6, eventReports.size()); |
| |
| eventReports = |
| dao.fetchMatchingEventReports( |
| Collections.emptyList(), |
| Arrays.asList( |
| triggers.get(0).getId(), triggers.get(2).getId())); |
| assertEquals(6, eventReports.size()); |
| |
| eventReports = |
| dao.fetchMatchingEventReports( |
| Arrays.asList( |
| sources.get(0).getId(), |
| sources.get(1).getId(), |
| sources.get(2).getId()), |
| Arrays.asList( |
| triggers.get(0).getId(), |
| triggers.get(1).getId(), |
| triggers.get(2).getId())); |
| assertEquals(9, eventReports.size()); |
| }); |
| } |
| |
| @Test |
| public void fetchMatchingSources_bringsMatchingSources() { |
| // Setup |
| Source source1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(5000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source1") |
| .build(); |
| Source source2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source2") |
| .build(); |
| Source source3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(3L)) |
| .setPublisher(WebUtil.validUri("https://subdomain2.site1.test")) |
| .setEventTime(15000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source3") |
| .build(); |
| Source source4 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(4L)) |
| .setPublisher(WebUtil.validUri("https://subdomain2.site2.test")) |
| .setEventTime(15000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source4") |
| .build(); |
| Source source5 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(5L)) |
| .setPublisher(WebUtil.validUri("https://subdomain2.site1.test")) |
| .setEventTime(20000) |
| .setRegistrant(Uri.parse("android-app://com.registrant2")) |
| .setId("source5") |
| .build(); |
| List<Source> sources = List.of(source1, source2, source3, source4, source5); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| sources.forEach(source -> insertInDb(db, source)); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| // --- DELETE behaviour --- |
| // Delete Nothing |
| // No matches |
| List<String> actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(0, actualSources.size()); |
| |
| // 1 & 2 match registrant1 and "https://subdomain1.site1.test" publisher |
| // origin |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(2, actualSources.size()); |
| |
| // Only 2 matches registrant1 and "https://subdomain1.site1.test" |
| // publisher origin within |
| // the range |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(8000), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(1, actualSources.size()); |
| |
| // 1,2 & 3 matches registrant1 and "https://site1.test" publisher origin |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(WebUtil.validUri("https://site1.test")), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(3, actualSources.size()); |
| |
| // 3 matches origin and 4 matches domain URI |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(10000), |
| Instant.ofEpochMilli(20000), |
| List.of(WebUtil.validUri("https://subdomain2.site1.test")), |
| List.of(WebUtil.validUri("https://site2.test")), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(2, actualSources.size()); |
| |
| // --- PRESERVE (anti-match exception registrant) behaviour --- |
| // Preserve Nothing |
| // 1,2,3 & 4 are match registrant1 |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(4, actualSources.size()); |
| |
| // 3 & 4 match registrant1 and don't match |
| // "https://subdomain1.site1.test" publisher origin |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(2, actualSources.size()); |
| |
| // 3 & 4 match registrant1, in range and don't match |
| // "https://subdomain1.site1.test" |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(8000), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(2, actualSources.size()); |
| |
| // Only 4 matches registrant1, in range and don't match |
| // "https://site1.test" |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(WebUtil.validUri("https://site1.test")), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(1, actualSources.size()); |
| |
| // only 2 is registrant1 based, in range and does not match either |
| // site2.test or subdomain2.site1.test |
| actualSources = |
| dao.fetchMatchingSources( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(10000), |
| Instant.ofEpochMilli(20000), |
| List.of(WebUtil.validUri("https://subdomain2.site1.test")), |
| List.of(WebUtil.validUri("https://site2.test")), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(1, actualSources.size()); |
| }); |
| } |
| |
| @Test |
| public void fetchFlexSourceIdsFor_bringsMatchingSources_expectedSourceReturned() |
| throws JSONException { |
| // Setup |
| TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased(); |
| Source source1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(5000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source1") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source1.buildTriggerSpecs(); |
| Source source2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source2") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source2.buildTriggerSpecs(); |
| |
| List<TriggerSpecs> triggerSpecsList = List.of( |
| source1.getTriggerSpecs(), |
| source2.getTriggerSpecs()); |
| |
| for (TriggerSpecs triggerSpecs : triggerSpecsList) { |
| insertAttributedTrigger( |
| triggerSpecs, |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("123456") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| insertAttributedTrigger( |
| triggerSpecs, |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("234567") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| insertAttributedTrigger( |
| triggerSpecs, |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("345678") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| } |
| |
| Source source3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source3") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source3.buildTriggerSpecs(); |
| |
| insertAttributedTrigger( |
| source3.getTriggerSpecs(), |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("123456") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| |
| List<Source> sources = List.of(source1, source2, source3); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| sources.forEach(source -> insertInDb(db, source)); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("123456")); |
| Set<String> expected = Set.of("source1", "source2", "source3"); |
| assertEquals(expected, actualSources); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("234567")); |
| Set<String> expected = Set.of("source1", "source2"); |
| assertEquals(expected, actualSources); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("23456")); |
| assertEquals(Collections.emptySet(), actualSources); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = dao.fetchFlexSourceIdsFor(new ArrayList<>()); |
| assertEquals(Collections.emptySet(), actualSources); |
| }); |
| } |
| |
| @Test |
| public void fetchFlexSourceIdsFor_emptyInputSourceId_noSourceReturned() |
| throws JSONException { |
| // Setup |
| TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased(); |
| Source source1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(5000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source1") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source1.buildTriggerSpecs(); |
| |
| Source source2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source2") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source2.buildTriggerSpecs(); |
| |
| List<TriggerSpecs> triggerSpecsList = List.of( |
| source1.getTriggerSpecs(), |
| source2.getTriggerSpecs()); |
| |
| for (TriggerSpecs triggerSpecs : triggerSpecsList) { |
| insertAttributedTrigger( |
| triggerSpecs, |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("123456") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| insertAttributedTrigger( |
| triggerSpecs, |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("234567") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| insertAttributedTrigger( |
| triggerSpecs, |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("345678") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| } |
| |
| Source source3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source3") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source3.buildTriggerSpecs(); |
| |
| insertAttributedTrigger( |
| source3.getTriggerSpecs(), |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("123456") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build()); |
| |
| List<Source> sources = List.of(source1, source2, source3); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| sources.forEach(source -> insertInDb(db, source)); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("123456")); |
| Set<String> expected = Set.of("source1", "source2", "source3"); |
| assertEquals(expected, actualSources); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("234567")); |
| Set<String> expected = Set.of("source1", "source2"); |
| assertEquals(expected, actualSources); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("23456")); |
| assertEquals(Collections.emptySet(), actualSources); |
| }); |
| } |
| |
| @Test |
| public void fetchFlexSourceIdsFor_doesNotMatchV1Sources() throws JSONException { |
| // Setup |
| EventReport eventReport1 = |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("123456") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build(); |
| EventReport eventReport2 = |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("234567") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build(); |
| EventReport eventReport3 = |
| EventReportFixture.getBaseEventReportBuild() |
| .setTriggerId("345678") |
| .setTriggerData(new UnsignedLong(2L)) |
| .setTriggerPriority(1L) |
| .setTriggerValue(1L) |
| .build(); |
| |
| TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased(); |
| |
| // Flex source |
| Source source1 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(1L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(5000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source1") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source1.buildTriggerSpecs(); |
| |
| insertAttributedTrigger(source1.getTriggerSpecs(), eventReport1); |
| insertAttributedTrigger(source1.getTriggerSpecs(), eventReport2); |
| insertAttributedTrigger(source1.getTriggerSpecs(), eventReport3); |
| |
| // Non-flex (V1) source |
| Source source2 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source2") |
| .build(); |
| source2.buildAttributedTriggers(); |
| |
| insertAttributedTrigger(source2.getAttributedTriggers(), eventReport1); |
| insertAttributedTrigger(source2.getAttributedTriggers(), eventReport2); |
| insertAttributedTrigger(source2.getAttributedTriggers(), eventReport3); |
| |
| // Flex source |
| Source source3 = |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setEventId(new UnsignedLong(2L)) |
| .setPublisher(WebUtil.validUri("https://subdomain1.site1.test")) |
| .setEventTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("source3") |
| .setTriggerSpecsString(testTriggerSpecs.encodeToJson()) |
| .setMaxEventLevelReports(testTriggerSpecs.getMaxReports()) |
| .setPrivacyParameters( |
| testTriggerSpecs.encodePrivacyParametersToJSONString()) |
| .build(); |
| source3.buildTriggerSpecs(); |
| |
| insertAttributedTrigger(source3.getTriggerSpecs(), eventReport1); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| List.of(source1, source2, source3).forEach(source -> insertInDb(db, source)); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("123456")); |
| Set<String> expected = Set.of("source1", "source3"); |
| assertEquals(expected, actualSources); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor( |
| new ArrayList<>(Collections.singletonList("234567"))); |
| Set<String> expected = Set.of("source1"); |
| assertEquals(expected, actualSources); |
| }); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| Set<String> actualSources = |
| dao.fetchFlexSourceIdsFor(Collections.singletonList("23456")); |
| assertEquals(Collections.emptySet(), actualSources); |
| }); |
| } |
| |
| @Test |
| public void fetchMatchingTriggers_bringsMatchingTriggers() { |
| // Setup |
| Trigger trigger1 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setAttributionDestination( |
| WebUtil.validUri("https://subdomain1.site1.test")) |
| .setTriggerTime(5000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("trigger1") |
| .build(); |
| Trigger trigger2 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setAttributionDestination( |
| WebUtil.validUri("https://subdomain1.site1.test")) |
| .setTriggerTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("trigger2") |
| .build(); |
| Trigger trigger3 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setAttributionDestination( |
| WebUtil.validUri("https://subdomain2.site1.test")) |
| .setTriggerTime(15000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("trigger3") |
| .build(); |
| Trigger trigger4 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setAttributionDestination( |
| WebUtil.validUri("https://subdomain2.site2.test")) |
| .setTriggerTime(15000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("trigger4") |
| .build(); |
| Trigger trigger5 = |
| TriggerFixture.getValidTriggerBuilder() |
| .setAttributionDestination( |
| WebUtil.validUri("https://subdomain2.site1.test")) |
| .setTriggerTime(20000) |
| .setRegistrant(Uri.parse("android-app://com.registrant2")) |
| .setId("trigger5") |
| .build(); |
| List<Trigger> triggers = List.of(trigger1, trigger2, trigger3, trigger4, trigger5); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| triggers.forEach( |
| trigger -> { |
| ContentValues values = new ContentValues(); |
| values.put(TriggerContract.ID, trigger.getId()); |
| values.put( |
| TriggerContract.ATTRIBUTION_DESTINATION, |
| trigger.getAttributionDestination().toString()); |
| values.put(TriggerContract.TRIGGER_TIME, trigger.getTriggerTime()); |
| values.put(TriggerContract.ENROLLMENT_ID, trigger.getEnrollmentId()); |
| values.put(TriggerContract.REGISTRANT, trigger.getRegistrant().toString()); |
| values.put(TriggerContract.STATUS, trigger.getStatus()); |
| db.insert(TriggerContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| // --- DELETE behaviour --- |
| // Delete Nothing |
| // No Matches |
| Set<String> actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(0, actualTriggers.size()); |
| |
| // 1 & 2 match registrant1 and "https://subdomain1.site1.test" |
| // destination origin |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(2, actualTriggers.size()); |
| |
| // Only 2 matches registrant1 and "https://subdomain1.site1.test" |
| // destination origin within the range |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(8000), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(1, actualTriggers.size()); |
| |
| // 1,2 & 3 matches registrant1 and "https://site1.test" destination |
| // origin |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(WebUtil.validUri("https://site1.test")), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(3, actualTriggers.size()); |
| |
| // 3 matches origin and 4 matches domain URI |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(10000), |
| Instant.ofEpochMilli(20000), |
| List.of(WebUtil.validUri("https://subdomain2.site1.test")), |
| List.of(WebUtil.validUri("https://site2.test")), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(2, actualTriggers.size()); |
| |
| // --- PRESERVE (anti-match exception registrant) behaviour --- |
| // Preserve Nothing |
| // 1,2,3 & 4 are match registrant1 |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(4, actualTriggers.size()); |
| |
| // 3 & 4 match registrant1 and don't match |
| // "https://subdomain1.site1.test" destination origin |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(2, actualTriggers.size()); |
| |
| // 3 & 4 match registrant1, in range and don't match |
| // "https://subdomain1.site1.test" |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(8000), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(2, actualTriggers.size()); |
| |
| // Only 4 matches registrant1, in range and don't match |
| // "https://site1.test" |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(WebUtil.validUri("https://site1.test")), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(1, actualTriggers.size()); |
| |
| // only 2 is registrant1 based, in range and does not match either |
| // site2.test or subdomain2.site1.test |
| actualTriggers = |
| dao.fetchMatchingTriggers( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(10000), |
| Instant.ofEpochMilli(20000), |
| List.of(WebUtil.validUri("https://subdomain2.site1.test")), |
| List.of(WebUtil.validUri("https://site2.test")), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(1, actualTriggers.size()); |
| }); |
| } |
| |
| @Test |
| public void fetchMatchingAsyncRegistrations_bringsMatchingAsyncRegistrations() { |
| // Setup |
| AsyncRegistration asyncRegistration1 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setTopOrigin( |
| WebUtil.validUri("https://subdomain1.site1.test")) |
| .setRequestTime(5000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("asyncRegistration1") |
| .build(); |
| AsyncRegistration asyncRegistration2 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setTopOrigin( |
| WebUtil.validUri("https://subdomain1.site1.test")) |
| .setRequestTime(10000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("asyncRegistration2") |
| .build(); |
| AsyncRegistration asyncRegistration3 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setTopOrigin( |
| WebUtil.validUri("https://subdomain2.site1.test")) |
| .setRequestTime(15000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("asyncRegistration3") |
| .build(); |
| AsyncRegistration asyncRegistration4 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setTopOrigin( |
| WebUtil.validUri("https://subdomain2.site2.test")) |
| .setRequestTime(15000) |
| .setRegistrant(Uri.parse("android-app://com.registrant1")) |
| .setId("asyncRegistration4") |
| .build(); |
| AsyncRegistration asyncRegistration5 = |
| AsyncRegistrationFixture.getValidAsyncRegistrationBuilder() |
| .setTopOrigin( |
| WebUtil.validUri("https://subdomain2.site1.test")) |
| .setRequestTime(20000) |
| .setRegistrant(Uri.parse("android-app://com.registrant2")) |
| .setId("asyncRegistration5") |
| .build(); |
| List<AsyncRegistration> asyncRegistrations = List.of( |
| asyncRegistration1, asyncRegistration2, asyncRegistration3, asyncRegistration4, |
| asyncRegistration5); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).getWritableDatabase(); |
| asyncRegistrations.forEach( |
| asyncRegistration -> { |
| ContentValues values = new ContentValues(); |
| values.put(AsyncRegistrationContract.ID, asyncRegistration.getId()); |
| values.put( |
| AsyncRegistrationContract.TOP_ORIGIN, |
| asyncRegistration.getTopOrigin().toString()); |
| values.put(AsyncRegistrationContract.REQUEST_TIME, |
| asyncRegistration.getRequestTime()); |
| values.put(AsyncRegistrationContract.REGISTRANT, |
| asyncRegistration.getRegistrant().toString()); |
| values.put(AsyncRegistrationContract.REGISTRATION_ID, |
| UUID.randomUUID().toString()); |
| db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values); |
| }); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| // --- DELETE behaviour --- |
| // Delete Nothing |
| // No Matches |
| List<String> actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(0, actualAsyncRegistrations.size()); |
| |
| // 1 & 2 match registrant1 and "https://subdomain1.site1.test" top- |
| // origin origin |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(2, actualAsyncRegistrations.size()); |
| |
| // Only 2 matches registrant1 and "https://subdomain1.site1.test" |
| // top-origin origin within the range |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(8000), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(1, actualAsyncRegistrations.size()); |
| |
| // 1,2 & 3 matches registrant1 and "https://site1.test" top-origin |
| // origin |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(WebUtil.validUri("https://site1.test")), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(3, actualAsyncRegistrations.size()); |
| |
| // 3 matches origin and 4 matches domain URI |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(10000), |
| Instant.ofEpochMilli(20000), |
| List.of(WebUtil.validUri("https://subdomain2.site1.test")), |
| List.of(WebUtil.validUri("https://site2.test")), |
| DeletionRequest.MATCH_BEHAVIOR_DELETE); |
| assertEquals(2, actualAsyncRegistrations.size()); |
| |
| // --- PRESERVE (anti-match exception registrant) behaviour --- |
| // Preserve Nothing |
| // 1,2,3 & 4 are match registrant1 |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(4, actualAsyncRegistrations.size()); |
| |
| // 3 & 4 match registrant1 and don't match |
| // "https://subdomain1.site1.test" top-origin origin |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(2, actualAsyncRegistrations.size()); |
| |
| // 3 & 4 match registrant1, in range and don't match |
| // "https://subdomain1.site1.test" |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(8000), |
| Instant.ofEpochMilli(50000), |
| List.of(WebUtil.validUri("https://subdomain1.site1.test")), |
| List.of(), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(2, actualAsyncRegistrations.size()); |
| |
| // Only 4 matches registrant1, in range and don't match |
| // "https://site1.test" |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(0), |
| Instant.ofEpochMilli(50000), |
| List.of(), |
| List.of(WebUtil.validUri("https://site1.test")), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(1, actualAsyncRegistrations.size()); |
| |
| // only 2 is registrant1 based, in range and does not match either |
| // site2.test or subdomain2.site1.test |
| actualAsyncRegistrations = |
| dao.fetchMatchingAsyncRegistrations( |
| Uri.parse("android-app://com.registrant1"), |
| Instant.ofEpochMilli(10000), |
| Instant.ofEpochMilli(20000), |
| List.of(WebUtil.validUri("https://subdomain2.site1.test")), |
| List.of(WebUtil.validUri("https://site2.test")), |
| DeletionRequest.MATCH_BEHAVIOR_PRESERVE); |
| assertEquals(1, actualAsyncRegistrations.size()); |
| }); |
| } |
| |
| @Test |
| public void testUpdateSourceAggregateReportDedupKeys_updatesKeysInList() { |
| Source validSource = SourceFixture.getValidSource(); |
| assertTrue(validSource.getAggregateReportDedupKeys().equals(new ArrayList<UnsignedLong>())); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource)); |
| |
| String sourceId = getFirstSourceIdFromDatastore(); |
| Source source = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> measurementDao.getSource(sourceId)) |
| .get(); |
| |
| source.getAggregateReportDedupKeys().add(new UnsignedLong(10L)); |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.updateSourceAggregateReportDedupKeys(source)); |
| |
| Source sourceAfterUpdate = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> measurementDao.getSource(sourceId)) |
| .get(); |
| |
| assertTrue(sourceAfterUpdate.getAggregateReportDedupKeys().size() == 1); |
| assertTrue(sourceAfterUpdate.getAggregateReportDedupKeys().get(0).getValue() == 10L); |
| } |
| |
| @Test |
| public void testUpdateSourceAggregateReportDedupKeys_acceptsUnsignedLongValue() { |
| Source validSource = SourceFixture.getValidSource(); |
| assertTrue(validSource.getAggregateReportDedupKeys().equals(new ArrayList<UnsignedLong>())); |
| mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource)); |
| |
| String sourceId = getFirstSourceIdFromDatastore(); |
| Source source = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> measurementDao.getSource(sourceId)) |
| .get(); |
| |
| UnsignedLong dedupKeyValue = new UnsignedLong("17293822569102704640"); |
| source.getAggregateReportDedupKeys().add(dedupKeyValue); |
| mDatastoreManager.runInTransaction( |
| (dao) -> dao.updateSourceAggregateReportDedupKeys(source)); |
| |
| Source sourceAfterUpdate = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| measurementDao -> measurementDao.getSource(sourceId)) |
| .get(); |
| |
| assertEquals(1, sourceAfterUpdate.getAggregateReportDedupKeys().size()); |
| assertEquals(dedupKeyValue, sourceAfterUpdate.getAggregateReportDedupKeys().get(0)); |
| } |
| |
| @Test |
| public void testPersistAndRetrieveSource_handlesPreExistingNegativeValues() { |
| // Setup |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Source validSource = SourceFixture.getValidSource(); |
| ContentValues values = new ContentValues(); |
| values.put(SourceContract.ID, validSource.getId()); |
| values.put(SourceContract.EVENT_ID, validSource.getEventId().toString()); |
| values.put(SourceContract.ENROLLMENT_ID, validSource.getEnrollmentId()); |
| values.put(SourceContract.REGISTRANT, validSource.getRegistrant().toString()); |
| values.put(SourceContract.PUBLISHER, validSource.getPublisher().toString()); |
| values.put( |
| SourceContract.REGISTRATION_ORIGIN, validSource.getRegistrationOrigin().toString()); |
| // Existing invalid values |
| Long val1 = -123L; |
| Long val2 = Long.MIN_VALUE; |
| values.put( |
| SourceContract.AGGREGATE_REPORT_DEDUP_KEYS, |
| val1.toString() + "," + val2.toString()); |
| db.insert(SourceContract.TABLE, /* nullColumnHack */ null, values); |
| maybeInsertSourceDestinations(db, validSource, validSource.getId()); |
| |
| // Execution |
| Optional<Source> source = |
| mDatastoreManager.runInTransactionWithResult( |
| measurementDao -> measurementDao.getSource(validSource.getId())); |
| assertTrue(source.isPresent()); |
| List<UnsignedLong> aggReportDedupKeys = source.get().getAggregateReportDedupKeys(); |
| assertEquals(2, aggReportDedupKeys.size()); |
| assertEquals(val1, (Long) aggReportDedupKeys.get(0).getValue()); |
| assertEquals(val2, (Long) aggReportDedupKeys.get(1).getValue()); |
| } |
| |
| @Test |
| public void fetchTriggerMatchingSourcesForXna_filtersSourcesCorrectly() { |
| // Setup |
| Uri matchingDestination = APP_ONE_DESTINATION; |
| Uri nonMatchingDestination = APP_TWO_DESTINATION; |
| String mmpMatchingEnrollmentId = "mmp1"; |
| String mmpNonMatchingEnrollmentId = "mmpx"; |
| String san1MatchingEnrollmentId = "san1EnrollmentId"; |
| String san2MatchingEnrollmentId = "san2EnrollmentId"; |
| String san3MatchingEnrollmentId = "san3EnrollmentId"; |
| String san4NonMatchingEnrollmentId = "san4EnrollmentId"; |
| String san5MatchingEnrollmentId = "san5EnrollmentId"; |
| |
| Trigger trigger = |
| TriggerFixture.getValidTriggerBuilder() |
| .setAttributionDestination(matchingDestination) |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT) |
| .setTriggerTime(TriggerFixture.ValidTriggerParams.TRIGGER_TIME) |
| .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS) |
| .setAggregateTriggerData( |
| TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA) |
| .setAggregateValues(TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES) |
| .setFilters(TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING) |
| .setNotFilters( |
| TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING) |
| .build(); |
| Source s1MmpMatchingWithDestinations = |
| createSourceBuilder() |
| .setId("s1") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .build(); |
| Source s1MmpMatchingWithoutDestinations = |
| createSourceBuilder() |
| .setId("s1") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrationId(s1MmpMatchingWithDestinations.getRegistrationId()) |
| .build(); |
| Source s2MmpDiffDestination = |
| createSourceBuilder() |
| .setId("s2") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(List.of(nonMatchingDestination)) |
| .build(); |
| Source s3MmpExpired = |
| createSourceBuilder() |
| .setId("s3") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(List.of(nonMatchingDestination)) |
| // expired before trigger time |
| .setExpiryTime(trigger.getTriggerTime() - TimeUnit.DAYS.toMillis(1)) |
| .build(); |
| Source s4NonMatchingMmp = |
| createSourceBuilder() |
| .setId("s4") |
| .setEnrollmentId(mmpNonMatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .build(); |
| Source s5MmpMatchingWithDestinations = |
| createSourceBuilder() |
| .setId("s5") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .build(); |
| Source s5MmpMatchingWithoutDestinations = |
| createSourceBuilder() |
| .setId("s5") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrationId(s5MmpMatchingWithDestinations.getRegistrationId()) |
| .build(); |
| Source s6San1MatchingWithDestinations = |
| createSourceBuilder() |
| .setId("s6") |
| .setEnrollmentId(san1MatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s6San1MatchingWithoutDestinations = |
| createSourceBuilder() |
| .setId("s6") |
| .setEnrollmentId(san1MatchingEnrollmentId) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrationId(s6San1MatchingWithDestinations.getRegistrationId()) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s7San1DiffDestination = |
| createSourceBuilder() |
| .setId("s7") |
| .setEnrollmentId(san1MatchingEnrollmentId) |
| .setAppDestinations(List.of(nonMatchingDestination)) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s8San2MatchingWithDestinations = |
| createSourceBuilder() |
| .setId("s8") |
| .setEnrollmentId(san2MatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s8San2MatchingWithoutDestinations = |
| createSourceBuilder() |
| .setId("s8") |
| .setEnrollmentId(san2MatchingEnrollmentId) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrationId(s8San2MatchingWithDestinations.getRegistrationId()) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s9San3XnaIgnored = |
| createSourceBuilder() |
| .setId("s9") |
| .setEnrollmentId(san3MatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s10San3MatchingWithDestinations = |
| createSourceBuilder() |
| .setId("s10") |
| .setEnrollmentId(san3MatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s10San3MatchingWithoutDestinations = |
| createSourceBuilder() |
| .setId("s10") |
| .setEnrollmentId(san3MatchingEnrollmentId) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrationId(s10San3MatchingWithDestinations.getRegistrationId()) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s11San4EnrollmentNonMatching = |
| createSourceBuilder() |
| .setId("s11") |
| .setEnrollmentId(san4NonMatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .build(); |
| Source s12San1NullSharedAggregationKeys = |
| createSourceBuilder() |
| .setId("s12") |
| .setEnrollmentId(san1MatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setSharedAggregationKeys(null) |
| .build(); |
| Source s13San1Expired = |
| createSourceBuilder() |
| .setId("s13") |
| .setEnrollmentId(san1MatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| // expired before trigger time |
| .setExpiryTime(trigger.getTriggerTime() - TimeUnit.DAYS.toMillis(1)) |
| .build(); |
| String registrationIdForTriggerAndOtherRegistration = UUID.randomUUID().toString(); |
| Source s14San5RegIdClasesWithMmp = |
| createSourceBuilder() |
| .setId("s14") |
| .setEnrollmentId(san5MatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setRegistrationId(registrationIdForTriggerAndOtherRegistration) |
| .build(); |
| Source s15MmpMatchingWithDestinations = |
| createSourceBuilder() |
| .setId("s15") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(List.of(matchingDestination)) |
| .setRegistrationId(registrationIdForTriggerAndOtherRegistration) |
| .build(); |
| Source s15MmpMatchingWithoutDestinations = |
| createSourceBuilder() |
| .setId("s15") |
| .setEnrollmentId(mmpMatchingEnrollmentId) |
| .setAppDestinations(null) |
| .setWebDestinations(null) |
| .setRegistrationId(registrationIdForTriggerAndOtherRegistration) |
| .build(); |
| List<Source> sources = |
| Arrays.asList( |
| s1MmpMatchingWithDestinations, |
| s2MmpDiffDestination, |
| s3MmpExpired, |
| s4NonMatchingMmp, |
| s5MmpMatchingWithDestinations, |
| s6San1MatchingWithDestinations, |
| s7San1DiffDestination, |
| s8San2MatchingWithDestinations, |
| s9San3XnaIgnored, |
| s10San3MatchingWithDestinations, |
| s11San4EnrollmentNonMatching, |
| s12San1NullSharedAggregationKeys, |
| s13San1Expired, |
| s14San5RegIdClasesWithMmp, |
| s15MmpMatchingWithDestinations); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| Objects.requireNonNull(db); |
| // Insert all sources to the DB |
| sources.forEach(source -> insertSource(source, source.getId())); |
| |
| // Insert XNA ignored sources |
| ContentValues values = new ContentValues(); |
| values.put(XnaIgnoredSourcesContract.SOURCE_ID, s9San3XnaIgnored.getId()); |
| values.put(XnaIgnoredSourcesContract.ENROLLMENT_ID, mmpMatchingEnrollmentId); |
| long row = db.insert(XnaIgnoredSourcesContract.TABLE, null, values); |
| assertEquals(1, row); |
| |
| List<Source> expectedMatchingSources = |
| Arrays.asList( |
| s1MmpMatchingWithoutDestinations, |
| s5MmpMatchingWithoutDestinations, |
| s6San1MatchingWithoutDestinations, |
| s8San2MatchingWithoutDestinations, |
| s10San3MatchingWithoutDestinations, |
| s15MmpMatchingWithoutDestinations); |
| Comparator<Source> sortingComparator = Comparator.comparing(Source::getId); |
| expectedMatchingSources.sort(sortingComparator); |
| |
| // Execution |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| List<String> matchingSanEnrollmentIds = |
| Arrays.asList( |
| san1MatchingEnrollmentId, |
| san2MatchingEnrollmentId, |
| san3MatchingEnrollmentId, |
| san5MatchingEnrollmentId); |
| List<Source> actualMatchingSources = |
| dao.fetchTriggerMatchingSourcesForXna( |
| trigger, matchingSanEnrollmentIds); |
| actualMatchingSources.sort(sortingComparator); |
| // Assertion |
| assertEquals(expectedMatchingSources, actualMatchingSources); |
| }); |
| } |
| |
| @Test |
| public void insertIgnoredSourceForEnrollment_success() { |
| // Setup |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| // Need to insert sources before, to honor the foreign key constraint |
| insertSource(createSourceBuilder().setId("s1").build(), "s1"); |
| insertSource(createSourceBuilder().setId("s2").build(), "s2"); |
| |
| Pair<String, String> entry11 = new Pair<>("s1", "e1"); |
| Pair<String, String> entry21 = new Pair<>("s2", "e1"); |
| Pair<String, String> entry22 = new Pair<>("s2", "e2"); |
| |
| mDatastoreManager.runInTransaction( |
| dao -> { |
| // Execution |
| dao.insertIgnoredSourceForEnrollment(entry11.first, entry11.second); |
| dao.insertIgnoredSourceForEnrollment(entry21.first, entry21.second); |
| dao.insertIgnoredSourceForEnrollment(entry22.first, entry22.second); |
| |
| // Assertion |
| queryAndAssertSourceEntries(db, "e1", Arrays.asList("s1", "s2")); |
| queryAndAssertSourceEntries(db, "e2", Collections.singletonList("s2")); |
| }); |
| } |
| |
| @Test |
| public void countDistinctDebugAdIdsUsedByEnrollment_oneTriggerAndSource() { |
| // Setup |
| Source.Builder sourceBuilder = |
| new Source.Builder() |
| .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE) |
| .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID) |
| .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN); |
| Source s1 = |
| sourceBuilder |
| .setId("s1") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1)); |
| |
| Trigger.Builder triggerBuilder = |
| new Trigger.Builder() |
| .setAttributionDestination( |
| TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION) |
| .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT) |
| .setRegistrationOrigin( |
| TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN); |
| Trigger t1 = |
| triggerBuilder |
| .setId("t1") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1)); |
| |
| // Assertion |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| dao -> |
| assertEquals( |
| 1, |
| dao.countDistinctDebugAdIdsUsedByEnrollment( |
| "enrollment-id-1")))); |
| } |
| |
| @Test |
| public void countDistinctDebugAdIdsUsedByEnrollment_nullValuesPresent() { |
| // Setup |
| Source.Builder sourceBuilder = |
| new Source.Builder() |
| .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE) |
| .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID) |
| .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN); |
| // Source with debug AdId present |
| Source s1 = |
| sourceBuilder |
| .setId("s1") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1)); |
| // Source with no debug AdId |
| Source s2 = |
| sourceBuilder |
| .setId("s2") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2)); |
| |
| Trigger.Builder triggerBuilder = |
| new Trigger.Builder() |
| .setAttributionDestination( |
| TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION) |
| .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT) |
| .setRegistrationOrigin( |
| TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN); |
| // Trigger with debug AdId present |
| Trigger t1 = |
| triggerBuilder |
| .setId("t1") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1)); |
| // Trigger with no debug AdId |
| Trigger t2 = |
| triggerBuilder |
| .setId("t2") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2)); |
| |
| // Assertion |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| dao -> |
| assertEquals( |
| 1, |
| dao.countDistinctDebugAdIdsUsedByEnrollment( |
| "enrollment-id-1")))); |
| } |
| |
| @Test |
| public void countDistinctDebugAdIdsUsedByEnrollment_multipleSourcesAndTriggers() { |
| // Setup |
| Source.Builder sourceBuilder = |
| new Source.Builder() |
| .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE) |
| .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID) |
| .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN); |
| // Multiple sources with same AdId |
| Source s1 = |
| sourceBuilder |
| .setId("s1") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1)); |
| Source s2 = |
| sourceBuilder |
| .setId("s2") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2)); |
| Source s3 = |
| sourceBuilder |
| .setId("s3") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3)); |
| |
| Trigger.Builder triggerBuilder = |
| new Trigger.Builder() |
| .setAttributionDestination( |
| TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION) |
| .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT) |
| .setRegistrationOrigin( |
| TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN); |
| // Multiple triggers with same AdId |
| Trigger t1 = |
| triggerBuilder |
| .setId("t1") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1)); |
| Trigger t2 = |
| triggerBuilder |
| .setId("t2") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2)); |
| Trigger t3 = |
| triggerBuilder |
| .setId("t3") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3)); |
| |
| // Assertion |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| dao -> |
| assertEquals( |
| 1, |
| dao.countDistinctDebugAdIdsUsedByEnrollment( |
| "enrollment-id-1")))); |
| } |
| |
| @Test |
| public void countDistinctDebugAdIdsUsedByEnrollment_multipleAdIdsPresent() { |
| // Setup |
| Source.Builder sourceBuilder = |
| new Source.Builder() |
| .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE) |
| .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID) |
| .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN); |
| // Multiple sources with different AdIds but the same enrollmentId |
| Source s1 = |
| sourceBuilder |
| .setId("s1") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1)); |
| Source s2 = |
| sourceBuilder |
| .setId("s2") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-2") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2)); |
| Source s3 = |
| sourceBuilder |
| .setId("s3") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-3") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3)); |
| |
| Trigger.Builder triggerBuilder = |
| new Trigger.Builder() |
| .setAttributionDestination( |
| TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION) |
| .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT) |
| .setRegistrationOrigin( |
| TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN); |
| // Multiple triggers with different AdIds but the same enrollmentId |
| Trigger t1 = |
| triggerBuilder |
| .setId("t1") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-4") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1)); |
| Trigger t2 = |
| triggerBuilder |
| .setId("t2") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-5") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2)); |
| Trigger t3 = |
| triggerBuilder |
| .setId("t3") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-6") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3)); |
| |
| // Assertion |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| dao -> |
| assertEquals( |
| 6, |
| dao.countDistinctDebugAdIdsUsedByEnrollment( |
| "enrollment-id-1")))); |
| } |
| |
| @Test |
| public void countDistinctDebugAdIdsUsedByEnrollment_multipleEnrollmentIdsPresent() { |
| // Setup |
| Source.Builder sourceBuilder = |
| new Source.Builder() |
| .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE) |
| .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID) |
| .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN); |
| // Multiple sources with different AdIds and differing enrollmentIds |
| Source s1 = |
| sourceBuilder |
| .setId("s1") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-1") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1)); |
| Source s2 = |
| sourceBuilder |
| .setId("s2") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-2") |
| .setEnrollmentId("enrollment-id-2") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2)); |
| Source s3 = |
| sourceBuilder |
| .setId("s3") |
| .setPublisherType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-3") |
| .setEnrollmentId("enrollment-id-2") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3)); |
| |
| Trigger.Builder triggerBuilder = |
| new Trigger.Builder() |
| .setAttributionDestination( |
| TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION) |
| .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT) |
| .setRegistrationOrigin( |
| TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN); |
| // Multiple triggers with different AdIds and differing enrollmentIds |
| Trigger t1 = |
| triggerBuilder |
| .setId("t1") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-4") |
| .setEnrollmentId("enrollment-id-1") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1)); |
| Trigger t2 = |
| triggerBuilder |
| .setId("t2") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-5") |
| .setEnrollmentId("enrollment-id-2") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2)); |
| Trigger t3 = |
| triggerBuilder |
| .setId("t3") |
| .setDestinationType(EventSurfaceType.WEB) |
| .setDebugAdId("debug-ad-id-6") |
| .setEnrollmentId("enrollment-id-2") |
| .build(); |
| mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3)); |
| |
| // Assertion |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| dao -> |
| assertEquals( |
| 2, |
| dao.countDistinctDebugAdIdsUsedByEnrollment( |
| "enrollment-id-1")))); |
| assertTrue( |
| mDatastoreManager.runInTransaction( |
| dao -> |
| assertEquals( |
| 4, |
| dao.countDistinctDebugAdIdsUsedByEnrollment( |
| "enrollment-id-2")))); |
| } |
| |
| @Test |
| public void getPendingAggregateReportIdsByCoordinatorInWindow() { |
| AggregateReport ar11 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("11") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .build(); |
| AggregateReport ar12 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("12") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .build(); |
| AggregateReport ar21 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("21") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test")) |
| .build(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AbstractDbIntegrationTest.insertToDb(ar11, db); |
| AbstractDbIntegrationTest.insertToDb(ar12, db); |
| AbstractDbIntegrationTest.insertToDb(ar21, db); |
| |
| Optional<Map<String, List<String>>> resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getPendingAggregateReportIdsByCoordinatorInWindow( |
| AggregateReportFixture.ValidAggregateReportParams |
| .TRIGGER_TIME, |
| AggregateReportFixture.ValidAggregateReportParams |
| .TRIGGER_TIME |
| + TimeUnit.DAYS.toMillis(30))); |
| assertTrue(resOpt.isPresent()); |
| Map<String, List<String>> res = resOpt.get(); |
| assertEquals(2, res.size()); |
| |
| // URL 1 |
| List<String> url1Ids = res.get("https://url1.test"); |
| url1Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(2, url1Ids.size()); |
| assertEquals("11", url1Ids.get(0)); |
| assertEquals("12", url1Ids.get(1)); |
| // URL 2 |
| List<String> url2Ids = res.get("https://url2.test"); |
| url2Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(1, url2Ids.size()); |
| assertEquals("21", url2Ids.get(0)); |
| } |
| |
| @Test |
| public void getPendingAggregateReportIdsByCoordinatorInWindowWithRetryLimit() { |
| // Mocking that the flags return a Max Retry of 1 |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit(); |
| ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled(); |
| |
| AggregateReport ar11 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("11") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .build(); |
| AggregateReport ar12 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("12") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .build(); |
| AggregateReport ar21 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("21") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test")) |
| .build(); |
| AggregateReport ar31 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("31") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url3.test")) |
| .build(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AbstractDbIntegrationTest.insertToDb(ar11, db); |
| AbstractDbIntegrationTest.insertToDb(ar12, db); |
| AbstractDbIntegrationTest.insertToDb(ar21, db); |
| AbstractDbIntegrationTest.insertToDb(ar31, db); |
| |
| Optional<Map<String, List<String>>> resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> { |
| return dao.getPendingAggregateReportIdsByCoordinatorInWindow( |
| AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME, |
| AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME |
| + TimeUnit.DAYS.toMillis(30)); |
| }); |
| assertTrue(resOpt.isPresent()); |
| Map<String, List<String>> res = resOpt.get(); |
| assertEquals(3, res.size()); |
| |
| // URL 1 |
| List<String> url1Ids = res.get("https://url1.test"); |
| url1Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(2, url1Ids.size()); |
| assertEquals("11", url1Ids.get(0)); |
| assertEquals("12", url1Ids.get(1)); |
| // URL 2 |
| List<String> url2Ids = res.get("https://url2.test"); |
| url2Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(1, url2Ids.size()); |
| assertEquals("21", url2Ids.get(0)); |
| |
| resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> { |
| // Adds records to KeyValueData table for Retry Count. |
| dao.incrementAndGetReportingRetryCount( |
| ar31.getId(), DataType.AGGREGATE_REPORT_RETRY_COUNT); |
| return dao.getPendingAggregateReportIdsByCoordinatorInWindow( |
| AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME, |
| AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME |
| + TimeUnit.DAYS.toMillis(30)); |
| }); |
| res = resOpt.get(); |
| |
| assertEquals(2, res.size()); |
| |
| // URL 1 |
| url1Ids = res.get("https://url1.test"); |
| url1Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(2, url1Ids.size()); |
| assertEquals("11", url1Ids.get(0)); |
| assertEquals("12", url1Ids.get(1)); |
| // URL 2 |
| url2Ids = res.get("https://url2.test"); |
| url2Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(1, url2Ids.size()); |
| assertEquals("21", url2Ids.get(0)); |
| } |
| |
| @Test |
| public void getPendingAggregateDebugReportIdsByCoordinator() { |
| AggregateReport ar11 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("11") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .build(); |
| AggregateReport ar12 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("12") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .build(); |
| AggregateReport ar21 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("21") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test")) |
| .build(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AbstractDbIntegrationTest.insertToDb(ar11, db); |
| AbstractDbIntegrationTest.insertToDb(ar12, db); |
| AbstractDbIntegrationTest.insertToDb(ar21, db); |
| |
| Optional<Map<String, List<String>>> resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getPendingAggregateReportIdsByCoordinatorInWindow( |
| AggregateReportFixture.ValidAggregateReportParams |
| .TRIGGER_TIME, |
| AggregateReportFixture.ValidAggregateReportParams |
| .TRIGGER_TIME |
| + TimeUnit.DAYS.toMillis(30))); |
| assertTrue(resOpt.isPresent()); |
| Map<String, List<String>> res = resOpt.get(); |
| assertEquals(2, res.size()); |
| |
| // URL 1 |
| List<String> url1Ids = res.get("https://url1.test"); |
| assertEquals(2, url1Ids.size()); |
| url1Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals("11", url1Ids.get(0)); |
| assertEquals("12", url1Ids.get(1)); |
| // URL 2 |
| List<String> url2Ids = res.get("https://url2.test"); |
| url2Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(1, url2Ids.size()); |
| assertEquals("21", url2Ids.get(0)); |
| } |
| |
| @Test |
| public void getPendingAggregateDebugReportIdsByCoordinatorWithRetryLimit() { |
| // Mocking that the flags return a Max Retry of 1 |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit(); |
| ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled(); |
| |
| AggregateReport ar11 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("11") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING) |
| .build(); |
| AggregateReport ar12 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("12") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test")) |
| .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING) |
| .build(); |
| AggregateReport ar21 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("21") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test")) |
| .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING) |
| .build(); |
| AggregateReport ar31 = |
| AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId("31") |
| .setAggregationCoordinatorOrigin(Uri.parse("https://url3.test")) |
| .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING) |
| .build(); |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AbstractDbIntegrationTest.insertToDb(ar11, db); |
| AbstractDbIntegrationTest.insertToDb(ar12, db); |
| AbstractDbIntegrationTest.insertToDb(ar21, db); |
| AbstractDbIntegrationTest.insertToDb(ar31, db); |
| |
| Optional<Map<String, List<String>>> resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getPendingAggregateDebugReportIdsByCoordinator()); |
| assertTrue(resOpt.isPresent()); |
| Map<String, List<String>> res = resOpt.get(); |
| assertEquals(3, res.size()); |
| |
| resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> { |
| // Adds records to KeyValueData table for Retry Count. |
| dao.incrementAndGetReportingRetryCount( |
| ar31.getId(), DataType.AGGREGATE_REPORT_RETRY_COUNT); |
| return dao.getPendingAggregateDebugReportIdsByCoordinator(); |
| }); |
| assertTrue(resOpt.isPresent()); |
| res = resOpt.get(); |
| assertEquals(2, res.size()); |
| |
| // URL 1 |
| List<String> url1Ids = res.get("https://url1.test"); |
| assertEquals(2, url1Ids.size()); |
| url1Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals("11", url1Ids.get(0)); |
| assertEquals("12", url1Ids.get(1)); |
| // URL 2 |
| List<String> url2Ids = res.get("https://url2.test"); |
| url2Ids.sort(String.CASE_INSENSITIVE_ORDER); |
| assertEquals(1, url2Ids.size()); |
| assertEquals("21", url2Ids.get(0)); |
| } |
| |
| @Test |
| public void getPendingEventReportIdsInWindowWithRetry() { |
| // Mocking that the flags return a Max Retry of 1 |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit(); |
| ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled(); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| EventReport er1 = |
| generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1); |
| EventReport er2 = |
| generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2); |
| List.of(er1, er2) |
| .forEach( |
| eventReport -> { |
| ContentValues values = new ContentValues(); |
| values.put( |
| MeasurementTables.EventReportContract.ID, eventReport.getId()); |
| values.put( |
| MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION, |
| eventReport.getAttributionDestinations().get(0).toString()); |
| values.put( |
| EventReportContract.REPORT_TIME, |
| EventReportFixture.ValidEventReportParams.TRIGGER_TIME |
| + TimeUnit.DAYS.toMillis(15)); |
| values.put(EventReportContract.STATUS, EventReport.Status.PENDING); |
| db.insert(MeasurementTables.EventReportContract.TABLE, null, values); |
| }); |
| Optional<List<String>> resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getPendingEventReportIdsInWindow( |
| EventReportFixture.ValidEventReportParams.TRIGGER_TIME, |
| EventReportFixture.ValidEventReportParams.TRIGGER_TIME |
| + TimeUnit.DAYS.toMillis(30))); |
| assertTrue(resOpt.isPresent()); |
| List<String> res = resOpt.get(); |
| assertEquals(2, res.size()); |
| assertTrue(res.containsAll(List.of("1", "2"))); |
| resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> { |
| // Adds records to KeyValueData table for Retry Count. |
| dao.incrementAndGetReportingRetryCount( |
| "1", DataType.EVENT_REPORT_RETRY_COUNT); |
| return dao.getPendingEventReportIdsInWindow( |
| EventReportFixture.ValidEventReportParams.TRIGGER_TIME, |
| EventReportFixture.ValidEventReportParams.TRIGGER_TIME |
| + TimeUnit.DAYS.toMillis(30)); |
| }); |
| res = resOpt.get(); |
| assertEquals(1, res.size()); |
| assertEquals(List.of("2"), res); |
| } |
| |
| @Test |
| public void getPendingEventDebugReportIdsWithRetryLimit() { |
| // Mocking that the flags return a Max Retry of 1 |
| Flags mockFlags = Mockito.mock(Flags.class); |
| ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags); |
| ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit(); |
| ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled(); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| EventReport er1 = |
| generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1); |
| EventReport er2 = |
| generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2); |
| List.of(er1, er2) |
| .forEach( |
| eventReport -> { |
| ContentValues values = new ContentValues(); |
| values.put( |
| MeasurementTables.EventReportContract.ID, eventReport.getId()); |
| values.put( |
| MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION, |
| eventReport.getAttributionDestinations().get(0).toString()); |
| values.put( |
| EventReportContract.DEBUG_REPORT_STATUS, |
| EventReport.DebugReportStatus.PENDING); |
| db.insert(MeasurementTables.EventReportContract.TABLE, null, values); |
| }); |
| |
| Optional<List<String>> resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getPendingDebugEventReportIds()); |
| assertTrue(resOpt.isPresent()); |
| List<String> res = resOpt.get(); |
| assertEquals(2, res.size()); |
| assertTrue(res.containsAll(List.of("1", "2"))); |
| resOpt = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> { |
| // Adds records to KeyValueData table for Retry Count. |
| dao.incrementAndGetReportingRetryCount( |
| "1", DataType.DEBUG_EVENT_REPORT_RETRY_COUNT); |
| return dao.getPendingDebugEventReportIds(); |
| }); |
| res = resOpt.get(); |
| assertEquals(1, res.size()); |
| assertEquals(List.of("2"), res); |
| } |
| |
| @Test |
| public void getNonExpiredAggregateEncryptionKeys() { |
| AggregateEncryptionKey ek11 = |
| new AggregateEncryptionKey.Builder() |
| .setId("11") |
| .setKeyId("11") |
| .setPublicKey("11") |
| .setExpiry(11) |
| .setAggregationCoordinatorOrigin(Uri.parse("https://1coordinator.test")) |
| .build(); |
| // ek12 will not be fetched because expiry (5) < 10 |
| AggregateEncryptionKey ek12 = |
| new AggregateEncryptionKey.Builder() |
| .setId("12") |
| .setKeyId("12") |
| .setPublicKey("12") |
| .setExpiry(5) |
| .setAggregationCoordinatorOrigin(Uri.parse("https://1coordinator.test")) |
| .build(); |
| |
| AggregateEncryptionKey ek21 = |
| new AggregateEncryptionKey.Builder() |
| .setId("21") |
| .setKeyId("21") |
| .setPublicKey("21") |
| .setExpiry(10) |
| .setAggregationCoordinatorOrigin(Uri.parse("https://2coordinator.test")) |
| .build(); |
| AggregateEncryptionKey ek22 = |
| new AggregateEncryptionKey.Builder() |
| .setId("22") |
| .setKeyId("22") |
| .setPublicKey("22") |
| .setExpiry(15) |
| .setAggregationCoordinatorOrigin(Uri.parse("https://2coordinator.test")) |
| .build(); |
| |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| AbstractDbIntegrationTest.insertToDb(ek11, db); |
| AbstractDbIntegrationTest.insertToDb(ek12, db); |
| AbstractDbIntegrationTest.insertToDb(ek21, db); |
| AbstractDbIntegrationTest.insertToDb(ek22, db); |
| |
| List<AggregateEncryptionKey> res1 = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| (dao) -> { |
| return dao.getNonExpiredAggregateEncryptionKeys( |
| Uri.parse("https://1coordinator.test"), 10); |
| }) |
| .orElseThrow(); |
| // ek12 will not be fetched because expiry (5) < 10 |
| assertEquals(1, res1.size()); |
| assertEquals(ek11, res1.get(0)); |
| |
| List<AggregateEncryptionKey> res2 = |
| mDatastoreManager |
| .runInTransactionWithResult( |
| (dao) -> { |
| return dao.getNonExpiredAggregateEncryptionKeys( |
| Uri.parse("https://2coordinator.test"), 10); |
| }) |
| .orElseThrow(); |
| res1.sort((x, y) -> String.CASE_INSENSITIVE_ORDER.compare(x.getId(), y.getId())); |
| assertEquals(2, res2.size()); |
| assertEquals(ek21, res2.get(0)); |
| assertEquals(ek22, res2.get(1)); |
| } |
| |
| @Test |
| public void incrementReportRetryIncrements() { |
| final String eventId = "TestIdEvent"; |
| final String aggregateId = "TestIdAggregate"; |
| final String debugId = "TestIdDebug"; |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.incrementAndGetReportingRetryCount( |
| eventId, DataType.EVENT_REPORT_RETRY_COUNT); |
| dao.incrementAndGetReportingRetryCount( |
| eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT); |
| dao.incrementAndGetReportingRetryCount( |
| aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT); |
| dao.incrementAndGetReportingRetryCount( |
| aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT); |
| dao.incrementAndGetReportingRetryCount( |
| debugId, DataType.DEBUG_REPORT_RETRY_COUNT); |
| }); |
| Optional<KeyValueData> eventCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT)); |
| Optional<KeyValueData> debugEventCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT)); |
| Optional<KeyValueData> aggregateCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT)); |
| Optional<KeyValueData> debugAggregateCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT)); |
| Optional<KeyValueData> debugCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT)); |
| |
| assertTrue(eventCount.isPresent()); |
| assertEquals(1, (eventCount.get().getReportRetryCount())); |
| assertTrue(debugEventCount.isPresent()); |
| assertEquals(1, (debugEventCount.get().getReportRetryCount())); |
| assertTrue(aggregateCount.isPresent()); |
| assertEquals(1, (aggregateCount.get().getReportRetryCount())); |
| assertTrue(debugAggregateCount.isPresent()); |
| assertEquals(1, (debugAggregateCount.get().getReportRetryCount())); |
| assertTrue(debugCount.isPresent()); |
| assertEquals(1, (debugCount.get().getReportRetryCount())); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.incrementAndGetReportingRetryCount( |
| eventId, DataType.EVENT_REPORT_RETRY_COUNT); |
| dao.incrementAndGetReportingRetryCount( |
| aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT); |
| }); |
| eventCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT)); |
| aggregateCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT)); |
| debugEventCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT)); |
| debugAggregateCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT)); |
| debugCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT)); |
| |
| assertTrue(eventCount.isPresent()); |
| assertEquals(2, (eventCount.get().getReportRetryCount())); |
| assertTrue(debugEventCount.isPresent()); |
| assertEquals(1, (debugEventCount.get().getReportRetryCount())); |
| assertTrue(aggregateCount.isPresent()); |
| assertEquals(2, (aggregateCount.get().getReportRetryCount())); |
| assertTrue(debugAggregateCount.isPresent()); |
| assertEquals(1, (debugAggregateCount.get().getReportRetryCount())); |
| assertTrue(debugCount.isPresent()); |
| assertEquals(1, (debugCount.get().getReportRetryCount())); |
| |
| mDatastoreManager.runInTransaction( |
| (dao) -> { |
| dao.incrementAndGetReportingRetryCount( |
| eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT); |
| dao.incrementAndGetReportingRetryCount( |
| aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT); |
| dao.incrementAndGetReportingRetryCount( |
| debugId, DataType.DEBUG_REPORT_RETRY_COUNT); |
| }); |
| eventCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT)); |
| aggregateCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT)); |
| debugEventCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT)); |
| debugAggregateCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> |
| dao.getKeyValueData( |
| aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT)); |
| debugCount = |
| mDatastoreManager.runInTransactionWithResult( |
| (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT)); |
| |
| assertTrue(eventCount.isPresent()); |
| assertEquals(2, (eventCount.get().getReportRetryCount())); |
| assertTrue(debugEventCount.isPresent()); |
| assertEquals(2, (debugEventCount.get().getReportRetryCount())); |
| assertTrue(aggregateCount.isPresent()); |
| assertEquals(2, (aggregateCount.get().getReportRetryCount())); |
| assertTrue(debugAggregateCount.isPresent()); |
| assertEquals(2, (debugAggregateCount.get().getReportRetryCount())); |
| assertTrue(debugCount.isPresent()); |
| assertEquals(2, (debugCount.get().getReportRetryCount())); |
| } |
| |
| private void insertInDb(SQLiteDatabase db, Source source) { |
| ContentValues values = new ContentValues(); |
| values.put(SourceContract.ID, source.getId()); |
| values.put(SourceContract.STATUS, Source.Status.ACTIVE); |
| values.put(SourceContract.EVENT_TIME, source.getEventTime()); |
| values.put(SourceContract.EXPIRY_TIME, source.getExpiryTime()); |
| values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId()); |
| values.put(SourceContract.PUBLISHER, source.getPublisher().toString()); |
| values.put(SourceContract.REGISTRANT, source.getRegistrant().toString()); |
| values.put(SourceContract.REGISTRATION_ORIGIN, source.getRegistrationOrigin().toString()); |
| if (source.getAttributedTriggers() != null) { |
| values.put(SourceContract.EVENT_ATTRIBUTION_STATUS, source.attributedTriggersToJson()); |
| } |
| if (source.getTriggerSpecs() != null) { |
| values.put( |
| MeasurementTables.SourceContract.TRIGGER_SPECS, |
| source.getTriggerSpecs().encodeToJson()); |
| } |
| db.insert(SourceContract.TABLE, null, values); |
| |
| // Insert source destinations |
| if (source.getAppDestinations() != null) { |
| for (Uri appDestination : source.getAppDestinations()) { |
| ContentValues destinationValues = new ContentValues(); |
| destinationValues.put( |
| MeasurementTables.SourceDestination.SOURCE_ID, source.getId()); |
| destinationValues.put( |
| MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.APP); |
| destinationValues.put( |
| MeasurementTables.SourceDestination.DESTINATION, appDestination.toString()); |
| db.insert(MeasurementTables.SourceDestination.TABLE, null, destinationValues); |
| } |
| } |
| |
| if (source.getWebDestinations() != null) { |
| for (Uri webDestination : source.getWebDestinations()) { |
| ContentValues destinationValues = new ContentValues(); |
| destinationValues.put( |
| MeasurementTables.SourceDestination.SOURCE_ID, source.getId()); |
| destinationValues.put( |
| MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.WEB); |
| destinationValues.put( |
| MeasurementTables.SourceDestination.DESTINATION, webDestination.toString()); |
| db.insert(MeasurementTables.SourceDestination.TABLE, null, destinationValues); |
| } |
| } |
| } |
| |
| private void queryAndAssertSourceEntries( |
| SQLiteDatabase db, String enrollmentId, List<String> expectedSourceIds) { |
| try (Cursor cursor = |
| db.query( |
| XnaIgnoredSourcesContract.TABLE, |
| new String[] {XnaIgnoredSourcesContract.SOURCE_ID}, |
| XnaIgnoredSourcesContract.ENROLLMENT_ID + " = ?", |
| new String[] {enrollmentId}, |
| null, |
| null, |
| null)) { |
| assertEquals(expectedSourceIds.size(), cursor.getCount()); |
| for (int i = 0; i < expectedSourceIds.size() && cursor.moveToNext(); i++) { |
| assertEquals(expectedSourceIds.get(i), cursor.getString(0)); |
| } |
| } |
| } |
| |
| private Source.Builder createSourceBuilder() { |
| return new Source.Builder() |
| .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID) |
| .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER) |
| .setAppDestinations(SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATIONS) |
| .setWebDestinations(SourceFixture.ValidSourceParams.WEB_DESTINATIONS) |
| .setEnrollmentId(SourceFixture.ValidSourceParams.ENROLLMENT_ID) |
| .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT) |
| .setEventTime(SourceFixture.ValidSourceParams.SOURCE_EVENT_TIME) |
| .setExpiryTime(SourceFixture.ValidSourceParams.EXPIRY_TIME) |
| .setPriority(SourceFixture.ValidSourceParams.PRIORITY) |
| .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE) |
| .setInstallAttributionWindow( |
| SourceFixture.ValidSourceParams.INSTALL_ATTRIBUTION_WINDOW) |
| .setInstallCooldownWindow(SourceFixture.ValidSourceParams.INSTALL_COOLDOWN_WINDOW) |
| .setAttributionMode(SourceFixture.ValidSourceParams.ATTRIBUTION_MODE) |
| .setAggregateSource(SourceFixture.ValidSourceParams.buildAggregateSource()) |
| .setFilterData(SourceFixture.ValidSourceParams.buildFilterData()) |
| .setSharedFilterDataKeys(SourceFixture.ValidSourceParams.SHARED_FILTER_DATA_KEYS) |
| .setIsDebugReporting(true) |
| .setRegistrationId(UUID.randomUUID().toString()) |
| .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS) |
| .setInstallTime(SourceFixture.ValidSourceParams.INSTALL_TIME) |
| .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN); |
| } |
| |
| private AggregateReport createAggregateReportForSourceAndTrigger( |
| Source source, Trigger trigger) { |
| return createAggregateReportForSourceAndTrigger( |
| UUID.randomUUID().toString(), source, trigger); |
| } |
| |
| private EventReport createEventReportForSourceAndTrigger(Source source, Trigger trigger) |
| throws JSONException { |
| return createEventReportForSourceAndTrigger(UUID.randomUUID().toString(), source, trigger); |
| } |
| |
| private AggregateReport createAggregateReportForSourceAndTrigger( |
| String reportId, Source source, Trigger trigger) { |
| return AggregateReportFixture.getValidAggregateReportBuilder() |
| .setId(reportId) |
| .setSourceId(source.getId()) |
| .setTriggerId(trigger.getId()) |
| .build(); |
| } |
| |
| private EventReport createEventReportForSourceAndTrigger( |
| String reportId, Source source, Trigger trigger) throws JSONException { |
| |
| return new EventReport.Builder() |
| .setId(reportId) |
| .populateFromSourceAndTrigger( |
| source, |
| trigger, |
| trigger.parseEventTriggers(FlagsFactory.getFlagsForTest()).get(0), |
| new Pair<>(null, null), |
| new EventReportWindowCalcDelegate(mFlags), |
| new SourceNoiseHandler(mFlags), |
| source.getAttributionDestinations(trigger.getDestinationType())) |
| .setSourceEventId(source.getEventId()) |
| .setSourceId(source.getId()) |
| .setTriggerId(trigger.getId()) |
| .build(); |
| } |
| |
| private DebugReport createDebugReport() { |
| return new DebugReport.Builder() |
| .setId("reportId") |
| .setType("trigger-event-deduplicated") |
| .setBody( |
| " {\n" |
| + " \"attribution_destination\":" |
| + " \"https://destination.example\",\n" |
| + " \"source_event_id\": \"45623\"\n" |
| + " }") |
| .setEnrollmentId("1") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setRegistrant(REGISTRANT) |
| .setInsertionTime(INSERTION_TIME) |
| .build(); |
| } |
| |
| private DebugReport buildDebugReportWithInstalledRegistrant(String id) { |
| return new DebugReport.Builder() |
| .setId(id) |
| .setType("trigger-event-deduplicated") |
| .setBody( |
| " {\n" |
| + " \"attribution_destination\":" |
| + " \"https://destination.example\",\n" |
| + " \"source_event_id\": \"45623\"\n" |
| + " }") |
| .setEnrollmentId("1") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setRegistrant(INSTALLED_REGISTRANT) |
| .setInsertionTime(INSERTION_TIME) |
| .build(); |
| } |
| |
| private DebugReport buildDebugReportWithNotInstalledRegistrant(String id) { |
| return new DebugReport.Builder() |
| .setId(id) |
| .setType("trigger-event-deduplicated") |
| .setBody( |
| " {\n" |
| + " \"attribution_destination\":" |
| + " \"https://destination.example\",\n" |
| + " \"source_event_id\": \"45623\"\n" |
| + " }") |
| .setEnrollmentId("1") |
| .setRegistrationOrigin(REGISTRATION_ORIGIN) |
| .setRegistrant(NOT_INSTALLED_REGISTRANT) |
| .setInsertionTime(INSERTION_TIME) |
| .build(); |
| } |
| |
| private void setupSourceAndTriggerData() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<Source> sourcesList = new ArrayList<>(); |
| sourcesList.add( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("S1") |
| .setRegistrant(APP_TWO_SOURCES) |
| .setPublisher(APP_TWO_PUBLISHER) |
| .setPublisherType(EventSurfaceType.APP) |
| .build()); |
| sourcesList.add( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("S2") |
| .setRegistrant(APP_TWO_SOURCES) |
| .setPublisher(APP_TWO_PUBLISHER) |
| .setPublisherType(EventSurfaceType.APP) |
| .build()); |
| sourcesList.add( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("S3") |
| .setRegistrant(APP_ONE_SOURCE) |
| .setPublisher(APP_ONE_PUBLISHER) |
| .setPublisherType(EventSurfaceType.APP) |
| .build()); |
| for (Source source : sourcesList) { |
| ContentValues values = new ContentValues(); |
| values.put("_id", source.getId()); |
| values.put("registrant", source.getRegistrant().toString()); |
| values.put("publisher", source.getPublisher().toString()); |
| values.put("publisher_type", source.getPublisherType()); |
| |
| long row = db.insert("msmt_source", null, values); |
| assertNotEquals("Source insertion failed", -1, row); |
| } |
| List<Trigger> triggersList = new ArrayList<>(); |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T1") |
| .setRegistrant(APP_TWO_DESTINATION) |
| .build()); |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T2") |
| .setRegistrant(APP_TWO_DESTINATION) |
| .build()); |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T3") |
| .setRegistrant(APP_ONE_DESTINATION) |
| .build()); |
| |
| // Add web triggers. |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T4") |
| .setRegistrant(APP_BROWSER) |
| .setAttributionDestination(WEB_ONE_DESTINATION) |
| .build()); |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T5") |
| .setRegistrant(APP_BROWSER) |
| .setAttributionDestination(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN) |
| .build()); |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T7") |
| .setRegistrant(APP_BROWSER) |
| .setAttributionDestination(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2) |
| .build()); |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T8") |
| .setRegistrant(APP_BROWSER) |
| .setAttributionDestination(WEB_TWO_DESTINATION) |
| .build()); |
| triggersList.add( |
| TriggerFixture.getValidTriggerBuilder() |
| .setId("T9") |
| .setRegistrant(APP_BROWSER) |
| .setAttributionDestination(WEB_TWO_DESTINATION_WITH_PATH) |
| .build()); |
| |
| for (Trigger trigger : triggersList) { |
| ContentValues values = new ContentValues(); |
| values.put("_id", trigger.getId()); |
| values.put("registrant", trigger.getRegistrant().toString()); |
| values.put("attribution_destination", trigger.getAttributionDestination().toString()); |
| long row = db.insert("msmt_trigger", null, values); |
| assertNotEquals("Trigger insertion failed", -1, row); |
| } |
| } |
| |
| private Trigger createWebTrigger(Uri attributionDestination) { |
| return TriggerFixture.getValidTriggerBuilder() |
| .setId("ID" + mValueId++) |
| .setAttributionDestination(attributionDestination) |
| .setRegistrant(APP_BROWSER) |
| .build(); |
| } |
| |
| private Trigger createAppTrigger(Uri registrant, Uri destination) { |
| return TriggerFixture.getValidTriggerBuilder() |
| .setId("ID" + mValueId++) |
| .setAttributionDestination(destination) |
| .setRegistrant(registrant) |
| .build(); |
| } |
| |
| private void addTriggersToDatabase(List<Trigger> triggersList) { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| |
| for (Trigger trigger : triggersList) { |
| ContentValues values = new ContentValues(); |
| values.put("_id", trigger.getId()); |
| values.put("registrant", trigger.getRegistrant().toString()); |
| values.put("attribution_destination", trigger.getAttributionDestination().toString()); |
| long row = db.insert("msmt_trigger", null, values); |
| assertNotEquals("Trigger insertion failed", -1, row); |
| } |
| } |
| |
| private void setupSourceDataForPublisherTypeWeb() { |
| SQLiteDatabase db = MeasurementDbHelper.getInstance(sContext).safeGetWritableDatabase(); |
| List<Source> sourcesList = new ArrayList<>(); |
| sourcesList.add( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("W1") |
| .setPublisher(WEB_PUBLISHER_ONE) |
| .setPublisherType(EventSurfaceType.WEB) |
| .build()); |
| sourcesList.add( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("W21") |
| .setPublisher(WEB_PUBLISHER_TWO) |
| .setPublisherType(EventSurfaceType.WEB) |
| .build()); |
| sourcesList.add( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("W22") |
| .setPublisher(WEB_PUBLISHER_TWO) |
| .setPublisherType(EventSurfaceType.WEB) |
| .build()); |
| sourcesList.add( |
| SourceFixture.getMinimalValidSourceBuilder() |
| .setId("S3") |
| .setPublisher(WEB_PUBLISHER_THREE) |
| .setPublisherType(EventSurfaceType.WEB) |
| .build()); |
| for (Source source : sourcesList) { |
| ContentValues values = new ContentValues(); |
| values.put("_id", source.getId()); |
| values.put("publisher", source.getPublisher().toString()); |
| values.put("publisher_type", source.getPublisherType()); |
| |
| long row = db.insert("msmt_source", null, values); |
| assertNotEquals("Source insertion failed", -1, row); |
| } |
| } |
| |
| private Source.Builder createSourceForIATest( |
| String id, |
| long currentTime, |
| long priority, |
| int eventTimePastDays, |
| boolean expiredIAWindow, |
| String enrollmentId) { |
| return new Source.Builder() |
| .setId(id) |
| .setPublisher(Uri.parse("android-app://com.example.sample")) |
| .setRegistrant(Uri.parse("android-app://com.example.sample")) |
| .setEnrollmentId(enrollmentId) |
| .setExpiryTime(currentTime + TimeUnit.DAYS.toMillis(30)) |
| .setInstallAttributionWindow(TimeUnit.DAYS.toMillis(expiredIAWindow ? 0 : 30)) |
| .setAppDestinations(List.of(INSTALLED_PACKAGE)) |
| .setEventTime( |
| currentTime |
| - TimeUnit.DAYS.toMillis( |
| eventTimePastDays == -1 ? 10 : eventTimePastDays)) |
| .setPriority(priority == -1 ? 100 : priority) |
| .setRegistrationOrigin(REGISTRATION_ORIGIN); |
| } |
| |
| private AggregateReport generateMockAggregateReport(String attributionDestination, int id) { |
| return new AggregateReport.Builder() |
| .setId(String.valueOf(id)) |
| .setAttributionDestination(Uri.parse(attributionDestination)) |
| .build(); |
| } |
| |
| private AggregateReport generateMockAggregateReport( |
| String attributionDestination, int id, String sourceId) { |
| return new AggregateReport.Builder() |
| .setId(String.valueOf(id)) |
| .setSourceId(sourceId) |
| .setAttributionDestination(Uri.parse(attributionDestination)) |
| .build(); |
| } |
| |
| private EventReport generateMockEventReport(String attributionDestination, int id) { |
| return new EventReport.Builder() |
| .setId(String.valueOf(id)) |
| .setAttributionDestinations(List.of(Uri.parse(attributionDestination))) |
| .build(); |
| } |
| |
| private void assertAggregateReportCount( |
| List<String> attributionDestinations, |
| int destinationType, |
| List<Integer> expectedCounts) { |
| IntStream.range(0, attributionDestinations.size()) |
| .forEach( |
| i -> { |
| DatastoreManager.ThrowingCheckedFunction<Integer> |
| aggregateReportCountPerDestination = |
| measurementDao -> |
| measurementDao |
| .getNumAggregateReportsPerDestination( |
| Uri.parse( |
| attributionDestinations |
| .get(i)), |
| destinationType); |
| assertEquals( |
| expectedCounts.get(i), |
| mDatastoreManager |
| .runInTransactionWithResult( |
| aggregateReportCountPerDestination) |
| .orElseThrow()); |
| }); |
| } |
| |
| private void assertEventReportCount( |
| List<String> attributionDestinations, |
| int destinationType, |
| List<Integer> expectedCounts) { |
| IntStream.range(0, attributionDestinations.size()) |
| .forEach( |
| i -> { |
| DatastoreManager.ThrowingCheckedFunction<Integer> |
| numEventReportsPerDestination = |
| measurementDao -> |
| measurementDao.getNumEventReportsPerDestination( |
| Uri.parse( |
| attributionDestinations.get(i)), |
| destinationType); |
| assertEquals( |
| expectedCounts.get(i), |
| mDatastoreManager |
| .runInTransactionWithResult( |
| numEventReportsPerDestination) |
| .orElseThrow()); |
| }); |
| } |
| |
| private List<String> createAppDestinationVariants(int destinationNum) { |
| return Arrays.asList( |
| "android-app://subdomain.destination-" + destinationNum + ".app/abcd", |
| "android-app://subdomain.destination-" + destinationNum + ".app", |
| "android-app://destination-" + destinationNum + ".app/abcd", |
| "android-app://destination-" + destinationNum + ".app", |
| "android-app://destination-" + destinationNum + ".ap"); |
| } |
| |
| private List<String> createWebDestinationVariants(int destinationNum) { |
| return Arrays.asList( |
| "https://subdomain.destination-" + destinationNum + ".com/abcd", |
| "https://subdomain.destination-" + destinationNum + ".com", |
| "https://destination-" + destinationNum + ".com/abcd", |
| "https://destination-" + destinationNum + ".com", |
| "https://destination-" + destinationNum + ".co"); |
| } |
| |
| private boolean getInstallAttributionStatus(String sourceDbId, SQLiteDatabase db) { |
| Cursor cursor = |
| db.query( |
| SourceContract.TABLE, |
| new String[] {SourceContract.IS_INSTALL_ATTRIBUTED}, |
| SourceContract.ID + " = ? ", |
| new String[] {sourceDbId}, |
| null, |
| null, |
| null, |
| null); |
| assertTrue(cursor.moveToFirst()); |
| return cursor.getInt(0) == 1; |
| } |
| |
| private Long getInstallAttributionInstallTime(String sourceDbId, SQLiteDatabase db) { |
| Cursor cursor = |
| db.query( |
| SourceContract.TABLE, |
| new String[] {SourceContract.INSTALL_TIME}, |
| SourceContract.ID + " = ? ", |
| new String[] {sourceDbId}, |
| null, |
| null, |
| null, |
| null); |
| assertTrue(cursor.moveToFirst()); |
| if (!cursor.isNull(0)) { |
| return cursor.getLong(0); |
| } |
| return null; |
| } |
| |
| private void removeSources(List<String> dbIds, SQLiteDatabase db) { |
| db.delete( |
| SourceContract.TABLE, |
| SourceContract.ID + " IN ( ? )", |
| new String[] {String.join(",", dbIds)}); |
| } |
| |
| private static void maybeInsertSourceDestinations( |
| SQLiteDatabase db, Source source, String sourceId) { |
| if (source.getAppDestinations() != null) { |
| for (Uri appDestination : source.getAppDestinations()) { |
| ContentValues values = new ContentValues(); |
| values.put(MeasurementTables.SourceDestination.SOURCE_ID, sourceId); |
| values.put( |
| MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.APP); |
| values.put( |
| MeasurementTables.SourceDestination.DESTINATION, appDestination.toString()); |
| long row = db.insert(MeasurementTables.SourceDestination.TABLE, null, values); |
| assertNotEquals("Source app destination insertion failed", -1, row); |
| } |
| } |
| if (source.getWebDestinations() != null) { |
| for (Uri webDestination : source.getWebDestinations()) { |
| ContentValues values = new ContentValues(); |
| values.put(MeasurementTables.SourceDestination.SOURCE_ID, sourceId); |
| values.put( |
| MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.WEB); |
| values.put( |
| MeasurementTables.SourceDestination.DESTINATION, webDestination.toString()); |
| long row = db.insert(MeasurementTables.SourceDestination.TABLE, null, values); |
| assertNotEquals("Source web destination insertion failed", -1, row); |
| } |
| } |
| } |
| |
| private static Attribution.Builder getAttributionBuilder(Source source, Trigger trigger) { |
| return new Attribution.Builder() |
| .setEnrollmentId(source.getEnrollmentId()) |
| .setDestinationOrigin(source.getWebDestinations().get(0).toString()) |
| .setDestinationSite(source.getAppDestinations().get(0).toString()) |
| .setSourceOrigin(source.getPublisher().toString()) |
| .setSourceSite(source.getPublisher().toString()) |
| .setRegistrant(source.getRegistrant().toString()) |
| .setTriggerTime( |
| trigger.getTriggerTime() |
| - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS |
| + 1) |
| .setRegistrationOrigin(trigger.getRegistrationOrigin()); |
| } |
| |
| /** Create {@link Attribution} object from SQLite datastore. */ |
| private static Attribution constructAttributionFromCursor(Cursor cursor) { |
| Attribution.Builder builder = new Attribution.Builder(); |
| int index = cursor.getColumnIndex(MeasurementTables.AttributionContract.ID); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setId(cursor.getString(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.SCOPE); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setScope(cursor.getInt(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.SOURCE_SITE); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setSourceSite(cursor.getString(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.SOURCE_ORIGIN); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setSourceOrigin(cursor.getString(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.DESTINATION_SITE); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setDestinationSite(cursor.getString(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.DESTINATION_ORIGIN); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setDestinationOrigin(cursor.getString(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.ENROLLMENT_ID); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setEnrollmentId(cursor.getString(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.TRIGGER_TIME); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setTriggerTime(cursor.getLong(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.REGISTRANT); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setRegistrant(cursor.getString(index)); |
| } |
| index = cursor.getColumnIndex(MeasurementTables.AttributionContract.REGISTRATION_ORIGIN); |
| if (index > -1 && !cursor.isNull(index)) { |
| builder.setRegistrationOrigin(Uri.parse(cursor.getString(index))); |
| } |
| return builder.build(); |
| } |
| |
| private static void insertAttributedTrigger(TriggerSpecs triggerSpecs, |
| EventReport eventReport) { |
| triggerSpecs.getAttributedTriggers().add( |
| new AttributedTrigger( |
| eventReport.getTriggerId(), |
| eventReport.getTriggerPriority(), |
| eventReport.getTriggerData(), |
| eventReport.getTriggerValue(), |
| eventReport.getTriggerTime(), |
| eventReport.getTriggerDedupKey(), |
| eventReport.getTriggerDebugKey(), |
| false)); |
| } |
| |
| private static void insertAttributedTrigger(List<AttributedTrigger> attributedTriggers, |
| EventReport eventReport) { |
| attributedTriggers.add( |
| new AttributedTrigger( |
| eventReport.getTriggerId(), |
| eventReport.getTriggerData(), |
| eventReport.getTriggerDedupKey())); |
| } |
| |
| private static String getFirstSourceIdFromDatastore() { |
| return getFirstIdFromDatastore(SourceContract.TABLE, SourceContract.ID); |
| } |
| |
| private static String getFirstIdFromDatastore(String tableName, String idColumn) { |
| try (Cursor cursor = |
| MeasurementDbHelper.getInstance(sContext) |
| .getReadableDatabase() |
| .query( |
| tableName, |
| new String[] {idColumn}, |
| null, |
| null, |
| null, |
| null, |
| null)) { |
| assertTrue(cursor.moveToNext()); |
| return cursor.getString(cursor.getColumnIndex(idColumn)); |
| } |
| } |
| |
| private static List<Uri> getNullableUriList(List<Uri> uris) { |
| return uris == null ? null : uris; |
| } |
| } |