• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.adservices.data.measurement;
18 
19 import static com.android.adservices.data.measurement.MeasurementTables.ALL_MSMT_TABLES;
20 import static com.android.adservices.data.measurement.MeasurementTables.AggregatableDebugReportBudgetTrackerContract;
21 import static com.android.adservices.data.measurement.MeasurementTables.AppReportHistoryContract;
22 import static com.android.adservices.data.measurement.MeasurementTables.AsyncRegistrationContract;
23 import static com.android.adservices.data.measurement.MeasurementTables.AttributionContract;
24 import static com.android.adservices.data.measurement.MeasurementTables.EventReportContract;
25 import static com.android.adservices.data.measurement.MeasurementTables.KeyValueDataContract;
26 import static com.android.adservices.data.measurement.MeasurementTables.MSMT_TABLE_PREFIX;
27 import static com.android.adservices.data.measurement.MeasurementTables.SourceContract;
28 import static com.android.adservices.data.measurement.MeasurementTables.TriggerContract;
29 import static com.android.adservices.data.measurement.MeasurementTables.XnaIgnoredSourcesContract;
30 import static com.android.adservices.service.Flags.MEASUREMENT_DB_SIZE_LIMIT;
31 import static com.android.adservices.service.Flags.MEASUREMENT_MAX_AGGREGATE_REPORTS_PER_SOURCE;
32 import static com.android.adservices.service.Flags.MEASUREMENT_MAX_EVENT_REPORTS_PER_DESTINATION;
33 import static com.android.adservices.service.Flags.MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
34 import static com.android.adservices.service.Flags.MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW;
35 import static com.android.adservices.service.Flags.MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS;
36 import static com.android.adservices.service.Flags.MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS;
37 import static com.android.adservices.service.measurement.SourceFixture.ValidSourceParams.SHARED_AGGREGATE_KEYS;
38 import static com.android.adservices.service.measurement.SourceFixture.ValidSourceParams.SOURCE_EVENT_TIME;
39 
40 import static com.google.common.truth.Truth.assertThat;
41 
42 import static org.junit.Assert.assertEquals;
43 import static org.junit.Assert.assertFalse;
44 import static org.junit.Assert.assertNotEquals;
45 import static org.junit.Assert.assertNotNull;
46 import static org.junit.Assert.assertNull;
47 import static org.junit.Assert.assertThrows;
48 import static org.junit.Assert.assertTrue;
49 import static org.junit.Assert.fail;
50 import static org.mockito.Mockito.doReturn;
51 import static org.mockito.Mockito.spy;
52 import static org.mockito.Mockito.when;
53 
54 import static java.util.concurrent.TimeUnit.DAYS;
55 
56 import android.adservices.measurement.DeletionRequest;
57 import android.content.ContentValues;
58 import android.database.Cursor;
59 import android.database.DatabaseUtils;
60 import android.database.sqlite.SQLiteDatabase;
61 import android.net.Uri;
62 import android.util.Pair;
63 
64 import androidx.annotation.NonNull;
65 import androidx.annotation.Nullable;
66 
67 import com.android.adservices.common.AdServicesExtendedMockitoTestCase;
68 import com.android.adservices.common.WebUtil;
69 import com.android.adservices.data.measurement.MeasurementTables.DebugReportContract;
70 import com.android.adservices.service.Flags;
71 import com.android.adservices.service.FlagsFactory;
72 import com.android.adservices.service.measurement.AggregatableNamedBudgets;
73 import com.android.adservices.service.measurement.AsyncRegistrationFixture;
74 import com.android.adservices.service.measurement.AsyncRegistrationFixture.ValidAsyncRegistrationParams;
75 import com.android.adservices.service.measurement.AttributedTrigger;
76 import com.android.adservices.service.measurement.Attribution;
77 import com.android.adservices.service.measurement.CountUniqueMetadata;
78 import com.android.adservices.service.measurement.CountUniqueReport;
79 import com.android.adservices.service.measurement.CountUniqueReportFixture;
80 import com.android.adservices.service.measurement.EventReport;
81 import com.android.adservices.service.measurement.EventReportFixture;
82 import com.android.adservices.service.measurement.EventSurfaceType;
83 import com.android.adservices.service.measurement.EventTrigger;
84 import com.android.adservices.service.measurement.KeyValueData;
85 import com.android.adservices.service.measurement.KeyValueData.DataType;
86 import com.android.adservices.service.measurement.Source;
87 import com.android.adservices.service.measurement.SourceFixture;
88 import com.android.adservices.service.measurement.Trigger;
89 import com.android.adservices.service.measurement.TriggerFixture;
90 import com.android.adservices.service.measurement.TriggerSpecs;
91 import com.android.adservices.service.measurement.aggregation.AggregateDebugReportRecord;
92 import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
93 import com.android.adservices.service.measurement.aggregation.AggregateReport;
94 import com.android.adservices.service.measurement.aggregation.AggregateReportFixture;
95 import com.android.adservices.service.measurement.noising.SourceNoiseHandler;
96 import com.android.adservices.service.measurement.registration.AsyncRegistration;
97 import com.android.adservices.service.measurement.reporting.AggregateDebugReportApi;
98 import com.android.adservices.service.measurement.reporting.DebugReport;
99 import com.android.adservices.service.measurement.reporting.EventReportWindowCalcDelegate;
100 import com.android.adservices.service.measurement.util.UnsignedLong;
101 import com.android.adservices.shared.errorlogging.AdServicesErrorLogger;
102 import com.android.dx.mockito.inline.extended.ExtendedMockito;
103 import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic;
104 
105 import com.google.common.collect.ImmutableList;
106 import com.google.common.collect.ImmutableMultiset;
107 import com.google.common.truth.Truth;
108 
109 import org.json.JSONException;
110 import org.junit.After;
111 import org.junit.Before;
112 import org.junit.Test;
113 import org.mockito.Mockito;
114 import org.mockito.internal.util.collections.Sets;
115 
116 import java.time.Instant;
117 import java.util.ArrayList;
118 import java.util.Arrays;
119 import java.util.Collection;
120 import java.util.Collections;
121 import java.util.Comparator;
122 import java.util.HashSet;
123 import java.util.List;
124 import java.util.Map;
125 import java.util.Objects;
126 import java.util.Optional;
127 import java.util.Set;
128 import java.util.UUID;
129 import java.util.concurrent.TimeUnit;
130 import java.util.concurrent.atomic.AtomicLong;
131 import java.util.function.Consumer;
132 import java.util.function.Function;
133 import java.util.stream.Collectors;
134 import java.util.stream.IntStream;
135 import java.util.stream.Stream;
136 
137 @SpyStatic(FlagsFactory.class)
138 @SpyStatic(MeasurementDbHelper.class)
139 public final class MeasurementDaoTest extends AdServicesExtendedMockitoTestCase {
140     private static final Uri APP_TWO_SOURCES = Uri.parse("android-app://com.example1.two-sources");
141     private static final Uri APP_ONE_SOURCE = Uri.parse("android-app://com.example2.one-source");
142     private static final String DEFAULT_ENROLLMENT_ID = "enrollment-id";
143     private static final String ENROLLMENT_ID1 = "enrollment-id1";
144     private static final Uri APP_TWO_PUBLISHER =
145             Uri.parse("android-app://com.publisher2.two-sources");
146     private static final Uri APP_ONE_PUBLISHER =
147             Uri.parse("android-app://com.publisher1.one-source");
148     private static final Uri APP_NO_PUBLISHER =
149             Uri.parse("android-app://com.publisher3.no-sources");
150     private static final Uri APP_BROWSER = Uri.parse("android-app://com.example1.browser");
151     private static final Uri WEB_ONE_DESTINATION = WebUtil.validUri("https://example1.test");
152     private static final Uri WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN =
153             WebUtil.validUri("https://store.example1.test");
154     private static final Uri WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2 =
155             WebUtil.validUri("https://foo.example1.test");
156     private static final Uri WEB_TWO_DESTINATION = WebUtil.validUri("https://example2.test");
157     private static final Uri WEB_THREE_DESTINATION = WebUtil.validUri("https://example3.test");
158     private static final Uri WEB_TWO_DESTINATION_WITH_PATH =
159             WebUtil.validUri("https://www.example2.test/ad/foo");
160     private static final Uri APP_ONE_DESTINATION =
161             Uri.parse("android-app://com.example1.one-trigger");
162     private static final Uri APP_TWO_DESTINATION =
163             Uri.parse("android-app://com.example1.two-triggers");
164     private static final Uri APP_THREE_DESTINATION =
165             Uri.parse("android-app://com.example1.three-triggers");
166     private static final Uri APP_THREE_DESTINATION_PATH1 =
167             Uri.parse("android-app://com.example1.three-triggers/path1");
168     private static final Uri APP_THREE_DESTINATION_PATH2 =
169             Uri.parse("android-app://com.example1.three-triggers/path2");
170     private static final Uri APP_NO_TRIGGERS = Uri.parse("android-app://com.example1.no-triggers");
171     private static final Uri INSTALLED_PACKAGE = Uri.parse("android-app://com.example.installed");
172     private static final Uri WEB_PUBLISHER_ONE = WebUtil.validUri("https://not.example.test");
173     private static final Uri WEB_PUBLISHER_TWO = WebUtil.validUri("https://notexample.test");
174     // Differs from WEB_PUBLISHER_ONE by scheme.
175     private static final Uri WEB_PUBLISHER_THREE = WebUtil.validUri("http://not.example.test");
176     private static final Uri APP_DESTINATION = Uri.parse("android-app://com.destination.example");
177     private static final Uri REGISTRATION_ORIGIN =
178             WebUtil.validUri("https://subdomain.example.test");
179 
180     private static final Uri REGISTRANT = Uri.parse("android-app://com.example.abc");
181     private static final Uri INSTALLED_REGISTRANT = Uri.parse("android-app://installed-registrant");
182     private static final Uri NOT_INSTALLED_REGISTRANT =
183             Uri.parse("android-app://not-installed-registrant");
184 
185     private static final long INSERTION_TIME = 1617297798;
186     private static final long COOLDOWN_WINDOW = TimeUnit.HOURS.toMillis(2);
187     private static final long ATTRIBUTION_SCOPE_LIMIT = 3L;
188     private static final long MAX_EVENT_STATES = 1000L;
189     private static final String REGISTRATION_ID2 = "R2";
190 
191     // Fake ID count for initializing triggers.
192     private int mValueId = 1;
193     // TODO(b/384798806): ideally it should use mFakeFlags, but this class mixes usage of fake and
194     // mock flags - some tests can only pass if mLegacyFlags is set to mMockFlags
195     private Flags mLegacyFlags = mFakeFlags;
196     private DatastoreManager mDatastoreManager;
197     public static final Uri REGISTRATION_ORIGIN_2 =
198             WebUtil.validUri("https://subdomain_2.example.test");
199     public static final Uri REGISTRATION_ORIGIN_3 =
200             WebUtil.validUri("https://subdomain_3.example.test");
201     public static final Uri REGISTRATION_ORIGIN_4 =
202             WebUtil.validUri("https://subdomain_4.example.test");
203     public static final Uri REGISTRATION_ORIGIN_5 =
204             WebUtil.validUri("https://subdomain_5.example.test");
205 
206     public static final Uri TOP_LEVEL_REGISTRANT_1 = Uri.parse("android-app://com.example1.sample");
207     public static final Uri TOP_LEVEL_REGISTRANT_2 = Uri.parse("android-app://com.example2.sample");
208 
209     @Before
before()210     public void before() {
211         mocker.mockGetFlags(mFakeFlags);
212         mDatastoreManager =
213                 new SQLDatastoreManager(
214                         MeasurementDbHelper.getInstance(),
215                         Mockito.mock(AdServicesErrorLogger.class));
216     }
217 
218     @After
cleanup()219     public void cleanup() {
220         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
221         for (String table : ALL_MSMT_TABLES) {
222             db.delete(table, null, null);
223         }
224     }
225 
226     @Test
testInsertSource()227     public void testInsertSource() {
228         Source validSource =
229                 SourceFixture.getValidSourceBuilder()
230                         .setEventReportWindows("{'start_time': 1, 'end_times': ['3600', '7200']}")
231                         .setStatus(Source.Status.MARKED_TO_DELETE)
232                         .setTriggerDataMatching(Source.TriggerDataMatching.EXACT)
233                         .setTriggerData(Set.of(new UnsignedLong(23L), new UnsignedLong(1L)))
234                         .build();
235         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
236 
237         String sourceId = getFirstSourceIdFromDatastore();
238         Source source =
239                 mDatastoreManager
240                         .runInTransactionWithResult(
241                                 measurementDao -> measurementDao.getSource(sourceId))
242                         .get();
243 
244         assertNotNull(source);
245         assertNotNull(source.getId());
246         assertNull(source.getAppDestinations());
247         assertNull(source.getWebDestinations());
248         assertEquals(validSource.getEnrollmentId(), source.getEnrollmentId());
249         assertEquals(validSource.getRegistrant(), source.getRegistrant());
250         assertEquals(validSource.getEventTime(), source.getEventTime());
251         assertEquals(validSource.getExpiryTime(), source.getExpiryTime());
252         assertEquals(validSource.getStatus(), source.getStatus());
253         assertEquals(validSource.getEventReportWindow(), source.getEventReportWindow());
254         assertEquals(
255                 validSource.getAggregatableReportWindow(), source.getAggregatableReportWindow());
256         assertEquals(validSource.getPriority(), source.getPriority());
257         assertEquals(validSource.getSourceType(), source.getSourceType());
258         assertEquals(
259                 validSource.getInstallAttributionWindow(), source.getInstallAttributionWindow());
260         assertEquals(validSource.getInstallCooldownWindow(), source.getInstallCooldownWindow());
261         assertEquals(validSource.getAttributionMode(), source.getAttributionMode());
262         assertEquals(
263                 validSource.getReinstallReattributionWindow(),
264                 source.getReinstallReattributionWindow());
265         assertEquals(validSource.getAggregateSource(), source.getAggregateSource());
266         assertEquals(validSource.getFilterDataString(), source.getFilterDataString());
267         assertEquals(validSource.getSharedFilterDataKeys(), source.getSharedFilterDataKeys());
268         assertEquals(validSource.getAggregateContributions(), source.getAggregateContributions());
269         assertEquals(validSource.isDebugReporting(), source.isDebugReporting());
270         assertEquals(validSource.getSharedAggregationKeys(), source.getSharedAggregationKeys());
271         assertEquals(validSource.getRegistrationId(), source.getRegistrationId());
272         assertEquals(validSource.getInstallTime(), source.getInstallTime());
273         assertEquals(validSource.getPlatformAdId(), source.getPlatformAdId());
274         assertEquals(validSource.getDebugAdId(), source.getDebugAdId());
275         assertEquals(validSource.getRegistrationOrigin(), source.getRegistrationOrigin());
276         assertEquals(
277                 validSource.hasCoarseEventReportDestinations(),
278                 source.hasCoarseEventReportDestinations());
279         assertEquals(validSource.getTriggerDataMatching(), source.getTriggerDataMatching());
280         assertEquals(validSource.getTriggerData(), source.getTriggerData());
281         assertEquals(validSource.getEventReportWindows(), source.getEventReportWindows());
282         assertEquals(SourceFixture.ValidSourceParams.SHARED_DEBUG_KEY, source.getSharedDebugKey());
283         assertEquals(0L, source.getDestinationLimitPriority());
284 
285         // Assert destinations were inserted into the source destination table.
286 
287         Pair<List<Uri>, List<Uri>> destinations =
288                 mDatastoreManager
289                         .runInTransactionWithResult(
290                                 measurementDao ->
291                                         measurementDao.getSourceDestinations(source.getId()))
292                         .get();
293         assertTrue(
294                 ImmutableMultiset.copyOf(validSource.getAppDestinations())
295                         .equals(ImmutableMultiset.copyOf(destinations.first)));
296         assertTrue(
297                 ImmutableMultiset.copyOf(validSource.getWebDestinations())
298                         .equals(ImmutableMultiset.copyOf(destinations.second)));
299     }
300 
301     @Test
testInsertSource_flexibleEventReport_equal()302     public void testInsertSource_flexibleEventReport_equal() throws Exception {
303         Source validSource = SourceFixture.getValidSourceWithFlexEventReport();
304         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
305 
306         String sourceId = getFirstSourceIdFromDatastore();
307         Source source =
308                 mDatastoreManager
309                         .runInTransactionWithResult(
310                                 measurementDao -> measurementDao.getSource(sourceId))
311                         .get();
312         source.buildTriggerSpecs();
313 
314         assertNotNull(source);
315         assertNotNull(source.getId());
316         assertNull(source.getAppDestinations());
317         assertNull(source.getWebDestinations());
318         assertEquals(validSource.getEnrollmentId(), source.getEnrollmentId());
319         assertEquals(validSource.getRegistrant(), source.getRegistrant());
320         assertEquals(validSource.getEventTime(), source.getEventTime());
321         assertEquals(validSource.getExpiryTime(), source.getExpiryTime());
322         assertEquals(validSource.getEventReportWindow(), source.getEventReportWindow());
323         assertEquals(
324                 validSource.getAggregatableReportWindow(), source.getAggregatableReportWindow());
325         assertEquals(validSource.getPriority(), source.getPriority());
326         assertEquals(validSource.getSourceType(), source.getSourceType());
327         assertEquals(
328                 validSource.getInstallAttributionWindow(), source.getInstallAttributionWindow());
329         assertEquals(validSource.getInstallCooldownWindow(), source.getInstallCooldownWindow());
330         assertEquals(validSource.getAttributionMode(), source.getAttributionMode());
331         assertEquals(validSource.getAggregateSource(), source.getAggregateSource());
332         assertEquals(validSource.getFilterDataString(), source.getFilterDataString());
333         assertEquals(validSource.getAggregateContributions(), source.getAggregateContributions());
334         assertEquals(validSource.isDebugReporting(), source.isDebugReporting());
335         assertEquals(validSource.getSharedAggregationKeys(), source.getSharedAggregationKeys());
336         assertEquals(validSource.getRegistrationId(), source.getRegistrationId());
337         assertEquals(validSource.getInstallTime(), source.getInstallTime());
338         assertEquals(validSource.getPlatformAdId(), source.getPlatformAdId());
339         assertEquals(validSource.getDebugAdId(), source.getDebugAdId());
340         assertEquals(validSource.getRegistrationOrigin(), source.getRegistrationOrigin());
341         assertEquals(
342                 validSource.getTriggerSpecs().getMaxReports(),
343                 source.getMaxEventLevelReports().intValue());
344         assertEquals(
345                 validSource.getTriggerSpecs().encodeToJson(),
346                 source.getTriggerSpecsString());
347         assertNull(source.getEventAttributionStatus());
348         assertEquals(
349                 validSource.getTriggerSpecs().encodePrivacyParametersToJsonString(),
350                 source.getPrivacyParameters());
351         assertEquals(validSource.getTriggerSpecs(), source.getTriggerSpecs());
352 
353         // Assert destinations were inserted into the source destination table.
354         Pair<List<Uri>, List<Uri>> destinations =
355                 mDatastoreManager
356                         .runInTransactionWithResult(
357                                 measurementDao ->
358                                         measurementDao.getSourceDestinations(source.getId()))
359                         .get();
360         assertTrue(
361                 ImmutableMultiset.copyOf(validSource.getAppDestinations())
362                         .equals(ImmutableMultiset.copyOf(destinations.first)));
363         assertTrue(
364                 ImmutableMultiset.copyOf(validSource.getWebDestinations())
365                         .equals(ImmutableMultiset.copyOf(destinations.second)));
366     }
367 
368     @Test
testInsertSource_reachedDbSizeLimitOnEdgeCase_doNotInsert()369     public void testInsertSource_reachedDbSizeLimitOnEdgeCase_doNotInsert() {
370         insertSourceReachingDbSizeLimit(/* dbSize= */ 100L, /* dbSizeMaxLimit= */ 100L);
371     }
372 
373     @Test
testInsertSource_reachedDbSizeLimitUpperEdgeCase_doNotInsert()374     public void testInsertSource_reachedDbSizeLimitUpperEdgeCase_doNotInsert() {
375         insertSourceReachingDbSizeLimit(/* dbSize= */ 101L, /* dbSizeMaxLimit= */ 100L);
376     }
377 
378     @Test
testInsertSource_attributionScopeEnabled_success()379     public void testInsertSource_attributionScopeEnabled_success() {
380         mLegacyFlags = mMockFlags;
381         mocker.mockGetFlags(mMockFlags);
382         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
383         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
384 
385         Source validSource =
386                 insertSourceForAttributionScope(
387                         List.of("1", "2", "3"),
388                         ATTRIBUTION_SCOPE_LIMIT,
389                         MAX_EVENT_STATES,
390                         SOURCE_EVENT_TIME,
391                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
392                         List.of(APP_ONE_DESTINATION));
393         Source source =
394                 mDatastoreManager
395                         .runInTransactionWithResult(
396                                 measurementDao -> measurementDao.getSource(validSource.getId()))
397                         .get();
398         assertThat(source.getAttributionScopeLimit())
399                 .isEqualTo(validSource.getAttributionScopeLimit());
400         assertThat(source.getMaxEventStates()).isEqualTo(validSource.getMaxEventStates());
401         List<String> attributionScopes =
402                 mDatastoreManager
403                         .runInTransactionWithResult(
404                                 measurementDao ->
405                                         measurementDao.getSourceAttributionScopes(source.getId()))
406                         .get();
407         assertThat(attributionScopes).containsExactlyElementsIn(validSource.getAttributionScopes());
408     }
409 
410     @Test
411     public void
testInsertSource_attributionScopeDisabled_doesNotInsertAttributionScopeRelatedData()412             testInsertSource_attributionScopeDisabled_doesNotInsertAttributionScopeRelatedData() {
413         mocker.mockGetFlags(mMockFlags);
414         doReturn(false).when(mMockFlags).getMeasurementEnableAttributionScope();
415         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
416 
417         Source validSource =
418                 insertSourceForAttributionScope(
419                         List.of("1", "2", "3"),
420                         ATTRIBUTION_SCOPE_LIMIT,
421                         MAX_EVENT_STATES,
422                         SOURCE_EVENT_TIME,
423                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
424                         List.of(APP_ONE_DESTINATION));
425         Source source =
426                 mDatastoreManager
427                         .runInTransactionWithResult(
428                                 measurementDao -> measurementDao.getSource(validSource.getId()))
429                         .get();
430 
431         assertThat(source.getAttributionScopeLimit()).isEqualTo(null);
432         assertThat(source.getMaxEventStates()).isEqualTo(null);
433         List<String> attributionScopes =
434                 mDatastoreManager
435                         .runInTransactionWithResult(
436                                 measurementDao ->
437                                         measurementDao.getSourceAttributionScopes(source.getId()))
438                         .get();
439         assertThat(attributionScopes).isEmpty();
440     }
441 
442     @Test
testInsertSource_aggregateDebugReportingEnabled_success()443     public void testInsertSource_aggregateDebugReportingEnabled_success() {
444         mocker.mockGetFlags(mMockFlags);
445         doReturn(true).when(mMockFlags).getMeasurementEnableAggregateDebugReporting();
446         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
447 
448         Source validSource =
449                 SourceFixture.getValidSourceBuilder()
450                         .setEventReportWindows("{'start_time': 1, 'end_times': ['3600', '7200']}")
451                         .setStatus(Source.Status.MARKED_TO_DELETE)
452                         .setTriggerDataMatching(Source.TriggerDataMatching.EXACT)
453                         .setTriggerData(Set.of(new UnsignedLong(23L), new UnsignedLong(1L)))
454                         .build();
455         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
456 
457         String sourceId = getFirstSourceIdFromDatastore();
458         Source source =
459                 mDatastoreManager
460                         .runInTransactionWithResult(
461                                 measurementDao -> measurementDao.getSource(sourceId))
462                         .get();
463 
464         assertThat(source).isNotNull();
465         assertThat(source.getId()).isNotNull();
466         assertThat(source.getAppDestinations()).isNull();
467         assertThat(source.getWebDestinations()).isNull();
468         assertThat(validSource.getEnrollmentId()).isEqualTo(source.getEnrollmentId());
469         assertThat(validSource.getRegistrant()).isEqualTo(source.getRegistrant());
470         assertThat(validSource.getEventTime()).isEqualTo(source.getEventTime());
471         assertThat(validSource.getExpiryTime()).isEqualTo(source.getExpiryTime());
472         assertThat(validSource.getStatus()).isEqualTo(source.getStatus());
473         assertThat(validSource.getEventReportWindow()).isEqualTo(source.getEventReportWindow());
474         assertThat(validSource.getAggregatableReportWindow())
475                 .isEqualTo(source.getAggregatableReportWindow());
476         assertThat(validSource.getPriority()).isEqualTo(source.getPriority());
477         assertThat(validSource.getSourceType()).isEqualTo(source.getSourceType());
478         assertThat(validSource.getInstallAttributionWindow())
479                 .isEqualTo(source.getInstallAttributionWindow());
480         assertThat(validSource.getInstallCooldownWindow())
481                 .isEqualTo(source.getInstallCooldownWindow());
482         assertThat(validSource.getAttributionMode()).isEqualTo(source.getAttributionMode());
483         assertThat(validSource.getReinstallReattributionWindow())
484                 .isEqualTo(source.getReinstallReattributionWindow());
485         assertThat(validSource.getAggregateSource()).isEqualTo(source.getAggregateSource());
486         assertThat(validSource.getFilterDataString()).isEqualTo(source.getFilterDataString());
487         assertThat(validSource.getSharedFilterDataKeys())
488                 .isEqualTo(source.getSharedFilterDataKeys());
489         assertThat(validSource.getAggregateContributions())
490                 .isEqualTo(source.getAggregateContributions());
491         assertThat(validSource.isDebugReporting()).isEqualTo(source.isDebugReporting());
492         assertThat(validSource.getSharedAggregationKeys())
493                 .isEqualTo(source.getSharedAggregationKeys());
494         assertThat(validSource.getRegistrationId()).isEqualTo(source.getRegistrationId());
495         assertThat(validSource.getInstallTime()).isEqualTo(source.getInstallTime());
496         assertThat(validSource.getPlatformAdId()).isEqualTo(source.getPlatformAdId());
497         assertThat(validSource.getDebugAdId()).isEqualTo(source.getDebugAdId());
498         assertThat(validSource.getRegistrationOrigin()).isEqualTo(source.getRegistrationOrigin());
499         assertThat(validSource.hasCoarseEventReportDestinations())
500                 .isEqualTo(source.hasCoarseEventReportDestinations());
501         assertThat(validSource.getTriggerDataMatching()).isEqualTo(source.getTriggerDataMatching());
502         assertThat(validSource.getTriggerData()).isEqualTo(source.getTriggerData());
503         assertThat(validSource.getEventReportWindows()).isEqualTo(source.getEventReportWindows());
504         assertThat(SourceFixture.ValidSourceParams.SHARED_DEBUG_KEY)
505                 .isEqualTo(source.getSharedDebugKey());
506         assertThat(source.getDestinationLimitPriority()).isEqualTo(0L);
507         assertThat(validSource.getAggregateDebugReportingString())
508                 .isEqualTo(source.getAggregateDebugReportingString());
509         assertThat(validSource.getAggregateDebugReportContributions())
510                 .isEqualTo(source.getAggregateDebugReportContributions());
511 
512         // Assert destinations were inserted into the source destination table.
513 
514         Pair<List<Uri>, List<Uri>> destinations =
515                 mDatastoreManager
516                         .runInTransactionWithResult(
517                                 measurementDao ->
518                                         measurementDao.getSourceDestinations(source.getId()))
519                         .get();
520         assertThat(
521                         ImmutableMultiset.copyOf(validSource.getAppDestinations())
522                                 .equals(ImmutableMultiset.copyOf(destinations.first)))
523                 .isTrue();
524         assertThat(
525                         ImmutableMultiset.copyOf(validSource.getWebDestinations())
526                                 .equals(ImmutableMultiset.copyOf(destinations.second)))
527                 .isTrue();
528     }
529 
530     @Test
testInsertSource_aggregateDebugReportingDisabled_relatedDataNotInserted()531     public void testInsertSource_aggregateDebugReportingDisabled_relatedDataNotInserted() {
532         mocker.mockGetFlags(mMockFlags);
533         doReturn(false).when(mMockFlags).getMeasurementEnableAggregateDebugReporting();
534         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
535 
536         Source validSource =
537                 SourceFixture.getValidSourceBuilder()
538                         .setEventReportWindows("{'start_time': 1, 'end_times': ['3600', '7200']}")
539                         .setStatus(Source.Status.MARKED_TO_DELETE)
540                         .setTriggerDataMatching(Source.TriggerDataMatching.EXACT)
541                         .setTriggerData(Set.of(new UnsignedLong(23L), new UnsignedLong(1L)))
542                         .build();
543         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
544 
545         String sourceId = getFirstSourceIdFromDatastore();
546         Source source =
547                 mDatastoreManager
548                         .runInTransactionWithResult(
549                                 measurementDao -> measurementDao.getSource(sourceId))
550                         .get();
551 
552         assertThat(source).isNotNull();
553         assertThat(source.getId()).isNotNull();
554         assertThat(source.getAppDestinations()).isNull();
555         assertThat(source.getWebDestinations()).isNull();
556         assertThat(validSource.getEnrollmentId()).isEqualTo(source.getEnrollmentId());
557         assertThat(validSource.getRegistrant()).isEqualTo(source.getRegistrant());
558         assertThat(validSource.getEventTime()).isEqualTo(source.getEventTime());
559         assertThat(validSource.getExpiryTime()).isEqualTo(source.getExpiryTime());
560         assertThat(validSource.getStatus()).isEqualTo(source.getStatus());
561         assertThat(validSource.getEventReportWindow()).isEqualTo(source.getEventReportWindow());
562         assertThat(validSource.getAggregatableReportWindow())
563                 .isEqualTo(source.getAggregatableReportWindow());
564         assertThat(validSource.getPriority()).isEqualTo(source.getPriority());
565         assertThat(validSource.getSourceType()).isEqualTo(source.getSourceType());
566         assertThat(validSource.getInstallAttributionWindow())
567                 .isEqualTo(source.getInstallAttributionWindow());
568         assertThat(validSource.getInstallCooldownWindow())
569                 .isEqualTo(source.getInstallCooldownWindow());
570         assertThat(validSource.getAttributionMode()).isEqualTo(source.getAttributionMode());
571         assertThat(validSource.getReinstallReattributionWindow())
572                 .isEqualTo(source.getReinstallReattributionWindow());
573         assertThat(validSource.getAggregateSource()).isEqualTo(source.getAggregateSource());
574         assertThat(validSource.getFilterDataString()).isEqualTo(source.getFilterDataString());
575         assertThat(validSource.getSharedFilterDataKeys())
576                 .isEqualTo(source.getSharedFilterDataKeys());
577         assertThat(validSource.getAggregateContributions())
578                 .isEqualTo(source.getAggregateContributions());
579         assertThat(validSource.isDebugReporting()).isEqualTo(source.isDebugReporting());
580         assertThat(validSource.getSharedAggregationKeys())
581                 .isEqualTo(source.getSharedAggregationKeys());
582         assertThat(validSource.getRegistrationId()).isEqualTo(source.getRegistrationId());
583         assertThat(validSource.getInstallTime()).isEqualTo(source.getInstallTime());
584         assertThat(validSource.getPlatformAdId()).isEqualTo(source.getPlatformAdId());
585         assertThat(validSource.getDebugAdId()).isEqualTo(source.getDebugAdId());
586         assertThat(validSource.getRegistrationOrigin()).isEqualTo(source.getRegistrationOrigin());
587         assertThat(validSource.hasCoarseEventReportDestinations())
588                 .isEqualTo(source.hasCoarseEventReportDestinations());
589         assertThat(validSource.getTriggerDataMatching()).isEqualTo(source.getTriggerDataMatching());
590         assertThat(validSource.getTriggerData()).isEqualTo(source.getTriggerData());
591         assertThat(validSource.getEventReportWindows()).isEqualTo(source.getEventReportWindows());
592         assertThat(SourceFixture.ValidSourceParams.SHARED_DEBUG_KEY)
593                 .isEqualTo(source.getSharedDebugKey());
594         assertThat(source.getDestinationLimitPriority()).isEqualTo(0L);
595         assertThat(source.getAggregateDebugReportingString()).isEqualTo(null);
596         assertThat(source.getAggregateDebugReportContributions()).isEqualTo(0L);
597 
598         // Assert destinations were inserted into the source destination table.
599 
600         Pair<List<Uri>, List<Uri>> destinations =
601                 mDatastoreManager
602                         .runInTransactionWithResult(
603                                 measurementDao ->
604                                         measurementDao.getSourceDestinations(source.getId()))
605                         .get();
606         assertThat(
607                         ImmutableMultiset.copyOf(validSource.getAppDestinations())
608                                 .equals(ImmutableMultiset.copyOf(destinations.first)))
609                 .isTrue();
610         assertThat(
611                         ImmutableMultiset.copyOf(validSource.getWebDestinations())
612                                 .equals(ImmutableMultiset.copyOf(destinations.second)))
613                 .isTrue();
614     }
615 
616     @Test
testInsertSource_aggregatableNamedBudgetsEnabled_success()617     public void testInsertSource_aggregatableNamedBudgetsEnabled_success() {
618         mocker.mockGetFlags(mMockFlags);
619         when(mMockFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(true);
620         when(mMockFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
621 
622         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
623         aggregatableNamedBudgets.createContributionBudget("budget1", 400);
624         aggregatableNamedBudgets.setContribution("budget1", 150);
625         aggregatableNamedBudgets.createContributionBudget("budget2", 750);
626         aggregatableNamedBudgets.setContribution("budget2", 320);
627         Source validSource =
628                 insertSourceForAggregatableNamedBudgets(
629                         aggregatableNamedBudgets,
630                         SOURCE_EVENT_TIME,
631                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
632                         List.of(APP_ONE_DESTINATION));
633 
634         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution1 =
635                 mDatastoreManager
636                         .runInTransactionWithResult(
637                                 measurementDao ->
638                                         measurementDao
639                                                 .getSourceAggregatableNamedBudgetAndContribution(
640                                                         validSource.getId(), "budget1"))
641                         .get();
642         assertThat(daoBudgetAndContribution1.getAggregateContribution())
643                 .isEqualTo(
644                         validSource
645                                 .getAggregatableNamedBudgets()
646                                 .maybeGetContribution("budget1")
647                                 .get());
648         assertThat(daoBudgetAndContribution1.getBudget())
649                 .isEqualTo(
650                         validSource.getAggregatableNamedBudgets().maybeGetBudget("budget1").get());
651         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution2 =
652                 mDatastoreManager
653                         .runInTransactionWithResult(
654                                 measurementDao ->
655                                         measurementDao
656                                                 .getSourceAggregatableNamedBudgetAndContribution(
657                                                         validSource.getId(), "budget2"))
658                         .get();
659         assertThat(daoBudgetAndContribution2.getAggregateContribution())
660                 .isEqualTo(
661                         validSource
662                                 .getAggregatableNamedBudgets()
663                                 .maybeGetContribution("budget2")
664                                 .get());
665         assertThat(daoBudgetAndContribution2.getBudget())
666                 .isEqualTo(
667                         validSource.getAggregatableNamedBudgets().maybeGetBudget("budget2").get());
668     }
669 
670     @Test
testInsertSource_aggregatableNamedBudgetsDisabled_relatedDataNotInserted()671     public void testInsertSource_aggregatableNamedBudgetsDisabled_relatedDataNotInserted() {
672         mocker.mockGetFlags(mMockFlags);
673         when(mMockFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(false);
674         when(mMockFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
675 
676         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
677         aggregatableNamedBudgets.createContributionBudget("budget3", 470);
678         aggregatableNamedBudgets.setContribution("budget3", 153);
679         Source validSource =
680                 insertSourceForAggregatableNamedBudgets(
681                         aggregatableNamedBudgets,
682                         SOURCE_EVENT_TIME,
683                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
684                         List.of(APP_ONE_DESTINATION));
685 
686         Optional<AggregatableNamedBudgets.BudgetAndContribution> daoBudgetAndContribution =
687                 mDatastoreManager.runInTransactionWithResult(
688                         measurementDao ->
689                                 measurementDao.getSourceAggregatableNamedBudgetAndContribution(
690                                         validSource.getId(), "budget3"));
691         assertThat(daoBudgetAndContribution).isEmpty();
692     }
693 
insertSourceReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit)694     private void insertSourceReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit) {
695         final Source validSource = SourceFixture.getValidSource();
696 
697         // Mocking that the DB file has a size of 100 bytes
698         final MeasurementDbHelper spyMeasurementDbHelper = spy(MeasurementDbHelper.getInstance());
699         ExtendedMockito.doReturn(spyMeasurementDbHelper).when(MeasurementDbHelper::getInstance);
700         ExtendedMockito.doReturn(dbSize).when(spyMeasurementDbHelper).getDbFileSize();
701 
702         // Mocking that the flags return a max limit size of 100 bytes
703         Flags mockFlags = Mockito.mock(Flags.class);
704         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
705         ExtendedMockito.doReturn(dbSizeMaxLimit).when(mockFlags).getMeasurementDbSizeLimit();
706 
707         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
708 
709         try (Cursor sourceCursor =
710                 MeasurementDbHelper.getInstance()
711                         .getReadableDatabase()
712                         .query(SourceContract.TABLE, null, null, null, null, null, null)) {
713             assertFalse(sourceCursor.moveToNext());
714         }
715     }
716 
717     @Test
testInsertTrigger()718     public void testInsertTrigger() {
719         Trigger validTrigger = TriggerFixture.getValidTrigger();
720         mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger));
721 
722         try (Cursor triggerCursor =
723                 MeasurementDbHelper.getInstance()
724                         .getReadableDatabase()
725                         .query(TriggerContract.TABLE, null, null, null, null, null, null)) {
726             assertTrue(triggerCursor.moveToNext());
727             Trigger trigger = SqliteObjectMapper.constructTriggerFromCursor(triggerCursor);
728             assertNotNull(trigger);
729             assertNotNull(trigger.getId());
730             assertEquals(
731                     validTrigger.getAttributionDestination(), trigger.getAttributionDestination());
732             assertEquals(validTrigger.getDestinationType(), trigger.getDestinationType());
733             assertEquals(validTrigger.getEnrollmentId(), trigger.getEnrollmentId());
734             assertEquals(validTrigger.getRegistrant(), trigger.getRegistrant());
735             assertEquals(validTrigger.getTriggerTime(), trigger.getTriggerTime());
736             assertEquals(validTrigger.getEventTriggers(), trigger.getEventTriggers());
737             assertEquals(validTrigger.getAttributionConfig(), trigger.getAttributionConfig());
738             assertEquals(validTrigger.getAdtechKeyMapping(), trigger.getAdtechKeyMapping());
739             assertEquals(validTrigger.getPlatformAdId(), trigger.getPlatformAdId());
740             assertEquals(validTrigger.getDebugAdId(), trigger.getDebugAdId());
741             assertEquals(validTrigger.getRegistrationOrigin(), trigger.getRegistrationOrigin());
742             assertEquals(
743                     validTrigger.getAggregationCoordinatorOrigin(),
744                     trigger.getAggregationCoordinatorOrigin());
745             assertEquals(
746                     validTrigger.getAggregatableSourceRegistrationTimeConfig(),
747                     trigger.getAggregatableSourceRegistrationTimeConfig());
748             assertEquals(validTrigger.getTriggerContextId(), trigger.getTriggerContextId());
749             assertEquals(
750                     validTrigger.getAttributionScopesString(),
751                     trigger.getAttributionScopesString());
752             assertEquals(
753                     validTrigger.getAggregatableFilteringIdMaxBytes(),
754                     trigger.getAggregatableFilteringIdMaxBytes());
755             assertThat(validTrigger.getAggregateDebugReportingString())
756                     .isEqualTo(trigger.getAggregateDebugReportingString());
757         }
758     }
759 
760     @Test
testInsertTrigger_reachedDbSizeLimitOnEdgeCase_doNotInsert()761     public void testInsertTrigger_reachedDbSizeLimitOnEdgeCase_doNotInsert() {
762         insertTriggerReachingDbSizeLimit(/* dbSize= */ 100L, /* dbSizeMaxLimit= */ 100L);
763     }
764 
765     @Test
testInsertTrigger_reachedDbSizeLimitUpperEdgeCase_doNotInsert()766     public void testInsertTrigger_reachedDbSizeLimitUpperEdgeCase_doNotInsert() {
767         insertTriggerReachingDbSizeLimit(/* dbSize= */ 101L, /* dbSizeMaxLimit= */ 100L);
768     }
769 
insertTriggerReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit)770     private void insertTriggerReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit) {
771         final Trigger validTrigger = TriggerFixture.getValidTrigger();
772 
773         // Mocking that the DB file has a size of 100 bytes
774         final MeasurementDbHelper spyMeasurementDbHelper = spy(MeasurementDbHelper.getInstance());
775         ExtendedMockito.doReturn(spyMeasurementDbHelper).when(MeasurementDbHelper::getInstance);
776         ExtendedMockito.doReturn(dbSize).when(spyMeasurementDbHelper).getDbFileSize();
777 
778         // Mocking that the flags return a max limit size of 100 bytes
779         Flags mockFlags = Mockito.mock(Flags.class);
780         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
781         ExtendedMockito.doReturn(dbSizeMaxLimit).when(mockFlags).getMeasurementDbSizeLimit();
782 
783         mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger));
784 
785         try (Cursor sourceCursor =
786                 MeasurementDbHelper.getInstance()
787                         .getReadableDatabase()
788                         .query(TriggerContract.TABLE, null, null, null, null, null, null)) {
789             assertFalse(sourceCursor.moveToNext());
790         }
791     }
792 
793     @Test
testGetNumSourcesPerPublisher_publisherTypeApp()794     public void testGetNumSourcesPerPublisher_publisherTypeApp() {
795         setupSourceAndTriggerData();
796         mDatastoreManager.runInTransaction(
797                 measurementDao -> {
798                     assertEquals(
799                             2,
800                             measurementDao.getNumSourcesPerPublisher(
801                                     APP_TWO_PUBLISHER, EventSurfaceType.APP));
802                 });
803         mDatastoreManager.runInTransaction(
804                 measurementDao -> {
805                     assertEquals(
806                             1,
807                             measurementDao.getNumSourcesPerPublisher(
808                                     APP_ONE_PUBLISHER, EventSurfaceType.APP));
809                 });
810         mDatastoreManager.runInTransaction(
811                 measurementDao -> {
812                     assertEquals(
813                             0,
814                             measurementDao.getNumSourcesPerPublisher(
815                                     APP_NO_PUBLISHER, EventSurfaceType.APP));
816                 });
817     }
818 
819     @Test
testGetNumSourcesPerPublisher_publisherTypeWeb()820     public void testGetNumSourcesPerPublisher_publisherTypeWeb() {
821         setupSourceDataForPublisherTypeWeb();
822         mDatastoreManager.runInTransaction(
823                 measurementDao -> {
824                     assertEquals(
825                             1,
826                             measurementDao.getNumSourcesPerPublisher(
827                                     WEB_PUBLISHER_ONE, EventSurfaceType.WEB));
828                 });
829         mDatastoreManager.runInTransaction(
830                 measurementDao -> {
831                     assertEquals(
832                             2,
833                             measurementDao.getNumSourcesPerPublisher(
834                                     WEB_PUBLISHER_TWO, EventSurfaceType.WEB));
835                 });
836         mDatastoreManager.runInTransaction(
837                 measurementDao -> {
838                     assertEquals(
839                             1,
840                             measurementDao.getNumSourcesPerPublisher(
841                                     WEB_PUBLISHER_THREE, EventSurfaceType.WEB));
842                 });
843     }
844 
845     @Test
testGetUninstalledAppNamesContainingData_withDataInSource()846     public void testGetUninstalledAppNamesContainingData_withDataInSource() {
847         // Setup records in source storage
848         final List<Uri> appsInstalled =
849                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
850         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
851         insertSourceForPackageName(
852                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
853                         .toArray(Uri[]::new));
854 
855         // Execution
856         final Optional<List<Uri>> uninstalledAppNames =
857                 mDatastoreManager.runInTransactionWithResult(
858                         measurementDao ->
859                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
860                                         appsInstalled));
861 
862         // Validation, apps not installed should be returned
863         assertNotNull(uninstalledAppNames);
864         assertTrue(uninstalledAppNames.isPresent());
865         assertEquals(2, uninstalledAppNames.get().size());
866         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
867         assertTrue(result.contains(buildRegistrant("qux")));
868         assertTrue(result.contains(buildRegistrant("quux")));
869     }
870 
871     @Test
testGetUninstalledAppNamesContainingData_withDataInTrigger()872     public void testGetUninstalledAppNamesContainingData_withDataInTrigger() {
873         // Setup records in trigger storage
874         final List<Uri> appsInstalled =
875                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
876         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
877         insertTriggerForPackageName(
878                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
879                         .toArray(Uri[]::new));
880 
881         // Execution
882         final Optional<List<Uri>> uninstalledAppNames =
883                 mDatastoreManager.runInTransactionWithResult(
884                         measurementDao ->
885                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
886                                         appsInstalled));
887 
888         // Validation, apps not installed should be returned
889         assertNotNull(uninstalledAppNames);
890         assertTrue(uninstalledAppNames.isPresent());
891         assertEquals(2, uninstalledAppNames.get().size());
892         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
893         assertTrue(result.contains(buildRegistrant("qux")));
894         assertTrue(result.contains(buildRegistrant("quux")));
895     }
896 
897     @Test
testGetUninstalledAppNamesContainingData_withDataInTriggerAndSource()898     public void testGetUninstalledAppNamesContainingData_withDataInTriggerAndSource() {
899         // Setup records in source and trigger storage
900         final List<Uri> appsInstalled =
901                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
902         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
903         final Uri[] appNames =
904                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
905                         .toArray(Uri[]::new);
906         insertSourceForPackageName(appNames);
907         insertTriggerForPackageName(appNames);
908 
909         // Execution
910         final Optional<List<Uri>> uninstalledAppNames =
911                 mDatastoreManager.runInTransactionWithResult(
912                         measurementDao ->
913                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
914                                         appsInstalled));
915 
916         // Validation, apps not installed should be returned
917         assertNotNull(uninstalledAppNames);
918         assertTrue(uninstalledAppNames.isPresent());
919         assertEquals(2, uninstalledAppNames.get().size());
920         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
921         assertTrue(result.contains(buildRegistrant("qux")));
922         assertTrue(result.contains(buildRegistrant("quux")));
923     }
924 
925     @Test
testGetUninstalledAppNamesContainingData_withDataInAsync()926     public void testGetUninstalledAppNamesContainingData_withDataInAsync() {
927         // Setup records in source storage
928         final List<Uri> appsInstalled =
929                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
930         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
931         insertAsyncRecordForPackageName(
932                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
933                         .toArray(Uri[]::new));
934 
935         // Execution
936         final Optional<List<Uri>> uninstalledAppNames =
937                 mDatastoreManager.runInTransactionWithResult(
938                         measurementDao ->
939                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
940                                         appsInstalled));
941 
942         // Validation, apps not installed should be returned
943         assertNotNull(uninstalledAppNames);
944         assertTrue(uninstalledAppNames.isPresent());
945         assertEquals(2, uninstalledAppNames.get().size());
946         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
947         assertTrue(result.contains(buildRegistrant("qux")));
948         assertTrue(result.contains(buildRegistrant("quux")));
949     }
950 
951     @Test
testGetUninstalledAppNamesContainingData_withAnyDataAndNoAppsUninstalled()952     public void testGetUninstalledAppNamesContainingData_withAnyDataAndNoAppsUninstalled() {
953         // Setup records in source storage (any storage), all these apps are still installed
954         final List<Uri> appsInstalled =
955                 List.of(
956                         buildRegistrant("foo"),
957                         buildRegistrant("bar"),
958                         buildRegistrant("baz"),
959                         buildRegistrant("qux"),
960                         buildRegistrant("quux"));
961         insertSourceForPackageName(appsInstalled.stream().toArray(Uri[]::new));
962 
963         // Execution
964         final Optional<List<Uri>> uninstalledAppNames =
965                 mDatastoreManager.runInTransactionWithResult(
966                         measurementDao ->
967                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
968                                         appsInstalled));
969 
970         // Validation, apps not installed should be returned
971         assertNotNull(uninstalledAppNames);
972         assertTrue(uninstalledAppNames.isPresent());
973         assertTrue(uninstalledAppNames.get().isEmpty());
974         assertEquals(0, uninstalledAppNames.get().size());
975     }
976 
977     @Test
testGetUninstalledAppNamesContainingData_withNoData()978     public void testGetUninstalledAppNamesContainingData_withNoData() {
979         // Setup records, these apps don't have source nor trigger data
980         final List<Uri> appsInstalled =
981                 List.of(
982                         buildRegistrant("foo"),
983                         buildRegistrant("bar"),
984                         buildRegistrant("baz"),
985                         buildRegistrant("qux"),
986                         buildRegistrant("quux"));
987 
988         // Execution
989         final Optional<List<Uri>> uninstalledAppNames =
990                 mDatastoreManager.runInTransactionWithResult(
991                         measurementDao ->
992                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
993                                         appsInstalled));
994 
995         // Validation, apps not installed should be returned
996         assertNotNull(uninstalledAppNames);
997         assertTrue(uninstalledAppNames.isPresent());
998         assertTrue(uninstalledAppNames.get().isEmpty());
999         assertEquals(0, uninstalledAppNames.get().size());
1000     }
1001 
1002     @Test
testCountDistinctReportingOriginPerPublisherXDestinationInAttribution_atWindow()1003     public void testCountDistinctReportingOriginPerPublisherXDestinationInAttribution_atWindow() {
1004         Uri sourceSite = Uri.parse("android-app://publisher.app");
1005         Uri appDestination = Uri.parse("android-app://destination.app");
1006         String registrant = "android-app://registrant.app";
1007         List<Attribution> attributionsWithAppDestinations =
1008                 getAttributionsWithDifferentReportingOrigins(
1009                         4, appDestination, 5000000001L, sourceSite, registrant);
1010         for (Attribution attribution : attributionsWithAppDestinations) {
1011             insertAttribution(attribution);
1012         }
1013         Uri excludedRegistrationOrigin = WebUtil.validUri("https://subdomain0.example.test");
1014         mDatastoreManager.runInTransaction(
1015                 measurementDao -> {
1016                     assertEquals(
1017                             Integer.valueOf(3),
1018                             measurementDao
1019                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1020                                             sourceSite,
1021                                             appDestination,
1022                                             excludedRegistrationOrigin,
1023                                             5000000000L,
1024                                             6000000000L));
1025                 });
1026     }
1027 
1028     @Test
testCountDistinctReportingOriginsPerPublisherXDestInAttribution_beyondWindow()1029     public void testCountDistinctReportingOriginsPerPublisherXDestInAttribution_beyondWindow() {
1030         Uri sourceSite = Uri.parse("android-app://publisher.app");
1031         Uri appDestination = Uri.parse("android-app://destination.app");
1032         String registrant = "android-app://registrant.app";
1033         List<Attribution> attributionsWithAppDestinations =
1034                 getAttributionsWithDifferentReportingOrigins(
1035                         4, appDestination, 5000000000L, sourceSite, registrant);
1036         for (Attribution attribution : attributionsWithAppDestinations) {
1037             insertAttribution(attribution);
1038         }
1039         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain.example0.test");
1040         mDatastoreManager.runInTransaction(
1041                 measurementDao -> {
1042                     assertEquals(
1043                             Integer.valueOf(0),
1044                             measurementDao
1045                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1046                                             sourceSite,
1047                                             appDestination,
1048                                             excludedReportingOrigin,
1049                                             5000000000L,
1050                                             6000000000L));
1051                 });
1052     }
1053 
1054     @Test
testInsertDebugReport()1055     public void testInsertDebugReport() {
1056         DebugReport debugReport = createDebugReport();
1057         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport));
1058 
1059         try (Cursor cursor =
1060                 MeasurementDbHelper.getInstance()
1061                         .getReadableDatabase()
1062                         .query(DebugReportContract.TABLE, null, null, null, null, null, null)) {
1063             assertTrue(cursor.moveToNext());
1064             DebugReport report = SqliteObjectMapper.constructDebugReportFromCursor(cursor);
1065             assertNotNull(report);
1066             assertNotNull(report.getId());
1067             assertEquals(debugReport.getType(), report.getType());
1068             assertEquals(debugReport.getBody().toString(), report.getBody().toString());
1069             assertEquals(debugReport.getEnrollmentId(), report.getEnrollmentId());
1070             assertEquals(debugReport.getRegistrationOrigin(), report.getRegistrationOrigin());
1071             assertEquals(debugReport.getReferenceId(), report.getReferenceId());
1072             assertEquals(debugReport.getInsertionTime(), report.getInsertionTime());
1073             assertEquals(debugReport.getRegistrant(), report.getRegistrant());
1074         }
1075     }
1076 
1077     @Test
singleAppTrigger_triggersPerDestination_returnsOne()1078     public void singleAppTrigger_triggersPerDestination_returnsOne() {
1079         List<Trigger> triggerList = new ArrayList<>();
1080         triggerList.add(createAppTrigger(APP_ONE_DESTINATION, APP_ONE_DESTINATION));
1081         addTriggersToDatabase(triggerList);
1082 
1083         mDatastoreManager.runInTransaction(
1084                 measurementDao ->
1085                         assertThat(
1086                                         measurementDao.getNumTriggersPerDestination(
1087                                                 APP_ONE_DESTINATION, EventSurfaceType.APP))
1088                                 .isEqualTo(1));
1089     }
1090 
1091     @Test
multipleAppTriggers_similarUris_triggersPerDestination()1092     public void multipleAppTriggers_similarUris_triggersPerDestination() {
1093         List<Trigger> triggerList = new ArrayList<>();
1094         triggerList.add(createAppTrigger(APP_TWO_DESTINATION, APP_TWO_DESTINATION));
1095         triggerList.add(createAppTrigger(APP_TWO_DESTINATION, APP_TWO_DESTINATION));
1096         addTriggersToDatabase(triggerList);
1097 
1098         mDatastoreManager.runInTransaction(
1099                 measurementDao ->
1100                         assertThat(
1101                                         measurementDao.getNumTriggersPerDestination(
1102                                                 APP_TWO_DESTINATION, EventSurfaceType.APP))
1103                                 .isEqualTo(2));
1104     }
1105 
1106     @Test
noAppTriggers_triggersPerDestination_returnsNone()1107     public void noAppTriggers_triggersPerDestination_returnsNone() {
1108         mDatastoreManager.runInTransaction(
1109                 measurementDao ->
1110                         assertThat(
1111                                         measurementDao.getNumTriggersPerDestination(
1112                                                 APP_NO_TRIGGERS, EventSurfaceType.APP))
1113                                 .isEqualTo(0));
1114     }
1115 
1116     @Test
multipleAppTriggers_differentPaths_returnsAllMatching()1117     public void multipleAppTriggers_differentPaths_returnsAllMatching() {
1118         List<Trigger> triggerList = new ArrayList<>();
1119         triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION));
1120         triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION_PATH1));
1121         triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION_PATH2));
1122         addTriggersToDatabase(triggerList);
1123 
1124         mDatastoreManager.runInTransaction(
1125                 measurementDao -> {
1126                     assertThat(
1127                                     measurementDao.getNumTriggersPerDestination(
1128                                             APP_THREE_DESTINATION, EventSurfaceType.APP))
1129                             .isEqualTo(3);
1130                     // Try the same thing, but use the app uri with path to find number of triggers.
1131                     assertThat(
1132                                     measurementDao.getNumTriggersPerDestination(
1133                                             APP_THREE_DESTINATION_PATH1, EventSurfaceType.APP))
1134                             .isEqualTo(3);
1135                     assertThat(
1136                                     measurementDao.getNumTriggersPerDestination(
1137                                             APP_THREE_DESTINATION_PATH2, EventSurfaceType.APP))
1138                             .isEqualTo(3);
1139                     Uri unseenAppThreePath =
1140                             Uri.parse("android-app://com.example1.three-triggers/path3");
1141                     assertThat(
1142                                     measurementDao.getNumTriggersPerDestination(
1143                                             unseenAppThreePath, EventSurfaceType.APP))
1144                             .isEqualTo(3);
1145                 });
1146     }
1147 
1148     @Test
singleWebTrigger_triggersPerDestination_returnsOne()1149     public void singleWebTrigger_triggersPerDestination_returnsOne() {
1150         List<Trigger> triggerList = new ArrayList<>();
1151         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION));
1152         addTriggersToDatabase(triggerList);
1153 
1154         mDatastoreManager.runInTransaction(
1155                 measurementDao -> {
1156                     assertThat(
1157                                     measurementDao.getNumTriggersPerDestination(
1158                                             WEB_ONE_DESTINATION, EventSurfaceType.WEB))
1159                             .isEqualTo(1);
1160                 });
1161     }
1162 
1163     @Test
webTriggerMultipleSubDomains_triggersPerDestination_returnsAllMatching()1164     public void webTriggerMultipleSubDomains_triggersPerDestination_returnsAllMatching() {
1165         List<Trigger> triggerList = new ArrayList<>();
1166         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION));
1167         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN));
1168         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2));
1169         addTriggersToDatabase(triggerList);
1170 
1171         mDatastoreManager.runInTransaction(
1172                 measurementDao -> {
1173                     assertThat(
1174                                     measurementDao.getNumTriggersPerDestination(
1175                                             WEB_ONE_DESTINATION, EventSurfaceType.WEB))
1176                             .isEqualTo(3);
1177                     assertThat(
1178                                     measurementDao.getNumTriggersPerDestination(
1179                                             WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN,
1180                                             EventSurfaceType.WEB))
1181                             .isEqualTo(3);
1182                     assertThat(
1183                                     measurementDao.getNumTriggersPerDestination(
1184                                             WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2,
1185                                             EventSurfaceType.WEB))
1186                             .isEqualTo(3);
1187                     assertThat(
1188                                     measurementDao.getNumTriggersPerDestination(
1189                                             WebUtil.validUri("https://new-subdomain.example1.test"),
1190                                             EventSurfaceType.WEB))
1191                             .isEqualTo(3);
1192                     assertThat(
1193                                     measurementDao.getNumTriggersPerDestination(
1194                                             WebUtil.validUri("https://example1.test"),
1195                                             EventSurfaceType.WEB))
1196                             .isEqualTo(3);
1197                 });
1198     }
1199 
1200     @Test
webTriggerWithoutSubdomains_triggersPerDestination_returnsAllMatching()1201     public void webTriggerWithoutSubdomains_triggersPerDestination_returnsAllMatching() {
1202         List<Trigger> triggerList = new ArrayList<>();
1203         Uri webDestinationWithoutSubdomain = WebUtil.validUri("https://example1.test");
1204         Uri webDestinationWithoutSubdomainPath1 = WebUtil.validUri("https://example1.test/path1");
1205         Uri webDestinationWithoutSubdomainPath2 = WebUtil.validUri("https://example1.test/path2");
1206         Uri webDestinationWithoutSubdomainPath3 = WebUtil.validUri("https://example1.test/path3");
1207         triggerList.add(createWebTrigger(webDestinationWithoutSubdomain));
1208         triggerList.add(createWebTrigger(webDestinationWithoutSubdomainPath1));
1209         triggerList.add(createWebTrigger(webDestinationWithoutSubdomainPath2));
1210         addTriggersToDatabase(triggerList);
1211 
1212         mDatastoreManager.runInTransaction(
1213                 measurementDao -> {
1214                     assertThat(
1215                                     measurementDao.getNumTriggersPerDestination(
1216                                             webDestinationWithoutSubdomain, EventSurfaceType.WEB))
1217                             .isEqualTo(3);
1218                     assertThat(
1219                                     measurementDao.getNumTriggersPerDestination(
1220                                             webDestinationWithoutSubdomainPath1,
1221                                             EventSurfaceType.WEB))
1222                             .isEqualTo(3);
1223                     assertThat(
1224                                     measurementDao.getNumTriggersPerDestination(
1225                                             webDestinationWithoutSubdomainPath2,
1226                                             EventSurfaceType.WEB))
1227                             .isEqualTo(3);
1228                     assertThat(
1229                                     measurementDao.getNumTriggersPerDestination(
1230                                             webDestinationWithoutSubdomainPath3,
1231                                             EventSurfaceType.WEB))
1232                             .isEqualTo(3);
1233                 });
1234     }
1235 
1236     @Test
webTriggerDifferentPaths_triggersPerDestination_returnsAllMatching()1237     public void webTriggerDifferentPaths_triggersPerDestination_returnsAllMatching() {
1238         List<Trigger> triggerList = new ArrayList<>();
1239         triggerList.add(createWebTrigger(WEB_TWO_DESTINATION));
1240         triggerList.add(createWebTrigger(WEB_TWO_DESTINATION_WITH_PATH));
1241         addTriggersToDatabase(triggerList);
1242 
1243         mDatastoreManager.runInTransaction(
1244                 measurementDao -> {
1245                     assertThat(
1246                                     measurementDao.getNumTriggersPerDestination(
1247                                             WEB_TWO_DESTINATION, EventSurfaceType.WEB))
1248                             .isEqualTo(2);
1249                     assertThat(
1250                                     measurementDao.getNumTriggersPerDestination(
1251                                             WEB_TWO_DESTINATION_WITH_PATH, EventSurfaceType.WEB))
1252                             .isEqualTo(2);
1253                 });
1254     }
1255 
1256     @Test
noMathingWebTriggers_triggersPerDestination_returnsZero()1257     public void noMathingWebTriggers_triggersPerDestination_returnsZero() {
1258         List<Trigger> triggerList = new ArrayList<>();
1259         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION));
1260         addTriggersToDatabase(triggerList);
1261 
1262         mDatastoreManager.runInTransaction(
1263                 measurementDao -> {
1264                     Uri differentScheme = WebUtil.validUri("http://www.example1.test");
1265                     assertThat(
1266                                     measurementDao.getNumTriggersPerDestination(
1267                                             differentScheme, EventSurfaceType.WEB))
1268                             .isEqualTo(0);
1269 
1270                     Uri notMatchingUrl2 = WebUtil.validUri("https://www.not-example1.test");
1271                     assertThat(
1272                                     measurementDao.getNumTriggersPerDestination(
1273                                             notMatchingUrl2, EventSurfaceType.WEB))
1274                             .isEqualTo(0);
1275 
1276                     Uri notMatchingUrl = WebUtil.validUri("https://www.not-example-1.test");
1277                     assertThat(
1278                                     measurementDao.getNumTriggersPerDestination(
1279                                             notMatchingUrl, EventSurfaceType.WEB))
1280                             .isEqualTo(0);
1281                 });
1282     }
1283 
1284     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_appDest()1285     public void testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_appDest() {
1286         Uri sourceSite = Uri.parse("android-app://publisher.app");
1287         Uri webDestination = WebUtil.validUri("https://web-destination.test");
1288         Uri appDestination = Uri.parse("android-app://destination.app");
1289         String registrant = "android-app://registrant.app";
1290         List<Attribution> attributionsWithAppDestinations1 =
1291                 getAttributionsWithDifferentReportingOrigins(
1292                         4, appDestination, 5000000000L, sourceSite, registrant);
1293         List<Attribution> attributionsWithAppDestinations2 =
1294                 getAttributionsWithDifferentReportingOrigins(
1295                         2, appDestination, 5000000000L, sourceSite, registrant);
1296         List<Attribution> attributionsWithWebDestinations =
1297                 getAttributionsWithDifferentReportingOrigins(
1298                         2, webDestination, 5500000000L, sourceSite, registrant);
1299         List<Attribution> attributionsOutOfWindow =
1300                 getAttributionsWithDifferentReportingOrigins(
1301                         10, appDestination, 50000000000L, sourceSite, registrant);
1302         for (Attribution attribution : attributionsWithAppDestinations1) {
1303             insertAttribution(attribution);
1304         }
1305         for (Attribution attribution : attributionsWithAppDestinations2) {
1306             insertAttribution(attribution);
1307         }
1308         for (Attribution attribution : attributionsWithWebDestinations) {
1309             insertAttribution(attribution);
1310         }
1311         for (Attribution attribution : attributionsOutOfWindow) {
1312             insertAttribution(attribution);
1313         }
1314         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain0.example.test");
1315         mDatastoreManager.runInTransaction(
1316                 measurementDao -> {
1317                     assertEquals(
1318                             Integer.valueOf(3),
1319                             measurementDao
1320                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1321                                             sourceSite,
1322                                             appDestination,
1323                                             excludedReportingOrigin,
1324                                             4000000000L,
1325                                             6000000000L));
1326                 });
1327     }
1328 
1329     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_webDest()1330     public void testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_webDest() {
1331         Uri sourceSite = Uri.parse("android-app://publisher.app");
1332         Uri webDestination = WebUtil.validUri("https://web-destination.test");
1333         Uri appDestination = Uri.parse("android-app://destination.app");
1334         String registrant = "android-app://registrant.app";
1335         List<Attribution> attributionsWithAppDestinations =
1336                 getAttributionsWithDifferentReportingOrigins(
1337                         2, appDestination, 5000000000L, sourceSite, registrant);
1338         List<Attribution> attributionsWithWebDestinations1 =
1339                 getAttributionsWithDifferentReportingOrigins(
1340                         4, webDestination, 5000000000L, sourceSite, registrant);
1341         List<Attribution> attributionsWithWebDestinations2 =
1342                 getAttributionsWithDifferentReportingOrigins(
1343                         2, webDestination, 5500000000L, sourceSite, registrant);
1344         List<Attribution> attributionsOutOfWindow =
1345                 getAttributionsWithDifferentReportingOrigins(
1346                         10, webDestination, 50000000000L, sourceSite, registrant);
1347         for (Attribution attribution : attributionsWithAppDestinations) {
1348             insertAttribution(attribution);
1349         }
1350         for (Attribution attribution : attributionsWithWebDestinations1) {
1351             insertAttribution(attribution);
1352         }
1353         for (Attribution attribution : attributionsWithWebDestinations2) {
1354             insertAttribution(attribution);
1355         }
1356         for (Attribution attribution : attributionsOutOfWindow) {
1357             insertAttribution(attribution);
1358         }
1359         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain3.example.test");
1360         mDatastoreManager.runInTransaction(
1361                 measurementDao -> {
1362                     assertEquals(
1363                             Integer.valueOf(3),
1364                             measurementDao
1365                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1366                                             sourceSite,
1367                                             webDestination,
1368                                             excludedReportingOrigin,
1369                                             4000000000L,
1370                                             6000000000L));
1371                 });
1372     }
1373 
1374     @Test
testCountDistinctDestinations_atWindow()1375     public void testCountDistinctDestinations_atWindow() {
1376         Uri publisher = Uri.parse("android-app://publisher.app");
1377         List<Source> activeSourcesWithAppAndWebDestinations =
1378                 getSourcesWithDifferentDestinations(
1379                         4,
1380                         true,
1381                         true,
1382                         4500000001L,
1383                         publisher,
1384                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1385                         Source.Status.ACTIVE);
1386         for (Source source : activeSourcesWithAppAndWebDestinations) {
1387             insertSource(source);
1388         }
1389         List<Uri> excludedDestinations =
1390                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1391         mDatastoreManager.runInTransaction(
1392                 measurementDao -> {
1393                     assertEquals(
1394                             Integer.valueOf(3),
1395                             measurementDao
1396                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1397                                             publisher,
1398                                             EventSurfaceType.APP,
1399                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1400                                             excludedDestinations,
1401                                             EventSurfaceType.WEB,
1402                                             4500000000L,
1403                                             6000000000L));
1404                 });
1405         mDatastoreManager.runInTransaction(
1406                 measurementDao -> {
1407                     assertEquals(
1408                             Integer.valueOf(3),
1409                             measurementDao
1410                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1411                                             publisher,
1412                                             EventSurfaceType.APP,
1413                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1414                                             excludedDestinations,
1415                                             EventSurfaceType.WEB,
1416                                             6000000000L));
1417                 });
1418         mDatastoreManager.runInTransaction(
1419                 measurementDao -> {
1420                     assertEquals(
1421                             Integer.valueOf(3),
1422                             measurementDao
1423                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1424                                             publisher,
1425                                             EventSurfaceType.APP,
1426                                             excludedDestinations,
1427                                             EventSurfaceType.WEB,
1428                                             4500000000L,
1429                                             6000000000L));
1430                 });
1431     }
1432 
1433     @Test
testCountDistinctDestinations_expiredSource()1434     public void testCountDistinctDestinations_expiredSource() {
1435         Uri publisher = Uri.parse("android-app://publisher.app");
1436         List<Source> activeSourcesWithAppAndWebDestinations =
1437                 getSourcesWithDifferentDestinations(
1438                         4,
1439                         true,
1440                         true,
1441                         4500000001L,
1442                         publisher,
1443                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1444                         Source.Status.ACTIVE);
1445         List<Source> expiredSourcesWithAppAndWebDestinations =
1446                 getSourcesWithDifferentDestinations(
1447                         6,
1448                         true,
1449                         true,
1450                         4500000001L,
1451                         6000000000L,
1452                         publisher,
1453                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1454                         Source.Status.ACTIVE,
1455                         REGISTRATION_ORIGIN);
1456         for (Source source : activeSourcesWithAppAndWebDestinations) {
1457             insertSource(source);
1458         }
1459         for (Source source : expiredSourcesWithAppAndWebDestinations) {
1460             insertSource(source);
1461         }
1462         List<Uri> excludedDestinations =
1463                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1464         mDatastoreManager.runInTransaction(
1465                 measurementDao -> {
1466                     assertEquals(
1467                             Integer.valueOf(3),
1468                             measurementDao
1469                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1470                                             publisher,
1471                                             EventSurfaceType.APP,
1472                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1473                                             excludedDestinations,
1474                                             EventSurfaceType.WEB,
1475                                             4500000000L,
1476                                             6000000000L));
1477                 });
1478         mDatastoreManager.runInTransaction(
1479                 measurementDao -> {
1480                     assertEquals(
1481                             Integer.valueOf(3),
1482                             measurementDao
1483                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1484                                             publisher,
1485                                             EventSurfaceType.APP,
1486                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1487                                             excludedDestinations,
1488                                             EventSurfaceType.WEB,
1489                                             6000000000L));
1490                 });
1491         mDatastoreManager.runInTransaction(
1492                 measurementDao -> {
1493                     assertEquals(
1494                             Integer.valueOf(3),
1495                             measurementDao
1496                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1497                                             publisher,
1498                                             EventSurfaceType.APP,
1499                                             excludedDestinations,
1500                                             EventSurfaceType.WEB,
1501                                             4500000000L,
1502                                             6000000000L));
1503                 });
1504     }
1505 
1506     @Test
testCountDistinctDestinations_beyondWindow()1507     public void testCountDistinctDestinations_beyondWindow() {
1508         Uri publisher = Uri.parse("android-app://publisher.app");
1509         List<Source> activeSourcesWithAppAndWebDestinations =
1510                 getSourcesWithDifferentDestinations(
1511                         4,
1512                         true,
1513                         true,
1514                         4500000000L,
1515                         publisher,
1516                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1517                         Source.Status.ACTIVE);
1518         for (Source source : activeSourcesWithAppAndWebDestinations) {
1519             insertSource(source);
1520         }
1521         List<Uri> excludedDestinations =
1522                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1523         mDatastoreManager.runInTransaction(
1524                 measurementDao -> {
1525                     assertEquals(
1526                             Integer.valueOf(0),
1527                             measurementDao
1528                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1529                                             publisher,
1530                                             EventSurfaceType.APP,
1531                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1532                                             excludedDestinations,
1533                                             EventSurfaceType.WEB,
1534                                             4500000000L,
1535                                             6000000000L));
1536                 });
1537         mDatastoreManager.runInTransaction(
1538                 measurementDao -> {
1539                     assertEquals(
1540                             Integer.valueOf(3),
1541                             measurementDao
1542                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1543                                             publisher,
1544                                             EventSurfaceType.APP,
1545                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1546                                             excludedDestinations,
1547                                             EventSurfaceType.WEB,
1548                                             6000000000L));
1549                 });
1550         mDatastoreManager.runInTransaction(
1551                 measurementDao -> {
1552                     assertEquals(
1553                             Integer.valueOf(0),
1554                             measurementDao
1555                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1556                                             publisher,
1557                                             EventSurfaceType.APP,
1558                                             excludedDestinations,
1559                                             EventSurfaceType.WEB,
1560                                             4500000000L,
1561                                             6000000000L));
1562                 });
1563     }
1564 
1565     @Test
testCountDistinctDestinations_appPublisher()1566     public void testCountDistinctDestinations_appPublisher() {
1567         Uri publisher = Uri.parse("android-app://publisher.app");
1568         List<Source> activeSourcesWithAppAndWebDestinations =
1569                 getSourcesWithDifferentDestinations(
1570                         4,
1571                         true,
1572                         true,
1573                         4500000000L,
1574                         publisher,
1575                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1576                         Source.Status.ACTIVE);
1577         List<Source> activeSourcesWithAppDestinations =
1578                 getSourcesWithDifferentDestinations(
1579                         2,
1580                         true,
1581                         false,
1582                         5000000000L,
1583                         publisher,
1584                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1585                         Source.Status.ACTIVE);
1586         List<Source> activeSourcesWithWebDestinations =
1587                 getSourcesWithDifferentDestinations(
1588                         2,
1589                         false,
1590                         true,
1591                         5500000000L,
1592                         publisher,
1593                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1594                         Source.Status.ACTIVE);
1595         List<Source> activeSourcesOutOfWindow =
1596                 getSourcesWithDifferentDestinations(
1597                         10,
1598                         true,
1599                         true,
1600                         50000000000L,
1601                         publisher,
1602                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1603                         Source.Status.ACTIVE);
1604         List<Source> ignoredSources =
1605                 getSourcesWithDifferentDestinations(
1606                         6,
1607                         true,
1608                         true,
1609                         5000000000L,
1610                         publisher,
1611                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1612                         Source.Status.IGNORED);
1613         for (Source source : activeSourcesWithAppAndWebDestinations) {
1614             insertSource(source);
1615         }
1616         for (Source source : activeSourcesWithAppDestinations) {
1617             insertSource(source);
1618         }
1619         for (Source source : activeSourcesWithWebDestinations) {
1620             insertSource(source);
1621         }
1622         for (Source source : activeSourcesOutOfWindow) {
1623             insertSource(source);
1624         }
1625         for (Source source : ignoredSources) {
1626             insertSource(source);
1627         }
1628         List<Uri> excludedDestinations =
1629                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1630         mDatastoreManager.runInTransaction(
1631                 measurementDao -> {
1632                     assertEquals(
1633                             Integer.valueOf(5),
1634                             measurementDao
1635                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1636                                             publisher,
1637                                             EventSurfaceType.APP,
1638                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1639                                             excludedDestinations,
1640                                             EventSurfaceType.WEB,
1641                                             4000000000L,
1642                                             6000000000L));
1643                 });
1644         mDatastoreManager.runInTransaction(
1645                 measurementDao -> {
1646                     assertEquals(
1647                             Integer.valueOf(9),
1648                             measurementDao
1649                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1650                                             publisher,
1651                                             EventSurfaceType.APP,
1652                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1653                                             excludedDestinations,
1654                                             EventSurfaceType.WEB,
1655                                             6000000000L));
1656                 });
1657         mDatastoreManager.runInTransaction(
1658                 measurementDao -> {
1659                     assertEquals(
1660                             Integer.valueOf(5),
1661                             measurementDao
1662                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1663                                             publisher,
1664                                             EventSurfaceType.APP,
1665                                             excludedDestinations,
1666                                             EventSurfaceType.WEB,
1667                                             4000000000L,
1668                                             6000000000L));
1669                 });
1670     }
1671 
1672     // (Testing countDistinctDestinationsPerPublisherInActiveSource)
1673     @Test
testCountDistinctDestinations_appPublisher_differentEnrollment()1674     public void testCountDistinctDestinations_appPublisher_differentEnrollment() {
1675         Uri publisher = Uri.parse("android-app://publisher.app");
1676         List<Source> activeSourcesWithAppAndWebDestinations =
1677                 getSourcesWithDifferentDestinations(
1678                         4,
1679                         true,
1680                         true,
1681                         4500000000L,
1682                         publisher,
1683                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1684                         Source.Status.ACTIVE);
1685         List<Source> activeSourcesWithAppDestinations =
1686                 getSourcesWithDifferentDestinations(
1687                         2,
1688                         true,
1689                         false,
1690                         5000000000L,
1691                         publisher,
1692                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1693                         Source.Status.ACTIVE);
1694         List<Source> activeSourcesWithWebDestinations =
1695                 getSourcesWithDifferentDestinations(
1696                         2,
1697                         false,
1698                         true,
1699                         5500000000L,
1700                         publisher,
1701                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1702                         Source.Status.ACTIVE);
1703         List<Source> activeSourcesOutOfWindow =
1704                 getSourcesWithDifferentDestinations(
1705                         10,
1706                         true,
1707                         true,
1708                         50000000000L,
1709                         publisher,
1710                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1711                         Source.Status.ACTIVE);
1712         List<Source> ignoredSources =
1713                 getSourcesWithDifferentDestinations(
1714                         6,
1715                         true,
1716                         true,
1717                         5000000000L,
1718                         publisher,
1719                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1720                         Source.Status.IGNORED);
1721         for (Source source : activeSourcesWithAppAndWebDestinations) {
1722             insertSource(source);
1723         }
1724         for (Source source : activeSourcesWithAppDestinations) {
1725             insertSource(source);
1726         }
1727         for (Source source : activeSourcesWithWebDestinations) {
1728             insertSource(source);
1729         }
1730         for (Source source : activeSourcesOutOfWindow) {
1731             insertSource(source);
1732         }
1733         for (Source source : ignoredSources) {
1734             insertSource(source);
1735         }
1736         List<Uri> excludedDestinations =
1737                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1738         mDatastoreManager.runInTransaction(
1739                 measurementDao -> {
1740                     assertEquals(
1741                             Integer.valueOf(0),
1742                             measurementDao
1743                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1744                                             publisher,
1745                                             EventSurfaceType.APP,
1746                                             "unmatched-enrollment-id",
1747                                             excludedDestinations,
1748                                             EventSurfaceType.WEB,
1749                                             4000000000L,
1750                                             6000000000L));
1751                 });
1752         mDatastoreManager.runInTransaction(
1753                 measurementDao -> {
1754                     assertEquals(
1755                             Integer.valueOf(0),
1756                             measurementDao
1757                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1758                                             publisher,
1759                                             EventSurfaceType.APP,
1760                                             "unmatched-enrollment-id",
1761                                             excludedDestinations,
1762                                             EventSurfaceType.WEB,
1763                                             6000000000L));
1764                 });
1765         mDatastoreManager.runInTransaction(
1766                 measurementDao -> {
1767                     assertEquals(
1768                             Integer.valueOf(5),
1769                             measurementDao
1770                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1771                                             publisher,
1772                                             EventSurfaceType.APP,
1773                                             excludedDestinations,
1774                                             EventSurfaceType.WEB,
1775                                             4000000000L,
1776                                             6000000000L));
1777                 });
1778     }
1779 
1780     @Test
testCountDistinctDestinations_webPublisher_exactMatch()1781     public void testCountDistinctDestinations_webPublisher_exactMatch() {
1782         Uri publisher = WebUtil.validUri("https://publisher.test");
1783         List<Source> activeSourcesWithAppAndWebDestinations =
1784                 getSourcesWithDifferentDestinations(
1785                         4,
1786                         true,
1787                         true,
1788                         4500000000L,
1789                         publisher,
1790                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1791                         Source.Status.ACTIVE);
1792         List<Source> activeSourcesWithAppDestinations =
1793                 getSourcesWithDifferentDestinations(
1794                         2,
1795                         true,
1796                         false,
1797                         5000000000L,
1798                         publisher,
1799                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1800                         Source.Status.ACTIVE);
1801         List<Source> activeSourcesWithWebDestinations =
1802                 getSourcesWithDifferentDestinations(
1803                         2,
1804                         false,
1805                         true,
1806                         5500000000L,
1807                         publisher,
1808                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1809                         Source.Status.ACTIVE);
1810         List<Source> activeSourcesOutOfWindow =
1811                 getSourcesWithDifferentDestinations(
1812                         10,
1813                         true,
1814                         true,
1815                         50000000000L,
1816                         publisher,
1817                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1818                         Source.Status.ACTIVE);
1819         List<Source> ignoredSources =
1820                 getSourcesWithDifferentDestinations(
1821                         6,
1822                         true,
1823                         true,
1824                         5000000000L,
1825                         publisher,
1826                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1827                         Source.Status.IGNORED);
1828         for (Source source : activeSourcesWithAppAndWebDestinations) {
1829             insertSource(source);
1830         }
1831         for (Source source : activeSourcesWithAppDestinations) {
1832             insertSource(source);
1833         }
1834         for (Source source : activeSourcesWithWebDestinations) {
1835             insertSource(source);
1836         }
1837         for (Source source : activeSourcesOutOfWindow) {
1838             insertSource(source);
1839         }
1840         for (Source source : ignoredSources) {
1841             insertSource(source);
1842         }
1843         List<Uri> excludedDestinations =
1844                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1845         mDatastoreManager.runInTransaction(
1846                 measurementDao -> {
1847                     assertEquals(
1848                             Integer.valueOf(5),
1849                             measurementDao
1850                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1851                                             publisher,
1852                                             EventSurfaceType.WEB,
1853                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1854                                             excludedDestinations,
1855                                             EventSurfaceType.WEB,
1856                                             4000000000L,
1857                                             6000000000L));
1858                 });
1859         mDatastoreManager.runInTransaction(
1860                 measurementDao -> {
1861                     assertEquals(
1862                             Integer.valueOf(9),
1863                             measurementDao
1864                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1865                                             publisher,
1866                                             EventSurfaceType.WEB,
1867                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1868                                             excludedDestinations,
1869                                             EventSurfaceType.WEB,
1870                                             6000000000L));
1871                 });
1872         mDatastoreManager.runInTransaction(
1873                 measurementDao -> {
1874                     assertEquals(
1875                             Integer.valueOf(5),
1876                             measurementDao
1877                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1878                                             publisher,
1879                                             EventSurfaceType.WEB,
1880                                             excludedDestinations,
1881                                             EventSurfaceType.WEB,
1882                                             4000000000L,
1883                                             6000000000L));
1884                 });
1885     }
1886 
1887     @Test
testCountDistinctDestinations_webPublisher_doesNotMatchDomainAsSuffix()1888     public void testCountDistinctDestinations_webPublisher_doesNotMatchDomainAsSuffix() {
1889         Uri publisher = WebUtil.validUri("https://publisher.test");
1890         Uri publisherAsSuffix = WebUtil.validUri("https://prefix-publisher.test");
1891         List<Source> activeSourcesWithAppAndWebDestinations =
1892                 getSourcesWithDifferentDestinations(
1893                         8,
1894                         true,
1895                         true,
1896                         4500000000L,
1897                         publisherAsSuffix,
1898                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1899                         Source.Status.ACTIVE);
1900         List<Source> ignoredSourcesWithAppAndWebDestinations =
1901                 getSourcesWithDifferentDestinations(
1902                         4,
1903                         true,
1904                         true,
1905                         4500000000L,
1906                         publisher,
1907                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1908                         Source.Status.IGNORED);
1909         List<Source> activeSourcesWithAppDestinations =
1910                 getSourcesWithDifferentDestinations(
1911                         2,
1912                         true,
1913                         false,
1914                         5000000000L,
1915                         publisher,
1916                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1917                         Source.Status.ACTIVE);
1918         List<Source> activeSourcesWithWebDestinations =
1919                 getSourcesWithDifferentDestinations(
1920                         2,
1921                         false,
1922                         true,
1923                         5500000000L,
1924                         publisher,
1925                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1926                         Source.Status.ACTIVE);
1927         List<Source> activeSourcesOutOfWindow =
1928                 getSourcesWithDifferentDestinations(
1929                         10,
1930                         true,
1931                         true,
1932                         50000000000L,
1933                         publisherAsSuffix,
1934                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1935                         Source.Status.ACTIVE);
1936         List<Source> markedAsDeletedSources =
1937                 getSourcesWithDifferentDestinations(
1938                         5,
1939                         true,
1940                         true,
1941                         5000000000L,
1942                         publisher,
1943                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1944                         Source.Status.MARKED_TO_DELETE);
1945         for (Source source : activeSourcesWithAppAndWebDestinations) {
1946             insertSource(source);
1947         }
1948         for (Source source : ignoredSourcesWithAppAndWebDestinations) {
1949             insertSource(source);
1950         }
1951         for (Source source : activeSourcesWithAppDestinations) {
1952             insertSource(source);
1953         }
1954         for (Source source : activeSourcesWithWebDestinations) {
1955             insertSource(source);
1956         }
1957         for (Source source : activeSourcesOutOfWindow) {
1958             insertSource(source);
1959         }
1960         for (Source source : markedAsDeletedSources) {
1961             insertSource(source);
1962         }
1963         List<Uri> excludedDestinations =
1964                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1965         mDatastoreManager.runInTransaction(
1966                 measurementDao -> {
1967                     assertEquals(
1968                             Integer.valueOf(4),
1969                             measurementDao
1970                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1971                                             publisher,
1972                                             EventSurfaceType.WEB,
1973                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1974                                             excludedDestinations,
1975                                             EventSurfaceType.WEB,
1976                                             4000000000L,
1977                                             6000000000L));
1978                 });
1979         mDatastoreManager.runInTransaction(
1980                 measurementDao -> {
1981                     assertEquals(
1982                             Integer.valueOf(3),
1983                             measurementDao
1984                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1985                                             publisher,
1986                                             EventSurfaceType.WEB,
1987                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1988                                             excludedDestinations,
1989                                             EventSurfaceType.WEB,
1990                                             6000000000L));
1991                 });
1992         mDatastoreManager.runInTransaction(
1993                 measurementDao ->
1994                         assertEquals(
1995                                 Integer.valueOf(4),
1996                                 measurementDao
1997                                         .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1998                                                 publisher,
1999                                                 EventSurfaceType.WEB,
2000                                                 excludedDestinations,
2001                                                 EventSurfaceType.WEB,
2002                                                 4000000000L,
2003                                                 6000000000L)));
2004     }
2005 
2006     @Test
testCountDistinctDestinations_webPublisher_doesNotMatchDifferentScheme()2007     public void testCountDistinctDestinations_webPublisher_doesNotMatchDifferentScheme() {
2008         Uri publisher = WebUtil.validUri("https://publisher.test");
2009         Uri publisherWithDifferentScheme = WebUtil.validUri("http://publisher.test");
2010         List<Source> activeSourcesWithAppAndWebDestinations =
2011                 getSourcesWithDifferentDestinations(
2012                         4,
2013                         true,
2014                         true,
2015                         4500000000L,
2016                         publisherWithDifferentScheme,
2017                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2018                         Source.Status.ACTIVE);
2019         List<Source> activeSourcesWithAppDestinations =
2020                 getSourcesWithDifferentDestinations(
2021                         2,
2022                         true,
2023                         false,
2024                         5000000000L,
2025                         publisher,
2026                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2027                         Source.Status.ACTIVE);
2028         List<Source> activeSourcesWithWebDestinations =
2029                 getSourcesWithDifferentDestinations(
2030                         2,
2031                         false,
2032                         true,
2033                         5500000000L,
2034                         publisher,
2035                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2036                         Source.Status.ACTIVE);
2037         List<Source> activeSourcesOutOfWindow =
2038                 getSourcesWithDifferentDestinations(
2039                         10,
2040                         true,
2041                         true,
2042                         50000000000L,
2043                         publisher,
2044                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2045                         Source.Status.ACTIVE);
2046         List<Source> ignoredSources =
2047                 getSourcesWithDifferentDestinations(
2048                         6,
2049                         true,
2050                         true,
2051                         5000000000L,
2052                         publisher,
2053                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2054                         Source.Status.IGNORED);
2055         for (Source source : activeSourcesWithAppAndWebDestinations) {
2056             insertSource(source);
2057         }
2058         for (Source source : activeSourcesWithAppDestinations) {
2059             insertSource(source);
2060         }
2061         for (Source source : activeSourcesWithWebDestinations) {
2062             insertSource(source);
2063         }
2064         for (Source source : activeSourcesOutOfWindow) {
2065             insertSource(source);
2066         }
2067         for (Source source : ignoredSources) {
2068             insertSource(source);
2069         }
2070         List<Uri> excludedDestinations =
2071                 List.of(WebUtil.validUri("https://web-destination-2.test"));
2072         mDatastoreManager.runInTransaction(
2073                 measurementDao -> {
2074                     assertEquals(
2075                             Integer.valueOf(5),
2076                             measurementDao
2077                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
2078                                             publisher,
2079                                             EventSurfaceType.WEB,
2080                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2081                                             excludedDestinations,
2082                                             EventSurfaceType.WEB,
2083                                             4000000000L,
2084                                             6000000000L));
2085                 });
2086         mDatastoreManager.runInTransaction(
2087                 measurementDao -> {
2088                     assertEquals(
2089                             Integer.valueOf(9),
2090                             measurementDao
2091                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
2092                                             publisher,
2093                                             EventSurfaceType.WEB,
2094                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2095                                             excludedDestinations,
2096                                             EventSurfaceType.WEB,
2097                                             6000000000L));
2098                 });
2099         mDatastoreManager.runInTransaction(
2100                 measurementDao -> {
2101                     assertEquals(
2102                             Integer.valueOf(5),
2103                             measurementDao
2104                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
2105                                             publisher,
2106                                             EventSurfaceType.WEB,
2107                                             excludedDestinations,
2108                                             EventSurfaceType.WEB,
2109                                             4000000000L,
2110                                             6000000000L));
2111                 });
2112     }
2113 
2114     @Test
testCountDistinctDestinations_webPublisher_multipleDestinations()2115     public void testCountDistinctDestinations_webPublisher_multipleDestinations() {
2116         Uri publisher = WebUtil.validUri("https://publisher.test");
2117         // One source with multiple destinations
2118         Source activeSourceWithAppAndWebDestinations =
2119                 getSourceWithDifferentDestinations(
2120                         3,
2121                         true,
2122                         true,
2123                         4500000000L,
2124                         publisher,
2125                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2126                         Source.Status.ACTIVE);
2127         List<Source> activeSourcesWithAppDestinations =
2128                 getSourcesWithDifferentDestinations(
2129                         2,
2130                         true,
2131                         false,
2132                         5000000000L,
2133                         publisher,
2134                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2135                         Source.Status.ACTIVE);
2136         List<Source> activeSourcesWithWebDestinations =
2137                 getSourcesWithDifferentDestinations(
2138                         1,
2139                         false,
2140                         true,
2141                         5500000000L,
2142                         publisher,
2143                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2144                         Source.Status.ACTIVE);
2145         List<Source> activeSourcesOutOfWindow =
2146                 getSourcesWithDifferentDestinations(
2147                         10,
2148                         true,
2149                         true,
2150                         50000000000L,
2151                         publisher,
2152                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2153                         Source.Status.ACTIVE);
2154         List<Source> ignoredSources =
2155                 getSourcesWithDifferentDestinations(
2156                         4,
2157                         true,
2158                         true,
2159                         5000000000L,
2160                         publisher,
2161                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2162                         Source.Status.IGNORED);
2163         insertSource(activeSourceWithAppAndWebDestinations);
2164         for (Source source : activeSourcesWithAppDestinations) {
2165             insertSource(source);
2166         }
2167         for (Source source : activeSourcesWithWebDestinations) {
2168             insertSource(source);
2169         }
2170         for (Source source : activeSourcesOutOfWindow) {
2171             insertSource(source);
2172         }
2173         for (Source source : ignoredSources) {
2174             insertSource(source);
2175         }
2176         List<Uri> excludedDestinations =
2177                 List.of(
2178                         WebUtil.validUri("https://web-destination-1.test"),
2179                         WebUtil.validUri("https://web-destination-2.test"));
2180         mDatastoreManager.runInTransaction(
2181                 measurementDao -> {
2182                     assertEquals(
2183                             Integer.valueOf(2),
2184                             measurementDao
2185                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
2186                                             publisher,
2187                                             EventSurfaceType.WEB,
2188                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2189                                             excludedDestinations,
2190                                             EventSurfaceType.WEB,
2191                                             4000000000L,
2192                                             6000000000L));
2193                 });
2194         mDatastoreManager.runInTransaction(
2195                 measurementDao -> {
2196                     assertEquals(
2197                             Integer.valueOf(8),
2198                             measurementDao
2199                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
2200                                             publisher,
2201                                             EventSurfaceType.WEB,
2202                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2203                                             excludedDestinations,
2204                                             EventSurfaceType.WEB,
2205                                             6000000000L));
2206                 });
2207         mDatastoreManager.runInTransaction(
2208                 measurementDao -> {
2209                     assertEquals(
2210                             Integer.valueOf(2),
2211                             measurementDao
2212                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
2213                                             publisher,
2214                                             EventSurfaceType.WEB,
2215                                             excludedDestinations,
2216                                             EventSurfaceType.WEB,
2217                                             4000000000L,
2218                                             6000000000L));
2219                 });
2220     }
2221 
2222     // Tests countSourcesPerPublisherXEnrollmentExcludingRegistrationOriginSinceTime
2223     @Test
testCountSourcesExclRegOrigin_forSameOrigin_returnsZero()2224     public void testCountSourcesExclRegOrigin_forSameOrigin_returnsZero() {
2225         // Positive case. For same registration origin we always pass the 1 origin
2226         // per site limit and return 0
2227         Uri appPublisher = Uri.parse("android-app://publisher.app");
2228         List<Source> sourcesMoreThanOneDayOld =
2229                 getSourcesWithDifferentDestinations(
2230                         5,
2231                         true,
2232                         true,
2233                         System.currentTimeMillis() - DAYS.toMillis(2),
2234                         appPublisher,
2235                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2236                         Source.Status.ACTIVE,
2237                         REGISTRATION_ORIGIN);
2238 
2239         List<Source> sourcesRecent =
2240                 getSourcesWithDifferentDestinations(
2241                         5,
2242                         true,
2243                         true,
2244                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2245                         appPublisher,
2246                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2247                         Source.Status.ACTIVE,
2248                         REGISTRATION_ORIGIN);
2249 
2250         for (Source source : sourcesMoreThanOneDayOld) {
2251             insertSource(source);
2252         }
2253         for (Source source : sourcesRecent) {
2254             insertSource(source);
2255         }
2256         mDatastoreManager.runInTransaction(
2257                 measurementDao -> {
2258                     assertEquals(
2259                             Integer.valueOf(0),
2260                             measurementDao
2261                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2262                                             REGISTRATION_ORIGIN,
2263                                             appPublisher,
2264                                             EventSurfaceType.APP,
2265                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2266                                             System.currentTimeMillis(),
2267                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2268                 });
2269     }
2270 
2271     @Test
testCountSourcesExclRegOrigin_forDifferentAppPublisher_returnsZero()2272     public void testCountSourcesExclRegOrigin_forDifferentAppPublisher_returnsZero() {
2273         // Positive case. For different publisher we always pass the 1 origin
2274         // per site limit and return 0
2275         Uri appPublisher = Uri.parse("android-app://publisher.app");
2276         Uri appPublisher2 = Uri.parse("android-app://publisher2.app");
2277         List<Source> sources =
2278                 getSourcesWithDifferentDestinations(
2279                         5,
2280                         true,
2281                         true,
2282                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2283                         appPublisher,
2284                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2285                         Source.Status.ACTIVE,
2286                         REGISTRATION_ORIGIN);
2287         for (Source source : sources) {
2288             insertSource(source);
2289         }
2290         mDatastoreManager.runInTransaction(
2291                 measurementDao -> {
2292                     assertEquals(
2293                             Integer.valueOf(0),
2294                             measurementDao
2295                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2296                                             REGISTRATION_ORIGIN_2,
2297                                             appPublisher2,
2298                                             EventSurfaceType.APP,
2299                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2300                                             System.currentTimeMillis(),
2301                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2302                 });
2303     }
2304 
2305     @Test
testCountSourcesExclRegOrigin_forDifferentWebPublisher_returnsZero()2306     public void testCountSourcesExclRegOrigin_forDifferentWebPublisher_returnsZero() {
2307         // Positive case. For different publisher we always pass the 1 origin
2308         // per site limit and return 0
2309         Uri publisher = WebUtil.validUri("https://publisher.test");
2310         Uri publisher2 = WebUtil.validUri("https://publisher2.test");
2311         List<Source> sources =
2312                 getSourcesWithDifferentDestinations(
2313                         5,
2314                         true,
2315                         true,
2316                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2317                         publisher,
2318                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2319                         Source.Status.ACTIVE,
2320                         REGISTRATION_ORIGIN);
2321         for (Source source : sources) {
2322             insertSource(source);
2323         }
2324         mDatastoreManager.runInTransaction(
2325                 measurementDao -> {
2326                     assertEquals(
2327                             Integer.valueOf(0),
2328                             measurementDao
2329                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2330                                             REGISTRATION_ORIGIN_2,
2331                                             publisher2,
2332                                             EventSurfaceType.WEB,
2333                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2334                                             System.currentTimeMillis(),
2335                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2336                 });
2337     }
2338 
2339     @Test
testCountSourcesExclRegOrigin_forDifferentEnrollment_returnsZero()2340     public void testCountSourcesExclRegOrigin_forDifferentEnrollment_returnsZero() {
2341         // Positive case. For different enrollment (aka reporting site)
2342         // we always pass the 1 origin per site limit and return 0
2343         String differentEnrollment = "new-enrollment";
2344         Uri differentSite = WebUtil.validUri("https://subdomain.different-site.test");
2345         Uri appPublisher = Uri.parse("android-app://publisher.app");
2346         List<Source> sources =
2347                 getSourcesWithDifferentDestinations(
2348                         5,
2349                         true,
2350                         true,
2351                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2352                         appPublisher,
2353                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2354                         Source.Status.ACTIVE,
2355                         REGISTRATION_ORIGIN);
2356         for (Source source : sources) {
2357             insertSource(source);
2358         }
2359         mDatastoreManager.runInTransaction(
2360                 measurementDao -> {
2361                     assertEquals(
2362                             Integer.valueOf(0),
2363                             measurementDao
2364                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2365                                             differentSite,
2366                                             appPublisher,
2367                                             EventSurfaceType.APP,
2368                                             differentEnrollment,
2369                                             System.currentTimeMillis(),
2370                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2371                 });
2372     }
2373 
2374     @Test
testCountSourcesExclRegOrigin_forDifferentOriginMoreThanTimeWindow_returnsZero()2375     public void testCountSourcesExclRegOrigin_forDifferentOriginMoreThanTimeWindow_returnsZero() {
2376         // Positive case. For different origin with same enrollment
2377         // more than time window of 1 day we always pass the 1 origin per site
2378         // limit and return 0
2379         Uri appPublisher = Uri.parse("android-app://publisher.app");
2380         List<Source> sources =
2381                 getSourcesWithDifferentDestinations(
2382                         5,
2383                         true,
2384                         true,
2385                         System.currentTimeMillis() - DAYS.toMillis(2),
2386                         appPublisher,
2387                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2388                         Source.Status.ACTIVE,
2389                         REGISTRATION_ORIGIN);
2390         for (Source source : sources) {
2391             insertSource(source);
2392         }
2393         mDatastoreManager.runInTransaction(
2394                 measurementDao -> {
2395                     assertEquals(
2396                             Integer.valueOf(0),
2397                             measurementDao
2398                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2399                                             REGISTRATION_ORIGIN_2,
2400                                             appPublisher,
2401                                             EventSurfaceType.APP,
2402                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2403                                             System.currentTimeMillis(),
2404                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2405                 });
2406     }
2407 
2408     // Tests countSourcesPerPublisherXEnrollmentExcludingRegistrationOriginSinceTime
2409     @Test
testCountSources_forDifferentOriginWithinTimeWindow_returnsNumOfSources()2410     public void testCountSources_forDifferentOriginWithinTimeWindow_returnsNumOfSources() {
2411         // Negative case. For different origin with same enrollment
2412         // we always fail the 1 origin per site limit and return 1
2413         Uri appPublisher = Uri.parse("android-app://publisher.app");
2414         List<Source> sources =
2415                 getSourcesWithDifferentDestinations(
2416                         5,
2417                         true,
2418                         true,
2419                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2420                         appPublisher,
2421                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2422                         Source.Status.ACTIVE,
2423                         REGISTRATION_ORIGIN);
2424         for (Source source : sources) {
2425             insertSource(source);
2426         }
2427         mDatastoreManager.runInTransaction(
2428                 measurementDao -> {
2429                     assertEquals(
2430                             Integer.valueOf(1),
2431                             measurementDao
2432                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2433                                             REGISTRATION_ORIGIN_2,
2434                                             appPublisher,
2435                                             EventSurfaceType.APP,
2436                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2437                                             System.currentTimeMillis(),
2438                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2439                 });
2440     }
2441 
2442     @Test
testCountDistinctRegistrationOriginPerPublisherXDestinationInSource_atWindow()2443     public void testCountDistinctRegistrationOriginPerPublisherXDestinationInSource_atWindow() {
2444         Uri publisher = Uri.parse("android-app://publisher.app");
2445         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2446         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2447         List<Source> activeSourcesWithAppAndWebDestinations =
2448                 getSourcesWithDifferentRegistrationOrigins(
2449                         2,
2450                         appDestinations,
2451                         webDestinations,
2452                         4500000001L,
2453                         publisher,
2454                         Source.Status.ACTIVE);
2455         for (Source source : activeSourcesWithAppAndWebDestinations) {
2456             insertSource(source);
2457         }
2458         Uri excludedRegistrationOrigin = WebUtil.validUri("https://subdomain1.example.test");
2459         mDatastoreManager.runInTransaction(
2460                 measurementDao -> {
2461                     assertEquals(
2462                             Integer.valueOf(1),
2463                             measurementDao
2464                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2465                                             publisher,
2466                                             EventSurfaceType.APP,
2467                                             appDestinations,
2468                                             excludedRegistrationOrigin,
2469                                             4500000000L,
2470                                             6000000000L));
2471                 });
2472     }
2473 
2474     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_beyondWindow()2475     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_beyondWindow() {
2476         Uri publisher = Uri.parse("android-app://publisher.app");
2477         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2478         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2479         List<Source> activeSourcesWithAppAndWebDestinations =
2480                 getSourcesWithDifferentRegistrationOrigins(
2481                         2,
2482                         appDestinations,
2483                         webDestinations,
2484                         4500000000L,
2485                         publisher,
2486                         Source.Status.ACTIVE);
2487         for (Source source : activeSourcesWithAppAndWebDestinations) {
2488             insertSource(source);
2489         }
2490         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2491         mDatastoreManager.runInTransaction(
2492                 measurementDao -> {
2493                     assertEquals(
2494                             Integer.valueOf(0),
2495                             measurementDao
2496                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2497                                             publisher,
2498                                             EventSurfaceType.APP,
2499                                             appDestinations,
2500                                             excludedReportingOrigin,
2501                                             4500000000L,
2502                                             6000000000L));
2503                 });
2504     }
2505 
2506     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_expiredSource()2507     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_expiredSource() {
2508         Uri publisher = Uri.parse("android-app://publisher.app");
2509         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2510         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2511         List<Source> activeSourcesWithAppAndWebDestinations =
2512                 getSourcesWithDifferentRegistrationOrigins(
2513                         2,
2514                         appDestinations,
2515                         webDestinations,
2516                         4500000001L,
2517                         publisher,
2518                         Source.Status.ACTIVE);
2519         List<Source> expiredSourcesWithAppAndWebDestinations =
2520                 getSourcesWithDifferentRegistrationOrigins(
2521                         4,
2522                         appDestinations,
2523                         webDestinations,
2524                         4500000001L,
2525                         6000000000L,
2526                         publisher,
2527                         Source.Status.ACTIVE);
2528         for (Source source : activeSourcesWithAppAndWebDestinations) {
2529             insertSource(source);
2530         }
2531         for (Source source : expiredSourcesWithAppAndWebDestinations) {
2532             insertSource(source);
2533         }
2534         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2535         mDatastoreManager.runInTransaction(
2536                 measurementDao -> {
2537                     assertEquals(
2538                             Integer.valueOf(3),
2539                             measurementDao
2540                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2541                                             publisher,
2542                                             EventSurfaceType.APP,
2543                                             appDestinations,
2544                                             excludedReportingOrigin,
2545                                             4500000000L,
2546                                             6000000000L));
2547                 });
2548     }
2549 
2550     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_appDestination()2551     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_appDestination() {
2552         Uri publisher = Uri.parse("android-app://publisher.app");
2553         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2554         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2555         List<Source> activeSourcesWithAppAndWebDestinations =
2556                 getSourcesWithDifferentRegistrationOrigins(
2557                         2,
2558                         appDestinations,
2559                         webDestinations,
2560                         4500000000L,
2561                         publisher,
2562                         Source.Status.ACTIVE);
2563         List<Source> activeSourcesWithAppDestinations =
2564                 getSourcesWithDifferentRegistrationOrigins(
2565                         2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE);
2566         List<Source> activeSourcesWithWebDestinations =
2567                 getSourcesWithDifferentRegistrationOrigins(
2568                         2, null, webDestinations, 5500000000L, publisher, Source.Status.ACTIVE);
2569         List<Source> activeSourcesOutOfWindow =
2570                 getSourcesWithDifferentRegistrationOrigins(
2571                         10,
2572                         appDestinations,
2573                         webDestinations,
2574                         50000000000L,
2575                         publisher,
2576                         Source.Status.ACTIVE);
2577         List<Source> ignoredSources =
2578                 getSourcesWithDifferentRegistrationOrigins(
2579                         3,
2580                         appDestinations,
2581                         webDestinations,
2582                         5000000000L,
2583                         publisher,
2584                         Source.Status.IGNORED);
2585         for (Source source : activeSourcesWithAppAndWebDestinations) {
2586             insertSource(source);
2587         }
2588         for (Source source : activeSourcesWithAppDestinations) {
2589             insertSource(source);
2590         }
2591         for (Source source : activeSourcesWithWebDestinations) {
2592             insertSource(source);
2593         }
2594         for (Source source : activeSourcesOutOfWindow) {
2595             insertSource(source);
2596         }
2597         for (Source source : ignoredSources) {
2598             insertSource(source);
2599         }
2600         String excludedEnrollmentId = "enrollment-id-1";
2601         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2602         mDatastoreManager.runInTransaction(
2603                 measurementDao -> {
2604                     assertEquals(
2605                             Integer.valueOf(2),
2606                             measurementDao
2607                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2608                                             publisher,
2609                                             EventSurfaceType.APP,
2610                                             appDestinations,
2611                                             excludedReportingOrigin,
2612                                             4000000000L,
2613                                             6000000000L));
2614                 });
2615     }
2616 
2617     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_webDestination()2618     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_webDestination() {
2619         Uri publisher = Uri.parse("android-app://publisher.app");
2620         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2621         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2622         List<Source> activeSourcesWithAppAndWebDestinations =
2623                 getSourcesWithDifferentRegistrationOrigins(
2624                         2,
2625                         appDestinations,
2626                         webDestinations,
2627                         4500000000L,
2628                         publisher,
2629                         Source.Status.ACTIVE);
2630         List<Source> activeSourcesWithAppDestinations =
2631                 getSourcesWithDifferentRegistrationOrigins(
2632                         2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE);
2633         List<Source> activeSourcesWithWebDestinations =
2634                 getSourcesWithDifferentRegistrationOrigins(
2635                         2, null, webDestinations, 5500000000L, publisher, Source.Status.ACTIVE);
2636         List<Source> activeSourcesOutOfWindow =
2637                 getSourcesWithDifferentRegistrationOrigins(
2638                         10,
2639                         appDestinations,
2640                         webDestinations,
2641                         50000000000L,
2642                         publisher,
2643                         Source.Status.ACTIVE);
2644         List<Source> ignoredSources =
2645                 getSourcesWithDifferentRegistrationOrigins(
2646                         3,
2647                         appDestinations,
2648                         webDestinations,
2649                         5000000000L,
2650                         publisher,
2651                         Source.Status.IGNORED);
2652         for (Source source : activeSourcesWithAppAndWebDestinations) {
2653             insertSource(source);
2654         }
2655         for (Source source : activeSourcesWithAppDestinations) {
2656             insertSource(source);
2657         }
2658         for (Source source : activeSourcesWithWebDestinations) {
2659             insertSource(source);
2660         }
2661         for (Source source : activeSourcesOutOfWindow) {
2662             insertSource(source);
2663         }
2664         for (Source source : ignoredSources) {
2665             insertSource(source);
2666         }
2667         String excludedEnrollmentId = "enrollment-id-22";
2668         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain22.example.test");
2669         mDatastoreManager.runInTransaction(
2670                 measurementDao -> {
2671                     assertEquals(
2672                             Integer.valueOf(3),
2673                             measurementDao
2674                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2675                                             publisher,
2676                                             EventSurfaceType.WEB,
2677                                             webDestinations,
2678                                             excludedReportingOrigin,
2679                                             4000000000L,
2680                                             6000000000L));
2681                 });
2682     }
2683 
2684     // countDistinctEnrollmentsPerPublisherXDestinationInSource
2685     @Test
countDistinctReportingOriginsPerPublisher_webDestination_multipleDestinations()2686     public void countDistinctReportingOriginsPerPublisher_webDestination_multipleDestinations() {
2687         Uri publisher = Uri.parse("android-app://publisher.app");
2688         List<Uri> webDestinations1 = List.of(WebUtil.validUri("https://web-destination-1.test"));
2689         List<Uri> webDestinations2 =
2690                 List.of(
2691                         WebUtil.validUri("https://web-destination-1.test"),
2692                         WebUtil.validUri("https://web-destination-2.test"));
2693         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2694         List<Source> activeSourcesWithAppAndWebDestinations =
2695                 getSourcesWithDifferentRegistrationOrigins(
2696                         3,
2697                         appDestinations,
2698                         webDestinations1,
2699                         4500000000L,
2700                         publisher,
2701                         Source.Status.ACTIVE);
2702         List<Source> activeSourcesWithAppDestinations =
2703                 getSourcesWithDifferentRegistrationOrigins(
2704                         2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE);
2705         List<Source> activeSourcesWithWebDestinations =
2706                 getSourcesWithDifferentRegistrationOrigins(
2707                         2, null, webDestinations2, 5500000000L, publisher, Source.Status.ACTIVE);
2708         List<Source> activeSourcesOutOfWindow =
2709                 getSourcesWithDifferentRegistrationOrigins(
2710                         10,
2711                         appDestinations,
2712                         webDestinations2,
2713                         50000000000L,
2714                         publisher,
2715                         Source.Status.ACTIVE);
2716         List<Source> ignoredSources =
2717                 getSourcesWithDifferentRegistrationOrigins(
2718                         2,
2719                         appDestinations,
2720                         webDestinations1,
2721                         5000000000L,
2722                         publisher,
2723                         Source.Status.IGNORED);
2724         for (Source source : activeSourcesWithAppAndWebDestinations) {
2725             insertSource(source);
2726         }
2727         for (Source source : activeSourcesWithAppDestinations) {
2728             insertSource(source);
2729         }
2730         for (Source source : activeSourcesWithWebDestinations) {
2731             insertSource(source);
2732         }
2733         for (Source source : activeSourcesOutOfWindow) {
2734             insertSource(source);
2735         }
2736         for (Source source : ignoredSources) {
2737             insertSource(source);
2738         }
2739         String excludedEnrollmentId = "enrollment-id-1";
2740         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2741         mDatastoreManager.runInTransaction(
2742                 measurementDao -> {
2743                     assertEquals(
2744                             Integer.valueOf(2),
2745                             measurementDao
2746                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2747                                             publisher,
2748                                             EventSurfaceType.WEB,
2749                                             webDestinations2,
2750                                             excludedReportingOrigin,
2751                                             4000000000L,
2752                                             6000000000L));
2753                 });
2754     }
2755 
2756     @Test
testInstallAttribution_selectHighestPriority()2757     public void testInstallAttribution_selectHighestPriority() {
2758         long currentTimestamp = System.currentTimeMillis();
2759 
2760         insertSource(
2761                 createSourceForIATest(
2762                                 "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID)
2763                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2764                         .build(),
2765                 "IA1");
2766         insertSource(
2767                 createSourceForIATest("IA2", currentTimestamp, 50, -1, false, DEFAULT_ENROLLMENT_ID)
2768                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2769                         .build(),
2770                 "IA2");
2771         // Should select id IA1 because it has higher priority
2772         assertTrue(
2773                 mDatastoreManager.runInTransaction(
2774                         measurementDao -> {
2775                             measurementDao.doInstallAttribution(
2776                                     INSTALLED_PACKAGE, currentTimestamp);
2777                         }));
2778         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2779         assertTrue(getInstallAttributionStatus("IA1", db));
2780         assertFalse(getInstallAttributionStatus("IA2", db));
2781         removeSources(Arrays.asList("IA1", "IA2"), db);
2782     }
2783 
2784     @Test
testInstallAttribution_selectLatest()2785     public void testInstallAttribution_selectLatest() {
2786         long currentTimestamp = System.currentTimeMillis();
2787         insertSource(
2788                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2789                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2790                         .build(),
2791                 "IA1");
2792         insertSource(
2793                 createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID)
2794                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2795                         .build(),
2796                 "IA2");
2797         // Should select id=IA2 as it is latest
2798         assertTrue(
2799                 mDatastoreManager.runInTransaction(
2800                         measurementDao -> {
2801                             measurementDao.doInstallAttribution(
2802                                     INSTALLED_PACKAGE, currentTimestamp);
2803                         }));
2804         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2805         assertFalse(getInstallAttributionStatus("IA1", db));
2806         assertTrue(getInstallAttributionStatus("IA2", db));
2807 
2808         removeSources(Arrays.asList("IA1", "IA2"), db);
2809     }
2810 
2811     @Test
installAttribution_noCooldownWindow_ignoredToBeMarked()2812     public void installAttribution_noCooldownWindow_ignoredToBeMarked() {
2813         long currentTimestamp = System.currentTimeMillis();
2814         insertSource(
2815                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2816                         .setInstallCooldownWindow(0)
2817                         .build(),
2818                 "IA1");
2819         // Should select id=IA2 as it is latest
2820         assertTrue(
2821                 mDatastoreManager.runInTransaction(
2822                         measurementDao -> {
2823                             measurementDao.doInstallAttribution(
2824                                     INSTALLED_PACKAGE, currentTimestamp);
2825                         }));
2826         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2827         assertFalse(getInstallAttributionStatus("IA1", db));
2828 
2829         removeSources(Arrays.asList("IA1", "IA2"), db);
2830     }
2831 
2832     @Test
testInstallAttribution_ignoreNewerSources()2833     public void testInstallAttribution_ignoreNewerSources() {
2834         long currentTimestamp = System.currentTimeMillis();
2835         insertSource(
2836                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2837                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2838                         .build(),
2839                 "IA1");
2840         insertSource(
2841                 createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID)
2842                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2843                         .build(),
2844                 "IA2");
2845         // Should select id=IA1 as it is the only valid choice.
2846         // id=IA2 is newer than the evenTimestamp of install event.
2847         assertTrue(
2848                 mDatastoreManager.runInTransaction(
2849                         measurementDao -> {
2850                             measurementDao.doInstallAttribution(
2851                                     INSTALLED_PACKAGE, currentTimestamp - DAYS.toMillis(7));
2852                         }));
2853         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2854         assertTrue(getInstallAttributionStatus("IA1", db));
2855         assertFalse(getInstallAttributionStatus("IA2", db));
2856         removeSources(Arrays.asList("IA1", "IA2"), db);
2857     }
2858 
2859     @Test
testInstallAttribution_noValidSource()2860     public void testInstallAttribution_noValidSource() {
2861         long currentTimestamp = System.currentTimeMillis();
2862         insertSource(
2863                 createSourceForIATest("IA1", currentTimestamp, 10, 10, true, DEFAULT_ENROLLMENT_ID)
2864                         .build(),
2865                 "IA1");
2866         insertSource(
2867                 createSourceForIATest("IA2", currentTimestamp, 10, 11, true, DEFAULT_ENROLLMENT_ID)
2868                         .build(),
2869                 "IA2");
2870         // Should not update any sources.
2871         assertTrue(
2872                 mDatastoreManager.runInTransaction(
2873                         measurementDao ->
2874                                 measurementDao.doInstallAttribution(
2875                                         INSTALLED_PACKAGE, currentTimestamp)));
2876         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2877         assertFalse(getInstallAttributionStatus("IA1", db));
2878         assertFalse(getInstallAttributionStatus("IA2", db));
2879         removeSources(Arrays.asList("IA1", "IA2"), db);
2880     }
2881 
2882     @Test
installAttribution_install_installTimeEqualsEventTime()2883     public void installAttribution_install_installTimeEqualsEventTime() {
2884         long currentTimestamp = System.currentTimeMillis();
2885         insertSource(
2886                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2887                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2888                         .build(),
2889                 "IA1");
2890         assertTrue(
2891                 mDatastoreManager.runInTransaction(
2892                         measurementDao -> {
2893                             measurementDao.doInstallAttribution(
2894                                     INSTALLED_PACKAGE, currentTimestamp - DAYS.toMillis(7));
2895                         }));
2896         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2897         assertEquals(
2898                 currentTimestamp - DAYS.toMillis(7),
2899                 getInstallAttributionInstallTime("IA1", db).longValue());
2900         removeSources(Arrays.asList("IA1"), db);
2901     }
2902 
2903     @Test
2904     public void
testInstallAttribution_reinstallReattributionEnabled_skipsAttributionForReinstall()2905             testInstallAttribution_reinstallReattributionEnabled_skipsAttributionForReinstall() {
2906         mocker.mockGetFlags(mMockFlags);
2907         doReturn(true).when(mMockFlags).getMeasurementEnableReinstallReattribution();
2908         long currentTimestamp = System.currentTimeMillis();
2909         long reinstallWindow = DAYS.toMillis(50);
2910         insertSource(
2911                 createSourceForIATest(
2912                                 /* id= */ "IA1",
2913                                 currentTimestamp,
2914                                 /* priority= */ -1,
2915                                 /* eventTimePastDays= */ 10,
2916                                 /* expiredIAWindow= */ false,
2917                                 DEFAULT_ENROLLMENT_ID)
2918                         .setReinstallReattributionWindow(reinstallWindow)
2919                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2920                         .setInstallAttributed(true)
2921                         .build(),
2922                 /* sourceId= */ "IA1");
2923 
2924         insertSource(
2925                 createSourceForIATest(
2926                                 /* id= */ "IA2",
2927                                 currentTimestamp,
2928                                 /* priority= */ -1,
2929                                 /* eventTimePastDays= */ 5,
2930                                 /* expiredIAWindow= */ false,
2931                                 DEFAULT_ENROLLMENT_ID)
2932                         .setReinstallReattributionWindow(reinstallWindow)
2933                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2934                         .build(),
2935                 /* sourceId= */ "IA2");
2936         insertSource(
2937                 createSourceForIATest(
2938                                 /* id= */ "IA3",
2939                                 currentTimestamp,
2940                                 /* priority= */ -1,
2941                                 /* eventTimePastDays= */ 10,
2942                                 /* expiredIAWindow= */ false,
2943                                 ENROLLMENT_ID1,
2944                                 REGISTRATION_ORIGIN_2)
2945                         .setReinstallReattributionWindow(reinstallWindow)
2946                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2947                         .build(),
2948                 /* sourceId= */ "IA3");
2949         insertSource(
2950                 createSourceForIATest(
2951                                 /* id= */ "IA4",
2952                                 currentTimestamp,
2953                                 /* priority= */ -1,
2954                                 /* eventTimePastDays= */ 5,
2955                                 /* expiredIAWindow= */ false,
2956                                 ENROLLMENT_ID1,
2957                                 REGISTRATION_ORIGIN_2)
2958                         .setReinstallReattributionWindow(reinstallWindow)
2959                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2960                         .build(),
2961                 /* sourceId= */ "IA4");
2962         assertTrue(
2963                 mDatastoreManager.runInTransaction(
2964                         measurementDao -> {
2965                             measurementDao.insertOrUpdateAppReportHistory(
2966                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
2967                         }));
2968 
2969         assertTrue(
2970                 mDatastoreManager.runInTransaction(
2971                         measurementDao -> {
2972                             measurementDao.doInstallAttribution(
2973                                     INSTALLED_PACKAGE, currentTimestamp);
2974                         }));
2975         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2976         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
2977         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
2978         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA3", db));
2979         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA4", db));
2980 
2981         removeSources(Arrays.asList("IA1", "IA2", "IA3", "IA4"), db);
2982     }
2983 
2984     @Test
testInstallAttribution_reinstallReattributionDisabled_doesNotSkipReinstall()2985     public void testInstallAttribution_reinstallReattributionDisabled_doesNotSkipReinstall() {
2986         mocker.mockGetFlags(mMockFlags);
2987         doReturn(false).when(mMockFlags).getMeasurementEnableReinstallReattribution();
2988         long currentTimestamp = System.currentTimeMillis();
2989         insertSource(
2990                 createSourceForIATest(
2991                                 /* id= */ "IA1",
2992                                 currentTimestamp,
2993                                 /* priority= */ -1,
2994                                 /* eventTimePastDays= */ 10,
2995                                 /* expiredIAWindow= */ false,
2996                                 DEFAULT_ENROLLMENT_ID)
2997                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2998                         .setInstallAttributed(true)
2999                         .build(),
3000                 "IA1");
3001         insertSource(
3002                 createSourceForIATest(
3003                                 /* id= */ "IA2",
3004                                 currentTimestamp,
3005                                 /* priority= */ -1,
3006                                 /* eventTimePastDays= */ 5,
3007                                 /* expiredIAWindow= */ false,
3008                                 DEFAULT_ENROLLMENT_ID)
3009                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3010                         .build(),
3011                 "IA2");
3012         insertSource(
3013                 createSourceForIATest(
3014                                 /* id= */ "IA3",
3015                                 currentTimestamp,
3016                                 /* priority= */ -1,
3017                                 /* eventTimePastDays= */ 10,
3018                                 /* expiredIAWindow= */ false,
3019                                 ENROLLMENT_ID1,
3020                                 REGISTRATION_ORIGIN_2)
3021                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3022                         .build(),
3023                 "IA3");
3024         insertSource(
3025                 createSourceForIATest(
3026                                 /* id= */ "IA4",
3027                                 currentTimestamp,
3028                                 /* priority= */ -1,
3029                                 /* eventTimePastDays= */ 5,
3030                                 /* expiredIAWindow= */ false,
3031                                 ENROLLMENT_ID1,
3032                                 REGISTRATION_ORIGIN_2)
3033                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3034                         .build(),
3035                 "IA4");
3036         assertTrue(
3037                 mDatastoreManager.runInTransaction(
3038                         measurementDao -> {
3039                             measurementDao.insertOrUpdateAppReportHistory(
3040                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
3041                         }));
3042         // Should select id=IA2 as it is latest
3043         assertTrue(
3044                 mDatastoreManager.runInTransaction(
3045                         measurementDao -> {
3046                             measurementDao.doInstallAttribution(
3047                                     INSTALLED_PACKAGE, currentTimestamp);
3048                         }));
3049         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3050         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
3051         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
3052         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA3", db));
3053         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA4", db));
3054 
3055         removeSources(Arrays.asList("IA1", "IA2", "IA3", "IA4"), db);
3056     }
3057 
3058     @Test
3059     public void
testInstallAttribution_reinstallReattributionEnabledNoWindow_doesNotSkipReinstall()3060             testInstallAttribution_reinstallReattributionEnabledNoWindow_doesNotSkipReinstall() {
3061         mocker.mockGetFlags(mMockFlags);
3062         doReturn(true).when(mMockFlags).getMeasurementEnableReinstallReattribution();
3063         long currentTimestamp = System.currentTimeMillis();
3064         insertSource(
3065                 createSourceForIATest(
3066                                 /* id= */ "IA1",
3067                                 currentTimestamp,
3068                                 /* priority= */ -1,
3069                                 /* eventTimePastDays= */ 10,
3070                                 /* expiredIAWindow= */ false,
3071                                 DEFAULT_ENROLLMENT_ID)
3072                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3073                         .setReinstallReattributionWindow(0L)
3074                         .setInstallAttributed(true)
3075                         .build(),
3076                 "IA1");
3077         insertSource(
3078                 createSourceForIATest(
3079                                 /* id= */ "IA2",
3080                                 currentTimestamp,
3081                                 /* priority= */ -1,
3082                                 /* eventTimePastDays= */ 5,
3083                                 /* expiredIAWindow= */ false,
3084                                 DEFAULT_ENROLLMENT_ID)
3085                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3086                         .setReinstallReattributionWindow(0L)
3087                         .build(),
3088                 "IA2");
3089         insertSource(
3090                 createSourceForIATest(
3091                                 /* id= */ "IA3",
3092                                 currentTimestamp,
3093                                 /* priority= */ -1,
3094                                 /* eventTimePastDays= */ 10,
3095                                 /* expiredIAWindow= */ false,
3096                                 ENROLLMENT_ID1,
3097                                 REGISTRATION_ORIGIN_2)
3098                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3099                         .setReinstallReattributionWindow(0L)
3100                         .build(),
3101                 "IA3");
3102         insertSource(
3103                 createSourceForIATest(
3104                                 /* id= */ "IA4",
3105                                 currentTimestamp,
3106                                 /* priority= */ -1,
3107                                 /* eventTimePastDays= */ 5,
3108                                 /* expiredIAWindow= */ false,
3109                                 ENROLLMENT_ID1,
3110                                 REGISTRATION_ORIGIN_2)
3111                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3112                         .setReinstallReattributionWindow(0L)
3113                         .build(),
3114                 "IA4");
3115         assertTrue(
3116                 mDatastoreManager.runInTransaction(
3117                         measurementDao -> {
3118                             measurementDao.insertOrUpdateAppReportHistory(
3119                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
3120                         }));
3121         // Should select id=IA2 as it is latest
3122         assertTrue(
3123                 mDatastoreManager.runInTransaction(
3124                         measurementDao -> {
3125                             measurementDao.doInstallAttribution(
3126                                     INSTALLED_PACKAGE, currentTimestamp);
3127                         }));
3128         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3129         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
3130         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
3131         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA3", db));
3132         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA4", db));
3133 
3134         removeSources(Arrays.asList("IA1", "IA2", "IA3", "IA4"), db);
3135     }
3136 
3137     @Test
testInstallAttribution_reinstallReattributionEnabledNoReinstall_doesNotSkip()3138     public void testInstallAttribution_reinstallReattributionEnabledNoReinstall_doesNotSkip() {
3139         mocker.mockGetFlags(mMockFlags);
3140         doReturn(true).when(mMockFlags).getMeasurementEnableReinstallReattribution();
3141         long currentTimestamp = System.currentTimeMillis();
3142         insertSource(
3143                 createSourceForIATest(
3144                                 /* id= */ "IA1",
3145                                 currentTimestamp,
3146                                 /* priority= */ -1,
3147                                 /* eventTimePastDays= */ 10,
3148                                 /* expiredIAWindow= */ false,
3149                                 DEFAULT_ENROLLMENT_ID)
3150                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3151                         .setReinstallReattributionWindow(0L)
3152                         .build(),
3153                 "IA1");
3154         insertSource(
3155                 createSourceForIATest(
3156                                 /* id= */ "IA2",
3157                                 currentTimestamp,
3158                                 /* priority= */ -1,
3159                                 /* eventTimePastDays= */ 5,
3160                                 /* expiredIAWindow= */ false,
3161                                 DEFAULT_ENROLLMENT_ID)
3162                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3163                         .setReinstallReattributionWindow(0L)
3164                         .build(),
3165                 "IA2");
3166 
3167         assertTrue(
3168                 mDatastoreManager.runInTransaction(
3169                         measurementDao -> {
3170                             measurementDao.insertOrUpdateAppReportHistory(
3171                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
3172                         }));
3173         // Should select id=IA2 as it is latest
3174         assertTrue(
3175                 mDatastoreManager.runInTransaction(
3176                         measurementDao -> {
3177                             measurementDao.doInstallAttribution(
3178                                     INSTALLED_PACKAGE, currentTimestamp);
3179                         }));
3180         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3181         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
3182         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
3183 
3184         removeSources(Arrays.asList("IA1", "IA2"), db);
3185     }
3186 
3187     @Test
doInstallAttribution_noValidSourceStatus_IgnoresSources()3188     public void doInstallAttribution_noValidSourceStatus_IgnoresSources() {
3189         long currentTimestamp = System.currentTimeMillis();
3190         Source source =
3191                 createSourceForIATest(
3192                                 "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID)
3193                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3194                         .build();
3195 
3196         // Execution
3197         // Active source should get install attributed
3198         source.setStatus(Source.Status.ACTIVE);
3199         insertSource(source, source.getId());
3200         assertTrue(
3201                 mDatastoreManager.runInTransaction(
3202                         measurementDao ->
3203                                 measurementDao.doInstallAttribution(
3204                                         INSTALLED_PACKAGE, currentTimestamp)));
3205         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3206         assertTrue(getInstallAttributionStatus("IA1", db));
3207         removeSources(Collections.singletonList("IA1"), db);
3208 
3209         // Active source should not get install attributed
3210         source.setStatus(Source.Status.IGNORED);
3211         insertSource(source, source.getId());
3212         assertTrue(
3213                 mDatastoreManager.runInTransaction(
3214                         measurementDao ->
3215                                 measurementDao.doInstallAttribution(
3216                                         INSTALLED_PACKAGE, currentTimestamp)));
3217         assertFalse(getInstallAttributionStatus("IA1", db));
3218         removeSources(Collections.singletonList("IA1"), db);
3219 
3220         // MARKED_TO_DELETE source should not get install attributed
3221         source.setStatus(Source.Status.MARKED_TO_DELETE);
3222         insertSource(source, source.getId());
3223         assertTrue(
3224                 mDatastoreManager.runInTransaction(
3225                         measurementDao ->
3226                                 measurementDao.doInstallAttribution(
3227                                         INSTALLED_PACKAGE, currentTimestamp)));
3228         assertFalse(getInstallAttributionStatus("IA1", db));
3229         removeSources(Collections.singletonList("IA1"), db);
3230     }
3231 
3232     @Test
3233     public void
doInstallAttribution_withSourcesAcrossEnrollments_marksOneInstallFromEachRegOrigin()3234             doInstallAttribution_withSourcesAcrossEnrollments_marksOneInstallFromEachRegOrigin() {
3235         long currentTimestamp = System.currentTimeMillis();
3236 
3237         // Enrollment1: Choose IA2 because that's newer and still occurred before install
3238         insertSource(
3239                 createSourceForIATest(
3240                                 "IA1",
3241                                 currentTimestamp,
3242                                 -1,
3243                                 10,
3244                                 false,
3245                                 DEFAULT_ENROLLMENT_ID + "_1")
3246                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example1.test"))
3247                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3248                         .build(),
3249                 "IA1");
3250         insertSource(
3251                 createSourceForIATest(
3252                                 "IA2", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_1")
3253                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example1.test"))
3254                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3255                         .build(),
3256                 "IA2");
3257 
3258         // Enrollment2: Choose IA4 because IA3's install attribution window has expired
3259         insertSource(
3260                 createSourceForIATest(
3261                                 "IA3", currentTimestamp, -1, 10, true, DEFAULT_ENROLLMENT_ID + "_2")
3262                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example2.test"))
3263                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3264                         .build(),
3265                 "IA3");
3266         insertSource(
3267                 createSourceForIATest(
3268                                 "IA4", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_2")
3269                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example2.test"))
3270                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3271                         .build(),
3272                 "IA4");
3273 
3274         // Enrollment3: Choose IA5 because IA6 was registered after install event
3275         insertSource(
3276                 createSourceForIATest(
3277                                 "IA5",
3278                                 currentTimestamp,
3279                                 -1,
3280                                 10,
3281                                 false,
3282                                 DEFAULT_ENROLLMENT_ID + "_3")
3283                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example3.test"))
3284                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3285                         .build(),
3286                 "IA5");
3287         insertSource(
3288                 createSourceForIATest(
3289                                 "IA6", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID + "_3")
3290                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example3.test"))
3291                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3292                         .build(),
3293                 "IA6");
3294 
3295         // Enrollment4: Choose IA8 due to higher priority
3296         insertSource(
3297                 createSourceForIATest(
3298                                 "IA7", currentTimestamp, 5, 10, false, DEFAULT_ENROLLMENT_ID + "_4")
3299                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example4.test"))
3300                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3301                         .build(),
3302                 "IA7");
3303         insertSource(
3304                 createSourceForIATest(
3305                                 "IA8",
3306                                 currentTimestamp,
3307                                 10,
3308                                 10,
3309                                 false,
3310                                 DEFAULT_ENROLLMENT_ID + "_4")
3311                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example4.test"))
3312                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3313                         .build(),
3314                 "IA8");
3315 
3316         // Enrollment5: Choose none because both sources are ineligible
3317         // Expired install attribution window
3318         insertSource(
3319                 createSourceForIATest(
3320                                 "IA9", currentTimestamp, 5, 31, true, DEFAULT_ENROLLMENT_ID + "_5")
3321                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example5.test"))
3322                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3323                         .build(),
3324                 "IA9");
3325         // Registered after install attribution
3326         insertSource(
3327                 createSourceForIATest(
3328                                 "IA10",
3329                                 currentTimestamp,
3330                                 10,
3331                                 3,
3332                                 false,
3333                                 DEFAULT_ENROLLMENT_ID + "_5")
3334                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example5.test"))
3335                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3336                         .build(),
3337                 "IA10");
3338 
3339         assertTrue(
3340                 mDatastoreManager.runInTransaction(
3341                         measurementDao -> {
3342                             measurementDao.doInstallAttribution(
3343                                     INSTALLED_PACKAGE, currentTimestamp - DAYS.toMillis(7));
3344                         }));
3345         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3346         assertTrue(getInstallAttributionStatus("IA2", db));
3347         assertTrue(getInstallAttributionStatus("IA4", db));
3348         assertTrue(getInstallAttributionStatus("IA5", db));
3349         assertTrue(getInstallAttributionStatus("IA8", db));
3350 
3351         assertFalse(getInstallAttributionStatus("IA1", db));
3352         assertFalse(getInstallAttributionStatus("IA3", db));
3353         assertFalse(getInstallAttributionStatus("IA6", db));
3354         assertFalse(getInstallAttributionStatus("IA7", db));
3355         assertFalse(getInstallAttributionStatus("IA9", db));
3356         assertFalse(getInstallAttributionStatus("IA10", db));
3357 
3358         removeSources(
3359                 Arrays.asList(
3360                         "IA1", "IA2", "IA3", "IA4", "IA5", "IA6", "IA7", "IA8", "IA8", "IA10"),
3361                 db);
3362     }
3363 
3364     @Test
testInsertCountUniqueReport_forValidEvent_isSuccess()3365     public void testInsertCountUniqueReport_forValidEvent_isSuccess() {
3366         String reportId = "reportId";
3367         String payload = "payload";
3368         Uri reportingOrigin = Uri.parse("https://test.foo");
3369         int status = CountUniqueReport.ReportDeliveryStatus.PENDING;
3370         int debugStatus = CountUniqueReport.ReportDeliveryStatus.PENDING;
3371         Long scheduledReportTime = 1726874188124L;
3372         String version = "0.1";
3373         String debugKey = "asadsadsa=";
3374         String contextId = "testContextId";
3375         String enrollmentId = "test-id";
3376         int contributionValue = 5;
3377         long contributionTime = 1726874188232L;
3378 
3379         CountUniqueReport report =
3380                 createCountUniqueReport(
3381                         reportId,
3382                         payload,
3383                         reportingOrigin,
3384                         status,
3385                         debugStatus,
3386                         scheduledReportTime,
3387                         version,
3388                         debugKey,
3389                         contextId,
3390                         enrollmentId,
3391                         contributionValue,
3392                         contributionTime);
3393 
3394         boolean result =
3395                 mDatastoreManager.runInTransaction(
3396                         (dao) -> {
3397                             dao.insertCountUniqueReport(report);
3398                         });
3399         assertThat(result).isTrue();
3400         try (Cursor cursor =
3401                 MeasurementDbHelper.getInstance()
3402                         .getReadableDatabase()
3403                         .query(
3404                                 MeasurementTables.CountUniqueReportingContract.TABLE,
3405                                 null,
3406                                 null,
3407                                 null,
3408                                 null,
3409                                 null,
3410                                 null)) {
3411 
3412             assertThat(cursor.getCount()).isEqualTo(1);
3413             List<CountUniqueReport> reports = new ArrayList<>();
3414 
3415             while (cursor.moveToNext()) {
3416                 CountUniqueReport reportFromDb =
3417                         SqliteObjectMapper.constructCountUniqueReport(cursor);
3418                 reports.add(reportFromDb);
3419             }
3420 
3421             for (CountUniqueReport r : reports) {
3422                 assertThat(r.getReportId()).isEqualTo(reportId);
3423                 assertThat(r.getPayload()).isEqualTo(payload);
3424                 assertThat(r.getReportingOrigin()).isEqualTo(reportingOrigin);
3425                 assertThat(r.getStatus()).isEqualTo(status);
3426                 assertThat(r.getDebugReportStatus()).isEqualTo(debugStatus);
3427                 assertThat(r.getScheduledReportTime()).isEqualTo(scheduledReportTime);
3428                 assertThat(r.getApiVersion()).isEqualTo(version);
3429                 assertThat(r.getDebugKey()).isEqualTo(debugKey);
3430                 assertThat(r.getContextId()).isEqualTo(contextId);
3431                 assertThat(r.getEnrollmentId()).isEqualTo(enrollmentId);
3432                 assertThat(r.getContributionValue()).isEqualTo(contributionValue);
3433                 assertThat(r.getContributionTime()).isEqualTo(contributionTime);
3434             }
3435         }
3436     }
3437 
3438     @Test
testInsertCountUniqueMetadata_setIgnoreIfPresentTrue_ignoresIfPresent()3439     public void testInsertCountUniqueMetadata_setIgnoreIfPresentTrue_ignoresIfPresent() {
3440         String key = "key1";
3441         Integer value1 = 1;
3442         Integer value2 = 2;
3443         Long expirationTime = 1726874188124L;
3444         Uri reportingOrigin = Uri.parse("https://test.foo");
3445 
3446         CountUniqueMetadata metadata =
3447                 new CountUniqueMetadata.Builder()
3448                         .setKey(key)
3449                         .setValue(value1)
3450                         .setExpirationTime(expirationTime)
3451                         .setReportingOrigin(reportingOrigin)
3452                         .build();
3453         CountUniqueMetadata metadata2 =
3454                 new CountUniqueMetadata.Builder()
3455                         .setKey(key)
3456                         .setValue(value2)
3457                         .setExpirationTime(expirationTime)
3458                         .setReportingOrigin(reportingOrigin)
3459                         .build();
3460 
3461         boolean result =
3462                 mDatastoreManager.runInTransaction(
3463                         (dao) -> {
3464                             dao.insertCountUniqueMetadata(metadata, true);
3465                         });
3466         assertThat(result).isTrue();
3467         try (Cursor cursor =
3468                 MeasurementDbHelper.getInstance()
3469                         .getReadableDatabase()
3470                         .query(
3471                                 MeasurementTables.CountUniqueMetadataContract.TABLE,
3472                                 null,
3473                                 null,
3474                                 null,
3475                                 null,
3476                                 null,
3477                                 null)) {
3478 
3479             assertThat(cursor.getCount()).isEqualTo(1);
3480             cursor.moveToNext();
3481             CountUniqueMetadata metadataFromDb =
3482                     SqliteObjectMapper.constructCountUniqueMetadata(cursor);
3483             assertThat(metadataFromDb.getKey()).isEqualTo(key);
3484             assertThat(metadataFromDb.getValue()).isEqualTo(value1);
3485             assertThat(metadataFromDb.getReportingOrigin()).isEqualTo(reportingOrigin);
3486             assertThat(metadataFromDb.getExpirationTime()).isEqualTo(expirationTime);
3487         }
3488 
3489         result =
3490                 mDatastoreManager.runInTransaction(
3491                         (dao) -> {
3492                             dao.insertCountUniqueMetadata(metadata2, true);
3493                         });
3494         assertThat(result).isTrue();
3495         try (Cursor cursor =
3496                 MeasurementDbHelper.getInstance()
3497                         .getReadableDatabase()
3498                         .query(
3499                                 MeasurementTables.CountUniqueMetadataContract.TABLE,
3500                                 null,
3501                                 null,
3502                                 null,
3503                                 null,
3504                                 null,
3505                                 null)) {
3506 
3507             assertThat(cursor.getCount()).isEqualTo(1);
3508             cursor.moveToNext();
3509             CountUniqueMetadata metadataFromDb =
3510                     SqliteObjectMapper.constructCountUniqueMetadata(cursor);
3511             assertThat(metadataFromDb.getKey()).isEqualTo(key);
3512             assertThat(metadataFromDb.getValue()).isEqualTo(value1); // should not be value2
3513             assertThat(metadataFromDb.getReportingOrigin()).isEqualTo(reportingOrigin);
3514             assertThat(metadataFromDb.getExpirationTime()).isEqualTo(expirationTime);
3515         }
3516     }
3517 
3518     @Test
testInsertCountUniqueMetadata_setIgnoreIfPresentFalse_updatesRecord()3519     public void testInsertCountUniqueMetadata_setIgnoreIfPresentFalse_updatesRecord() {
3520         String key = "key1";
3521         Integer value1 = 1;
3522         Integer value2 = 2;
3523         Long expirationTime = 1726874188124L;
3524         Uri reportingOrigin = Uri.parse("https://test.foo");
3525 
3526         CountUniqueMetadata metadata1 =
3527                 new CountUniqueMetadata.Builder()
3528                         .setKey(key)
3529                         .setValue(value1)
3530                         .setExpirationTime(expirationTime)
3531                         .setReportingOrigin(reportingOrigin)
3532                         .build();
3533 
3534         CountUniqueMetadata metadata2 =
3535                 new CountUniqueMetadata.Builder()
3536                         .setKey(key)
3537                         .setValue(value2)
3538                         .setExpirationTime(expirationTime)
3539                         .setReportingOrigin(reportingOrigin)
3540                         .build();
3541 
3542         boolean result =
3543                 mDatastoreManager.runInTransaction(
3544                         (dao) -> {
3545                             dao.insertCountUniqueMetadata(metadata1, false);
3546                         });
3547         assertThat(result).isTrue();
3548         try (Cursor cursor =
3549                 MeasurementDbHelper.getInstance()
3550                         .getReadableDatabase()
3551                         .query(
3552                                 MeasurementTables.CountUniqueMetadataContract.TABLE,
3553                                 null,
3554                                 null,
3555                                 null,
3556                                 null,
3557                                 null,
3558                                 null)) {
3559 
3560             assertThat(cursor.getCount()).isEqualTo(1);
3561             cursor.moveToNext();
3562             CountUniqueMetadata metadataFromDb =
3563                     SqliteObjectMapper.constructCountUniqueMetadata(cursor);
3564             assertThat(metadataFromDb.getKey()).isEqualTo(key);
3565             assertThat(metadataFromDb.getValue()).isEqualTo(value1);
3566             assertThat(metadataFromDb.getReportingOrigin()).isEqualTo(reportingOrigin);
3567             assertThat(metadataFromDb.getExpirationTime()).isEqualTo(expirationTime);
3568         }
3569 
3570         result =
3571                 mDatastoreManager.runInTransaction(
3572                         (dao) -> {
3573                             dao.insertCountUniqueMetadata(metadata2, false);
3574                         });
3575         assertThat(result).isTrue();
3576         try (Cursor cursor =
3577                 MeasurementDbHelper.getInstance()
3578                         .getReadableDatabase()
3579                         .query(
3580                                 MeasurementTables.CountUniqueMetadataContract.TABLE,
3581                                 null,
3582                                 null,
3583                                 null,
3584                                 null,
3585                                 null,
3586                                 null)) {
3587 
3588             assertThat(cursor.getCount()).isEqualTo(1);
3589             cursor.moveToNext();
3590             CountUniqueMetadata metadataFromDb =
3591                     SqliteObjectMapper.constructCountUniqueMetadata(cursor);
3592             assertThat(metadataFromDb.getKey()).isEqualTo(key);
3593             assertThat(metadataFromDb.getValue()).isEqualTo(value2); // record updated to value2
3594             assertThat(metadataFromDb.getReportingOrigin()).isEqualTo(reportingOrigin);
3595             assertThat(metadataFromDb.getExpirationTime()).isEqualTo(expirationTime);
3596         }
3597     }
3598 
3599     @Test
testDeleteCountUniqueMetadata_forKeyAndOrigin_deletesRecord()3600     public void testDeleteCountUniqueMetadata_forKeyAndOrigin_deletesRecord() {
3601         String key1 = "key1";
3602         String key2 = "key2";
3603         Integer value1 = 1;
3604         Integer value2 = 2;
3605         Long expirationTime = 1726874188124L;
3606         Uri reportingOrigin = Uri.parse("https://test.foo");
3607 
3608         CountUniqueMetadata metadata1 =
3609                 new CountUniqueMetadata.Builder()
3610                         .setKey(key1)
3611                         .setValue(value1)
3612                         .setExpirationTime(expirationTime)
3613                         .setReportingOrigin(reportingOrigin)
3614                         .build();
3615         CountUniqueMetadata metadata2 =
3616                 new CountUniqueMetadata.Builder()
3617                         .setKey(key2)
3618                         .setValue(value2)
3619                         .setExpirationTime(expirationTime)
3620                         .setReportingOrigin(reportingOrigin)
3621                         .build();
3622 
3623         boolean result =
3624                 mDatastoreManager.runInTransaction(
3625                         (dao) -> {
3626                             dao.insertCountUniqueMetadata(metadata1, false);
3627                             dao.insertCountUniqueMetadata(metadata2, false);
3628                         });
3629         assertThat(result).isTrue();
3630         try (Cursor cursor =
3631                 MeasurementDbHelper.getInstance()
3632                         .getReadableDatabase()
3633                         .query(
3634                                 MeasurementTables.CountUniqueMetadataContract.TABLE,
3635                                 null,
3636                                 null,
3637                                 null,
3638                                 null,
3639                                 null,
3640                                 null)) {
3641             assertThat(cursor.getCount()).isEqualTo(2);
3642         }
3643 
3644         result =
3645                 mDatastoreManager.runInTransaction(
3646                         (dao) -> {
3647                             dao.deleteCountUniqueMetadata(key1, reportingOrigin);
3648                         });
3649         assertThat(result).isTrue();
3650         try (Cursor cursor =
3651                 MeasurementDbHelper.getInstance()
3652                         .getReadableDatabase()
3653                         .query(
3654                                 MeasurementTables.CountUniqueMetadataContract.TABLE,
3655                                 null,
3656                                 null,
3657                                 null,
3658                                 null,
3659                                 null,
3660                                 null)) {
3661             assertThat(cursor.getCount()).isEqualTo(1);
3662         }
3663 
3664         // verify if deletion for key that does not exist fails silently
3665         result =
3666                 mDatastoreManager.runInTransaction(
3667                         (dao) -> {
3668                             dao.deleteCountUniqueMetadata(key1, reportingOrigin);
3669                         });
3670         assertThat(result).isTrue();
3671         try (Cursor cursor =
3672                 MeasurementDbHelper.getInstance()
3673                         .getReadableDatabase()
3674                         .query(
3675                                 MeasurementTables.CountUniqueMetadataContract.TABLE,
3676                                 null,
3677                                 null,
3678                                 null,
3679                                 null,
3680                                 null,
3681                                 null)) {
3682             assertThat(cursor.getCount()).isEqualTo(1);
3683         }
3684     }
3685 
3686     @Test
testGetCountUniqueMetadata_IfPresent_returnsMetadata()3687     public void testGetCountUniqueMetadata_IfPresent_returnsMetadata() {
3688         String key = "key1";
3689         Integer value1 = 1;
3690         Long expirationTime = 1726874188124L;
3691         Uri reportingOrigin = Uri.parse("https://test.foo");
3692 
3693         CountUniqueMetadata m =
3694                 new CountUniqueMetadata.Builder()
3695                         .setKey(key)
3696                         .setValue(value1)
3697                         .setExpirationTime(expirationTime)
3698                         .setReportingOrigin(reportingOrigin)
3699                         .build();
3700 
3701         mDatastoreManager.runInTransaction(
3702                 (dao) -> {
3703                     dao.insertCountUniqueMetadata(m, true);
3704                     CountUniqueMetadata metadata = dao.getCountUniqueMetadata(key, reportingOrigin);
3705                     assertThat(metadata.getKey()).isEqualTo(key);
3706                     assertThat(metadata.getValue()).isEqualTo(value1);
3707                     assertThat(metadata.getReportingOrigin()).isEqualTo(reportingOrigin);
3708                     assertThat(metadata.getExpirationTime()).isEqualTo(expirationTime);
3709                 });
3710     }
3711 
3712     @Test
testGetCountUniqueMetadata_IfAbsent_throwsException()3713     public void testGetCountUniqueMetadata_IfAbsent_throwsException() {
3714         String key = "key1";
3715         String key2 = "key2";
3716         Integer value1 = 1;
3717         Long expirationTime = 1726874188124L;
3718         Uri reportingOrigin = Uri.parse("https://test.foo");
3719 
3720         CountUniqueMetadata m =
3721                 new CountUniqueMetadata.Builder()
3722                         .setKey(key)
3723                         .setValue(value1)
3724                         .setExpirationTime(expirationTime)
3725                         .setReportingOrigin(reportingOrigin)
3726                         .build();
3727 
3728         mDatastoreManager.runInTransaction(
3729                 (dao) -> {
3730                     dao.insertCountUniqueMetadata(m, true);
3731                     assertThrows(
3732                             DatastoreException.class,
3733                             () -> dao.getCountUniqueMetadata(key2, reportingOrigin));
3734                 });
3735     }
3736 
3737     @Test
deleteFlexEventReportsAndAttributions_success()3738     public void deleteFlexEventReportsAndAttributions_success() throws Exception {
3739         // Setup - Creates the following -
3740         // source - S1, S2
3741         // trigger - T1, T2, T3
3742         // event reports - (E13, E23) (E11_1, E11_2, E11_3) (E21) (E22_1, E22_2)
3743         // attributions
3744         //    (ATT11_00 (aggregate scope), ATT11_01 (aggregate scope))
3745         //    (ATT11_1, ATT11_2, ATT11_3) (ATT21) (ATT22_1, ATT22_2)
3746         prepareDataForFlexEventReportAndAttributionDeletion();
3747 
3748         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3749 
3750         // Assert attributions present
3751         assertNotNull(getAttribution("ATT11_1", db));
3752         assertNotNull(getAttribution("ATT11_2", db));
3753         assertNotNull(getAttribution("ATT11_3", db));
3754         assertNotNull(getAttribution("ATT21", db));
3755         assertNotNull(getAttribution("ATT22_1", db));
3756         assertNotNull(getAttribution("ATT22_2", db));
3757 
3758         mDatastoreManager.runInTransaction(
3759                 measurementDao -> {
3760                     // Assert sources and triggers present
3761                     assertNotNull(measurementDao.getSource("S1"));
3762                     assertNotNull(measurementDao.getSource("S2"));
3763                     assertNotNull(measurementDao.getTrigger("T1"));
3764                     assertNotNull(measurementDao.getTrigger("T2"));
3765 
3766                     // Validate presence of unmatched event reports
3767                     measurementDao.getEventReport("E13");
3768                     measurementDao.getEventReport("E23");
3769 
3770                     // Event report group 1
3771                     // Validate event reports present
3772                     EventReport e111 = measurementDao.getEventReport("E11_1");
3773                     EventReport e112 = measurementDao.getEventReport("E11_2");
3774 
3775                     // Deletion
3776                     measurementDao.deleteFlexEventReportsAndAttributions(
3777                             List.of(e111, e112));
3778 
3779                     // Validate event report deletion
3780                     assertThrows(
3781                             DatastoreException.class,
3782                             () -> {
3783                                 measurementDao.getEventReport("E11_1");
3784                             });
3785                     assertThrows(
3786                             DatastoreException.class,
3787                             () -> {
3788                                 measurementDao.getEventReport("E11_2");
3789                             });
3790                     assertNotNull(measurementDao.getEventReport("E11_3"));
3791 
3792                     // Event report group 2
3793                     // Validate event reports present
3794                     EventReport e21 = measurementDao.getEventReport("E21");
3795 
3796                     // Deletion
3797                     measurementDao.deleteFlexEventReportsAndAttributions(
3798                             List.of(e21));
3799 
3800                     // Validate event report deletion
3801                     assertThrows(
3802                             DatastoreException.class,
3803                             () -> {
3804                                 measurementDao.getEventReport("E21");
3805                             });
3806 
3807                     // Event report group 3
3808                     // Validate event reports present (retrieval doesn't throw)
3809                     measurementDao.getEventReport("E22_1");
3810                     EventReport e222 = measurementDao.getEventReport("E22_2");
3811 
3812                     // Deletion
3813                     measurementDao.deleteFlexEventReportsAndAttributions(
3814                             List.of(e222));
3815 
3816                     // Validate event report deletion
3817                     assertThrows(
3818                             DatastoreException.class,
3819                             () -> {
3820                                 measurementDao.getEventReport("E22_2");
3821                             });
3822                     assertNotNull(measurementDao.getEventReport("E22_1"));
3823 
3824                     // Validate sources and triggers present
3825                     assertNotNull(measurementDao.getSource("S1"));
3826                     assertNotNull(measurementDao.getSource("S2"));
3827                     assertNotNull(measurementDao.getTrigger("T1"));
3828                     assertNotNull(measurementDao.getTrigger("T2"));
3829 
3830                     // Validate presence of unmatched event reports
3831                     measurementDao.getEventReport("E13");
3832                     measurementDao.getEventReport("E23");
3833                 });
3834 
3835         // Validate attribution deletion
3836         assertNotNull(getAttribution("ATT11_00", db));
3837         assertNotNull(getAttribution("ATT11_01", db));
3838         assertNull(getAttribution("ATT11_1", db));
3839         assertNull(getAttribution("ATT11_2", db));
3840         assertNotNull(getAttribution("ATT11_3", db));
3841         assertNull(getAttribution("ATT21", db));
3842         // Attribution deletion order within the group associated with an event report is generally
3843         // by insertion order, although it's not guaranteed. We deleted event report E22_2 but the
3844         // first limited associated attribution returned is ATT22_1.
3845         assertNull(getAttribution("ATT22_1", db));
3846         assertNotNull(getAttribution("ATT22_2", db));
3847     }
3848 
3849     @Test
deleteSources_providedIds_deletesMatchingSourcesAndRelatedData()3850     public void deleteSources_providedIds_deletesMatchingSourcesAndRelatedData() throws Exception {
3851         // Setup - Creates the following -
3852         // source - S1, S2, S3, S4
3853         // trigger - T1, T2, T3, T4
3854         // event reports - E11, E12, E21, E22, E23, E33, E44
3855         // aggregate reports - AR11, AR12, AR21, AR34
3856         // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
3857         prepareDataForSourceAndTriggerDeletion();
3858 
3859         // Execution
3860         mDatastoreManager.runInTransaction(
3861                 measurementDao -> {
3862                     measurementDao.deleteSources(List.of("S1", "S2"));
3863 
3864                     assertThrows(
3865                             DatastoreException.class,
3866                             () -> {
3867                                 measurementDao.getSource("S1");
3868                             });
3869                     assertThrows(
3870                             DatastoreException.class,
3871                             () -> {
3872                                 measurementDao.getSource("S2");
3873                             });
3874 
3875                     assertNotNull(measurementDao.getSource("S3"));
3876                     assertNotNull(measurementDao.getSource("S4"));
3877                     assertNotNull(measurementDao.getTrigger("T1"));
3878                     assertNotNull(measurementDao.getTrigger("T2"));
3879                     assertNotNull(measurementDao.getTrigger("T3"));
3880                     assertNotNull(measurementDao.getTrigger("T4"));
3881 
3882                     assertThrows(
3883                             DatastoreException.class,
3884                             () -> {
3885                                 measurementDao.getEventReport("E11");
3886                             });
3887                     assertThrows(
3888                             DatastoreException.class,
3889                             () -> {
3890                                 measurementDao.getEventReport("E12");
3891                             });
3892                     assertThrows(
3893                             DatastoreException.class,
3894                             () -> {
3895                                 measurementDao.getEventReport("E21");
3896                             });
3897                     assertThrows(
3898                             DatastoreException.class,
3899                             () -> {
3900                                 measurementDao.getEventReport("E22");
3901                             });
3902                     assertThrows(
3903                             DatastoreException.class,
3904                             () -> {
3905                                 measurementDao.getEventReport("E23");
3906                             });
3907                     assertNotNull(measurementDao.getEventReport("E33"));
3908                     assertNotNull(measurementDao.getEventReport("E44"));
3909 
3910                     assertThrows(
3911                             DatastoreException.class,
3912                             () -> {
3913                                 measurementDao.getAggregateReport("AR11");
3914                             });
3915                     assertThrows(
3916                             DatastoreException.class,
3917                             () -> {
3918                                 measurementDao.getAggregateReport("AR12");
3919                             });
3920                     assertThrows(
3921                             DatastoreException.class,
3922                             () -> {
3923                                 measurementDao.getAggregateReport("AR21");
3924                             });
3925                     assertNotNull(measurementDao.getAggregateReport("AR34"));
3926                 });
3927 
3928         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3929         assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
3930     }
3931 
3932     @Test
deleteSource_providedId_deletesMatchingXnaIgnoredSource()3933     public void deleteSource_providedId_deletesMatchingXnaIgnoredSource() {
3934         // Setup
3935         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3936 
3937         Source s1 =
3938                 SourceFixture.getMinimalValidSourceBuilder()
3939                         .setEventId(new UnsignedLong(1L))
3940                         .setId("S1")
3941                         .setEnrollmentId("1")
3942                         .build();
3943 
3944         ContentValues sourceValues = new ContentValues();
3945         sourceValues.put(SourceContract.ID, s1.getId());
3946         sourceValues.put(SourceContract.EVENT_ID, s1.getEventId().getValue());
3947 
3948         ContentValues xnaIgnoredSourceValues = new ContentValues();
3949         xnaIgnoredSourceValues.put(XnaIgnoredSourcesContract.SOURCE_ID, s1.getId());
3950         xnaIgnoredSourceValues.put(XnaIgnoredSourcesContract.ENROLLMENT_ID, s1.getEnrollmentId());
3951 
3952         // Execution
3953         db.insert(SourceContract.TABLE, null, sourceValues);
3954         db.insert(XnaIgnoredSourcesContract.TABLE, null, xnaIgnoredSourceValues);
3955 
3956         // Assertion
3957         assertEquals(1, DatabaseUtils.queryNumEntries(db, SourceContract.TABLE));
3958         assertEquals(1, DatabaseUtils.queryNumEntries(db, XnaIgnoredSourcesContract.TABLE));
3959 
3960         // Execution
3961         removeSources(Collections.singletonList(s1.getId()), db);
3962 
3963         // Assertion
3964         assertEquals(0, DatabaseUtils.queryNumEntries(db, SourceContract.TABLE));
3965         assertEquals(0, DatabaseUtils.queryNumEntries(db, XnaIgnoredSourcesContract.TABLE));
3966     }
3967 
3968     @Test
deleteTriggers_providedIds_deletesMatchingTriggersAndRelatedData()3969     public void deleteTriggers_providedIds_deletesMatchingTriggersAndRelatedData()
3970             throws Exception {
3971         // Setup - Creates the following -
3972         // source - S1, S2, S3, S4
3973         // trigger - T1, T2, T3, T4
3974         // event reports - E11, E12, E21, E22, E23, E33, E44
3975         // aggregate reports - AR11, AR12, AR21, AR34
3976         // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
3977         prepareDataForSourceAndTriggerDeletion();
3978 
3979         // Execution
3980         mDatastoreManager.runInTransaction(
3981                 measurementDao -> {
3982                     measurementDao.deleteTriggers(List.of("T1", "T2"));
3983 
3984                     assertNotNull(measurementDao.getSource("S1"));
3985                     assertNotNull(measurementDao.getSource("S2"));
3986                     assertNotNull(measurementDao.getSource("S3"));
3987                     assertNotNull(measurementDao.getSource("S4"));
3988                     assertThrows(DatastoreException.class, () -> measurementDao.getTrigger("T1"));
3989                     assertThrows(DatastoreException.class, () -> measurementDao.getTrigger("T2"));
3990                     assertNotNull(measurementDao.getTrigger("T3"));
3991                     assertNotNull(measurementDao.getTrigger("T4"));
3992 
3993                     assertThrows(
3994                             DatastoreException.class, () -> measurementDao.getEventReport("E11"));
3995                     assertThrows(
3996                             DatastoreException.class, () -> measurementDao.getEventReport("E12"));
3997                     assertThrows(
3998                             DatastoreException.class, () -> measurementDao.getEventReport("E21"));
3999                     assertThrows(
4000                             DatastoreException.class, () -> measurementDao.getEventReport("E22"));
4001                     assertNotNull(measurementDao.getEventReport("E23"));
4002                     assertNotNull(measurementDao.getEventReport("E33"));
4003                     assertNotNull(measurementDao.getEventReport("E44"));
4004 
4005                     assertThrows(
4006                             DatastoreException.class,
4007                             () -> measurementDao.getAggregateReport("AR11"));
4008                     assertThrows(
4009                             DatastoreException.class,
4010                             () -> measurementDao.getAggregateReport("AR12"));
4011                     assertThrows(
4012                             DatastoreException.class,
4013                             () -> measurementDao.getAggregateReport("AR21"));
4014 
4015                     assertNotNull(measurementDao.getAggregateReport("AR34"));
4016                 });
4017 
4018         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4019         assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
4020     }
4021 
4022     // Setup - Creates the following -
4023     // source - S1, S2
4024     // trigger - T1, T2
4025     // event reports - E11_1, E11_2, E11_3, E21, E22_1, E22_2
4026     // attributions - ATT11_1, ATT11_2, ATT11_3, ATT21, ATT22_1, ATT22_2
prepareDataForFlexEventReportAndAttributionDeletion()4027     private void prepareDataForFlexEventReportAndAttributionDeletion() throws Exception {
4028         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4029         Source s1 =
4030                 SourceFixture.getMinimalValidSourceBuilder()
4031                         .setEventId(new UnsignedLong(1L))
4032                         .setId("S1")
4033                         .build();
4034         Source s2 =
4035                 SourceFixture.getMinimalValidSourceBuilder()
4036                         .setEventId(new UnsignedLong(2L))
4037                         .setId("S2")
4038                         .build();
4039         Trigger t1 =
4040                 TriggerFixture.getValidTriggerBuilder()
4041                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
4042                         .setId("T1")
4043                         .build();
4044         Trigger t2 =
4045                 TriggerFixture.getValidTriggerBuilder()
4046                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
4047                         .setId("T2")
4048                         .build();
4049         Trigger t3 =
4050                 TriggerFixture.getValidTriggerBuilder()
4051                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
4052                         .setId("T2")
4053                         .build();
4054         EventReport e111 = createEventReportForSourceAndTrigger("E11_1", s1, t1);
4055         EventReport e112 = createEventReportForSourceAndTrigger("E11_2", s1, t1);
4056         EventReport e113 = createEventReportForSourceAndTrigger("E11_3", s1, t1);
4057         EventReport e21 = createEventReportForSourceAndTrigger("E21", s2, t1);
4058         EventReport e221 = createEventReportForSourceAndTrigger("E22_1", s2, t2);
4059         EventReport e222 = createEventReportForSourceAndTrigger("E22_2", s2, t2);
4060         EventReport e13 = createEventReportForSourceAndTrigger("E13", s1, t3);
4061         EventReport e23 = createEventReportForSourceAndTrigger("E23", s2, t3);
4062         Attribution att1100 =
4063                 createAttribution(
4064                         "ATT11_00",
4065                         Attribution.Scope.AGGREGATE,
4066                         s1.getId(),
4067                         t1.getId());
4068         Attribution att1101 =
4069                 createAttribution(
4070                         "ATT11_01",
4071                         Attribution.Scope.AGGREGATE,
4072                         s1.getId(),
4073                         t1.getId());
4074         Attribution att111 =
4075                 createAttribution(
4076                         "ATT11_1", s1.getId(), t1.getId());
4077         Attribution att112 =
4078                 createAttribution(
4079                         "ATT11_2", s1.getId(), t1.getId());
4080         Attribution att113 =
4081                 createAttribution(
4082                         "ATT11_3", s1.getId(), t1.getId());
4083         Attribution att21 =
4084                 createAttribution(
4085                         "ATT21", s2.getId(), t1.getId());
4086         Attribution att221 =
4087                 createAttribution(
4088                         "ATT22_1", s2.getId(), t2.getId());
4089         Attribution att222 =
4090                 createAttribution(
4091                         "ATT22_2", s2.getId(), t2.getId());
4092 
4093         insertSource(s1, s1.getId());
4094         insertSource(s2, s2.getId());
4095         AbstractDbIntegrationTest.insertToDb(t1, db);
4096         AbstractDbIntegrationTest.insertToDb(t2, db);
4097         AbstractDbIntegrationTest.insertToDb(e111, db);
4098         AbstractDbIntegrationTest.insertToDb(e112, db);
4099         AbstractDbIntegrationTest.insertToDb(e113, db);
4100         AbstractDbIntegrationTest.insertToDb(e21, db);
4101         AbstractDbIntegrationTest.insertToDb(e221, db);
4102         AbstractDbIntegrationTest.insertToDb(e222, db);
4103         AbstractDbIntegrationTest.insertToDb(e13, db);
4104         AbstractDbIntegrationTest.insertToDb(e23, db);
4105         AbstractDbIntegrationTest.insertToDb(att1100, db);
4106         AbstractDbIntegrationTest.insertToDb(att1101, db);
4107         AbstractDbIntegrationTest.insertToDb(att111, db);
4108         AbstractDbIntegrationTest.insertToDb(att112, db);
4109         AbstractDbIntegrationTest.insertToDb(att113, db);
4110         AbstractDbIntegrationTest.insertToDb(att21, db);
4111         AbstractDbIntegrationTest.insertToDb(att221, db);
4112         AbstractDbIntegrationTest.insertToDb(att222, db);
4113     }
4114 
4115     // Setup - Creates the following -
4116     // source - S1, S2, S3, S4
4117     // trigger - T1, T2, T3, T4
4118     // event reports - E11, E12, E21, E22, E23, E33, E44
4119     // aggregate reports - AR11, AR12, AR21, AR34
4120     // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
prepareDataForSourceAndTriggerDeletion()4121     private void prepareDataForSourceAndTriggerDeletion() throws Exception {
4122         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4123         Source s1 =
4124                 SourceFixture.getMinimalValidSourceBuilder()
4125                         .setEventId(new UnsignedLong(1L))
4126                         .setId("S1")
4127                         .build(); // deleted
4128         Source s2 =
4129                 SourceFixture.getMinimalValidSourceBuilder()
4130                         .setEventId(new UnsignedLong(2L))
4131                         .setId("S2")
4132                         .build(); // deleted
4133         Source s3 =
4134                 SourceFixture.getMinimalValidSourceBuilder()
4135                         .setEventId(new UnsignedLong(3L))
4136                         .setId("S3")
4137                         .build();
4138         Source s4 =
4139                 SourceFixture.getMinimalValidSourceBuilder()
4140                         .setEventId(new UnsignedLong(4L))
4141                         .setId("S4")
4142                         .build();
4143         Trigger t1 =
4144                 TriggerFixture.getValidTriggerBuilder()
4145                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
4146                         .setId("T1")
4147                         .build();
4148         Trigger t2 =
4149                 TriggerFixture.getValidTriggerBuilder()
4150                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
4151                         .setId("T2")
4152                         .build();
4153         Trigger t3 =
4154                 TriggerFixture.getValidTriggerBuilder()
4155                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
4156                         .setId("T3")
4157                         .build();
4158         Trigger t4 =
4159                 TriggerFixture.getValidTriggerBuilder()
4160                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
4161                         .setId("T4")
4162                         .build();
4163         EventReport e11 = createEventReportForSourceAndTrigger("E11", s1, t1);
4164         EventReport e12 = createEventReportForSourceAndTrigger("E12", s1, t2);
4165         EventReport e21 = createEventReportForSourceAndTrigger("E21", s2, t1);
4166         EventReport e22 = createEventReportForSourceAndTrigger("E22", s2, t2);
4167         EventReport e23 = createEventReportForSourceAndTrigger("E23", s2, t3);
4168         EventReport e33 = createEventReportForSourceAndTrigger("E33", s3, t3);
4169         EventReport e44 = createEventReportForSourceAndTrigger("E44", s4, t4);
4170         AggregateReport ar11 = createAggregateReportForSourceAndTrigger("AR11", s1, t1);
4171         AggregateReport ar12 = createAggregateReportForSourceAndTrigger("AR12", s1, t2);
4172         AggregateReport ar21 = createAggregateReportForSourceAndTrigger("AR21", s2, t1);
4173         AggregateReport ar34 = createAggregateReportForSourceAndTrigger("AR34", s3, t4);
4174         Attribution att11 =
4175                 createAttribution(
4176                         "ATT11", s1.getId(), t1.getId()); // deleted
4177         Attribution att12 =
4178                 createAttribution(
4179                         "ATT12", s1.getId(), t2.getId()); // deleted
4180         Attribution att21 =
4181                 createAttribution(
4182                         "ATT21", s2.getId(), t1.getId()); // deleted
4183         Attribution att22 =
4184                 createAttribution(
4185                         "ATT22", s2.getId(), t2.getId()); // deleted
4186         Attribution att33 =
4187                 createAttribution("ATT33", s3.getId(), t3.getId());
4188         Attribution att44 =
4189                 createAttribution("ATT44", s4.getId(), t4.getId());
4190 
4191         insertSource(s1, s1.getId());
4192         insertSource(s2, s2.getId());
4193         insertSource(s3, s3.getId());
4194         insertSource(s4, s4.getId());
4195 
4196         AbstractDbIntegrationTest.insertToDb(t1, db);
4197         AbstractDbIntegrationTest.insertToDb(t2, db);
4198         AbstractDbIntegrationTest.insertToDb(t3, db);
4199         AbstractDbIntegrationTest.insertToDb(t4, db);
4200 
4201         AbstractDbIntegrationTest.insertToDb(e11, db);
4202         AbstractDbIntegrationTest.insertToDb(e12, db);
4203         AbstractDbIntegrationTest.insertToDb(e21, db);
4204         AbstractDbIntegrationTest.insertToDb(e22, db);
4205         AbstractDbIntegrationTest.insertToDb(e23, db);
4206         AbstractDbIntegrationTest.insertToDb(e33, db);
4207         AbstractDbIntegrationTest.insertToDb(e44, db);
4208 
4209         AbstractDbIntegrationTest.insertToDb(ar11, db);
4210         AbstractDbIntegrationTest.insertToDb(ar12, db);
4211         AbstractDbIntegrationTest.insertToDb(ar21, db);
4212         AbstractDbIntegrationTest.insertToDb(ar34, db);
4213 
4214         AbstractDbIntegrationTest.insertToDb(att11, db);
4215         AbstractDbIntegrationTest.insertToDb(att12, db);
4216         AbstractDbIntegrationTest.insertToDb(att21, db);
4217         AbstractDbIntegrationTest.insertToDb(att22, db);
4218         AbstractDbIntegrationTest.insertToDb(att33, db);
4219         AbstractDbIntegrationTest.insertToDb(att44, db);
4220     }
4221 
4222     @Test
testUndoInstallAttribution_noMarkedSource()4223     public void testUndoInstallAttribution_noMarkedSource() {
4224         long currentTimestamp = System.currentTimeMillis();
4225         Source source =
4226                 createSourceForIATest("IA1", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID)
4227                         .build();
4228         source.setInstallAttributed(true);
4229         insertSource(source, source.getId());
4230         assertTrue(
4231                 mDatastoreManager.runInTransaction(
4232                         measurementDao ->
4233                                 measurementDao.undoInstallAttribution(INSTALLED_PACKAGE)));
4234         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4235         // Should set installAttributed = false for id=IA1
4236         assertFalse(getInstallAttributionStatus("IA1", db));
4237     }
4238 
4239     @Test
undoInstallAttribution_uninstall_nullInstallTime()4240     public void undoInstallAttribution_uninstall_nullInstallTime() {
4241         long currentTimestamp = System.currentTimeMillis();
4242         Source source =
4243                 createSourceForIATest("IA1", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID)
4244                         .build();
4245         source.setInstallAttributed(true);
4246         insertSource(source, source.getId());
4247         assertTrue(
4248                 mDatastoreManager.runInTransaction(
4249                         measurementDao ->
4250                                 measurementDao.undoInstallAttribution(INSTALLED_PACKAGE)));
4251         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4252         // Should set installTime = null for id=IA1
4253         assertNull(getInstallAttributionInstallTime("IA1", db));
4254     }
4255 
4256     @Test
getSourceDestinations_returnsExpected()4257     public void getSourceDestinations_returnsExpected() {
4258         // Insert two sources with some intersection of destinations
4259         // and assert all destination queries work.
4260 
4261         // First source
4262         List<Uri> webDestinations1 =
4263                 List.of(
4264                         Uri.parse("https://first-place.test"),
4265                         Uri.parse("https://second-place.test"),
4266                         Uri.parse("https://third-place.test"));
4267         List<Uri> appDestinations1 = List.of(Uri.parse("android-app://test.first-place"));
4268         Source source1 =
4269                 SourceFixture.getMinimalValidSourceBuilder()
4270                         .setId("1")
4271                         .setAppDestinations(appDestinations1)
4272                         .setWebDestinations(webDestinations1)
4273                         .build();
4274         insertSource(source1, source1.getId());
4275 
4276         // Second source
4277         List<Uri> webDestinations2 =
4278                 List.of(
4279                         Uri.parse("https://not-first-place.test"),
4280                         Uri.parse("https://not-second-place.test"),
4281                         Uri.parse("https://third-place.test"));
4282         List<Uri> appDestinations2 = List.of(Uri.parse("android-app://test.not-first-place"));
4283         Source source2 =
4284                 SourceFixture.getMinimalValidSourceBuilder()
4285                         .setId("2")
4286                         .setAppDestinations(appDestinations2)
4287                         .setWebDestinations(webDestinations2)
4288                         .build();
4289         insertSource(source2, source2.getId());
4290 
4291         Pair<List<Uri>, List<Uri>> result1 =
4292                 mDatastoreManager
4293                         .runInTransactionWithResult(
4294                                 measurementDao ->
4295                                         measurementDao.getSourceDestinations(source1.getId()))
4296                         .get();
4297         // Assert first app destinations
4298         assertTrue(
4299                 ImmutableMultiset.copyOf(source1.getAppDestinations())
4300                         .equals(ImmutableMultiset.copyOf(result1.first)));
4301         // Assert first web destinations
4302         assertTrue(
4303                 ImmutableMultiset.copyOf(source1.getWebDestinations())
4304                         .equals(ImmutableMultiset.copyOf(result1.second)));
4305 
4306         Pair<List<Uri>, List<Uri>> result2 =
4307                 mDatastoreManager
4308                         .runInTransactionWithResult(
4309                                 measurementDao ->
4310                                         measurementDao.getSourceDestinations(source2.getId()))
4311                         .get();
4312         // Assert second app destinations
4313         assertTrue(
4314                 ImmutableMultiset.copyOf(source2.getAppDestinations())
4315                         .equals(ImmutableMultiset.copyOf(result2.first)));
4316         // Assert second web destinations
4317         assertTrue(
4318                 ImmutableMultiset.copyOf(source2.getWebDestinations())
4319                         .equals(ImmutableMultiset.copyOf(result2.second)));
4320     }
4321 
4322     @Test
countNumAggregateReportsPerSource_returnsExpected()4323     public void countNumAggregateReportsPerSource_returnsExpected() {
4324         List<Source> sources =
4325                 Arrays.asList(
4326                         SourceFixture.getMinimalValidSourceBuilder()
4327                                 .setEventId(new UnsignedLong(1L))
4328                                 .setId("source1")
4329                                 .build(),
4330                         SourceFixture.getMinimalValidSourceBuilder()
4331                                 .setEventId(new UnsignedLong(2L))
4332                                 .setId("source2")
4333                                 .build(),
4334                         SourceFixture.getMinimalValidSourceBuilder()
4335                                 .setEventId(new UnsignedLong(3L))
4336                                 .setId("source3")
4337                                 .build());
4338         List<AggregateReport> reports =
4339                 Arrays.asList(
4340                         generateMockAggregateReport(
4341                                 WebUtil.validUrl("https://destination-1.test"),
4342                                 1,
4343                                 "source1",
4344                                 AggregateReportFixture.ValidAggregateReportParams.API),
4345                         generateMockAggregateReport(
4346                                 WebUtil.validUrl("https://destination-1.test"),
4347                                 2,
4348                                 "source1",
4349                                 AggregateReportFixture.ValidAggregateReportParams.API),
4350                         generateMockAggregateReport(
4351                                 WebUtil.validUrl("https://destination-2.test"),
4352                                 3,
4353                                 "source2",
4354                                 AggregateReportFixture.ValidAggregateReportParams.API),
4355                         generateMockAggregateReportBuilder(
4356                                 WebUtil.validUrl("https://destination-2.test"),
4357                                 33,
4358                                 "source2",
4359                                 AggregateReportFixture.ValidAggregateReportParams.API)
4360                                         .setTriggerContextId("12345")
4361                                         .build(),
4362                         generateMockAggregateReport(
4363                                 WebUtil.validUrl("https://destination-1.test"),
4364                                 4,
4365                                 "source3",
4366                                 AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API),
4367                         generateMockAggregateReport(
4368                                 WebUtil.validUrl("https://destination-1.test"),
4369                                 5,
4370                                 "source3",
4371                                 AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API),
4372                         generateMockAggregateReport(
4373                                 WebUtil.validUrl("https://destination-2.test"),
4374                                 6,
4375                                 "source3",
4376                                 AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API));
4377 
4378         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4379         Objects.requireNonNull(db);
4380         sources.forEach(source -> insertSource(source, source.getId()));
4381         Consumer<AggregateReport> aggregateReportConsumer =
4382                 aggregateReport -> {
4383                     ContentValues values = new ContentValues();
4384                     values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId());
4385                     values.put(
4386                             MeasurementTables.AggregateReport.SOURCE_ID,
4387                             aggregateReport.getSourceId());
4388                     values.put(
4389                             MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
4390                             aggregateReport.getAttributionDestination().toString());
4391                     values.put(MeasurementTables.AggregateReport.API, aggregateReport.getApi());
4392                     values.put(
4393                             MeasurementTables.AggregateReport.TRIGGER_CONTEXT_ID,
4394                             aggregateReport.getTriggerContextId());
4395                     db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
4396                 };
4397         reports.forEach(aggregateReportConsumer);
4398 
4399         mDatastoreManager.runInTransaction(
4400                 measurementDao -> {
4401                     assertThat(
4402                                     measurementDao.countNumAggregateReportsPerSource(
4403                                             "source1",
4404                                             AggregateReportFixture.ValidAggregateReportParams.API))
4405                             .isEqualTo(2);
4406                     assertThat(
4407                                     measurementDao.countNumAggregateReportsPerSource(
4408                                             "source2",
4409                                             AggregateReportFixture.ValidAggregateReportParams.API))
4410                             .isEqualTo(2);
4411                     assertThat(
4412                                     measurementDao.countNumAggregateReportsPerSource(
4413                                             "source3",
4414                                             AggregateReportFixture.ValidAggregateReportParams.API))
4415                             .isEqualTo(0);
4416                     assertThat(
4417                                     measurementDao.countNumAggregateReportsPerSource(
4418                                             "source1",
4419                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4420                             .isEqualTo(0);
4421                     assertThat(
4422                                     measurementDao.countNumAggregateReportsPerSource(
4423                                             "source2",
4424                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4425                             .isEqualTo(0);
4426                     assertThat(
4427                                     measurementDao.countNumAggregateReportsPerSource(
4428                                             "source3",
4429                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4430                             .isEqualTo(3);
4431                 });
4432     }
4433 
4434     @Test
countNumAggregateReportsPerSource_unboundedReportsWithTriggerContextId_expected()4435     public void countNumAggregateReportsPerSource_unboundedReportsWithTriggerContextId_expected() {
4436         mLegacyFlags = mMockFlags;
4437         mocker.mockGetFlags(mMockFlags);
4438         doReturn(true).when(mMockFlags).getMeasurementEnableUnboundedReportsWithTriggerContextId();
4439 
4440         List<Source> sources =
4441                 Arrays.asList(
4442                         SourceFixture.getMinimalValidSourceBuilder()
4443                                 .setEventId(new UnsignedLong(1L))
4444                                 .setId("source1")
4445                                 .build(),
4446                         SourceFixture.getMinimalValidSourceBuilder()
4447                                 .setEventId(new UnsignedLong(2L))
4448                                 .setId("source2")
4449                                 .build());
4450         List<AggregateReport> reports =
4451                 Arrays.asList(
4452                         generateMockAggregateReport(
4453                                 WebUtil.validUrl("https://destination-1.test"),
4454                                 1,
4455                                 "source1",
4456                                 AggregateReportFixture.ValidAggregateReportParams.API),
4457                         generateMockAggregateReport(
4458                                 WebUtil.validUrl("https://destination-1.test"),
4459                                 2,
4460                                 "source1",
4461                                 AggregateReportFixture.ValidAggregateReportParams.API),
4462                         generateMockAggregateReport(
4463                                 WebUtil.validUrl("https://destination-2.test"),
4464                                 3,
4465                                 "source2",
4466                                 AggregateReportFixture.ValidAggregateReportParams.API),
4467                         // Report should not be counted since trigger context ID is present.
4468                         generateMockAggregateReportBuilder(
4469                                 WebUtil.validUrl("https://destination-2.test"),
4470                                 33,
4471                                 "source2",
4472                                 AggregateReportFixture.ValidAggregateReportParams.API)
4473                                         .setTriggerContextId("12345")
4474                                         .build());
4475 
4476         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4477         Objects.requireNonNull(db);
4478         sources.forEach(source -> insertSource(source, source.getId()));
4479         Consumer<AggregateReport> aggregateReportConsumer =
4480                 aggregateReport -> {
4481                     ContentValues values = new ContentValues();
4482                     values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId());
4483                     values.put(
4484                             MeasurementTables.AggregateReport.SOURCE_ID,
4485                             aggregateReport.getSourceId());
4486                     values.put(
4487                             MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
4488                             aggregateReport.getAttributionDestination().toString());
4489                     values.put(MeasurementTables.AggregateReport.API, aggregateReport.getApi());
4490                     values.put(
4491                             MeasurementTables.AggregateReport.TRIGGER_CONTEXT_ID,
4492                             aggregateReport.getTriggerContextId());
4493                     db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
4494                 };
4495         reports.forEach(aggregateReportConsumer);
4496 
4497         mDatastoreManager.runInTransaction(
4498                 measurementDao -> {
4499                     assertThat(
4500                                     measurementDao.countNumAggregateReportsPerSource(
4501                                             "source1",
4502                                             AggregateReportFixture.ValidAggregateReportParams.API))
4503                             .isEqualTo(2);
4504                     assertThat(
4505                                     measurementDao.countNumAggregateReportsPerSource(
4506                                             "source2",
4507                                             AggregateReportFixture.ValidAggregateReportParams.API))
4508                             .isEqualTo(1);
4509                     assertThat(
4510                                     measurementDao.countNumAggregateReportsPerSource(
4511                                             "source1",
4512                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4513                             .isEqualTo(0);
4514                     assertThat(
4515                                     measurementDao.countNumAggregateReportsPerSource(
4516                                             "source2",
4517                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4518                             .isEqualTo(0);
4519                 });
4520     }
4521 
4522     @Test
getNumAggregateReportsPerDestination_returnsExpected()4523     public void getNumAggregateReportsPerDestination_returnsExpected() {
4524         List<AggregateReport> reportsWithPlainDestination =
4525                 Arrays.asList(
4526                         generateMockAggregateReport(
4527                                 WebUtil.validUrl("https://destination-1.test"), 1));
4528         List<AggregateReport> reportsWithPlainAndSubDomainDestination =
4529                 Arrays.asList(
4530                         generateMockAggregateReport(
4531                                 WebUtil.validUrl("https://destination-2.test"), 2),
4532                         generateMockAggregateReport(
4533                                 WebUtil.validUrl("https://subdomain.destination-2.test"), 3));
4534         List<AggregateReport> reportsWithPlainAndPathDestination =
4535                 Arrays.asList(
4536                         generateMockAggregateReport(
4537                                 WebUtil.validUrl("https://subdomain.destination-3.test"), 4),
4538                         generateMockAggregateReport(
4539                                 WebUtil.validUrl("https://subdomain.destination-3.test/abcd"), 5));
4540         List<AggregateReport> reportsWithAll3Types =
4541                 Arrays.asList(
4542                         generateMockAggregateReport(
4543                                 WebUtil.validUrl("https://destination-4.test"), 6),
4544                         generateMockAggregateReport(
4545                                 WebUtil.validUrl("https://subdomain.destination-4.test"), 7),
4546                         generateMockAggregateReport(
4547                                 WebUtil.validUrl("https://subdomain.destination-4.test/abcd"), 8));
4548         List<AggregateReport> reportsWithAndroidAppDestination =
4549                 Arrays.asList(generateMockAggregateReport("android-app://destination-5.app", 9));
4550 
4551         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4552         Objects.requireNonNull(db);
4553         Stream.of(
4554                         reportsWithPlainDestination,
4555                         reportsWithPlainAndSubDomainDestination,
4556                         reportsWithPlainAndPathDestination,
4557                         reportsWithAll3Types,
4558                         reportsWithAndroidAppDestination)
4559                 .flatMap(Collection::stream)
4560                 .forEach(
4561                         aggregateReport -> {
4562                             ContentValues values = new ContentValues();
4563                             values.put(
4564                                     MeasurementTables.AggregateReport.ID, aggregateReport.getId());
4565                             values.put(
4566                                     MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
4567                                     aggregateReport.getAttributionDestination().toString());
4568                             values.put(
4569                                     MeasurementTables.AggregateReport.IS_FAKE_REPORT,
4570                                     aggregateReport.isFakeReport());
4571                             db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
4572                         });
4573 
4574         List<String> attributionDestinations1 = createWebDestinationVariants(1);
4575         List<String> attributionDestinations2 = createWebDestinationVariants(2);
4576         List<String> attributionDestinations3 = createWebDestinationVariants(3);
4577         List<String> attributionDestinations4 = createWebDestinationVariants(4);
4578         List<String> attributionDestinations5 = createAppDestinationVariants(5);
4579 
4580         // expected query return values for attribution destination variants
4581         List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0);
4582         List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4583         List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4584         List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0);
4585         List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0);
4586         assertAggregateReportCount(
4587                 attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts);
4588         assertAggregateReportCount(
4589                 attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts);
4590         assertAggregateReportCount(
4591                 attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts);
4592         assertAggregateReportCount(
4593                 attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts);
4594         assertAggregateReportCount(
4595                 attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts);
4596     }
4597 
4598     @Test
getAggregateReportById_fakeReport()4599     public void getAggregateReportById_fakeReport() {
4600         AggregateReport ar11 =
4601                 AggregateReportFixture.getValidAggregateReportBuilder()
4602                         .setId("11")
4603                         .setIsFakeReport(true)
4604                         .build();
4605         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4606         AbstractDbIntegrationTest.insertToDb(ar11, db);
4607 
4608         Optional<AggregateReport> resOpt =
4609                 mDatastoreManager.runInTransactionWithResult((dao) -> dao.getAggregateReport("11"));
4610         assertTrue(resOpt.isPresent());
4611         AggregateReport res = resOpt.get();
4612         assertTrue(res.isFakeReport());
4613     }
4614 
4615     @Test
getNumEventReportsPerDestination_returnsExpected()4616     public void getNumEventReportsPerDestination_returnsExpected() {
4617         List<EventReport> reportsWithPlainDestination =
4618                 Arrays.asList(
4619                         generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1));
4620         List<EventReport> reportsWithPlainAndSubDomainDestination =
4621                 Arrays.asList(
4622                         generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2),
4623                         generateMockEventReport(
4624                                 WebUtil.validUrl("https://subdomain.destination-2.test"), 3));
4625         List<EventReport> reportsWithPlainAndPathDestination =
4626                 Arrays.asList(
4627                         generateMockEventReport(
4628                                 WebUtil.validUrl("https://subdomain.destination-3.test"), 4),
4629                         generateMockEventReport(
4630                                 WebUtil.validUrl("https://subdomain.destination-3.test/abcd"), 5));
4631         List<EventReport> reportsWithAll3Types =
4632                 Arrays.asList(
4633                         generateMockEventReport(WebUtil.validUrl("https://destination-4.test"), 6),
4634                         generateMockEventReport(
4635                                 WebUtil.validUrl("https://subdomain.destination-4.test"), 7),
4636                         generateMockEventReport(
4637                                 WebUtil.validUrl("https://subdomain.destination-4.test/abcd"), 8));
4638         List<EventReport> reportsWithAndroidAppDestination =
4639                 Arrays.asList(generateMockEventReport("android-app://destination-5.app", 9));
4640 
4641         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4642         Objects.requireNonNull(db);
4643         Stream.of(
4644                         reportsWithPlainDestination,
4645                         reportsWithPlainAndSubDomainDestination,
4646                         reportsWithPlainAndPathDestination,
4647                         reportsWithAll3Types,
4648                         reportsWithAndroidAppDestination)
4649                 .flatMap(Collection::stream)
4650                 .forEach(
4651                         eventReport -> {
4652                             ContentValues values = new ContentValues();
4653                             values.put(EventReportContract.ID, eventReport.getId());
4654                             values.put(
4655                                     EventReportContract.ATTRIBUTION_DESTINATION,
4656                                     eventReport.getAttributionDestinations().get(0).toString());
4657                             db.insert(EventReportContract.TABLE, null, values);
4658                         });
4659 
4660         List<String> attributionDestinations1 = createWebDestinationVariants(1);
4661         List<String> attributionDestinations2 = createWebDestinationVariants(2);
4662         List<String> attributionDestinations3 = createWebDestinationVariants(3);
4663         List<String> attributionDestinations4 = createWebDestinationVariants(4);
4664         List<String> attributionDestinations5 = createAppDestinationVariants(5);
4665 
4666         // expected query return values for attribution destination variants
4667         List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0);
4668         List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4669         List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4670         List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0);
4671         List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0);
4672         assertEventReportCount(
4673                 attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts);
4674         assertEventReportCount(
4675                 attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts);
4676         assertEventReportCount(
4677                 attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts);
4678         assertEventReportCount(
4679                 attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts);
4680         assertEventReportCount(
4681                 attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts);
4682     }
4683 
4684     @Test
testGetSourceEventReports()4685     public void testGetSourceEventReports() {
4686         List<Source> sourceList =
4687                 Arrays.asList(
4688                         SourceFixture.getMinimalValidSourceBuilder()
4689                                 .setId("1")
4690                                 .setEventId(new UnsignedLong(3L))
4691                                 .setEnrollmentId("1")
4692                                 .build(),
4693                         SourceFixture.getMinimalValidSourceBuilder()
4694                                 .setId("2")
4695                                 .setEventId(new UnsignedLong(4L))
4696                                 .setEnrollmentId("1")
4697                                 .build(),
4698                         // Should always be ignored
4699                         SourceFixture.getMinimalValidSourceBuilder()
4700                                 .setId("3")
4701                                 .setEventId(new UnsignedLong(4L))
4702                                 .setEnrollmentId("2")
4703                                 .build(),
4704                         SourceFixture.getMinimalValidSourceBuilder()
4705                                 .setId("15")
4706                                 .setEventId(new UnsignedLong(15L))
4707                                 .setEnrollmentId("2")
4708                                 .build(),
4709                         SourceFixture.getMinimalValidSourceBuilder()
4710                                 .setId("16")
4711                                 .setEventId(new UnsignedLong(16L))
4712                                 .setEnrollmentId("2")
4713                                 .build(),
4714                         SourceFixture.getMinimalValidSourceBuilder()
4715                                 .setId("20")
4716                                 .setEventId(new UnsignedLong(20L))
4717                                 .setEnrollmentId("2")
4718                                 .build());
4719 
4720         List<Trigger> triggers =
4721                 Arrays.asList(
4722                         TriggerFixture.getValidTriggerBuilder()
4723                                 .setId("101")
4724                                 .setEnrollmentId("2")
4725                                 .build(),
4726                         TriggerFixture.getValidTriggerBuilder()
4727                                 .setId("102")
4728                                 .setEnrollmentId("2")
4729                                 .build(),
4730                         TriggerFixture.getValidTriggerBuilder()
4731                                 .setId("201")
4732                                 .setEnrollmentId("2")
4733                                 .build(),
4734                         TriggerFixture.getValidTriggerBuilder()
4735                                 .setId("202")
4736                                 .setEnrollmentId("2")
4737                                 .build(),
4738                         TriggerFixture.getValidTriggerBuilder()
4739                                 .setId("1001")
4740                                 .setEnrollmentId("2")
4741                                 .build());
4742 
4743         // Should match with source 1
4744         List<EventReport> reportList1 = new ArrayList<>();
4745         reportList1.add(
4746                 new EventReport.Builder()
4747                         .setId("1")
4748                         .setSourceEventId(new UnsignedLong(3L))
4749                         .setEnrollmentId("1")
4750                         .setAttributionDestinations(sourceList.get(0).getAppDestinations())
4751                         .setSourceType(sourceList.get(0).getSourceType())
4752                         .setSourceId("1")
4753                         .setTriggerId("101")
4754                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4755                         .build());
4756         reportList1.add(
4757                 new EventReport.Builder()
4758                         .setId("7")
4759                         .setSourceEventId(new UnsignedLong(3L))
4760                         .setEnrollmentId("1")
4761                         .setAttributionDestinations(List.of(APP_DESTINATION))
4762                         .setSourceType(sourceList.get(0).getSourceType())
4763                         .setSourceId("1")
4764                         .setTriggerId("102")
4765                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4766                         .build());
4767 
4768         // Should match with source 2
4769         List<EventReport> reportList2 = new ArrayList<>();
4770         reportList2.add(
4771                 new EventReport.Builder()
4772                         .setId("3")
4773                         .setSourceEventId(new UnsignedLong(4L))
4774                         .setEnrollmentId("1")
4775                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4776                         .setSourceType(sourceList.get(1).getSourceType())
4777                         .setSourceId("2")
4778                         .setTriggerId("201")
4779                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4780                         .build());
4781         reportList2.add(
4782                 new EventReport.Builder()
4783                         .setId("8")
4784                         .setSourceEventId(new UnsignedLong(4L))
4785                         .setEnrollmentId("1")
4786                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4787                         .setSourceType(sourceList.get(1).getSourceType())
4788                         .setSourceId("2")
4789                         .setTriggerId("202")
4790                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4791                         .build());
4792 
4793         List<EventReport> reportList3 = new ArrayList<>();
4794         // Should not match with any source
4795         reportList3.add(
4796                 new EventReport.Builder()
4797                         .setId("2")
4798                         .setSourceEventId(new UnsignedLong(5L))
4799                         .setEnrollmentId("1")
4800                         .setSourceType(Source.SourceType.EVENT)
4801                         .setAttributionDestinations(List.of(APP_DESTINATION))
4802                         .setSourceId("15")
4803                         .setTriggerId("1001")
4804                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4805                         .build());
4806         reportList3.add(
4807                 new EventReport.Builder()
4808                         .setId("4")
4809                         .setSourceEventId(new UnsignedLong(6L))
4810                         .setEnrollmentId("1")
4811                         .setSourceType(Source.SourceType.EVENT)
4812                         .setAttributionDestinations(List.of(APP_DESTINATION))
4813                         .setSourceId("16")
4814                         .setTriggerId("1001")
4815                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4816                         .setTriggerValue(100L)
4817                         .build());
4818         reportList3.add(
4819                 new EventReport.Builder()
4820                         .setId("5")
4821                         .setSourceEventId(new UnsignedLong(1L))
4822                         .setEnrollmentId("1")
4823                         .setSourceType(Source.SourceType.EVENT)
4824                         .setAttributionDestinations(List.of(APP_DESTINATION))
4825                         .setSourceId("15")
4826                         .setTriggerId("1001")
4827                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4828                         .setTriggerValue(120L)
4829                         .build());
4830         reportList3.add(
4831                 new EventReport.Builder()
4832                         .setId("6")
4833                         .setSourceEventId(new UnsignedLong(2L))
4834                         .setEnrollmentId("1")
4835                         .setSourceType(Source.SourceType.EVENT)
4836                         .setAttributionDestinations(List.of(APP_DESTINATION))
4837                         .setSourceId("20")
4838                         .setTriggerId("1001")
4839                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4840                         .setTriggerValue(200L)
4841                         .build());
4842 
4843         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4844         Objects.requireNonNull(db);
4845         sourceList.forEach(source -> insertSource(source, source.getId()));
4846         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
4847 
4848         Stream.of(reportList1, reportList2, reportList3)
4849                 .flatMap(Collection::stream)
4850                 .forEach(
4851                         (eventReport -> {
4852                             mDatastoreManager.runInTransaction(
4853                                     (dao) -> dao.insertEventReport(eventReport));
4854                         }));
4855 
4856         assertEquals(
4857                 reportList1,
4858                 mDatastoreManager
4859                         .runInTransactionWithResult(
4860                                 measurementDao ->
4861                                         measurementDao.getSourceEventReports(sourceList.get(0)))
4862                         .orElseThrow());
4863 
4864         assertEquals(
4865                 reportList2,
4866                 mDatastoreManager
4867                         .runInTransactionWithResult(
4868                                 measurementDao ->
4869                                         measurementDao.getSourceEventReports(sourceList.get(1)))
4870                         .orElseThrow());
4871     }
4872 
4873     @Test
getSourceEventReports_sourcesWithSameEventId_haveSeparateEventReportsMatch()4874     public void getSourceEventReports_sourcesWithSameEventId_haveSeparateEventReportsMatch() {
4875         List<Source> sourceList =
4876                 Arrays.asList(
4877                         SourceFixture.getMinimalValidSourceBuilder()
4878                                 .setId("1")
4879                                 .setEventId(new UnsignedLong(1L))
4880                                 .setEnrollmentId("1")
4881                                 .build(),
4882                         SourceFixture.getMinimalValidSourceBuilder()
4883                                 .setId("2")
4884                                 .setEventId(new UnsignedLong(1L))
4885                                 .setEnrollmentId("1")
4886                                 .build(),
4887                         SourceFixture.getMinimalValidSourceBuilder()
4888                                 .setId("3")
4889                                 .setEventId(new UnsignedLong(2L))
4890                                 .setEnrollmentId("2")
4891                                 .build(),
4892                         SourceFixture.getMinimalValidSourceBuilder()
4893                                 .setId("4")
4894                                 .setEventId(new UnsignedLong(2L))
4895                                 .setEnrollmentId("2")
4896                                 .build());
4897 
4898         List<Trigger> triggers =
4899                 Arrays.asList(
4900                         TriggerFixture.getValidTriggerBuilder()
4901                                 .setId("101")
4902                                 .setEnrollmentId("2")
4903                                 .build(),
4904                         TriggerFixture.getValidTriggerBuilder()
4905                                 .setId("102")
4906                                 .setEnrollmentId("2")
4907                                 .build());
4908 
4909         // Should match with source 1
4910         List<EventReport> reportList1 = new ArrayList<>();
4911         reportList1.add(
4912                 new EventReport.Builder()
4913                         .setId("1")
4914                         .setSourceEventId(new UnsignedLong(1L))
4915                         .setEnrollmentId("1")
4916                         .setAttributionDestinations(sourceList.get(0).getAppDestinations())
4917                         .setSourceType(sourceList.get(0).getSourceType())
4918                         .setSourceId("1")
4919                         .setTriggerId("101")
4920                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4921                         .build());
4922         reportList1.add(
4923                 new EventReport.Builder()
4924                         .setId("2")
4925                         .setSourceEventId(new UnsignedLong(1L))
4926                         .setEnrollmentId("1")
4927                         .setAttributionDestinations(List.of(APP_DESTINATION))
4928                         .setSourceType(sourceList.get(0).getSourceType())
4929                         .setSourceId("1")
4930                         .setTriggerId("102")
4931                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4932                         .build());
4933 
4934         // Should match with source 2
4935         List<EventReport> reportList2 = new ArrayList<>();
4936         reportList2.add(
4937                 new EventReport.Builder()
4938                         .setId("3")
4939                         .setSourceEventId(new UnsignedLong(2L))
4940                         .setEnrollmentId("1")
4941                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4942                         .setSourceType(sourceList.get(1).getSourceType())
4943                         .setSourceId("2")
4944                         .setTriggerId("101")
4945                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4946                         .build());
4947         reportList2.add(
4948                 new EventReport.Builder()
4949                         .setId("4")
4950                         .setSourceEventId(new UnsignedLong(2L))
4951                         .setEnrollmentId("1")
4952                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4953                         .setSourceType(sourceList.get(1).getSourceType())
4954                         .setSourceId("2")
4955                         .setTriggerId("102")
4956                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4957                         .build());
4958 
4959         // Match with source3
4960         List<EventReport> reportList3 = new ArrayList<>();
4961         reportList3.add(
4962                 new EventReport.Builder()
4963                         .setId("5")
4964                         .setSourceEventId(new UnsignedLong(2L))
4965                         .setEnrollmentId("2")
4966                         .setSourceType(Source.SourceType.EVENT)
4967                         .setAttributionDestinations(List.of(APP_DESTINATION))
4968                         .setSourceId("3")
4969                         .setTriggerId("101")
4970                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4971                         .build());
4972         reportList3.add(
4973                 new EventReport.Builder()
4974                         .setId("6")
4975                         .setSourceEventId(new UnsignedLong(2L))
4976                         .setEnrollmentId("2")
4977                         .setSourceType(Source.SourceType.EVENT)
4978                         .setAttributionDestinations(List.of(APP_DESTINATION))
4979                         .setSourceId("3")
4980                         .setTriggerId("102")
4981                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4982                         .build());
4983 
4984         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4985         Objects.requireNonNull(db);
4986         sourceList.forEach(source -> insertSource(source, source.getId()));
4987         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
4988 
4989         Stream.of(reportList1, reportList2, reportList3)
4990                 .flatMap(Collection::stream)
4991                 .forEach(
4992                         (eventReport -> {
4993                             mDatastoreManager.runInTransaction(
4994                                     (dao) -> dao.insertEventReport(eventReport));
4995                         }));
4996 
4997         assertEquals(
4998                 reportList1,
4999                 mDatastoreManager
5000                         .runInTransactionWithResult(
5001                                 measurementDao ->
5002                                         measurementDao.getSourceEventReports(sourceList.get(0)))
5003                         .orElseThrow());
5004 
5005         assertEquals(
5006                 reportList2,
5007                 mDatastoreManager
5008                         .runInTransactionWithResult(
5009                                 measurementDao ->
5010                                         measurementDao.getSourceEventReports(sourceList.get(1)))
5011                         .orElseThrow());
5012 
5013         assertEquals(
5014                 reportList3,
5015                 mDatastoreManager
5016                         .runInTransactionWithResult(
5017                                 measurementDao ->
5018                                         measurementDao.getSourceEventReports(sourceList.get(2)))
5019                         .orElseThrow());
5020     }
5021 
5022     @Test
testUpdateSourceStatus()5023     public void testUpdateSourceStatus() {
5024         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5025         Objects.requireNonNull(db);
5026 
5027         List<Source> sourceList = new ArrayList<>();
5028         sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("1").build());
5029         sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("2").build());
5030         sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("3").build());
5031         sourceList.forEach(
5032                 source -> {
5033                     ContentValues values = new ContentValues();
5034                     values.put(SourceContract.ID, source.getId());
5035                     values.put(SourceContract.STATUS, 1);
5036                     db.insert(SourceContract.TABLE, null, values);
5037                 });
5038 
5039         // Multiple Elements
5040         assertTrue(
5041                 mDatastoreManager.runInTransaction(
5042                         measurementDao ->
5043                                 measurementDao.updateSourceStatus(
5044                                         List.of("1", "2", "3"), Source.Status.IGNORED)));
5045 
5046         // Single Element
5047         assertTrue(
5048                 mDatastoreManager.runInTransaction(
5049                         measurementDao ->
5050                                 measurementDao.updateSourceStatus(
5051                                         List.of("1", "2"), Source.Status.IGNORED)));
5052     }
5053 
5054     @Test
updateSourceAttributedTriggers_baseline_equal()5055     public void updateSourceAttributedTriggers_baseline_equal() throws Exception {
5056         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5057         Objects.requireNonNull(db);
5058 
5059         List<Source> sourceList = new ArrayList<>();
5060         sourceList.add(
5061                 SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum()
5062                         .setId("1")
5063                         .build());
5064         sourceList.add(
5065                 SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum()
5066                         .setId("2")
5067                         .build());
5068         sourceList.add(
5069                 SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum()
5070                         .setId("3")
5071                         .build());
5072         mDatastoreManager.runInTransaction(
5073                 (dao) -> {
5074                     for (Source source : sourceList) {
5075                         dao.insertSource(source);
5076                     }
5077                 });
5078         List<EventReport> eventReportList = new ArrayList<>();
5079         eventReportList.add(
5080                 EventReportFixture.getBaseEventReportBuild()
5081                         .setTriggerData(new UnsignedLong(1L))
5082                         .setTriggerPriority(3L)
5083                         .setTriggerValue(5L)
5084                         .setReportTime(10000L)
5085                         .build());
5086         Source originalSource = sourceList.get(0);
5087         insertAttributedTrigger(originalSource.getTriggerSpecs(), eventReportList.get(0));
5088         Optional<Source> newSource =
5089                 mDatastoreManager.runInTransactionWithResult(
5090                         measurementDao -> measurementDao.getSource(originalSource.getId()));
5091         assertTrue(newSource.isPresent());
5092 
5093         assertNotEquals(newSource.get(), originalSource);
5094         newSource.get().buildTriggerSpecs();
5095         assertEquals(0, newSource.get().getTriggerSpecs().getAttributedTriggers().size());
5096 
5097         mDatastoreManager.runInTransaction(
5098                 measurementDao ->
5099                         measurementDao.updateSourceAttributedTriggers(
5100                                 originalSource.getId(),
5101                                 originalSource.attributedTriggersToJsonFlexApi()));
5102 
5103         mDatastoreManager.runInTransaction(
5104                 measurementDao -> {
5105                     assertEquals(
5106                             originalSource.attributedTriggersToJsonFlexApi(),
5107                             measurementDao
5108                                     .getSource(originalSource.getId())
5109                                     .getEventAttributionStatus());
5110                 });
5111     }
5112 
5113     @Test
testGetMatchingActiveSources()5114     public void testGetMatchingActiveSources() {
5115         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5116         Objects.requireNonNull(db);
5117         String enrollmentId = "enrollment-id";
5118         Uri appDestination = Uri.parse("android-app://com.example.abc");
5119         Uri webDestination = WebUtil.validUri("https://example.test");
5120         Uri webDestinationWithSubdomain = WebUtil.validUri("https://xyz.example.test");
5121         Source sApp1 =
5122                 SourceFixture.getMinimalValidSourceBuilder()
5123                         .setId("1")
5124                         .setEventTime(10)
5125                         .setExpiryTime(20)
5126                         .setAppDestinations(List.of(appDestination))
5127                         .setEnrollmentId(enrollmentId)
5128                         .build();
5129         Source sApp2 =
5130                 SourceFixture.getMinimalValidSourceBuilder()
5131                         .setId("2")
5132                         .setEventTime(10)
5133                         .setExpiryTime(50)
5134                         .setAppDestinations(List.of(appDestination))
5135                         .setEnrollmentId(enrollmentId)
5136                         .build();
5137         Source sApp3 =
5138                 SourceFixture.getMinimalValidSourceBuilder()
5139                         .setId("3")
5140                         .setEventTime(20)
5141                         .setExpiryTime(50)
5142                         .setAppDestinations(List.of(appDestination))
5143                         .setEnrollmentId(enrollmentId)
5144                         .build();
5145         Source sApp4 =
5146                 SourceFixture.getMinimalValidSourceBuilder()
5147                         .setId("4")
5148                         .setEventTime(30)
5149                         .setExpiryTime(50)
5150                         .setAppDestinations(List.of(appDestination))
5151                         .setEnrollmentId(enrollmentId)
5152                         .build();
5153         Source sWeb5 =
5154                 SourceFixture.getMinimalValidSourceBuilder()
5155                         .setId("5")
5156                         .setEventTime(10)
5157                         .setExpiryTime(20)
5158                         .setWebDestinations(List.of(webDestination))
5159                         .setEnrollmentId(enrollmentId)
5160                         .build();
5161         Source sWeb6 =
5162                 SourceFixture.getMinimalValidSourceBuilder()
5163                         .setId("6")
5164                         .setEventTime(10)
5165                         .setExpiryTime(50)
5166                         .setWebDestinations(List.of(webDestination))
5167                         .setEnrollmentId(enrollmentId)
5168                         .build();
5169         Source sAppWeb7 =
5170                 SourceFixture.getMinimalValidSourceBuilder()
5171                         .setId("7")
5172                         .setEventTime(10)
5173                         .setExpiryTime(20)
5174                         .setAppDestinations(List.of(appDestination))
5175                         .setWebDestinations(List.of(webDestination))
5176                         .setEnrollmentId(enrollmentId)
5177                         .build();
5178 
5179         List<Source> sources = Arrays.asList(sApp1, sApp2, sApp3, sApp4, sWeb5, sWeb6, sAppWeb7);
5180         sources.forEach(source -> insertInDb(db, source));
5181 
5182         Function<Trigger, List<Source>> runFunc =
5183                 trigger -> {
5184                     List<Source> result =
5185                             mDatastoreManager
5186                                     .runInTransactionWithResult(
5187                                             measurementDao ->
5188                                                     measurementDao.getMatchingActiveSources(
5189                                                             trigger))
5190                                     .orElseThrow();
5191                     result.sort(Comparator.comparing(Source::getId));
5192                     return result;
5193                 };
5194 
5195         // Trigger Time > sApp1's eventTime and < sApp1's expiryTime
5196         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
5197         // Trigger Time < sApp3's eventTime
5198         // Trigger Time < sApp4's eventTime
5199         // sApp5 and sApp6 don't have app destination
5200         // Trigger Time > sAppWeb7's eventTime and < sAppWeb7's expiryTime
5201         // Expected: Match with sApp1, sApp2, sAppWeb7
5202         Trigger trigger1MatchSource1And2 =
5203                 TriggerFixture.getValidTriggerBuilder()
5204                         .setTriggerTime(12)
5205                         .setEnrollmentId(enrollmentId)
5206                         .setAttributionDestination(appDestination)
5207                         .setDestinationType(EventSurfaceType.APP)
5208                         .build();
5209         List<Source> result1 = runFunc.apply(trigger1MatchSource1And2);
5210         assertEquals(3, result1.size());
5211         assertEquals(sApp1.getId(), result1.get(0).getId());
5212         assertEquals(sApp2.getId(), result1.get(1).getId());
5213         assertEquals(sAppWeb7.getId(), result1.get(2).getId());
5214 
5215         // Trigger Time > sApp1's eventTime and = sApp1's expiryTime
5216         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
5217         // Trigger Time = sApp3's eventTime
5218         // Trigger Time < sApp4's eventTime
5219         // sApp5 and sApp6 don't have app destination
5220         // Trigger Time > sAppWeb7's eventTime and = sAppWeb7's expiryTime
5221         // Expected: Match with sApp2, sApp3
5222         Trigger trigger2MatchSource127 =
5223                 TriggerFixture.getValidTriggerBuilder()
5224                         .setTriggerTime(20)
5225                         .setEnrollmentId(enrollmentId)
5226                         .setAttributionDestination(appDestination)
5227                         .setDestinationType(EventSurfaceType.APP)
5228                         .build();
5229 
5230         List<Source> result2 = runFunc.apply(trigger2MatchSource127);
5231         assertEquals(2, result2.size());
5232         assertEquals(sApp2.getId(), result2.get(0).getId());
5233         assertEquals(sApp3.getId(), result2.get(1).getId());
5234 
5235         // Trigger Time > sApp1's expiryTime
5236         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
5237         // Trigger Time > sApp3's eventTime and < sApp3's expiryTime
5238         // Trigger Time < sApp4's eventTime
5239         // sApp5 and sApp6 don't have app destination
5240         // Trigger Time > sAppWeb7's expiryTime
5241         // Expected: Match with sApp2, sApp3
5242         Trigger trigger3MatchSource237 =
5243                 TriggerFixture.getValidTriggerBuilder()
5244                         .setTriggerTime(21)
5245                         .setEnrollmentId(enrollmentId)
5246                         .setAttributionDestination(appDestination)
5247                         .setDestinationType(EventSurfaceType.APP)
5248                         .build();
5249 
5250         List<Source> result3 = runFunc.apply(trigger3MatchSource237);
5251         assertEquals(2, result3.size());
5252         assertEquals(sApp2.getId(), result3.get(0).getId());
5253         assertEquals(sApp3.getId(), result3.get(1).getId());
5254 
5255         // Trigger Time > sApp1's expiryTime
5256         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
5257         // Trigger Time > sApp3's eventTime and < sApp3's expiryTime
5258         // Trigger Time > sApp4's eventTime and < sApp4's expiryTime
5259         // sApp5 and sApp6 don't have app destination
5260         // Trigger Time > sAppWeb7's expiryTime
5261         // Expected: Match with sApp2, sApp3 and sApp4
5262         Trigger trigger4MatchSource1And2And3 =
5263                 TriggerFixture.getValidTriggerBuilder()
5264                         .setTriggerTime(31)
5265                         .setEnrollmentId(enrollmentId)
5266                         .setAttributionDestination(appDestination)
5267                         .setDestinationType(EventSurfaceType.APP)
5268                         .build();
5269 
5270         List<Source> result4 = runFunc.apply(trigger4MatchSource1And2And3);
5271         assertEquals(3, result4.size());
5272         assertEquals(sApp2.getId(), result4.get(0).getId());
5273         assertEquals(sApp3.getId(), result4.get(1).getId());
5274         assertEquals(sApp4.getId(), result4.get(2).getId());
5275 
5276         // sApp1, sApp2, sApp3, sApp4 don't have web destination
5277         // Trigger Time > sWeb5's eventTime and < sApp5's expiryTime
5278         // Trigger Time > sWeb6's eventTime and < sApp6's expiryTime
5279         // Trigger Time > sAppWeb7's eventTime and < sAppWeb7's expiryTime
5280         // Expected: Match with sApp5, sApp6, sAppWeb7
5281         Trigger trigger5MatchSource567 =
5282                 TriggerFixture.getValidTriggerBuilder()
5283                         .setTriggerTime(12)
5284                         .setEnrollmentId(enrollmentId)
5285                         .setAttributionDestination(webDestination)
5286                         .setDestinationType(EventSurfaceType.WEB)
5287                         .build();
5288         List<Source> result5 = runFunc.apply(trigger5MatchSource567);
5289         assertEquals(3, result1.size());
5290         assertEquals(sWeb5.getId(), result5.get(0).getId());
5291         assertEquals(sWeb6.getId(), result5.get(1).getId());
5292         assertEquals(sAppWeb7.getId(), result5.get(2).getId());
5293 
5294         // sApp1, sApp2, sApp3, sApp4 don't have web destination
5295         // Trigger Time > sWeb5's expiryTime
5296         // Trigger Time > sWeb6's eventTime and < sApp6's expiryTime
5297         // Trigger Time > sWeb7's expiryTime
5298         // Expected: Match with sApp6 only
5299         Trigger trigger6MatchSource67 =
5300                 TriggerFixture.getValidTriggerBuilder()
5301                         .setTriggerTime(21)
5302                         .setEnrollmentId(enrollmentId)
5303                         .setAttributionDestination(webDestinationWithSubdomain)
5304                         .setDestinationType(EventSurfaceType.WEB)
5305                         .build();
5306 
5307         List<Source> result6 = runFunc.apply(trigger6MatchSource67);
5308         assertEquals(1, result6.size());
5309         assertEquals(sWeb6.getId(), result6.get(0).getId());
5310 
5311         // Trigger with different subdomain than source
5312         // Expected: No Match found
5313         Trigger triggerDifferentRegistrationOrigin =
5314                 TriggerFixture.getValidTriggerBuilder()
5315                         .setTriggerTime(12)
5316                         .setEnrollmentId(enrollmentId)
5317                         .setAttributionDestination(appDestination)
5318                         .setDestinationType(EventSurfaceType.APP)
5319                         .setRegistrationOrigin(
5320                                 WebUtil.validUri("https://subdomain-different.example.test"))
5321                         .build();
5322 
5323         List<Source> result7 = runFunc.apply(triggerDifferentRegistrationOrigin);
5324         assertTrue(result7.isEmpty());
5325 
5326         // Trigger with different domain than source
5327         // Expected: No Match found
5328         Trigger triggerDifferentDomainOrigin =
5329                 TriggerFixture.getValidTriggerBuilder()
5330                         .setTriggerTime(12)
5331                         .setEnrollmentId(enrollmentId)
5332                         .setAttributionDestination(appDestination)
5333                         .setDestinationType(EventSurfaceType.APP)
5334                         .setRegistrationOrigin(
5335                                 WebUtil.validUri("https://subdomain.example-different.test"))
5336                         .build();
5337 
5338         List<Source> result8 = runFunc.apply(triggerDifferentDomainOrigin);
5339         assertTrue(result8.isEmpty());
5340 
5341         // Trigger with different port than source
5342         // Expected: No Match found
5343         Trigger triggerDifferentPort =
5344                 TriggerFixture.getValidTriggerBuilder()
5345                         .setTriggerTime(12)
5346                         .setEnrollmentId(enrollmentId)
5347                         .setAttributionDestination(appDestination)
5348                         .setDestinationType(EventSurfaceType.APP)
5349                         .setRegistrationOrigin(
5350                                 WebUtil.validUri("https://subdomain.example.test:8083"))
5351                         .build();
5352 
5353         List<Source> result9 = runFunc.apply(triggerDifferentPort);
5354         assertTrue(result9.isEmpty());
5355 
5356         // Enrollment id for trigger and source not same
5357         // Registration Origin for trigger and source same
5358         // Expected: Match with sApp1, sApp2, sAppWeb7
5359         Trigger triggerDifferentEnrollmentSameRegistration =
5360                 TriggerFixture.getValidTriggerBuilder()
5361                         .setTriggerTime(12)
5362                         .setEnrollmentId("different-enrollment-id")
5363                         .setAttributionDestination(appDestination)
5364                         .setDestinationType(EventSurfaceType.APP)
5365                         .build();
5366         List<Source> result10 = runFunc.apply(triggerDifferentEnrollmentSameRegistration);
5367         assertEquals(3, result10.size());
5368         assertEquals(sApp1.getId(), result10.get(0).getId());
5369         assertEquals(sApp2.getId(), result10.get(1).getId());
5370         assertEquals(sAppWeb7.getId(), result10.get(2).getId());
5371     }
5372 
5373     @Test
testGetMatchingActiveSources_multipleDestinations()5374     public void testGetMatchingActiveSources_multipleDestinations() {
5375         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5376         String enrollmentId = "enrollment-id";
5377         Uri webDestination1 = WebUtil.validUri("https://example.test");
5378         Uri webDestination1WithSubdomain = WebUtil.validUri("https://xyz.example.test");
5379         Uri webDestination2 = WebUtil.validUri("https://example2.test");
5380         Source sWeb1 =
5381                 SourceFixture.getMinimalValidSourceBuilder()
5382                         .setId("1")
5383                         .setEventTime(10)
5384                         .setExpiryTime(20)
5385                         .setWebDestinations(List.of(webDestination1))
5386                         .setEnrollmentId(enrollmentId)
5387                         .build();
5388         Source sWeb2 =
5389                 SourceFixture.getMinimalValidSourceBuilder()
5390                         .setId("2")
5391                         .setEventTime(10)
5392                         .setExpiryTime(50)
5393                         .setWebDestinations(List.of(webDestination1, webDestination2))
5394                         .setEnrollmentId(enrollmentId)
5395                         .build();
5396         Source sAppWeb3 =
5397                 SourceFixture.getMinimalValidSourceBuilder()
5398                         .setId("3")
5399                         .setEventTime(10)
5400                         .setExpiryTime(20)
5401                         .setWebDestinations(List.of(webDestination1))
5402                         .setEnrollmentId(enrollmentId)
5403                         .build();
5404 
5405         List<Source> sources = Arrays.asList(sWeb1, sWeb2, sAppWeb3);
5406         sources.forEach(source -> insertInDb(db, source));
5407 
5408         Function<Trigger, List<Source>> getMatchingSources =
5409                 trigger -> {
5410                     List<Source> result =
5411                             mDatastoreManager
5412                                     .runInTransactionWithResult(
5413                                             measurementDao ->
5414                                                     measurementDao.getMatchingActiveSources(
5415                                                             trigger))
5416                                     .orElseThrow();
5417                     result.sort(Comparator.comparing(Source::getId));
5418                     return result;
5419                 };
5420 
5421         Trigger triggerMatchSourceWeb2 =
5422                 TriggerFixture.getValidTriggerBuilder()
5423                         .setTriggerTime(21)
5424                         .setEnrollmentId(enrollmentId)
5425                         .setAttributionDestination(webDestination1WithSubdomain)
5426                         .setDestinationType(EventSurfaceType.WEB)
5427                         .build();
5428 
5429         List<Source> result = getMatchingSources.apply(triggerMatchSourceWeb2);
5430         assertEquals(1, result.size());
5431         assertEquals(sWeb2.getId(), result.get(0).getId());
5432     }
5433 
5434     @Test
testGetMatchingActiveSources_attributionScopeEnabled_populateScopes()5435     public void testGetMatchingActiveSources_attributionScopeEnabled_populateScopes() {
5436         mLegacyFlags = mMockFlags;
5437         mocker.mockGetFlags(mMockFlags);
5438         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
5439         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
5440 
5441         // S0: attribution scopes -> [], destinations -> [D1, D2]
5442         Source source0 =
5443                 insertSourceForAttributionScope(
5444                         /* attributionScopes= */ null,
5445                         /* attributionScopeLimit= */ null,
5446                         /* maxEventStates= */ null,
5447                         SOURCE_EVENT_TIME,
5448                         List.of(WEB_ONE_DESTINATION),
5449                         List.of(APP_ONE_DESTINATION));
5450         // S1: attribution scopes -> ["1", "2"], destinations -> [D1]
5451         Source source1 =
5452                 insertSourceForAttributionScope(
5453                         List.of("1", "2"),
5454                         ATTRIBUTION_SCOPE_LIMIT,
5455                         MAX_EVENT_STATES,
5456                         SOURCE_EVENT_TIME + 1,
5457                         null,
5458                         List.of(APP_ONE_DESTINATION));
5459         // S2: attribution scopes -> ["2", "3"], destinations -> [D2]
5460         Source source2 =
5461                 insertSourceForAttributionScope(
5462                         List.of("2", "3"),
5463                         ATTRIBUTION_SCOPE_LIMIT,
5464                         MAX_EVENT_STATES,
5465                         SOURCE_EVENT_TIME + 2,
5466                         List.of(WEB_ONE_DESTINATION),
5467                         null);
5468 
5469         Trigger trigger0 =
5470                 TriggerFixture.getValidTriggerBuilder()
5471                         .setTriggerTime(SOURCE_EVENT_TIME + 3)
5472                         .setAttributionDestination(APP_ONE_DESTINATION)
5473                         .setDestinationType(EventSurfaceType.APP)
5474                         .build();
5475         List<Source> matchingSources0 = getMatchingSources(trigger0);
5476         assertThat(matchingSources0.size()).isEqualTo(2);
5477         List<String> matchingSourceIds0 =
5478                 matchingSources0.stream().map(Source::getId).collect(Collectors.toList());
5479         List<List<String>> matchingSourceAttributionScopes0 =
5480                 matchingSources0.stream()
5481                         .map(Source::getAttributionScopes)
5482                         .collect(Collectors.toList());
5483         assertThat(matchingSourceIds0).containsExactly(source0.getId(), source1.getId());
5484         // Source attribution scopes won't be populated if trigger doesn't have attribution scope.
5485         assertThat(matchingSourceAttributionScopes0).containsExactly(null, null);
5486 
5487         Trigger trigger1 =
5488                 TriggerFixture.getValidTriggerBuilder()
5489                         .setTriggerTime(SOURCE_EVENT_TIME + 4)
5490                         .setAttributionScopesString("1")
5491                         .setAttributionDestination(APP_ONE_DESTINATION)
5492                         .setDestinationType(EventSurfaceType.APP)
5493                         .build();
5494         List<Source> matchingSources1 = getMatchingSources(trigger1);
5495         List<String> matchingSourceIds1 =
5496                 matchingSources1.stream().map(Source::getId).collect(Collectors.toList());
5497         List<List<String>> matchingSourceAttributionScopes1 =
5498                 matchingSources1.stream()
5499                         .map(Source::getAttributionScopes)
5500                         .collect(Collectors.toList());
5501         assertThat(matchingSourceIds1).containsExactly(source0.getId(), source1.getId());
5502         assertThat(matchingSourceAttributionScopes1)
5503                 .containsExactly(source0.getAttributionScopes(), source1.getAttributionScopes());
5504 
5505         Trigger trigger2 =
5506                 TriggerFixture.getValidTriggerBuilder()
5507                         .setTriggerTime(SOURCE_EVENT_TIME + 5)
5508                         .setAttributionScopesString("2")
5509                         .setAttributionDestination(WEB_ONE_DESTINATION)
5510                         .setDestinationType(EventSurfaceType.WEB)
5511                         .build();
5512         List<Source> matchingSources2 = getMatchingSources(trigger2);
5513         List<String> matchingSourceIds2 =
5514                 matchingSources2.stream().map(Source::getId).collect(Collectors.toList());
5515         List<List<String>> matchingSourceAttributionScopes2 =
5516                 matchingSources2.stream()
5517                         .map(Source::getAttributionScopes)
5518                         .collect(Collectors.toList());
5519         assertThat(matchingSourceIds2).containsExactly(source0.getId(), source2.getId());
5520         assertThat(matchingSourceAttributionScopes2)
5521                 .containsExactly(source0.getAttributionScopes(), source2.getAttributionScopes());
5522     }
5523 
5524     @Test
testGetAttributionScopesForRegistration()5525     public void testGetAttributionScopesForRegistration() {
5526         mLegacyFlags = mMockFlags;
5527         mocker.mockGetFlags(mMockFlags);
5528         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
5529         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
5530 
5531         insertSourceForAttributionScope(
5532                 List.of("1"),
5533                 ATTRIBUTION_SCOPE_LIMIT,
5534                 MAX_EVENT_STATES,
5535                 SOURCE_EVENT_TIME,
5536                 List.of(WEB_ONE_DESTINATION),
5537                 List.of(APP_ONE_DESTINATION),
5538                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN,
5539                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
5540                 Source.SourceType.NAVIGATION,
5541                 Source.Status.ACTIVE);
5542         insertSourceForAttributionScope(
5543                 List.of("2"),
5544                 ATTRIBUTION_SCOPE_LIMIT,
5545                 MAX_EVENT_STATES,
5546                 SOURCE_EVENT_TIME,
5547                 List.of(WEB_ONE_DESTINATION),
5548                 List.of(APP_ONE_DESTINATION),
5549                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN,
5550                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
5551                 Source.SourceType.NAVIGATION,
5552                 Source.Status.ACTIVE);
5553         insertSourceForAttributionScope(
5554                 List.of("3"),
5555                 ATTRIBUTION_SCOPE_LIMIT,
5556                 MAX_EVENT_STATES,
5557                 SOURCE_EVENT_TIME,
5558                 List.of(WEB_ONE_DESTINATION),
5559                 List.of(APP_ONE_DESTINATION),
5560                 REGISTRATION_ORIGIN_2,
5561                 REGISTRATION_ID2,
5562                 Source.SourceType.NAVIGATION,
5563                 Source.Status.ACTIVE);
5564         // Ignored source, attribution scopes ignored.
5565         insertSourceForAttributionScope(
5566                 List.of("4"),
5567                 ATTRIBUTION_SCOPE_LIMIT,
5568                 MAX_EVENT_STATES,
5569                 SOURCE_EVENT_TIME,
5570                 List.of(WEB_ONE_DESTINATION),
5571                 List.of(APP_ONE_DESTINATION),
5572                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN,
5573                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
5574                 Source.SourceType.NAVIGATION,
5575                 Source.Status.IGNORED);
5576 
5577         // Execution
5578         mDatastoreManager.runInTransaction(
5579                 (dao) -> {
5580                     assertThat(
5581                                     dao.getAttributionScopesForRegistration(
5582                                                     SourceFixture.ValidSourceParams.REGISTRATION_ID,
5583                                                     SourceFixture.ValidSourceParams
5584                                                             .REGISTRATION_ORIGIN
5585                                                             .toString())
5586                                             .get())
5587                             .containsExactly("1", "2");
5588                     assertThat(
5589                                     dao.getAttributionScopesForRegistration(
5590                                                     REGISTRATION_ID2,
5591                                                     REGISTRATION_ORIGIN_2.toString())
5592                                             .get())
5593                             .containsExactly("3");
5594                     assertThat(
5595                                     dao.getAttributionScopesForRegistration(
5596                                                     SourceFixture.ValidSourceParams.REGISTRATION_ID,
5597                                                     SourceFixture.ValidSourceParams
5598                                                             .REGISTRATION_ORIGIN
5599                                                             .toString())
5600                                             .get())
5601                             .containsExactly("1", "2");
5602                     assertThat(
5603                                     dao.getAttributionScopesForRegistration(
5604                                                     REGISTRATION_ID2,
5605                                                     REGISTRATION_ORIGIN_2.toString())
5606                                             .get())
5607                             .containsExactly("3");
5608                     assertThat(
5609                                     dao.getAttributionScopesForRegistration(
5610                                                     SourceFixture.ValidSourceParams.REGISTRATION_ID,
5611                                                     REGISTRATION_ORIGIN_2.toString())
5612                                             .isEmpty())
5613                             .isTrue();
5614                 });
5615     }
5616 
5617     @Test
testGetMatchingActiveDelayedSources()5618     public void testGetMatchingActiveDelayedSources() {
5619         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5620         Objects.requireNonNull(db);
5621         String enrollmentId = "enrollment-id";
5622         Uri appDestination = Uri.parse("android-app://com.example.abc");
5623         Uri webDestination = WebUtil.validUri("https://example.test");
5624         Source sApp1 =
5625                 SourceFixture.getMinimalValidSourceBuilder()
5626                         .setId("1")
5627                         .setEventTime(10)
5628                         .setExpiryTime(20)
5629                         .setAppDestinations(List.of(appDestination))
5630                         .setEnrollmentId(enrollmentId)
5631                         .build();
5632         Source sApp2 =
5633                 SourceFixture.getMinimalValidSourceBuilder()
5634                         .setId("2")
5635                         .setEventTime(140)
5636                         .setExpiryTime(200)
5637                         .setAppDestinations(List.of(appDestination))
5638                         .setEnrollmentId(enrollmentId)
5639                         .build();
5640         Source sApp3 =
5641                 SourceFixture.getMinimalValidSourceBuilder()
5642                         .setId("3")
5643                         .setEventTime(20)
5644                         .setExpiryTime(50)
5645                         .setAppDestinations(List.of(appDestination))
5646                         .setEnrollmentId(enrollmentId)
5647                         .build();
5648         Source sApp4 =
5649                 SourceFixture.getMinimalValidSourceBuilder()
5650                         .setId("4")
5651                         .setEventTime(16)
5652                         .setExpiryTime(50)
5653                         .setAppDestinations(List.of(appDestination))
5654                         .setEnrollmentId(enrollmentId)
5655                         .build();
5656         Source sWeb5 =
5657                 SourceFixture.getMinimalValidSourceBuilder()
5658                         .setId("5")
5659                         .setEventTime(13)
5660                         .setExpiryTime(20)
5661                         .setWebDestinations(List.of(webDestination))
5662                         .setEnrollmentId(enrollmentId)
5663                         .build();
5664         Source sWeb6 =
5665                 SourceFixture.getMinimalValidSourceBuilder()
5666                         .setId("6")
5667                         .setEventTime(14)
5668                         .setExpiryTime(50)
5669                         .setWebDestinations(List.of(webDestination))
5670                         .setEnrollmentId(enrollmentId)
5671                         .build();
5672         Source sAppWeb7 =
5673                 SourceFixture.getMinimalValidSourceBuilder()
5674                         .setId("7")
5675                         .setEventTime(10)
5676                         .setExpiryTime(20)
5677                         .setAppDestinations(List.of(appDestination))
5678                         .setWebDestinations(List.of(webDestination))
5679                         .setEnrollmentId(enrollmentId)
5680                         .build();
5681         Source sAppWeb8 =
5682                 SourceFixture.getMinimalValidSourceBuilder()
5683                         .setId("8")
5684                         .setEventTime(15)
5685                         .setExpiryTime(25)
5686                         .setAppDestinations(List.of(appDestination))
5687                         .setWebDestinations(List.of(webDestination))
5688                         .setEnrollmentId(enrollmentId)
5689                         .build();
5690 
5691         List<Source> sources =
5692                 Arrays.asList(sApp1, sApp2, sApp3, sApp4, sWeb5, sWeb6, sAppWeb7, sAppWeb8);
5693         sources.forEach(source -> insertInDb(db, source));
5694 
5695         Function<Trigger, Optional<Source>> runFunc =
5696                 trigger -> {
5697                     Optional<Source> result =
5698                             mDatastoreManager
5699                                     .runInTransactionWithResult(
5700                                             measurementDao ->
5701                                                     measurementDao
5702                                                             .getNearestDelayedMatchingActiveSource(
5703                                                                     trigger))
5704                                     .orElseThrow();
5705                     return result;
5706                 };
5707 
5708         // sApp1's eventTime <= Trigger Time
5709         // Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW > sApp2's eventTime
5710         // Trigger Time < sApp3's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5711         // Trigger Time < sApp4's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5712         // sWeb5 and sWeb6 don't have app destination
5713         // sAppWeb7's eventTime <= Trigger Time
5714         // Trigger Time < sAppWeb8's eventTime <= Trigger Time +
5715         // MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5716         // Expected: Match with sAppWeb8
5717         Trigger trigger1MatchSource8 =
5718                 TriggerFixture.getValidTriggerBuilder()
5719                         .setTriggerTime(12)
5720                         .setEnrollmentId(enrollmentId)
5721                         .setAttributionDestination(appDestination)
5722                         .setDestinationType(EventSurfaceType.APP)
5723                         .build();
5724         Optional<Source> result1 = runFunc.apply(trigger1MatchSource8);
5725         assertEquals(sAppWeb8.getId(), result1.get().getId());
5726 
5727         // sApp1's eventTime <= Trigger Time
5728         // Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW > sApp2's eventTime
5729         // Trigger Time < sApp3's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5730         // Trigger Time < sApp4's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5731         // sWeb5 and sWeb6 don't have app destination
5732         // sAppWeb7's eventTime <= Trigger Time
5733         // sAppWeb8's eventTime <= Trigger Time
5734         // Expected: Match with sApp4
5735         Trigger trigger2MatchSource4 =
5736                 TriggerFixture.getValidTriggerBuilder()
5737                         .setTriggerTime(15)
5738                         .setEnrollmentId(enrollmentId)
5739                         .setAttributionDestination(appDestination)
5740                         .setDestinationType(EventSurfaceType.APP)
5741                         .build();
5742         Optional<Source> result2 = runFunc.apply(trigger2MatchSource4);
5743         assertEquals(sApp4.getId(), result2.get().getId());
5744 
5745         // sApp1's eventTime <= Trigger Time
5746         // sApp2's eventTime <= Trigger Time
5747         // sApp3's eventTime <= Trigger Time
5748         // sApp4's eventTime <= Trigger Time
5749         // sWeb5 and sWeb6 don't have app destination
5750         // sAppWeb7's eventTime <= Trigger Time
5751         // sAppWeb8's eventTime <= Trigger Time
5752         // Expected: no match
5753         Trigger trigger3NoMatchingSource =
5754                 TriggerFixture.getValidTriggerBuilder()
5755                         .setTriggerTime(150)
5756                         .setEnrollmentId(enrollmentId)
5757                         .setAttributionDestination(appDestination)
5758                         .setDestinationType(EventSurfaceType.APP)
5759                         .build();
5760         Optional<Source> result3 = runFunc.apply(trigger3NoMatchingSource);
5761         assertFalse(result3.isPresent());
5762     }
5763 
5764     @Test
testInsertAggregateEncryptionKey()5765     public void testInsertAggregateEncryptionKey() {
5766         String keyId = "38b1d571-f924-4dc0-abe1-e2bac9b6a6be";
5767         String publicKey = "/amqBgfDOvHAIuatDyoHxhfHaMoYA4BDxZxwtWBRQhc=";
5768         long expiry = 1653620135831L;
5769         Uri aggregationOrigin = WebUtil.validUri("https://a.test");
5770 
5771         mDatastoreManager.runInTransaction(
5772                 (dao) ->
5773                         dao.insertAggregateEncryptionKey(
5774                                 new AggregateEncryptionKey.Builder()
5775                                         .setKeyId(keyId)
5776                                         .setPublicKey(publicKey)
5777                                         .setExpiry(expiry)
5778                                         .setAggregationCoordinatorOrigin(aggregationOrigin)
5779                                         .build()));
5780 
5781         try (Cursor cursor =
5782                 MeasurementDbHelper.getInstance()
5783                         .getReadableDatabase()
5784                         .query(
5785                                 MeasurementTables.AggregateEncryptionKey.TABLE,
5786                                 null,
5787                                 null,
5788                                 null,
5789                                 null,
5790                                 null,
5791                                 null)) {
5792             assertTrue(cursor.moveToNext());
5793             AggregateEncryptionKey aggregateEncryptionKey =
5794                     SqliteObjectMapper.constructAggregateEncryptionKeyFromCursor(cursor);
5795             assertNotNull(aggregateEncryptionKey);
5796             assertNotNull(aggregateEncryptionKey.getId());
5797             assertEquals(keyId, aggregateEncryptionKey.getKeyId());
5798             assertEquals(publicKey, aggregateEncryptionKey.getPublicKey());
5799             assertEquals(expiry, aggregateEncryptionKey.getExpiry());
5800             assertEquals(
5801                     aggregationOrigin, aggregateEncryptionKey.getAggregationCoordinatorOrigin());
5802         }
5803     }
5804 
5805     @Test
testInsertAggregateReport()5806     public void testInsertAggregateReport() {
5807         AggregateReport validAggregateReport = AggregateReportFixture.getValidAggregateReport();
5808         mDatastoreManager.runInTransaction(
5809                 (dao) -> dao.insertAggregateReport(validAggregateReport));
5810 
5811         try (Cursor cursor =
5812                 MeasurementDbHelper.getInstance()
5813                         .getReadableDatabase()
5814                         .query(
5815                                 MeasurementTables.AggregateReport.TABLE,
5816                                 null,
5817                                 null,
5818                                 null,
5819                                 null,
5820                                 null,
5821                                 null)) {
5822             assertTrue(cursor.moveToNext());
5823             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5824             assertNotNull(aggregateReport);
5825             assertNotNull(aggregateReport.getId());
5826             assertEquals(validAggregateReport, aggregateReport);
5827         }
5828     }
5829 
5830     @Test
testInsertAggregateReport_withNullSourceRegistrationTime()5831     public void testInsertAggregateReport_withNullSourceRegistrationTime() {
5832         AggregateReport.Builder builder = AggregateReportFixture.getValidAggregateReportBuilder();
5833         builder.setSourceRegistrationTime(null);
5834         AggregateReport aggregateReportWithNullSourceRegistrationTime = builder.build();
5835         mDatastoreManager.runInTransaction(
5836                 (dao) -> dao.insertAggregateReport(aggregateReportWithNullSourceRegistrationTime));
5837 
5838         try (Cursor cursor =
5839                 MeasurementDbHelper.getInstance()
5840                         .getReadableDatabase()
5841                         .query(
5842                                 MeasurementTables.AggregateReport.TABLE,
5843                                 null,
5844                                 null,
5845                                 null,
5846                                 null,
5847                                 null,
5848                                 null)) {
5849             assertTrue(cursor.moveToNext());
5850             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5851             assertNotNull(aggregateReport);
5852             assertNotNull(aggregateReport.getId());
5853             assertEquals(aggregateReportWithNullSourceRegistrationTime, aggregateReport);
5854         }
5855     }
5856 
5857     @Test
testExistsSourcesWithSameDestination()5858     public void testExistsSourcesWithSameDestination() {
5859         long currentTime = System.currentTimeMillis();
5860         Uri destination1 = Uri.parse("android-app://com.destination1");
5861         Uri destination2 = Uri.parse("android-app://com.destination2");
5862         Uri destination3 = Uri.parse("android-app://com.destination3");
5863         Uri destination4 = Uri.parse("android-app://com.destination4");
5864         Uri destination5 = Uri.parse("android-app://com.destination5");
5865 
5866         // Return true.
5867         Source s1 =
5868                 SourceFixture.getMinimalValidSourceBuilder()
5869                         .setId("S1")
5870                         .setStatus(0)
5871                         .setExpiryTime(currentTime + 1000L)
5872                         .setAppDestinations(List.of(destination1))
5873                         .build();
5874 
5875         // Inactive source. Return false.
5876         Source s2 =
5877                 SourceFixture.getMinimalValidSourceBuilder()
5878                         .setId("S1")
5879                         .setStatus(1)
5880                         .setExpiryTime(currentTime + 1000L)
5881                         .setAppDestinations(List.of(destination2))
5882                         .build();
5883 
5884         // Expired source. Return false.
5885         Source s3 =
5886                 SourceFixture.getMinimalValidSourceBuilder()
5887                         .setId("S3")
5888                         .setStatus(0)
5889                         .setExpiryTime(currentTime - 1000L)
5890                         .setAppDestinations(List.of(destination3))
5891                         .build();
5892 
5893         // Web source. Return false.
5894         Source s4 =
5895                 SourceFixture.getMinimalValidSourceBuilder()
5896                         .setId("S4")
5897                         .setStatus(0)
5898                         .setExpiryTime(currentTime + 1000L)
5899                         .setWebDestinations(List.of(destination4))
5900                         .build();
5901 
5902         insertSource(s1, "S1");
5903         insertSource(s2, "S2");
5904         insertSource(s3, "S3");
5905         insertSource(s4, "S4");
5906 
5907         Optional<Boolean> result1 =
5908                 mDatastoreManager.runInTransactionWithResult(
5909                         (dao) -> dao.existsActiveSourcesWithDestination(destination1, currentTime));
5910 
5911         Optional<Boolean> result2 =
5912                 mDatastoreManager.runInTransactionWithResult(
5913                         (dao) -> dao.existsActiveSourcesWithDestination(destination2, currentTime));
5914 
5915         Optional<Boolean> result3 =
5916                 mDatastoreManager.runInTransactionWithResult(
5917                         (dao) -> dao.existsActiveSourcesWithDestination(destination3, currentTime));
5918 
5919         Optional<Boolean> result4 =
5920                 mDatastoreManager.runInTransactionWithResult(
5921                         (dao) -> dao.existsActiveSourcesWithDestination(destination4, currentTime));
5922 
5923         // No source with app destination 5. Return false.
5924         Optional<Boolean> result5 =
5925                 mDatastoreManager.runInTransactionWithResult(
5926                         (dao) -> dao.existsActiveSourcesWithDestination(destination5, currentTime));
5927 
5928         assertThat(result1.get()).isTrue();
5929         assertThat(result2.get()).isFalse();
5930         assertThat(result3.get()).isFalse();
5931         assertThat(result4.get()).isFalse();
5932         assertThat(result5.get()).isFalse();
5933     }
5934 
5935     @Test
testInsertAggregateReport_withTriggerTime()5936     public void testInsertAggregateReport_withTriggerTime() {
5937         AggregateReport.Builder builder = AggregateReportFixture.getValidAggregateReportBuilder();
5938         builder.setTriggerTime(1L);
5939         AggregateReport aggregateReportWithTriggerTime = builder.build();
5940         mDatastoreManager.runInTransaction(
5941                 (dao) -> dao.insertAggregateReport(aggregateReportWithTriggerTime));
5942 
5943         try (Cursor cursor =
5944                 MeasurementDbHelper.getInstance()
5945                         .getReadableDatabase()
5946                         .query(
5947                                 MeasurementTables.AggregateReport.TABLE,
5948                                 null,
5949                                 null,
5950                                 null,
5951                                 null,
5952                                 null,
5953                                 null)) {
5954             assertTrue(cursor.moveToNext());
5955             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5956             assertNotNull(aggregateReport);
5957             assertNotNull(aggregateReport.getId());
5958             assertEquals(aggregateReportWithTriggerTime, aggregateReport);
5959         }
5960     }
5961 
5962     @Test
testInsertAggregateReport_withAggregatableFilteringIdMaxBytes()5963     public void testInsertAggregateReport_withAggregatableFilteringIdMaxBytes() {
5964         AggregateReport.Builder builder = AggregateReportFixture.getValidAggregateReportBuilder();
5965         builder.setAggregatableFilteringIdMaxBytes(2);
5966         AggregateReport aggregateReportWithAggregatableFilteringIdMaxBytes = builder.build();
5967         mDatastoreManager.runInTransaction(
5968                 (dao) ->
5969                         dao.insertAggregateReport(
5970                                 aggregateReportWithAggregatableFilteringIdMaxBytes));
5971 
5972         try (Cursor cursor =
5973                 MeasurementDbHelper.getInstance()
5974                         .getReadableDatabase()
5975                         .query(
5976                                 MeasurementTables.AggregateReport.TABLE,
5977                                 null,
5978                                 null,
5979                                 null,
5980                                 null,
5981                                 null,
5982                                 null)) {
5983             assertTrue(cursor.moveToNext());
5984             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5985             assertNotNull(aggregateReport);
5986             assertNotNull(aggregateReport.getId());
5987             assertEquals(aggregateReportWithAggregatableFilteringIdMaxBytes, aggregateReport);
5988         }
5989     }
5990 
5991     @Test
testDeleteAllMeasurementDataWithEmptyList()5992     public void testDeleteAllMeasurementDataWithEmptyList() {
5993         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5994 
5995         Source source = SourceFixture.getMinimalValidSourceBuilder().setId("S1").build();
5996         ContentValues sourceValue = new ContentValues();
5997         sourceValue.put("_id", source.getId());
5998         db.insert(SourceContract.TABLE, null, sourceValue);
5999 
6000         Trigger trigger = TriggerFixture.getValidTriggerBuilder().setId("T1").build();
6001         ContentValues triggerValue = new ContentValues();
6002         triggerValue.put("_id", trigger.getId());
6003         db.insert(TriggerContract.TABLE, null, triggerValue);
6004 
6005         EventReport eventReport = new EventReport.Builder().setId("E1").build();
6006         ContentValues eventReportValue = new ContentValues();
6007         eventReportValue.put("_id", eventReport.getId());
6008         db.insert(EventReportContract.TABLE, null, eventReportValue);
6009 
6010         AggregateReport aggregateReport = new AggregateReport.Builder().setId("A1").build();
6011         ContentValues aggregateReportValue = new ContentValues();
6012         aggregateReportValue.put("_id", aggregateReport.getId());
6013         db.insert(MeasurementTables.AggregateReport.TABLE, null, aggregateReportValue);
6014 
6015         ContentValues rateLimitValue = new ContentValues();
6016         rateLimitValue.put(AttributionContract.ID, "ARL1");
6017         rateLimitValue.put(AttributionContract.SOURCE_SITE, "sourceSite");
6018         rateLimitValue.put(AttributionContract.SOURCE_ORIGIN, "sourceOrigin");
6019         rateLimitValue.put(AttributionContract.DESTINATION_SITE, "destinationSite");
6020         rateLimitValue.put(AttributionContract.TRIGGER_TIME, 5L);
6021         rateLimitValue.put(AttributionContract.REGISTRANT, "registrant");
6022         rateLimitValue.put(AttributionContract.ENROLLMENT_ID, "enrollmentId");
6023 
6024         db.insert(AttributionContract.TABLE, null, rateLimitValue);
6025 
6026         AggregateEncryptionKey key =
6027                 new AggregateEncryptionKey.Builder()
6028                         .setId("K1")
6029                         .setKeyId("keyId")
6030                         .setPublicKey("publicKey")
6031                         .setExpiry(1)
6032                         .setAggregationCoordinatorOrigin(Uri.parse("https://1.test"))
6033                         .build();
6034         ContentValues keyValues = new ContentValues();
6035         keyValues.put("_id", key.getId());
6036 
6037         mDatastoreManager.runInTransaction(
6038                 (dao) -> dao.deleteAllMeasurementData(Collections.emptyList()));
6039 
6040         for (String table : ALL_MSMT_TABLES) {
6041             assertThat(
6042                             db.query(
6043                                             /* table */ table,
6044                                             /* columns */ null,
6045                                             /* selection */ null,
6046                                             /* selectionArgs */ null,
6047                                             /* groupBy */ null,
6048                                             /* having */ null,
6049                                             /* orderedBy */ null)
6050                                     .getCount())
6051                     .isEqualTo(0);
6052         }
6053     }
6054 
6055     @Test
testDeleteAllMeasurementDataWithNonEmptyList()6056     public void testDeleteAllMeasurementDataWithNonEmptyList() {
6057         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6058 
6059         Source source = SourceFixture.getMinimalValidSourceBuilder().setId("S1").build();
6060         ContentValues sourceValue = new ContentValues();
6061         sourceValue.put("_id", source.getId());
6062         db.insert(SourceContract.TABLE, null, sourceValue);
6063 
6064         Trigger trigger = TriggerFixture.getValidTriggerBuilder().setId("T1").build();
6065         ContentValues triggerValue = new ContentValues();
6066         triggerValue.put("_id", trigger.getId());
6067         db.insert(TriggerContract.TABLE, null, triggerValue);
6068 
6069         EventReport eventReport = new EventReport.Builder().setId("E1").build();
6070         ContentValues eventReportValue = new ContentValues();
6071         eventReportValue.put("_id", eventReport.getId());
6072         db.insert(EventReportContract.TABLE, null, eventReportValue);
6073 
6074         AggregateReport aggregateReport = new AggregateReport.Builder().setId("A1").build();
6075         ContentValues aggregateReportValue = new ContentValues();
6076         aggregateReportValue.put("_id", aggregateReport.getId());
6077         db.insert(MeasurementTables.AggregateReport.TABLE, null, aggregateReportValue);
6078 
6079         ContentValues rateLimitValue = new ContentValues();
6080         rateLimitValue.put(AttributionContract.ID, "ARL1");
6081         rateLimitValue.put(AttributionContract.SOURCE_SITE, "sourceSite");
6082         rateLimitValue.put(AttributionContract.SOURCE_ORIGIN, "sourceOrigin");
6083         rateLimitValue.put(AttributionContract.DESTINATION_SITE, "destinationSite");
6084         rateLimitValue.put(AttributionContract.TRIGGER_TIME, 5L);
6085         rateLimitValue.put(AttributionContract.REGISTRANT, "registrant");
6086         rateLimitValue.put(AttributionContract.ENROLLMENT_ID, "enrollmentId");
6087         db.insert(AttributionContract.TABLE, null, rateLimitValue);
6088 
6089         AggregateEncryptionKey key =
6090                 new AggregateEncryptionKey.Builder()
6091                         .setId("K1")
6092                         .setKeyId("keyId")
6093                         .setPublicKey("publicKey")
6094                         .setExpiry(1)
6095                         .setAggregationCoordinatorOrigin(Uri.parse("https://1.test"))
6096                         .build();
6097         ContentValues keyValues = new ContentValues();
6098         keyValues.put("_id", key.getId());
6099 
6100         List<String> excludedTables = List.of(SourceContract.TABLE);
6101 
6102         mDatastoreManager.runInTransaction((dao) -> dao.deleteAllMeasurementData(excludedTables));
6103 
6104         for (String table : ALL_MSMT_TABLES) {
6105             if (!excludedTables.contains(table)) {
6106                 assertThat(
6107                                 db.query(
6108                                                 /* table */ table,
6109                                                 /* columns */ null,
6110                                                 /* selection */ null,
6111                                                 /* selectionArgs */ null,
6112                                                 /* groupBy */ null,
6113                                                 /* having */ null,
6114                                                 /* orderedBy */ null)
6115                                         .getCount())
6116                         .isEqualTo(0);
6117             } else {
6118                 assertThat(
6119                                 db.query(
6120                                                 /* table */ table,
6121                                                 /* columns */ null,
6122                                                 /* selection */ null,
6123                                                 /* selectionArgs */ null,
6124                                                 /* groupBy */ null,
6125                                                 /* having */ null,
6126                                                 /* orderedBy */ null)
6127                                         .getCount())
6128                         .isNotEqualTo(0);
6129             }
6130         }
6131     }
6132 
6133     /** Test that the variable ALL_MSMT_TABLES actually has all the measurement related tables. */
6134     @Test
testAllMsmtTables()6135     public void testAllMsmtTables() {
6136         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6137         Cursor cursor =
6138                 db.query(
6139                         "sqlite_master",
6140                         /* columns */ null,
6141                         /* selection */ "type = ? AND name like ?",
6142                         /* selectionArgs*/ new String[] {"table", MSMT_TABLE_PREFIX + "%"},
6143                         /* groupBy */ null,
6144                         /* having */ null,
6145                         /* orderBy */ null);
6146 
6147         List<String> tableNames = new ArrayList<>();
6148         while (cursor.moveToNext()) {
6149             String tableName = cursor.getString(cursor.getColumnIndex("name"));
6150             tableNames.add(tableName);
6151         }
6152         assertThat(tableNames.size()).isEqualTo(ALL_MSMT_TABLES.length);
6153         for (String tableName : tableNames) {
6154             assertThat(ALL_MSMT_TABLES).asList().contains(tableName);
6155         }
6156     }
6157 
6158     @Test
insertAttributionRateLimit()6159     public void insertAttributionRateLimit() {
6160         // Setup
6161         Source source = SourceFixture.getValidSource();
6162         Trigger trigger =
6163                 TriggerFixture.getValidTriggerBuilder()
6164                         .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
6165                         .build();
6166         Attribution attribution =
6167                 new Attribution.Builder()
6168                         .setScope(Attribution.Scope.AGGREGATE)
6169                         .setEnrollmentId(source.getEnrollmentId())
6170                         .setDestinationOrigin(source.getWebDestinations().get(0).toString())
6171                         .setDestinationSite(source.getAppDestinations().get(0).toString())
6172                         .setSourceOrigin(source.getPublisher().toString())
6173                         .setSourceSite(source.getPublisher().toString())
6174                         .setRegistrant(source.getRegistrant().toString())
6175                         .setTriggerTime(trigger.getTriggerTime())
6176                         .setRegistrationOrigin(trigger.getRegistrationOrigin())
6177                         .setReportId(UUID.randomUUID().toString())
6178                         .build();
6179 
6180         // Execution
6181         mDatastoreManager.runInTransaction(
6182                 (dao) -> {
6183                     dao.insertAttribution(attribution);
6184                 });
6185 
6186         // Assertion
6187         try (Cursor cursor =
6188                 MeasurementDbHelper.getInstance()
6189                         .getReadableDatabase()
6190                         .query(AttributionContract.TABLE, null, null, null, null, null, null)) {
6191             assertTrue(cursor.moveToNext());
6192             assertEquals(
6193                     attribution.getScope(),
6194                     cursor.getInt(cursor.getColumnIndex(AttributionContract.SCOPE)));
6195             assertEquals(
6196                     attribution.getEnrollmentId(),
6197                     cursor.getString(cursor.getColumnIndex(AttributionContract.ENROLLMENT_ID)));
6198             assertEquals(
6199                     attribution.getDestinationOrigin(),
6200                     cursor.getString(
6201                             cursor.getColumnIndex(AttributionContract.DESTINATION_ORIGIN)));
6202             assertEquals(
6203                     attribution.getDestinationSite(),
6204                     cursor.getString(cursor.getColumnIndex(AttributionContract.DESTINATION_SITE)));
6205             assertEquals(
6206                     attribution.getSourceOrigin(),
6207                     cursor.getString(cursor.getColumnIndex(AttributionContract.SOURCE_ORIGIN)));
6208             assertEquals(
6209                     attribution.getSourceSite(),
6210                     cursor.getString(cursor.getColumnIndex(AttributionContract.SOURCE_SITE)));
6211             assertEquals(
6212                     attribution.getRegistrant(),
6213                     cursor.getString(cursor.getColumnIndex(AttributionContract.REGISTRANT)));
6214             assertEquals(
6215                     attribution.getTriggerTime(),
6216                     cursor.getLong(cursor.getColumnIndex(AttributionContract.TRIGGER_TIME)));
6217             assertEquals(
6218                     attribution.getRegistrationOrigin(),
6219                     Uri.parse(
6220                             cursor.getString(
6221                                     cursor.getColumnIndex(
6222                                             AttributionContract.REGISTRATION_ORIGIN))));
6223             assertEquals(
6224                     attribution.getReportId(),
6225                     cursor.getString(cursor.getColumnIndex(AttributionContract.REPORT_ID)));
6226         }
6227     }
6228 
6229     @Test
getAttributionsPerRateLimitWindow_atTimeWindowScoped_countsAttribution()6230     public void getAttributionsPerRateLimitWindow_atTimeWindowScoped_countsAttribution() {
6231         // Setup
6232         Source source = SourceFixture.getValidSource();
6233         Trigger trigger =
6234                 TriggerFixture.getValidTriggerBuilder()
6235                         .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
6236                         .build();
6237 
6238         Attribution eventAttribution =
6239                 getAttributionBuilder(source, trigger).setScope(Attribution.Scope.EVENT).build();
6240 
6241         Attribution aggregateAttribution =
6242                 getAttributionBuilder(source, trigger)
6243                         .setScope(Attribution.Scope.AGGREGATE)
6244                         .build();
6245 
6246         // Execution
6247         mDatastoreManager.runInTransaction(
6248                 (dao) -> {
6249                     dao.insertAttribution(eventAttribution);
6250                     dao.insertAttribution(aggregateAttribution);
6251                 });
6252 
6253         // Assertion
6254         AtomicLong eventAttributionsCount = new AtomicLong();
6255         AtomicLong aggregateAttributionsCount = new AtomicLong();
6256         mDatastoreManager.runInTransaction(
6257                 (dao) -> {
6258                     eventAttributionsCount.set(
6259                             dao.getAttributionsPerRateLimitWindow(
6260                                     Attribution.Scope.EVENT, source, trigger));
6261                     aggregateAttributionsCount.set(
6262                             dao.getAttributionsPerRateLimitWindow(
6263                                     Attribution.Scope.AGGREGATE, source, trigger));
6264                 });
6265 
6266         assertEquals(1L, eventAttributionsCount.get());
6267         assertEquals(1L, aggregateAttributionsCount.get());
6268     }
6269 
6270     @Test
getAttributionsPerRateLimitWindow_beyondTimeWindowScoped_countsAttribution()6271     public void getAttributionsPerRateLimitWindow_beyondTimeWindowScoped_countsAttribution() {
6272         // Setup
6273         Source source = SourceFixture.getValidSource();
6274         Trigger trigger =
6275                 TriggerFixture.getValidTriggerBuilder()
6276                         .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
6277                         .build();
6278 
6279         Attribution eventAttribution =
6280                 getAttributionBuilder(source, trigger)
6281                         .setTriggerTime(
6282                                 trigger.getTriggerTime()
6283                                         - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS)
6284                         .setScope(Attribution.Scope.EVENT)
6285                         .build();
6286 
6287         Attribution aggregateAttribution =
6288                 getAttributionBuilder(source, trigger)
6289                         .setTriggerTime(
6290                                 trigger.getTriggerTime()
6291                                         - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS)
6292                         .setScope(Attribution.Scope.AGGREGATE)
6293                         .build();
6294 
6295         // Execution
6296         mDatastoreManager.runInTransaction(
6297                 (dao) -> {
6298                     dao.insertAttribution(eventAttribution);
6299                     dao.insertAttribution(aggregateAttribution);
6300                 });
6301 
6302         // Assertion
6303         AtomicLong eventAttributionsCount = new AtomicLong();
6304         AtomicLong aggregateAttributionsCount = new AtomicLong();
6305         mDatastoreManager.runInTransaction(
6306                 (dao) -> {
6307                     eventAttributionsCount.set(
6308                             dao.getAttributionsPerRateLimitWindow(
6309                                     Attribution.Scope.EVENT, source, trigger));
6310                     aggregateAttributionsCount.set(
6311                             dao.getAttributionsPerRateLimitWindow(
6312                                     Attribution.Scope.AGGREGATE, source, trigger));
6313                 });
6314 
6315         assertEquals(0L, eventAttributionsCount.get());
6316         assertEquals(0L, aggregateAttributionsCount.get());
6317     }
6318 
6319     @Test
testTransactionRollbackForRuntimeException()6320     public void testTransactionRollbackForRuntimeException() {
6321         assertThrows(
6322                 IllegalArgumentException.class,
6323                 () ->
6324                         mDatastoreManager.runInTransaction(
6325                                 (dao) -> {
6326                                     dao.insertSource(SourceFixture.getValidSource());
6327                                     // build() call throws IllegalArgumentException
6328                                     Trigger trigger = new Trigger.Builder().build();
6329                                     dao.insertTrigger(trigger);
6330                                 }));
6331         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6332         Objects.requireNonNull(db);
6333         // There should be no insertions
6334         assertEquals(
6335                 0, db.query(SourceContract.TABLE, null, null, null, null, null, null).getCount());
6336         assertEquals(
6337                 0, db.query(TriggerContract.TABLE, null, null, null, null, null, null).getCount());
6338     }
6339 
6340     @Test
testDeleteEventReportAndAttribution()6341     public void testDeleteEventReportAndAttribution() throws Exception {
6342         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6343         Source s1 =
6344                 SourceFixture.getMinimalValidSourceBuilder()
6345                         .setEventId(new UnsignedLong(1L))
6346                         .setId("S1")
6347                         .build();
6348         Trigger t1 =
6349                 TriggerFixture.getValidTriggerBuilder()
6350                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
6351                         .setId("T1")
6352                         .build();
6353         Trigger t2 =
6354                 TriggerFixture.getValidTriggerBuilder()
6355                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
6356                         .setId("T2")
6357                         .build();
6358         EventReport e11 = createEventReportForSourceAndTrigger("E11", s1, t1);
6359         EventReport e12 = createEventReportForSourceAndTrigger("E12", s1, t2);
6360 
6361         Attribution aggregateAttribution11 =
6362                 createAttribution(
6363                         "ATT11_aggregate",
6364                         Attribution.Scope.AGGREGATE,
6365                         s1.getId(),
6366                         t1.getId(),
6367                         "E11");
6368         Attribution aggregateAttribution12 =
6369                 createAttribution(
6370                         "ATT12_aggregate",
6371                         Attribution.Scope.AGGREGATE,
6372                         s1.getId(),
6373                         t2.getId(),
6374                         "E12");
6375         Attribution eventAttribution11 =
6376                 createAttribution(
6377                         "ATT11_event",
6378                         Attribution.Scope.EVENT,
6379                         s1.getId(),
6380                         t1.getId(),
6381                         "E11");
6382         Attribution eventAttribution12 =
6383                 createAttribution(
6384                         "ATT12_event",
6385                         Attribution.Scope.EVENT,
6386                         s1.getId(),
6387                         t2.getId(),
6388                         "E12");
6389 
6390         insertSource(s1, s1.getId());
6391         AbstractDbIntegrationTest.insertToDb(t1, db);
6392         AbstractDbIntegrationTest.insertToDb(t2, db);
6393         AbstractDbIntegrationTest.insertToDb(e11, db);
6394         AbstractDbIntegrationTest.insertToDb(e12, db);
6395         AbstractDbIntegrationTest.insertToDb(aggregateAttribution11, db);
6396         AbstractDbIntegrationTest.insertToDb(aggregateAttribution12, db);
6397         AbstractDbIntegrationTest.insertToDb(eventAttribution11, db);
6398         AbstractDbIntegrationTest.insertToDb(eventAttribution12, db);
6399 
6400         // Assert attributions present
6401         assertNotNull(getAttribution("ATT11_aggregate", db));
6402         assertNotNull(getAttribution("ATT12_aggregate", db));
6403         assertNotNull(getAttribution("ATT11_event", db));
6404         assertNotNull(getAttribution("ATT12_event", db));
6405 
6406         mDatastoreManager.runInTransaction(
6407                 measurementDao -> {
6408                     // Assert sources and triggers present
6409                     assertNotNull(measurementDao.getSource("S1"));
6410                     assertNotNull(measurementDao.getTrigger("T1"));
6411                     assertNotNull(measurementDao.getTrigger("T2"));
6412 
6413                     // Validate presence of event reports
6414                     measurementDao.getEventReport("E11");
6415                     measurementDao.getEventReport("E12");
6416 
6417                     // Deletion
6418                     measurementDao.deleteEventReportAndAttribution(e11);
6419 
6420                     // Validate event report deletion
6421                     assertThrows(
6422                             DatastoreException.class,
6423                             () -> {
6424                                 measurementDao.getEventReport("E11");
6425                             });
6426                     assertNotNull(measurementDao.getEventReport("E12"));
6427 
6428                     // Validate sources and triggers present
6429                     assertNotNull(measurementDao.getSource("S1"));
6430                     assertNotNull(measurementDao.getTrigger("T1"));
6431                     assertNotNull(measurementDao.getTrigger("T2"));
6432                 });
6433 
6434         // Validate attribution deletion
6435         assertNotNull(getAttribution("ATT11_aggregate", db));
6436         assertNotNull(getAttribution("ATT12_aggregate", db));
6437         assertNull(getAttribution("ATT11_event", db));
6438         assertNotNull(getAttribution("ATT12_event", db));
6439     }
6440 
6441     @Test
testDeleteDebugReport()6442     public void testDeleteDebugReport() {
6443         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6444         DebugReport debugReport = createDebugReport();
6445 
6446         ContentValues values = new ContentValues();
6447         values.put(DebugReportContract.ID, debugReport.getId());
6448         values.put(DebugReportContract.TYPE, debugReport.getType());
6449         values.put(DebugReportContract.BODY, debugReport.getBody().toString());
6450         values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId());
6451         values.put(
6452                 DebugReportContract.REGISTRATION_ORIGIN,
6453                 debugReport.getRegistrationOrigin().toString());
6454         db.insert(DebugReportContract.TABLE, null, values);
6455 
6456         long count =
6457                 DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6458         assertEquals(1, count);
6459 
6460         assertTrue(
6461                 mDatastoreManager.runInTransaction(
6462                         measurementDao -> measurementDao.deleteDebugReport(debugReport.getId())));
6463 
6464         count = DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6465         assertEquals(0, count);
6466     }
6467 
6468     @Test
testDeleteDebugReports_byRegistrantAndRange()6469     public void testDeleteDebugReports_byRegistrantAndRange() {
6470         final String registrantMatching = "foo";
6471         final long insertionTimeWithinRange = 1701206853050L;
6472         final long insertionTimeNotWithinRange = 1701206853000L;
6473         DebugReport debugReportMatchingRegistrantAndWithinRange =
6474                 createDebugReport(
6475                         /* id= */ "1",
6476                         buildRegistrant(registrantMatching),
6477                         insertionTimeWithinRange);
6478 
6479         DebugReport debugReportNotMatchingRegistrantAndWithinRange =
6480                 createDebugReport(/* id= */ "2", buildRegistrant("bar"), insertionTimeWithinRange);
6481 
6482         DebugReport debugReportMatchingRegistrantAndNotWithinRange =
6483                 createDebugReport(
6484                         /* id= */ "3",
6485                         buildRegistrant(registrantMatching),
6486                         insertionTimeNotWithinRange);
6487 
6488         mDatastoreManager.runInTransaction(
6489                 (dao) -> {
6490                     dao.insertDebugReport(debugReportMatchingRegistrantAndWithinRange);
6491                     dao.insertDebugReport(debugReportNotMatchingRegistrantAndWithinRange);
6492                     dao.insertDebugReport(debugReportMatchingRegistrantAndNotWithinRange);
6493                 });
6494 
6495         mDatastoreManager.runInTransaction(
6496                 (dao) ->
6497                         dao.deleteDebugReports(
6498                                 buildRegistrant(registrantMatching),
6499                                 /* start= */ Instant.ofEpochMilli(insertionTimeWithinRange - 1),
6500                                 /* end= */ Instant.ofEpochMilli(insertionTimeWithinRange + 1)));
6501 
6502         Set<String> ids = new HashSet<>();
6503         try (Cursor cursor =
6504                 MeasurementDbHelper.getInstance()
6505                         .getReadableDatabase()
6506                         .query(DebugReportContract.TABLE, null, null, null, null, null, null)) {
6507             while (cursor.moveToNext()) {
6508                 ids.add(cursor.getString(cursor.getColumnIndexOrThrow(DebugReportContract.ID)));
6509             }
6510         }
6511         assertEquals(2, ids.size());
6512         assertTrue(ids.contains("2"));
6513         assertTrue(ids.contains("3"));
6514     }
6515 
6516     @Test
testGetDebugReportIds()6517     public void testGetDebugReportIds() {
6518         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6519         DebugReport debugReport = createDebugReport();
6520 
6521         ContentValues values = new ContentValues();
6522         values.put(DebugReportContract.ID, debugReport.getId());
6523         values.put(DebugReportContract.TYPE, debugReport.getType());
6524         values.put(DebugReportContract.BODY, debugReport.getBody().toString());
6525         values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId());
6526         values.put(
6527                 DebugReportContract.REGISTRATION_ORIGIN,
6528                 debugReport.getRegistrationOrigin().toString());
6529         db.insert(DebugReportContract.TABLE, null, values);
6530 
6531         long count =
6532                 DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6533         assertEquals(1, count);
6534 
6535         assertTrue(
6536                 mDatastoreManager.runInTransaction(
6537                         measurementDao ->
6538                                 assertEquals(
6539                                         List.of(debugReport.getId()),
6540                                         measurementDao.getDebugReportIds())));
6541     }
6542 
6543     @Test
testGetDebugReportIdsWithRetryLimit()6544     public void testGetDebugReportIdsWithRetryLimit() {
6545         // Mocking that the flags return a Max Retry of 1
6546         Flags mockFlags = Mockito.mock(Flags.class);
6547         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6548         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
6549         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
6550 
6551         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6552         DebugReport debugReport = createDebugReport();
6553 
6554         ContentValues values = new ContentValues();
6555         values.put(DebugReportContract.ID, debugReport.getId());
6556         values.put(DebugReportContract.TYPE, debugReport.getType());
6557         values.put(DebugReportContract.BODY, debugReport.getBody().toString());
6558         values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId());
6559         values.put(
6560                 DebugReportContract.REGISTRATION_ORIGIN,
6561                 debugReport.getRegistrationOrigin().toString());
6562         db.insert(DebugReportContract.TABLE, null, values);
6563 
6564         long count =
6565                 DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6566         assertEquals(1, count);
6567 
6568         assertTrue(
6569                 mDatastoreManager.runInTransaction(
6570                         measurementDao ->
6571                                 assertEquals(
6572                                         List.of(debugReport.getId()),
6573                                         measurementDao.getDebugReportIds())));
6574         assertTrue(
6575                 mDatastoreManager.runInTransaction(
6576                         measurementDao -> {
6577                             // Adds records to KeyValueData table for Retry Count.
6578                             measurementDao.incrementAndGetReportingRetryCount(
6579                                     debugReport.getId(), DataType.DEBUG_REPORT_RETRY_COUNT);
6580                             assertTrue(measurementDao.getDebugReportIds().isEmpty());
6581                         }));
6582     }
6583 
6584     @Test
testDeleteExpiredRecordsForAsyncRegistrations()6585     public void testDeleteExpiredRecordsForAsyncRegistrations() {
6586         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6587 
6588         List<AsyncRegistration> asyncRegistrationList = new ArrayList<>();
6589         int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST;
6590 
6591         // Will be deleted by request time
6592         asyncRegistrationList.add(
6593                 new AsyncRegistration.Builder()
6594                         .setId("1")
6595                         .setOsDestination(Uri.parse("android-app://installed-app-destination"))
6596                         .setRegistrant(INSTALLED_REGISTRANT)
6597                         .setTopOrigin(INSTALLED_REGISTRANT)
6598                         .setAdIdPermission(false)
6599                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6600                         .setRequestTime(1)
6601                         .setRetryCount(retryLimit - 1L)
6602                         .setRegistrationId(UUID.randomUUID().toString())
6603                         .build());
6604 
6605         // Will be deleted by either request time or retry limit
6606         asyncRegistrationList.add(
6607                 new AsyncRegistration.Builder()
6608                         .setId("2")
6609                         .setOsDestination(Uri.parse("android-app://installed-app-destination"))
6610                         .setRegistrant(INSTALLED_REGISTRANT)
6611                         .setTopOrigin(INSTALLED_REGISTRANT)
6612                         .setAdIdPermission(false)
6613                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6614                         .setRequestTime(1)
6615                         .setRetryCount(retryLimit)
6616                         .setRegistrationId(UUID.randomUUID().toString())
6617                         .build());
6618 
6619         // Will not be deleted
6620         asyncRegistrationList.add(
6621                 new AsyncRegistration.Builder()
6622                         .setId("3")
6623                         .setOsDestination(Uri.parse("android-app://not-installed-app-destination"))
6624                         .setRegistrant(INSTALLED_REGISTRANT)
6625                         .setTopOrigin(INSTALLED_REGISTRANT)
6626                         .setAdIdPermission(false)
6627                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6628                         .setRequestTime(Long.MAX_VALUE)
6629                         .setRetryCount(retryLimit - 1L)
6630                         .setRegistrationId(UUID.randomUUID().toString())
6631                         .build());
6632 
6633         // Will be deleted due to retry limit
6634         asyncRegistrationList.add(
6635                 new AsyncRegistration.Builder()
6636                         .setId("4")
6637                         .setOsDestination(Uri.parse("android-app://not-installed-app-destination"))
6638                         .setRegistrant(INSTALLED_REGISTRANT)
6639                         .setTopOrigin(INSTALLED_REGISTRANT)
6640                         .setAdIdPermission(false)
6641                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6642                         .setRequestTime(Long.MAX_VALUE)
6643                         .setRetryCount(retryLimit)
6644                         .setRegistrationId(UUID.randomUUID().toString())
6645                         .build());
6646 
6647         asyncRegistrationList.forEach(
6648                 asyncRegistration -> {
6649                     ContentValues values = new ContentValues();
6650                     values.put(AsyncRegistrationContract.ID, asyncRegistration.getId());
6651                     values.put(
6652                             AsyncRegistrationContract.REGISTRANT,
6653                             asyncRegistration.getRegistrant().toString());
6654                     values.put(
6655                             AsyncRegistrationContract.TOP_ORIGIN,
6656                             asyncRegistration.getTopOrigin().toString());
6657                     values.put(
6658                             AsyncRegistrationContract.OS_DESTINATION,
6659                             asyncRegistration.getOsDestination().toString());
6660                     values.put(
6661                             AsyncRegistrationContract.AD_ID_PERMISSION,
6662                             asyncRegistration.getDebugKeyAllowed());
6663                     values.put(
6664                             AsyncRegistrationContract.TYPE, asyncRegistration.getType().toString());
6665                     values.put(
6666                             AsyncRegistrationContract.REQUEST_TIME,
6667                             asyncRegistration.getRequestTime());
6668                     values.put(
6669                             AsyncRegistrationContract.RETRY_COUNT,
6670                             asyncRegistration.getRetryCount());
6671                     values.put(
6672                             AsyncRegistrationContract.REGISTRATION_ID,
6673                             asyncRegistration.getRegistrationId());
6674                     db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values);
6675                 });
6676 
6677         long count =
6678                 DatabaseUtils.queryNumEntries(
6679                         db, AsyncRegistrationContract.TABLE, /* selection */ null);
6680         assertEquals(4, count);
6681 
6682         long earliestValidInsertion = System.currentTimeMillis() - 2;
6683 
6684         assertTrue(
6685                 mDatastoreManager.runInTransaction(
6686                         measurementDao ->
6687                                 measurementDao.deleteExpiredRecords(
6688                                         earliestValidInsertion,
6689                                         retryLimit,
6690                                         /* earliestValidAppReportInsertion */ null,
6691                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
6692 
6693         count =
6694                 DatabaseUtils.queryNumEntries(
6695                         db, AsyncRegistrationContract.TABLE, /* selection */ null);
6696         assertEquals(1, count);
6697 
6698         Cursor cursor =
6699                 db.query(
6700                         AsyncRegistrationContract.TABLE,
6701                         /* columns */ null,
6702                         /* selection */ null,
6703                         /* selectionArgs */ null,
6704                         /* groupBy */ null,
6705                         /* having */ null,
6706                         /* orderBy */ null);
6707 
6708         Set<String> ids = new HashSet<>(Arrays.asList("3"));
6709         List<AsyncRegistration> asyncRegistrations = new ArrayList<>();
6710         while (cursor.moveToNext()) {
6711             AsyncRegistration asyncRegistration =
6712                     SqliteObjectMapper.constructAsyncRegistration(cursor);
6713             asyncRegistrations.add(asyncRegistration);
6714         }
6715         for (AsyncRegistration asyncRegistration : asyncRegistrations) {
6716             assertTrue(ids.contains(asyncRegistration.getId()));
6717         }
6718     }
6719 
6720     @Test
deleteExpiredRecords_registrationRedirectCount()6721     public void deleteExpiredRecords_registrationRedirectCount() {
6722         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6723         List<Pair<String, String>> regIdCounts =
6724                 List.of(
6725                         new Pair<>("reg1", "1"),
6726                         new Pair<>("reg2", "2"),
6727                         new Pair<>("reg3", "3"),
6728                         new Pair<>("reg4", "4"));
6729         for (Pair<String, String> regIdCount : regIdCounts) {
6730             ContentValues contentValues = new ContentValues();
6731             contentValues.put(
6732                     KeyValueDataContract.DATA_TYPE,
6733                     KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
6734             contentValues.put(KeyValueDataContract.KEY, regIdCount.first);
6735             contentValues.put(KeyValueDataContract.VALUE, regIdCount.second);
6736             db.insert(KeyValueDataContract.TABLE, null, contentValues);
6737         }
6738         AsyncRegistration asyncRegistration1 =
6739                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
6740                         .setRegistrationId("reg1")
6741                         .setRequestTime(System.currentTimeMillis() + 60000) // Avoid deletion
6742                         .build();
6743         AsyncRegistration asyncRegistration2 =
6744                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
6745                         .setRegistrationId("reg2")
6746                         .setRequestTime(System.currentTimeMillis() + 60000) // Avoid deletion
6747                         .build();
6748         List<AsyncRegistration> asyncRegistrations =
6749                 List.of(asyncRegistration1, asyncRegistration2);
6750         asyncRegistrations.forEach(
6751                 asyncRegistration ->
6752                         mDatastoreManager.runInTransaction(
6753                                 dao -> dao.insertAsyncRegistration(asyncRegistration)));
6754 
6755         long earliestValidInsertion = System.currentTimeMillis() - 60000;
6756         int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST;
6757         assertTrue(
6758                 mDatastoreManager.runInTransaction(
6759                         (dao) ->
6760                                 dao.deleteExpiredRecords(
6761                                         earliestValidInsertion,
6762                                         retryLimit,
6763                                         /* earliestValidAggregateDebugReportInsertion */ null,
6764                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
6765 
6766         Cursor cursor =
6767                 db.query(
6768                         KeyValueDataContract.TABLE,
6769                         null,
6770                         null,
6771                         null,
6772                         null,
6773                         null,
6774                         KeyValueDataContract.KEY);
6775         assertEquals(2, cursor.getCount());
6776         cursor.moveToNext();
6777         assertEquals(
6778                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
6779                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
6780         assertEquals("reg1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
6781         assertEquals("1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
6782         cursor.moveToNext();
6783         assertEquals(
6784                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
6785                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
6786         assertEquals("reg2", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
6787         assertEquals("2", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
6788         cursor.close();
6789     }
6790 
6791     @Test
deleteExpiredRecords_skipDeliveredEventReportsOutsideWindow()6792     public void deleteExpiredRecords_skipDeliveredEventReportsOutsideWindow() {
6793         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6794         ContentValues sourceValid = new ContentValues();
6795         sourceValid.put(SourceContract.ID, "s1");
6796         sourceValid.put(SourceContract.EVENT_TIME, System.currentTimeMillis());
6797 
6798         ContentValues sourceExpired = new ContentValues();
6799         sourceExpired.put(SourceContract.ID, "s2");
6800         sourceExpired.put(
6801                 SourceContract.EVENT_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6802 
6803         ContentValues triggerValid = new ContentValues();
6804         triggerValid.put(TriggerContract.ID, "t1");
6805         triggerValid.put(TriggerContract.TRIGGER_TIME, System.currentTimeMillis());
6806 
6807         ContentValues triggerExpired = new ContentValues();
6808         triggerExpired.put(TriggerContract.ID, "t2");
6809         triggerExpired.put(
6810                 TriggerContract.TRIGGER_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6811 
6812         db.insert(SourceContract.TABLE, null, sourceValid);
6813         db.insert(SourceContract.TABLE, null, sourceExpired);
6814         db.insert(TriggerContract.TABLE, null, triggerValid);
6815         db.insert(TriggerContract.TABLE, null, triggerExpired);
6816 
6817         ContentValues eventReport_NotDelivered_WithinWindow = new ContentValues();
6818         eventReport_NotDelivered_WithinWindow.put(EventReportContract.ID, "e1");
6819         eventReport_NotDelivered_WithinWindow.put(
6820                 EventReportContract.REPORT_TIME, System.currentTimeMillis());
6821         eventReport_NotDelivered_WithinWindow.put(
6822                 EventReportContract.STATUS, EventReport.Status.PENDING);
6823         eventReport_NotDelivered_WithinWindow.put(
6824                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6825         eventReport_NotDelivered_WithinWindow.put(
6826                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6827         db.insert(EventReportContract.TABLE, null, eventReport_NotDelivered_WithinWindow);
6828 
6829         ContentValues eventReport_Delivered_WithinWindow = new ContentValues();
6830         eventReport_Delivered_WithinWindow.put(EventReportContract.ID, "e2");
6831         eventReport_Delivered_WithinWindow.put(
6832                 EventReportContract.REPORT_TIME, System.currentTimeMillis());
6833         eventReport_Delivered_WithinWindow.put(
6834                 EventReportContract.STATUS, EventReport.Status.DELIVERED);
6835         eventReport_Delivered_WithinWindow.put(
6836                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6837         eventReport_Delivered_WithinWindow.put(
6838                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6839         db.insert(EventReportContract.TABLE, null, eventReport_Delivered_WithinWindow);
6840 
6841         ContentValues eventReport_Delivered_OutsideWindow = new ContentValues();
6842         eventReport_Delivered_OutsideWindow.put(EventReportContract.ID, "e3");
6843         eventReport_Delivered_OutsideWindow.put(
6844                 EventReportContract.REPORT_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6845         eventReport_Delivered_OutsideWindow.put(
6846                 EventReportContract.STATUS, EventReport.Status.DELIVERED);
6847         eventReport_Delivered_OutsideWindow.put(
6848                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6849         eventReport_Delivered_OutsideWindow.put(
6850                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6851         db.insert(EventReportContract.TABLE, null, eventReport_Delivered_OutsideWindow);
6852 
6853         ContentValues eventReport_NotDelivered_OutsideWindow = new ContentValues();
6854         eventReport_NotDelivered_OutsideWindow.put(EventReportContract.ID, "e4");
6855         eventReport_NotDelivered_OutsideWindow.put(
6856                 EventReportContract.REPORT_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6857         eventReport_NotDelivered_OutsideWindow.put(
6858                 EventReportContract.STATUS, EventReport.Status.PENDING);
6859         eventReport_NotDelivered_OutsideWindow.put(
6860                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6861         eventReport_NotDelivered_OutsideWindow.put(
6862                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6863         db.insert(EventReportContract.TABLE, null, eventReport_NotDelivered_OutsideWindow);
6864 
6865         ContentValues eventReport_expiredSource = new ContentValues();
6866         eventReport_expiredSource.put(EventReportContract.ID, "e5");
6867         eventReport_expiredSource.put(EventReportContract.REPORT_TIME, System.currentTimeMillis());
6868         eventReport_expiredSource.put(EventReportContract.STATUS, EventReport.Status.PENDING);
6869         eventReport_expiredSource.put(
6870                 EventReportContract.SOURCE_ID, sourceExpired.getAsString(SourceContract.ID));
6871         eventReport_expiredSource.put(
6872                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6873         db.insert(EventReportContract.TABLE, null, eventReport_expiredSource);
6874 
6875         ContentValues eventReport_expiredTrigger = new ContentValues();
6876         eventReport_expiredTrigger.put(EventReportContract.ID, "e6");
6877         eventReport_expiredTrigger.put(EventReportContract.REPORT_TIME, System.currentTimeMillis());
6878         eventReport_expiredTrigger.put(EventReportContract.STATUS, EventReport.Status.PENDING);
6879         eventReport_expiredTrigger.put(
6880                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6881         eventReport_expiredTrigger.put(
6882                 EventReportContract.TRIGGER_ID, triggerExpired.getAsString(TriggerContract.ID));
6883         db.insert(EventReportContract.TABLE, null, eventReport_expiredTrigger);
6884 
6885         long earliestValidInsertion = System.currentTimeMillis() - DAYS.toMillis(10);
6886         int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST;
6887         mDatastoreManager.runInTransaction(
6888                 measurementDao ->
6889                         measurementDao.deleteExpiredRecords(
6890                                 earliestValidInsertion,
6891                                 retryLimit,
6892                                 /* earliestValidAppReportInsertion */ null,
6893                                 /* earliestValidAggregateDebugReportInsertion */ 0));
6894 
6895         List<ContentValues> deletedReports =
6896                 List.of(eventReport_expiredSource, eventReport_expiredTrigger);
6897 
6898         List<ContentValues> notDeletedReports =
6899                 List.of(
6900                         eventReport_Delivered_OutsideWindow,
6901                         eventReport_Delivered_WithinWindow,
6902                         eventReport_NotDelivered_OutsideWindow,
6903                         eventReport_NotDelivered_WithinWindow);
6904 
6905         assertEquals(
6906                 notDeletedReports.size(),
6907                 DatabaseUtils.longForQuery(
6908                         db,
6909                         "SELECT COUNT("
6910                                 + EventReportContract.ID
6911                                 + ") FROM "
6912                                 + EventReportContract.TABLE
6913                                 + " WHERE "
6914                                 + EventReportContract.ID
6915                                 + " IN ("
6916                                 + notDeletedReports.stream()
6917                                         .map(
6918                                                 (eR) -> {
6919                                                     return DatabaseUtils.sqlEscapeString(
6920                                                             eR.getAsString(EventReportContract.ID));
6921                                                 })
6922                                         .collect(Collectors.joining(","))
6923                                 + ")",
6924                         null));
6925 
6926         assertEquals(
6927                 0,
6928                 DatabaseUtils.longForQuery(
6929                         db,
6930                         "SELECT COUNT("
6931                                 + EventReportContract.ID
6932                                 + ") FROM "
6933                                 + EventReportContract.TABLE
6934                                 + " WHERE "
6935                                 + EventReportContract.ID
6936                                 + " IN ("
6937                                 + deletedReports.stream()
6938                                         .map(
6939                                                 (eR) -> {
6940                                                     return DatabaseUtils.sqlEscapeString(
6941                                                             eR.getAsString(EventReportContract.ID));
6942                                                 })
6943                                         .collect(Collectors.joining(","))
6944                                 + ")",
6945                         null));
6946     }
6947 
6948     @Test
deleteExpiredRecords_VerboseDebugReportsWhileLimitingRetries()6949     public void deleteExpiredRecords_VerboseDebugReportsWhileLimitingRetries() {
6950         // Mocking that the flags return a Max Retry of 1
6951         Flags mockFlags = Mockito.mock(Flags.class);
6952         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6953         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
6954         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
6955 
6956         SQLiteDatabase db = MeasurementDbHelper.getInstance().getReadableDatabase();
6957 
6958         DebugReport debugReport1 =
6959                 new DebugReport.Builder()
6960                         .setId("reportId1")
6961                         .setType("trigger-event-deduplicated")
6962                         .setBody(
6963                                 " {\n"
6964                                         + "      \"attribution_destination\":"
6965                                         + " \"https://destination.example\",\n"
6966                                         + "      \"source_event_id\": \"45623\"\n"
6967                                         + "    }")
6968                         .setEnrollmentId("1")
6969                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
6970                         .build();
6971 
6972         DebugReport debugReport2 =
6973                 new DebugReport.Builder()
6974                         .setId("reportId2")
6975                         .setType("trigger-event-deduplicated")
6976                         .setBody(
6977                                 " {\n"
6978                                         + "      \"attribution_destination\":"
6979                                         + " \"https://destination.example\",\n"
6980                                         + "      \"source_event_id\": \"45623\"\n"
6981                                         + "    }")
6982                         .setEnrollmentId("1")
6983                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
6984                         .build();
6985 
6986         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport1));
6987         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport2));
6988 
6989         mDatastoreManager.runInTransaction(
6990                 dao ->
6991                         dao.deleteExpiredRecords(
6992                                 /* earliestValidInsertion */ 0,
6993                                 /* registrationRetryLimit */ 0,
6994                                 /* earliestValidAppReportInsertion */ null,
6995                                 /* earliestValidAggregateDebugReportInsertion */ 0));
6996         assertEquals(
6997                 2,
6998                 DatabaseUtils.longForQuery(
6999                         db,
7000                         "SELECT COUNT("
7001                                 + DebugReportContract.ID
7002                                 + ") FROM "
7003                                 + DebugReportContract.TABLE,
7004                         null));
7005         // Increment Attempt Record 1
7006         mDatastoreManager.runInTransaction(
7007                 (dao) ->
7008                         dao.incrementAndGetReportingRetryCount(
7009                                 debugReport1.getId(), DataType.DEBUG_REPORT_RETRY_COUNT));
7010         // Delete Expired (Record 1)
7011         mDatastoreManager.runInTransaction(
7012                 dao ->
7013                         dao.deleteExpiredRecords(
7014                                 /* earliestValidInsertion */ 0,
7015                                 /* registrationRetryLimit */ 0,
7016                                 /* earliestValidAppReportInsertion */ null,
7017                                 /* earliestValidAggregateDebugReportInsertion */ 0));
7018 
7019         // Assert Record 2 remains.
7020         assertEquals(
7021                 1,
7022                 DatabaseUtils.longForQuery(
7023                         db,
7024                         "SELECT COUNT("
7025                                 + DebugReportContract.ID
7026                                 + ") FROM "
7027                                 + DebugReportContract.TABLE
7028                                 + " WHERE "
7029                                 + DebugReportContract.ID
7030                                 + " = ?",
7031                         new String[] {debugReport2.getId()}));
7032 
7033         // Assert Record 1 Removed
7034         assertEquals(
7035                 0,
7036                 DatabaseUtils.longForQuery(
7037                         db,
7038                         "SELECT COUNT("
7039                                 + DebugReportContract.ID
7040                                 + ") FROM "
7041                                 + DebugReportContract.TABLE
7042                                 + " WHERE "
7043                                 + DebugReportContract.ID
7044                                 + " = ?",
7045                         new String[] {debugReport1.getId()}));
7046     }
7047 
7048     @Test
deleteExpiredRecords_VerboseDebugReportsWhileNotLimitingRetries()7049     public void deleteExpiredRecords_VerboseDebugReportsWhileNotLimitingRetries() {
7050         // Mocking that the retry Limiting Disable, but has limit number,
7051         Flags mockFlags = Mockito.mock(Flags.class);
7052         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
7053         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
7054         ExtendedMockito.doReturn(false).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
7055 
7056         SQLiteDatabase db = MeasurementDbHelper.getInstance().getReadableDatabase();
7057 
7058         DebugReport debugReport1 =
7059                 new DebugReport.Builder()
7060                         .setId("reportId1")
7061                         .setType("trigger-event-deduplicated")
7062                         .setBody(
7063                                 " {\n"
7064                                         + "      \"attribution_destination\":"
7065                                         + " \"https://destination.example\",\n"
7066                                         + "      \"source_event_id\": \"45623\"\n"
7067                                         + "    }")
7068                         .setEnrollmentId("1")
7069                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
7070                         .setInsertionTime(System.currentTimeMillis() + 60000L)
7071                         .build();
7072 
7073         DebugReport debugReport2 =
7074                 new DebugReport.Builder()
7075                         .setId("reportId2")
7076                         .setType("trigger-event-deduplicated")
7077                         .setBody(
7078                                 " {\n"
7079                                         + "      \"attribution_destination\":"
7080                                         + " \"https://destination.example\",\n"
7081                                         + "      \"source_event_id\": \"45623\"\n"
7082                                         + "    }")
7083                         .setEnrollmentId("1")
7084                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
7085                         .setInsertionTime(System.currentTimeMillis() - 60000L)
7086                         .build();
7087         // Insert
7088         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport1));
7089         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport2));
7090 
7091         // Increment Attempt
7092         mDatastoreManager.runInTransaction(
7093                 (dao) ->
7094                         dao.incrementAndGetReportingRetryCount(
7095                                 debugReport1.getId(), DataType.DEBUG_REPORT_RETRY_COUNT));
7096         // Delete Expired
7097         long earliestValidInsertion = System.currentTimeMillis();
7098 
7099         assertTrue(
7100                 mDatastoreManager.runInTransaction(
7101                         measurementDao ->
7102                                 measurementDao.deleteExpiredRecords(
7103                                         earliestValidInsertion,
7104                                         /* earliestValidAppReportInsertion */ 0,
7105                                         /* earliestValidAppReportInsertion */ null,
7106                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
7107 
7108         // Assert Record 1 remains because not expired and Retry Limiting Off.
7109         assertEquals(
7110                 1,
7111                 DatabaseUtils.longForQuery(
7112                         db,
7113                         "SELECT COUNT("
7114                                 + DebugReportContract.ID
7115                                 + ") FROM "
7116                                 + DebugReportContract.TABLE
7117                                 + " WHERE "
7118                                 + DebugReportContract.ID
7119                                 + " = ?",
7120                         new String[] {debugReport1.getId()}));
7121 
7122         // Assert Record 2 Removed because expired.
7123         assertEquals(
7124                 0,
7125                 DatabaseUtils.longForQuery(
7126                         db,
7127                         "SELECT COUNT("
7128                                 + DebugReportContract.ID
7129                                 + ") FROM "
7130                                 + DebugReportContract.TABLE
7131                                 + " WHERE "
7132                                 + DebugReportContract.ID
7133                                 + " = ?",
7134                         new String[] {debugReport2.getId()}));
7135     }
7136 
7137     @Test
deleteExpiredRecords_RetryKeyValueData()7138     public void deleteExpiredRecords_RetryKeyValueData() {
7139         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7140         // Non-stale join record
7141         DebugReport debugReport = createDebugReport();
7142         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport));
7143 
7144         // Should Remain
7145         ContentValues nonStaleValues = new ContentValues();
7146         nonStaleValues.put(
7147                 KeyValueDataContract.DATA_TYPE, DataType.DEBUG_REPORT_RETRY_COUNT.toString());
7148         nonStaleValues.put(KeyValueDataContract.KEY, debugReport.getId());
7149         nonStaleValues.put(KeyValueDataContract.VALUE, "1");
7150         db.insert(KeyValueDataContract.TABLE, null, nonStaleValues);
7151 
7152         // Should Delete
7153         ContentValues staleValues = new ContentValues();
7154         staleValues.put(
7155                 KeyValueDataContract.DATA_TYPE, DataType.DEBUG_REPORT_RETRY_COUNT.toString());
7156         staleValues.put(KeyValueDataContract.KEY, "stale-key");
7157         staleValues.put(KeyValueDataContract.VALUE, "1");
7158         db.insert(KeyValueDataContract.TABLE, null, staleValues);
7159 
7160         mDatastoreManager.runInTransaction(
7161                 dao ->
7162                         dao.deleteExpiredRecords(
7163                                 /* earliestValidInsertion */ 0,
7164                                 /* registrationRetryLimit */ 0,
7165                                 /* earliestValidAppReportInsertion */ null,
7166                                 /* earliestValidAggregateDebugReportInsertion */ 0));
7167 
7168         // Assert Non-Stale record remains.
7169         assertEquals(
7170                 1,
7171                 DatabaseUtils.longForQuery(
7172                         db,
7173                         "SELECT COUNT("
7174                                 + KeyValueDataContract.KEY
7175                                 + ") FROM "
7176                                 + KeyValueDataContract.TABLE
7177                                 + " WHERE "
7178                                 + KeyValueDataContract.KEY
7179                                 + " = ?",
7180                         new String[] {nonStaleValues.getAsString(KeyValueDataContract.KEY)}));
7181 
7182         // Assert Stale Record Removed
7183         assertEquals(
7184                 0,
7185                 DatabaseUtils.longForQuery(
7186                         db,
7187                         "SELECT COUNT("
7188                                 + KeyValueDataContract.KEY
7189                                 + ") FROM "
7190                                 + KeyValueDataContract.TABLE
7191                                 + " WHERE "
7192                                 + KeyValueDataContract.KEY
7193                                 + " = ?",
7194                         new String[] {staleValues.getAsString(KeyValueDataContract.KEY)}));
7195     }
7196 
7197     @Test
deleteExpiredRecords_reinstallAttributionEnabled_deletesExpiredAppInstallHistory()7198     public void deleteExpiredRecords_reinstallAttributionEnabled_deletesExpiredAppInstallHistory() {
7199         Flags mockFlags = Mockito.mock(Flags.class);
7200         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
7201         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementEnableReinstallReattribution();
7202 
7203         SQLiteDatabase db = MeasurementDbHelper.getInstance().getReadableDatabase();
7204         long now = System.currentTimeMillis();
7205         mDatastoreManager.runInTransaction(
7206                 (dao) ->
7207                         dao.insertOrUpdateAppReportHistory(
7208                                 INSTALLED_PACKAGE,
7209                                 REGISTRATION_ORIGIN,
7210                                 /* lastReportDeliveredTimestamp= */ now
7211                                         - TimeUnit.DAYS.toMillis(1)));
7212         mDatastoreManager.runInTransaction(
7213                 (dao) ->
7214                         dao.insertOrUpdateAppReportHistory(
7215                                 INSTALLED_PACKAGE,
7216                                 REGISTRATION_ORIGIN_2,
7217                                 /* lastReportDeliveredTimestamp= */ now
7218                                         - TimeUnit.DAYS.toMillis(3)));
7219 
7220         long earliestValidInsertion = now - TimeUnit.DAYS.toMillis(2);
7221         assertTrue(
7222                 mDatastoreManager.runInTransaction(
7223                         measurementDao ->
7224                                 measurementDao.deleteExpiredRecords(
7225                                         earliestValidInsertion,
7226                                         /* registrationRetryLimit= */ 0,
7227                                         earliestValidInsertion,
7228                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
7229         // Assert Record 1 remains because not expired and Retry Limiting Off.
7230         assertEquals(
7231                 1,
7232                 DatabaseUtils.longForQuery(
7233                         db,
7234                         "SELECT COUNT("
7235                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
7236                                 + ") FROM "
7237                                 + AppReportHistoryContract.TABLE
7238                                 + " WHERE "
7239                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
7240                                 + " = ?",
7241                         new String[] {REGISTRATION_ORIGIN.toString()}));
7242 
7243         // Assert Record 2 Removed because expired.
7244         assertEquals(
7245                 0,
7246                 DatabaseUtils.longForQuery(
7247                         db,
7248                         "SELECT COUNT("
7249                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
7250                                 + ") FROM "
7251                                 + AppReportHistoryContract.TABLE
7252                                 + " WHERE "
7253                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
7254                                 + " = ?",
7255                         new String[] {REGISTRATION_ORIGIN_2.toString()}));
7256     }
7257 
7258     @Test
deleteExpiredRecords_withAdrBudgetTrackerRecords_deletesOlderRecords()7259     public void deleteExpiredRecords_withAdrBudgetTrackerRecords_deletesOlderRecords() {
7260         // Setup
7261         Flags mockFlags = Mockito.mock(Flags.class);
7262         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
7263         ExtendedMockito.doReturn(true)
7264                 .when(mockFlags)
7265                 .getMeasurementEnableAggregateDebugReporting();
7266         long currentTime = System.currentTimeMillis();
7267 
7268         // record1 - 6 hour old report record
7269         AggregateDebugReportRecord record1 =
7270                 new AggregateDebugReportRecord.Builder(
7271                                 currentTime - TimeUnit.HOURS.toMillis(6),
7272                                 Uri.parse("android-app://com.example.abc"),
7273                                 APP_ONE_PUBLISHER,
7274                                 REGISTRATION_ORIGIN,
7275                                 1)
7276                         .build();
7277         // record2 - 1 day 1 millisecond old report record
7278         AggregateDebugReportRecord record2 =
7279                 new AggregateDebugReportRecord.Builder(
7280                                 currentTime - DAYS.toMillis(1) - 1,
7281                                 Uri.parse("android-app://com.example.def"),
7282                                 APP_TWO_PUBLISHER,
7283                                 REGISTRATION_ORIGIN_2,
7284                                 10)
7285                         .build();
7286         // record3 - 10 days old report record
7287         AggregateDebugReportRecord record3 =
7288                 new AggregateDebugReportRecord.Builder(
7289                                 currentTime - DAYS.toMillis(10),
7290                                 Uri.parse("android-app://com.example.ghi"),
7291                                 APP_ONE_PUBLISHER,
7292                                 REGISTRATION_ORIGIN_3,
7293                                 100)
7294                         .build();
7295         // record4 - 1 minute old report record
7296         AggregateDebugReportRecord record4 =
7297                 new AggregateDebugReportRecord.Builder(
7298                                 currentTime - TimeUnit.MINUTES.toMillis(1),
7299                                 Uri.parse("android-app://com.example.abc"),
7300                                 APP_TWO_SOURCES,
7301                                 REGISTRATION_ORIGIN_4,
7302                                 1000)
7303                         .build();
7304         List<AggregateDebugReportRecord> records =
7305                 Arrays.asList(record1, record2, record3, record4);
7306         records.forEach(
7307                 record ->
7308                         mDatastoreManager.runInTransaction(
7309                                 dao -> dao.insertAggregateDebugReportRecord(record)));
7310 
7311         // Execution - delete records older than 1 day - should retain record1 & record4
7312         mDatastoreManager.runInTransaction(
7313                 dao ->
7314                         dao.deleteExpiredRecords(
7315                                 /* earliestValidInsertion */ 0L,
7316                                 /* registrationRetryLimit */ 0,
7317                                 /* earliestValidAppReportInsertion */ null,
7318                                 currentTime - DAYS.toMillis(1)));
7319 
7320         // Assertion
7321         assertThat(
7322                         DatabaseUtils.queryNumEntries(
7323                                 MeasurementDbHelper.getInstance().getReadableDatabase(),
7324                                 AggregatableDebugReportBudgetTrackerContract.TABLE))
7325                 .isEqualTo(2);
7326         try (Cursor recordCursor =
7327                 MeasurementDbHelper.getInstance()
7328                         .getReadableDatabase()
7329                         .query(
7330                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
7331                                 new String[] {
7332                                     AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN
7333                                 },
7334                                 null,
7335                                 null,
7336                                 null,
7337                                 null,
7338                                 AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN)) {
7339             assertThat(recordCursor.getCount()).isEqualTo(2);
7340             assertThat(recordCursor.moveToNext()).isTrue();
7341             assertThat(
7342                             recordCursor.getString(
7343                                     recordCursor.getColumnIndex(
7344                                             AggregatableDebugReportBudgetTrackerContract
7345                                                     .REGISTRATION_ORIGIN)))
7346                     .isEqualTo(REGISTRATION_ORIGIN.toString());
7347             assertThat(recordCursor.moveToNext()).isTrue();
7348             assertThat(
7349                             recordCursor.getString(
7350                                     recordCursor.getColumnIndex(
7351                                             AggregatableDebugReportBudgetTrackerContract
7352                                                     .REGISTRATION_ORIGIN)))
7353                     .isEqualTo(REGISTRATION_ORIGIN_4.toString());
7354         }
7355     }
7356 
7357     @Test
deleteExpiredRecords_withAdrRecords_deletesRecordsDueToSourceTriggerDeletion()7358     public void deleteExpiredRecords_withAdrRecords_deletesRecordsDueToSourceTriggerDeletion() {
7359         // Setup
7360         Flags mockFlags = Mockito.mock(Flags.class);
7361         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
7362         ExtendedMockito.doReturn(true)
7363                 .when(mockFlags)
7364                 .getMeasurementEnableAggregateDebugReporting();
7365         long currentTime = System.currentTimeMillis();
7366         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7367 
7368         // 30 days old
7369         Source s1 =
7370                 SourceFixture.getValidSourceBuilder()
7371                         .setId("S1")
7372                         .setEventTime(currentTime - DAYS.toMillis(30))
7373                         .build();
7374         Trigger t1 =
7375                 TriggerFixture.getValidTriggerBuilder()
7376                         .setId("T1")
7377                         .setTriggerTime(currentTime - DAYS.toMillis(30))
7378                         .build();
7379         AbstractDbIntegrationTest.insertToDb(s1, db);
7380         AbstractDbIntegrationTest.insertToDb(t1, db);
7381 
7382         // 1 day old
7383         Source s2 =
7384                 SourceFixture.getValidSourceBuilder()
7385                         .setId("S2")
7386                         .setEventTime(currentTime - DAYS.toMillis(1))
7387                         .build();
7388         Trigger t2 =
7389                 TriggerFixture.getValidTriggerBuilder()
7390                         .setId("T2")
7391                         .setTriggerTime(currentTime - DAYS.toMillis(1))
7392                         .build();
7393         AbstractDbIntegrationTest.insertToDb(s2, db);
7394         AbstractDbIntegrationTest.insertToDb(t2, db);
7395 
7396         // deleted - record1 - 6 hour old report record but source and trigger are 30 days old
7397         AggregateDebugReportRecord record1 =
7398                 new AggregateDebugReportRecord.Builder(
7399                                 currentTime - TimeUnit.HOURS.toMillis(6),
7400                                 Uri.parse("android-app://com.example.abc"),
7401                                 APP_ONE_PUBLISHER,
7402                                 REGISTRATION_ORIGIN,
7403                                 1)
7404                         .setSourceId(s1.getId())
7405                         .setTriggerId(t1.getId())
7406                         .build();
7407         // deleted - record2 - 6 hour old report record but source is 30 days old
7408         AggregateDebugReportRecord record2 =
7409                 new AggregateDebugReportRecord.Builder(
7410                                 currentTime - TimeUnit.HOURS.toMillis(6),
7411                                 Uri.parse("android-app://com.example.def"),
7412                                 APP_TWO_PUBLISHER,
7413                                 REGISTRATION_ORIGIN_2,
7414                                 10)
7415                         .setSourceId(s1.getId())
7416                         .setTriggerId(null)
7417                         .build();
7418         // deleted - record3 - 6 hour old report record but trigger is 30 days old
7419         AggregateDebugReportRecord record3 =
7420                 new AggregateDebugReportRecord.Builder(
7421                                 currentTime - TimeUnit.HOURS.toMillis(6),
7422                                 Uri.parse("android-app://com.example.ghi"),
7423                                 APP_ONE_PUBLISHER,
7424                                 REGISTRATION_ORIGIN_3,
7425                                 100)
7426                         .setSourceId(null)
7427                         .setTriggerId(t1.getId())
7428                         .build();
7429         // retained - record4 - 6 hour old report record and source is 1 day old
7430         AggregateDebugReportRecord record4 =
7431                 new AggregateDebugReportRecord.Builder(
7432                                 currentTime - TimeUnit.HOURS.toMillis(6),
7433                                 Uri.parse("android-app://com.example.jkl"),
7434                                 APP_TWO_SOURCES,
7435                                 REGISTRATION_ORIGIN_4,
7436                                 1000)
7437                         .setSourceId(s2.getId())
7438                         .setTriggerId(null)
7439                         .build();
7440         // retained - record4 - 6 hour old report record and trigger is 1 day old
7441         AggregateDebugReportRecord record5 =
7442                 new AggregateDebugReportRecord.Builder(
7443                                 currentTime - TimeUnit.HOURS.toMillis(6),
7444                                 Uri.parse("android-app://com.example.mno"),
7445                                 APP_TWO_SOURCES,
7446                                 REGISTRATION_ORIGIN_5,
7447                                 1000)
7448                         .setSourceId(null)
7449                         .setTriggerId(t2.getId())
7450                         .build();
7451         List<AggregateDebugReportRecord> records =
7452                 Arrays.asList(record1, record2, record3, record4, record5);
7453         records.forEach(
7454                 record ->
7455                         mDatastoreManager.runInTransaction(
7456                                 dao -> dao.insertAggregateDebugReportRecord(record)));
7457 
7458         // Execution - delete records older than 10 days - should retain record4 & record5
7459         mDatastoreManager.runInTransaction(
7460                 dao ->
7461                         dao.deleteExpiredRecords(
7462                                 /* earliestValidInsertion */ currentTime - DAYS.toMillis(10),
7463                                 /* registrationRetryLimit */ 0,
7464                                 /* earliestValidAppReportInsertion */ null,
7465                                 /* earliestValidAggregateDebugReportInsertion */ 0));
7466 
7467         // Assertion
7468         assertThat(
7469                         DatabaseUtils.queryNumEntries(
7470                                 MeasurementDbHelper.getInstance().getReadableDatabase(),
7471                                 AggregatableDebugReportBudgetTrackerContract.TABLE))
7472                 .isEqualTo(2);
7473         try (Cursor recordCursor =
7474                 MeasurementDbHelper.getInstance()
7475                         .getReadableDatabase()
7476                         .query(
7477                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
7478                                 new String[] {
7479                                     AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN
7480                                 },
7481                                 null,
7482                                 null,
7483                                 null,
7484                                 null,
7485                                 AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN)) {
7486             assertThat(recordCursor.getCount()).isEqualTo(2);
7487             assertThat(recordCursor.moveToNext()).isTrue();
7488             assertThat(
7489                             recordCursor.getString(
7490                                     recordCursor.getColumnIndex(
7491                                             AggregatableDebugReportBudgetTrackerContract
7492                                                     .REGISTRATION_ORIGIN)))
7493                     .isEqualTo(REGISTRATION_ORIGIN_4.toString());
7494             assertThat(recordCursor.moveToNext()).isTrue();
7495             assertThat(
7496                             recordCursor.getString(
7497                                     recordCursor.getColumnIndex(
7498                                             AggregatableDebugReportBudgetTrackerContract
7499                                                     .REGISTRATION_ORIGIN)))
7500                     .isEqualTo(REGISTRATION_ORIGIN_5.toString());
7501         }
7502     }
7503 
7504     @Test
getRegistrationRedirectCount_keyMissing()7505     public void getRegistrationRedirectCount_keyMissing() {
7506         Optional<KeyValueData> optKeyValueData =
7507                 mDatastoreManager.runInTransactionWithResult(
7508                         (dao) ->
7509                                 dao.getKeyValueData(
7510                                         "missing_random_id", DataType.REGISTRATION_REDIRECT_COUNT));
7511         assertTrue(optKeyValueData.isPresent());
7512         KeyValueData keyValueData = optKeyValueData.get();
7513         assertEquals(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT, keyValueData.getDataType());
7514         assertEquals("missing_random_id", keyValueData.getKey());
7515         assertNull(keyValueData.getValue());
7516         assertEquals(1, keyValueData.getRegistrationRedirectCount());
7517     }
7518 
7519     @Test
getRegistrationRedirectCount_keyExists()7520     public void getRegistrationRedirectCount_keyExists() {
7521         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7522         ContentValues contentValues = new ContentValues();
7523         contentValues.put(
7524                 KeyValueDataContract.DATA_TYPE,
7525                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7526         contentValues.put(KeyValueDataContract.KEY, "random_id");
7527         contentValues.put(KeyValueDataContract.VALUE, "2");
7528         db.insert(KeyValueDataContract.TABLE, null, contentValues);
7529         Optional<KeyValueData> optKeyValueData =
7530                 mDatastoreManager.runInTransactionWithResult(
7531                         (dao) ->
7532                                 dao.getKeyValueData(
7533                                         "random_id",
7534                                         KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT));
7535         assertTrue(optKeyValueData.isPresent());
7536         KeyValueData keyValueData = optKeyValueData.get();
7537         assertEquals(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT, keyValueData.getDataType());
7538         assertEquals("random_id", keyValueData.getKey());
7539         assertEquals("2", keyValueData.getValue());
7540         assertEquals(2, keyValueData.getRegistrationRedirectCount());
7541     }
7542 
7543     @Test
updateRegistrationRedirectCount_keyMissing()7544     public void updateRegistrationRedirectCount_keyMissing() {
7545         KeyValueData keyValueData =
7546                 new KeyValueData.Builder()
7547                         .setDataType(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT)
7548                         .setKey("key_1")
7549                         .setValue("4")
7550                         .build();
7551         mDatastoreManager.runInTransaction((dao) -> dao.insertOrUpdateKeyValueData(keyValueData));
7552         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7553         Cursor cursor = db.query(KeyValueDataContract.TABLE, null, null, null, null, null, null);
7554         assertEquals(1, cursor.getCount());
7555         cursor.moveToNext();
7556         assertEquals(
7557                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
7558                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
7559         assertEquals("key_1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
7560         assertEquals("4", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
7561         cursor.close();
7562     }
7563 
7564     @Test
updateRegistrationRedirectCount_keyExists()7565     public void updateRegistrationRedirectCount_keyExists() {
7566         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7567         ContentValues contentValues = new ContentValues();
7568         contentValues.put(
7569                 KeyValueDataContract.DATA_TYPE,
7570                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7571         contentValues.put(KeyValueDataContract.KEY, "key_1");
7572         contentValues.put(KeyValueDataContract.VALUE, "2");
7573         db.insert(KeyValueDataContract.TABLE, null, contentValues);
7574 
7575         KeyValueData keyValueData =
7576                 new KeyValueData.Builder()
7577                         .setDataType(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT)
7578                         .setKey("key_1")
7579                         .setValue("4")
7580                         .build();
7581         mDatastoreManager.runInTransaction((dao) -> dao.insertOrUpdateKeyValueData(keyValueData));
7582 
7583         Cursor cursor = db.query(KeyValueDataContract.TABLE, null, null, null, null, null, null);
7584         assertEquals(1, cursor.getCount());
7585         cursor.moveToNext();
7586         assertEquals(
7587                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
7588                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
7589         assertEquals("key_1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
7590         assertEquals("4", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
7591         cursor.close();
7592     }
7593 
7594     @Test
keyValueDataTable_PrimaryKeyConstraint()7595     public void keyValueDataTable_PrimaryKeyConstraint() {
7596         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7597         ContentValues contentValues1 = new ContentValues();
7598         contentValues1.put(
7599                 KeyValueDataContract.DATA_TYPE,
7600                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7601         contentValues1.put(KeyValueDataContract.KEY, "key_1");
7602         contentValues1.put(KeyValueDataContract.VALUE, "2");
7603 
7604         assertNotEquals(-1, db.insert(KeyValueDataContract.TABLE, null, contentValues1));
7605 
7606         // Should fail because we are using <DataType, Key> as primary key
7607         assertEquals(-1, db.insert(KeyValueDataContract.TABLE, null, contentValues1));
7608 
7609         ContentValues contentValues2 = new ContentValues();
7610         contentValues2.put(
7611                 KeyValueDataContract.DATA_TYPE,
7612                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7613         contentValues2.put(KeyValueDataContract.KEY, "key_2");
7614         contentValues2.put(KeyValueDataContract.VALUE, "2");
7615 
7616         assertNotEquals(-1, db.insert(KeyValueDataContract.TABLE, null, contentValues2));
7617     }
7618 
7619     @Test
7620     public void
fetchSourceIdsForLowestPriorityDest_appDestEmptyExclusions_delLowPriorityDestination()7621             fetchSourceIdsForLowestPriorityDest_appDestEmptyExclusions_delLowPriorityDestination() {
7622         // Setup
7623         mocker.mockGetFlags(mMockFlags);
7624         doReturn(true).when(mMockFlags).getMeasurementEnableSourceDestinationLimitPriority();
7625         long baseEventTime = System.currentTimeMillis();
7626         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7627         insertSource(
7628                 createSourceBuilder()
7629                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7630                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7631                         .setEventTime(baseEventTime)
7632                         .setExpiryTime(commonExpiryTime)
7633                         .setDestinationLimitPriority(10)
7634                         .build(),
7635                 "s11");
7636         insertSource(
7637                 createSourceBuilder()
7638                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7639                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7640                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7641                         .setExpiryTime(commonExpiryTime)
7642                         .setDestinationLimitPriority(20)
7643                         .build(),
7644                 "s21");
7645         insertSource(
7646                 createSourceBuilder()
7647                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7648                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7649                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7650                         .setExpiryTime(commonExpiryTime)
7651                         .setDestinationLimitPriority(30)
7652                         .build(),
7653                 "s31");
7654         insertSource(
7655                 createSourceBuilder()
7656                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7657                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7658                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7659                         .setExpiryTime(commonExpiryTime)
7660                         .setDestinationLimitPriority(10)
7661                         .build(),
7662                 "s12");
7663         insertSource(
7664                 createSourceBuilder()
7665                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7666                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7667                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7668                         .setExpiryTime(commonExpiryTime)
7669                         .setDestinationLimitPriority(20)
7670                         .build(),
7671                 "s22");
7672 
7673         // Execute
7674         // com.example.app1 has the least priority of all as 10
7675         mDatastoreManager.runInTransaction(
7676                 (dao) -> {
7677                     Pair<Long, List<String>> destinationPriorityAndSourcesTodelete =
7678                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7679                                     SourceFixture.ValidSourceParams.PUBLISHER,
7680                                     EventSurfaceType.APP,
7681                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7682                                     Collections.emptyList(),
7683                                     EventSurfaceType.APP,
7684                                     baseEventTime + DAYS.toMillis(10) // request time
7685                                     );
7686 
7687                     assertEquals(10L, (long) destinationPriorityAndSourcesTodelete.first);
7688                     assertEquals(
7689                             Sets.newSet("s11", "s12"),
7690                             new HashSet<>(destinationPriorityAndSourcesTodelete.second));
7691                 });
7692     }
7693 
7694     @Test
7695     public void
fetchSourceIdsForLowPriorityDest_webDestEmptyExclusions_delLowPriorityDestinations()7696             fetchSourceIdsForLowPriorityDest_webDestEmptyExclusions_delLowPriorityDestinations() {
7697         // Setup
7698         mocker.mockGetFlags(mMockFlags);
7699         doReturn(true).when(mMockFlags).getMeasurementEnableSourceDestinationLimitPriority();
7700         long baseEventTime = System.currentTimeMillis();
7701         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7702         insertSource(
7703                 createSourceBuilder()
7704                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7705                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7706                         .setEventTime(baseEventTime)
7707                         .setExpiryTime(commonExpiryTime)
7708                         .setDestinationLimitPriority(10)
7709                         .build(),
7710                 "s11");
7711         insertSource(
7712                 createSourceBuilder()
7713                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7714                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7715                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7716                         .setExpiryTime(commonExpiryTime)
7717                         .setDestinationLimitPriority(20)
7718                         .build(),
7719                 "s21");
7720         insertSource(
7721                 createSourceBuilder()
7722                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7723                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7724                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7725                         .setExpiryTime(commonExpiryTime)
7726                         .setDestinationLimitPriority(30)
7727                         .build(),
7728                 "s31");
7729         insertSource(
7730                 createSourceBuilder()
7731                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7732                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7733                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7734                         .setExpiryTime(commonExpiryTime)
7735                         .setDestinationLimitPriority(40)
7736                         .build(),
7737                 "s12");
7738         insertSource(
7739                 createSourceBuilder()
7740                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7741                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7742                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7743                         .setExpiryTime(commonExpiryTime)
7744                         .setDestinationLimitPriority(20)
7745                         .build(),
7746                 "s22");
7747 
7748         // Execute
7749         // web1.example.com has priority 10 with s11 and priority 40 with s12, the higher one will
7750         // be considered, i.e. 40. web2.example.com" has priority as 20 through both s21 and s22,
7751         // its associated sources will be deleted instead.
7752         mDatastoreManager.runInTransaction(
7753                 (dao) -> {
7754                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7755                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7756                                     SourceFixture.ValidSourceParams.PUBLISHER,
7757                                     EventSurfaceType.APP,
7758                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7759                                     Collections.emptyList(),
7760                                     EventSurfaceType.WEB,
7761                                     baseEventTime + DAYS.toMillis(10) // request time
7762                                     );
7763 
7764                     assertEquals(20L, (long) destinationPriorityAndSourcesToDelete.first);
7765                     assertEquals(
7766                             Sets.newSet("s21", "s22"),
7767                             new HashSet<>(destinationPriorityAndSourcesToDelete.second));
7768                 });
7769     }
7770 
7771     @Test
fetchSourceIdsForLowPriorityDest_appDestEmptyExclusions_delLruDestinations()7772     public void fetchSourceIdsForLowPriorityDest_appDestEmptyExclusions_delLruDestinations() {
7773         // Setup
7774         long baseEventTime = System.currentTimeMillis();
7775         insert5SourcesForLruDestDeletion(baseEventTime);
7776 
7777         // Execute
7778         // com.example.app3 would be the least recently used destination, as 1 & 2 are used
7779         // afterwards
7780         mDatastoreManager.runInTransaction(
7781                 (dao) -> {
7782                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7783                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7784                                     SourceFixture.ValidSourceParams.PUBLISHER,
7785                                     EventSurfaceType.APP,
7786                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7787                                     Collections.emptyList(),
7788                                     EventSurfaceType.APP,
7789                                     baseEventTime + DAYS.toMillis(10) // request time
7790                                     );
7791 
7792                     assertEquals(0, (long) destinationPriorityAndSourcesToDelete.first);
7793                     assertEquals(List.of("s31"), destinationPriorityAndSourcesToDelete.second);
7794                 });
7795     }
7796 
7797     @Test
7798     public void
fetchSourceIdsForLowPriorityDest_appDestWebPubEmptyExclusions_delLowPrioDestSource()7799             fetchSourceIdsForLowPriorityDest_appDestWebPubEmptyExclusions_delLowPrioDestSource() {
7800         // Setup
7801         long baseEventTime = System.currentTimeMillis();
7802         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7803         insertSource(
7804                 createSourceBuilder()
7805                         .setPublisher(Uri.parse("https://web.example.com"))
7806                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7807                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7808                         .setEventTime(baseEventTime)
7809                         .setExpiryTime(commonExpiryTime)
7810                         .build(),
7811                 "s11");
7812         insertSource(
7813                 createSourceBuilder()
7814                         .setPublisher(Uri.parse("https://web.example.com"))
7815                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7816                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7817                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7818                         .setExpiryTime(commonExpiryTime)
7819                         .build(),
7820                 "s21");
7821         insertSource(
7822                 createSourceBuilder()
7823                         .setPublisher(Uri.parse("https://web.example.com"))
7824                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7825                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7826                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7827                         .setExpiryTime(commonExpiryTime)
7828                         .build(),
7829                 "s31");
7830         insertSource(
7831                 createSourceBuilder()
7832                         .setPublisher(Uri.parse("https://web.example.com"))
7833                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7834                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7835                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7836                         .setExpiryTime(commonExpiryTime)
7837                         .build(),
7838                 "s12");
7839         insertSource(
7840                 createSourceBuilder()
7841                         .setPublisher(Uri.parse("https://web.example.com"))
7842                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7843                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7844                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7845                         .setExpiryTime(commonExpiryTime)
7846                         .build(),
7847                 "s22");
7848 
7849         // Execute
7850         // com.example.app3 would be the least recently used destination, as 1 & 2 are used
7851         // afterwards
7852         mDatastoreManager.runInTransaction(
7853                 (dao) -> {
7854                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7855                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7856                                     Uri.parse("https://web.example.com"),
7857                                     EventSurfaceType.WEB,
7858                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7859                                     Collections.emptyList(),
7860                                     EventSurfaceType.APP,
7861                                     baseEventTime + DAYS.toMillis(10) // request time
7862                                     );
7863 
7864                     assertEquals(0L, (long) destinationPriorityAndSourcesToDelete.first);
7865                     assertEquals(List.of("s31"), destinationPriorityAndSourcesToDelete.second);
7866                 });
7867     }
7868 
7869     @Test
7870     public void
fetchSourceIdsForLowPriorityDest_diffEnrollments_delOldDestSourceForChosenEnrollment()7871             fetchSourceIdsForLowPriorityDest_diffEnrollments_delOldDestSourceForChosenEnrollment() {
7872         // Setup
7873         long baseEventTime = System.currentTimeMillis();
7874         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7875         insertSource(
7876                 createSourceBuilder()
7877                         .setEnrollmentId("enrollment1")
7878                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7879                         .setEventTime(baseEventTime)
7880                         .setExpiryTime(commonExpiryTime)
7881                         .build(),
7882                 "s11");
7883         insertSource(
7884                 createSourceBuilder()
7885                         .setEnrollmentId("enrollment1")
7886                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7887                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7888                         .setExpiryTime(commonExpiryTime)
7889                         .build(),
7890                 "s21");
7891         insertSource(
7892                 createSourceBuilder()
7893                         .setEnrollmentId("enrollment1")
7894                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7895                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7896                         .setExpiryTime(commonExpiryTime)
7897                         .build(),
7898                 "s31");
7899         insertSource(
7900                 createSourceBuilder()
7901                         .setEnrollmentId("enrollment2")
7902                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7903                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7904                         .setExpiryTime(commonExpiryTime)
7905                         .build(),
7906                 "s12");
7907         insertSource(
7908                 createSourceBuilder()
7909                         .setEnrollmentId("enrollment2")
7910                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7911                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7912                         .setExpiryTime(commonExpiryTime)
7913                         .build(),
7914                 "s22");
7915 
7916         // Execute
7917         // com.example.app1 would be the least recently used destination for enrollment2, that will
7918         // be deleted
7919         mDatastoreManager.runInTransaction(
7920                 (dao) -> {
7921                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7922                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7923                                     SourceFixture.ValidSourceParams.PUBLISHER,
7924                                     EventSurfaceType.APP,
7925                                     "enrollment2",
7926                                     Collections.emptyList(),
7927                                     EventSurfaceType.APP,
7928                                     baseEventTime + DAYS.toMillis(10) // request time
7929                                     );
7930 
7931                     assertEquals(0L, (long) destinationPriorityAndSourcesToDelete.first);
7932                     assertEquals(List.of("s12"), destinationPriorityAndSourcesToDelete.second);
7933                 });
7934     }
7935 
7936     @Test
7937     public void
fetchSourceIdsForLowPriorityDest_appDestExcludeLruSource_deletes2ndLruDestSources()7938             fetchSourceIdsForLowPriorityDest_appDestExcludeLruSource_deletes2ndLruDestSources() {
7939         // Setup
7940         long baseEventTime = System.currentTimeMillis();
7941         insert5SourcesForLruDestDeletion(System.currentTimeMillis());
7942 
7943         // Execute
7944         // com.example.app1 would be the second least recently used destination, as 2 is used
7945         // afterwards and 3 is ignored to be deleted.
7946         mDatastoreManager.runInTransaction(
7947                 (dao) -> {
7948                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7949                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7950                                     SourceFixture.ValidSourceParams.PUBLISHER,
7951                                     EventSurfaceType.APP,
7952                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7953                                     List.of(Uri.parse("android-app://com.example.app3")),
7954                                     EventSurfaceType.APP,
7955                                     baseEventTime + DAYS.toMillis(10) // request time
7956                                     );
7957 
7958                     assertEquals(0L, (long) destinationPriorityAndSourcesToDelete.first);
7959                     assertEquals(
7960                             List.of("s11", "s12"), destinationPriorityAndSourcesToDelete.second);
7961                 });
7962     }
7963 
insert5SourcesForLruDestDeletion(long baseEventTime)7964     private void insert5SourcesForLruDestDeletion(long baseEventTime) {
7965         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7966         insertSource(
7967                 createSourceBuilder()
7968                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7969                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7970                         .setEventTime(baseEventTime)
7971                         .setExpiryTime(commonExpiryTime)
7972                         .build(),
7973                 "s11");
7974         insertSource(
7975                 createSourceBuilder()
7976                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7977                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7978                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7979                         .setExpiryTime(commonExpiryTime)
7980                         .build(),
7981                 "s21");
7982         insertSource(
7983                 createSourceBuilder()
7984                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7985                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7986                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7987                         .setExpiryTime(commonExpiryTime)
7988                         .build(),
7989                 "s31");
7990         insertSource(
7991                 createSourceBuilder()
7992                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7993                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7994                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7995                         .setExpiryTime(commonExpiryTime)
7996                         .build(),
7997                 "s12");
7998         insertSource(
7999                 createSourceBuilder()
8000                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
8001                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
8002                         .setEventTime(baseEventTime + DAYS.toMillis(4))
8003                         .setExpiryTime(commonExpiryTime)
8004                         .build(),
8005                 "s22");
8006     }
8007 
8008     @Test
deletePendingAggregateReportsAndAttributionsForSources_success()8009     public void deletePendingAggregateReportsAndAttributionsForSources_success() {
8010         // Setup
8011         long baseTime = System.currentTimeMillis();
8012         // Sources
8013         insertSource(SourceFixture.getValidSource(), "S1");
8014         insertSource(SourceFixture.getValidSource(), "S2");
8015         insertSource(SourceFixture.getValidSource(), "S3");
8016         insertSource(SourceFixture.getValidSource(), "S4");
8017 
8018         mDatastoreManager.runInTransaction(
8019                 (dao) -> {
8020                     // Aggregate reports
8021                     // Should get deleted
8022                     AggregateReport agg1 =
8023                             AggregateReportFixture.getValidAggregateReportBuilder()
8024                                     .setId("Agg1")
8025                                     .setSourceId("S1")
8026                                     .setScheduledReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8027                                     .setStatus(AggregateReport.Status.PENDING)
8028                                     .build();
8029                     dao.insertAggregateReport(agg1);
8030                     dao.insertAttribution(
8031                             createAttribution(
8032                                     "Att1", Attribution.Scope.AGGREGATE, "S1", null, agg1.getId()));
8033 
8034                     // Should not get deleted because S2 is not provided
8035                     AggregateReport agg2 =
8036                             AggregateReportFixture.getValidAggregateReportBuilder()
8037                                     .setId("Agg2")
8038                                     .setSourceId("S2")
8039                                     .setScheduledReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8040                                     .setStatus(AggregateReport.Status.PENDING)
8041                                     .build();
8042                     dao.insertAggregateReport(agg2);
8043                     dao.insertAttribution(
8044                             createAttribution(
8045                                     "Att2", Attribution.Scope.AGGREGATE, "S2", null, agg2.getId()));
8046 
8047                     // Infeasible case, but it should not get deleted because its status is
8048                     // DELIVERED
8049                     AggregateReport agg3 =
8050                             AggregateReportFixture.getValidAggregateReportBuilder()
8051                                     .setId("Agg3")
8052                                     .setSourceId("S3")
8053                                     .setScheduledReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8054                                     .setStatus(AggregateReport.Status.DELIVERED)
8055                                     .build();
8056                     dao.insertAggregateReport(agg3);
8057                     dao.insertAttribution(
8058                             createAttribution(
8059                                     "Att3", Attribution.Scope.AGGREGATE, "S3", null, agg3.getId()));
8060 
8061                     // Execution
8062                     dao.deletePendingAggregateReportsAndAttributionsForSources(List.of("S1", "S3"));
8063 
8064                     // Assertion
8065                     assertThrows(DatastoreException.class, () -> dao.getAggregateReport("Agg1"));
8066                     assertEquals(agg2, dao.getAggregateReport("Agg2"));
8067                     assertEquals(agg3, dao.getAggregateReport("Agg3"));
8068                 });
8069 
8070         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8071         assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
8072         Set<String> reportIds = new HashSet<>();
8073         try (Cursor cursor =
8074                 db.rawQuery(
8075                         "SELECT "
8076                                 + AttributionContract.REPORT_ID
8077                                 + " FROM "
8078                                 + AttributionContract.TABLE,
8079                         null)) {
8080             while (cursor.moveToNext()) {
8081                 reportIds.add(cursor.getString(0));
8082             }
8083         }
8084         assertEquals(Set.of("Agg2", "Agg3"), reportIds);
8085     }
8086 
8087     @Test
fetchMatchingSourcesUninstall_outsideReportLifetime_deleteSources()8088     public void fetchMatchingSourcesUninstall_outsideReportLifetime_deleteSources()
8089             throws Exception {
8090         // Setup
8091         mocker.mockGetFlags(mMockFlags);
8092         doReturn(true).when(mMockFlags).getMeasurementEnableMinReportLifespanForUninstall();
8093         doReturn(TimeUnit.DAYS.toSeconds(1))
8094                 .when(mMockFlags)
8095                 .getMeasurementMinReportLifespanForUninstallSeconds();
8096 
8097         long currentTime = System.currentTimeMillis();
8098         long baseEventTime = currentTime - DAYS.toMillis(3);
8099         long expiryTime = baseEventTime + DAYS.toMillis(30);
8100 
8101         List<Source> sources =
8102                 Arrays.asList(
8103                         SourceFixture.getMinimalValidSourceBuilder()
8104                                 .setEventId(new UnsignedLong(1L))
8105                                 .setId("source1")
8106                                 .setEventTime(baseEventTime)
8107                                 .setExpiryTime(expiryTime)
8108                                 .build());
8109 
8110         // All trigger times more than 24 hours ago.
8111         List<Trigger> triggers =
8112                 Arrays.asList(
8113                         TriggerFixture.getValidTriggerBuilder()
8114                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8115                                 .setId("trigger1")
8116                                 .setTriggerTime(
8117                                         currentTime - DAYS.toMillis(1) - TimeUnit.HOURS.toMillis(1))
8118                                 .build(),
8119                         TriggerFixture.getValidTriggerBuilder()
8120                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8121                                 .setId("trigger2")
8122                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
8123                                 .build(),
8124                         TriggerFixture.getValidTriggerBuilder()
8125                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8126                                 .setId("trigger3")
8127                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
8128                                 .build());
8129 
8130         EventReport report0 =
8131                 createEventReportForSourceAndTriggerForUninstall(
8132                         "report0", sources.get(0), triggers.get(0));
8133         EventReport report1 =
8134                 createEventReportForSourceAndTriggerForUninstall(
8135                         "report1", sources.get(0), triggers.get(1));
8136 
8137         AggregateReport aggregateReport0 =
8138                 createAggregateReportForSourceAndTrigger(
8139                         "areport0", sources.get(0), triggers.get(2));
8140 
8141         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8142         sources.forEach(source -> insertSource(source, source.getId()));
8143         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8144 
8145         mDatastoreManager.runInTransaction(
8146                 (dao) -> {
8147                     dao.insertEventReport(report0);
8148                     dao.insertEventReport(report1);
8149                     dao.insertAggregateReport(aggregateReport0);
8150                 });
8151 
8152         // Execution
8153         mDatastoreManager.runInTransaction(
8154                 dao -> {
8155                     Pair<List<String>, List<String>> actualSources =
8156                             dao.fetchMatchingSourcesUninstall(
8157                                     SourceFixture.ValidSourceParams.REGISTRANT, currentTime);
8158                     // Source is deleted
8159                     Truth.assertThat(actualSources.first.size()).isEqualTo(1);
8160                     Truth.assertThat(actualSources.second.size()).isEqualTo(0);
8161                 });
8162     }
8163 
8164     @Test
fetchMatchingSourcesUninstall_withinReportLifetime_ignoreSources()8165     public void fetchMatchingSourcesUninstall_withinReportLifetime_ignoreSources()
8166             throws Exception {
8167         // Setup
8168         mocker.mockGetFlags(mMockFlags);
8169         doReturn(true).when(mMockFlags).getMeasurementEnableMinReportLifespanForUninstall();
8170         doReturn(TimeUnit.DAYS.toSeconds(1))
8171                 .when(mMockFlags)
8172                 .getMeasurementMinReportLifespanForUninstallSeconds();
8173 
8174         long currentTime = System.currentTimeMillis();
8175         long baseEventTime = currentTime - DAYS.toMillis(3);
8176         long expiryTime = baseEventTime + DAYS.toMillis(30);
8177 
8178         List<Source> sources =
8179                 Arrays.asList(
8180                         SourceFixture.getMinimalValidSourceBuilder()
8181                                 .setEventId(new UnsignedLong(1L))
8182                                 .setId("source1")
8183                                 .setEventTime(baseEventTime)
8184                                 .setExpiryTime(expiryTime)
8185                                 .build());
8186 
8187         // All trigger times more than 24 hours ago except trigger1.
8188         List<Trigger> triggers =
8189                 Arrays.asList(
8190                         TriggerFixture.getValidTriggerBuilder()
8191                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8192                                 .setId("trigger1")
8193                                 .setTriggerTime(currentTime)
8194                                 .build(),
8195                         TriggerFixture.getValidTriggerBuilder()
8196                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8197                                 .setId("trigger2")
8198                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
8199                                 .build(),
8200                         TriggerFixture.getValidTriggerBuilder()
8201                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8202                                 .setId("trigger3")
8203                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
8204                                 .build());
8205 
8206         EventReport report0 =
8207                 createEventReportForSourceAndTriggerForUninstall(
8208                         "report0", sources.get(0), triggers.get(0));
8209         EventReport report1 =
8210                 createEventReportForSourceAndTriggerForUninstall(
8211                         "report1", sources.get(0), triggers.get(1));
8212 
8213         AggregateReport aggregateReport0 =
8214                 createAggregateReportForSourceAndTrigger(
8215                         "areport0", sources.get(0), triggers.get(2));
8216 
8217         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8218         sources.forEach(source -> insertSource(source, source.getId()));
8219         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8220 
8221         mDatastoreManager.runInTransaction(
8222                 (dao) -> {
8223                     dao.insertEventReport(report0);
8224                     dao.insertEventReport(report1);
8225                     dao.insertAggregateReport(aggregateReport0);
8226                 });
8227 
8228         // Execution
8229         mDatastoreManager.runInTransaction(
8230                 dao -> {
8231                     Pair<List<String>, List<String>> actualSources =
8232                             dao.fetchMatchingSourcesUninstall(
8233                                     SourceFixture.ValidSourceParams.REGISTRANT, currentTime);
8234                     // Source is ignored
8235                     Truth.assertThat(actualSources.first.size()).isEqualTo(0);
8236                     Truth.assertThat(actualSources.second.size()).isEqualTo(1);
8237                 });
8238     }
8239 
8240     @Test
fetchMatchingSourcesUninstall_deleteAndIgnoreSources()8241     public void fetchMatchingSourcesUninstall_deleteAndIgnoreSources() throws Exception {
8242         // Setup
8243         mocker.mockGetFlags(mMockFlags);
8244         doReturn(true).when(mMockFlags).getMeasurementEnableMinReportLifespanForUninstall();
8245         doReturn(TimeUnit.DAYS.toSeconds(1))
8246                 .when(mMockFlags)
8247                 .getMeasurementMinReportLifespanForUninstallSeconds();
8248 
8249         long currentTime = System.currentTimeMillis();
8250         long baseEventTime = currentTime - DAYS.toMillis(3);
8251         long expiryTime = baseEventTime + DAYS.toMillis(30);
8252 
8253         List<Source> sources =
8254                 Arrays.asList(
8255                         SourceFixture.getMinimalValidSourceBuilder()
8256                                 .setEventId(new UnsignedLong(1L))
8257                                 .setId("source1")
8258                                 .setEventTime(baseEventTime)
8259                                 .setExpiryTime(expiryTime)
8260                                 .build(),
8261                         SourceFixture.getMinimalValidSourceBuilder()
8262                                 .setEventId(new UnsignedLong(1L))
8263                                 .setId("source2")
8264                                 .setEventTime(baseEventTime)
8265                                 .setExpiryTime(expiryTime)
8266                                 .build(),
8267                         SourceFixture.getMinimalValidSourceBuilder()
8268                                 .setEventId(new UnsignedLong(1L))
8269                                 .setId("source3")
8270                                 .setEventTime(baseEventTime)
8271                                 .setExpiryTime(expiryTime)
8272                                 .build());
8273 
8274         // All trigger times more than 24 hours ago except trigger1.
8275         List<Trigger> triggers =
8276                 Arrays.asList(
8277                         TriggerFixture.getValidTriggerBuilder()
8278                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8279                                 .setId("trigger1")
8280                                 .setTriggerTime(currentTime)
8281                                 .build(),
8282                         TriggerFixture.getValidTriggerBuilder()
8283                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8284                                 .setId("trigger2")
8285                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
8286                                 .build(),
8287                         TriggerFixture.getValidTriggerBuilder()
8288                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8289                                 .setId("trigger3")
8290                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
8291                                 .build());
8292 
8293         EventReport report0 =
8294                 createEventReportForSourceAndTriggerForUninstall(
8295                         "report0", sources.get(0), triggers.get(0));
8296         EventReport report1 =
8297                 createEventReportForSourceAndTriggerForUninstall(
8298                         "report1", sources.get(1), triggers.get(1));
8299 
8300         AggregateReport aggregateReport0 =
8301                 createAggregateReportForSourceAndTrigger(
8302                         "areport0", sources.get(2), triggers.get(2));
8303 
8304         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8305         sources.forEach(source -> insertSource(source, source.getId()));
8306         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8307 
8308         mDatastoreManager.runInTransaction(
8309                 (dao) -> {
8310                     dao.insertEventReport(report0);
8311                     dao.insertEventReport(report1);
8312                     dao.insertAggregateReport(aggregateReport0);
8313                 });
8314 
8315         // Execution
8316         mDatastoreManager.runInTransaction(
8317                 dao -> {
8318                     Pair<List<String>, List<String>> actualSources =
8319                             dao.fetchMatchingSourcesUninstall(
8320                                     SourceFixture.ValidSourceParams.REGISTRANT, currentTime);
8321                     // Source is ignored
8322                     Truth.assertThat(actualSources.first.size()).isEqualTo(2);
8323                     Truth.assertThat(actualSources.second.size()).isEqualTo(1);
8324                 });
8325     }
8326 
8327     @Test
fetchMatchingTriggersUninstall_outsideReportLifetime_deleteTriggers()8328     public void fetchMatchingTriggersUninstall_outsideReportLifetime_deleteTriggers()
8329             throws Exception {
8330         // Setup
8331         mocker.mockGetFlags(mMockFlags);
8332         doReturn(true).when(mMockFlags).getMeasurementEnableMinReportLifespanForUninstall();
8333         doReturn(TimeUnit.DAYS.toSeconds(1))
8334                 .when(mMockFlags)
8335                 .getMeasurementMinReportLifespanForUninstallSeconds();
8336 
8337         long currentTime = System.currentTimeMillis();
8338         long baseEventTime = currentTime - DAYS.toMillis(3);
8339         long expiryTime = baseEventTime + DAYS.toMillis(30);
8340 
8341         List<Source> sources =
8342                 Arrays.asList(
8343                         SourceFixture.getMinimalValidSourceBuilder()
8344                                 .setEventId(new UnsignedLong(1L))
8345                                 .setId("source1")
8346                                 .setEventTime(baseEventTime)
8347                                 .setExpiryTime(expiryTime)
8348                                 .build());
8349 
8350         List<Trigger> triggers =
8351                 Arrays.asList(
8352                         TriggerFixture.getValidTriggerBuilder()
8353                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8354                                 .setId("trigger1")
8355                                 .setTriggerTime(
8356                                         currentTime - DAYS.toMillis(1) - TimeUnit.HOURS.toMillis(1))
8357                                 .build(),
8358                         TriggerFixture.getValidTriggerBuilder()
8359                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8360                                 .setId("trigger2")
8361                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
8362                                 .build(),
8363                         TriggerFixture.getValidTriggerBuilder()
8364                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8365                                 .setId("trigger3")
8366                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
8367                                 .build());
8368 
8369         EventReport report0 =
8370                 createEventReportForSourceAndTriggerForUninstall(
8371                         "report0", sources.get(0), triggers.get(0));
8372         EventReport report1 =
8373                 createEventReportForSourceAndTriggerForUninstall(
8374                         "report1", sources.get(0), triggers.get(1));
8375 
8376         AggregateReport aggregateReport0 =
8377                 createAggregateReportForSourceAndTrigger(
8378                         "areport0", sources.get(0), triggers.get(2));
8379 
8380         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8381         sources.forEach(source -> insertSource(source, source.getId()));
8382         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8383 
8384         mDatastoreManager.runInTransaction(
8385                 (dao) -> {
8386                     dao.insertEventReport(report0);
8387                     dao.insertEventReport(report1);
8388                     dao.insertAggregateReport(aggregateReport0);
8389                 });
8390 
8391         // Execution
8392         mDatastoreManager.runInTransaction(
8393                 dao -> {
8394                     Pair<List<String>, List<String>> actualTriggers =
8395                             dao.fetchMatchingTriggersUninstall(
8396                                     TriggerFixture.ValidTriggerParams.REGISTRANT, currentTime);
8397                     // Triggers are deleted
8398                     Truth.assertThat(actualTriggers.first.size()).isEqualTo(3);
8399                     Truth.assertThat(actualTriggers.second.size()).isEqualTo(0);
8400                 });
8401     }
8402 
8403     @Test
fetchMatchingTriggersUninstall_withinReportLifetime_ignoreTriggers()8404     public void fetchMatchingTriggersUninstall_withinReportLifetime_ignoreTriggers()
8405             throws Exception {
8406         // Setup
8407         mocker.mockGetFlags(mMockFlags);
8408         doReturn(true).when(mMockFlags).getMeasurementEnableMinReportLifespanForUninstall();
8409         doReturn(TimeUnit.DAYS.toSeconds(1))
8410                 .when(mMockFlags)
8411                 .getMeasurementMinReportLifespanForUninstallSeconds();
8412 
8413         long currentTime = System.currentTimeMillis();
8414         long baseEventTime = currentTime - DAYS.toMillis(3);
8415         long expiryTime = baseEventTime + DAYS.toMillis(30);
8416 
8417         List<Source> sources =
8418                 Arrays.asList(
8419                         SourceFixture.getMinimalValidSourceBuilder()
8420                                 .setEventId(new UnsignedLong(1L))
8421                                 .setId("source1")
8422                                 .setEventTime(baseEventTime)
8423                                 .setExpiryTime(expiryTime)
8424                                 .build());
8425 
8426         List<Trigger> triggers =
8427                 Arrays.asList(
8428                         TriggerFixture.getValidTriggerBuilder()
8429                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8430                                 .setId("trigger1")
8431                                 .setTriggerTime(currentTime - TimeUnit.HOURS.toMillis(23))
8432                                 .build(),
8433                         TriggerFixture.getValidTriggerBuilder()
8434                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8435                                 .setId("trigger2")
8436                                 .setTriggerTime(currentTime - TimeUnit.HOURS.toMillis(1))
8437                                 .build(),
8438                         TriggerFixture.getValidTriggerBuilder()
8439                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8440                                 .setId("trigger3")
8441                                 .setTriggerTime(currentTime - TimeUnit.HOURS.toMillis(12))
8442                                 .build());
8443 
8444         EventReport report0 =
8445                 createEventReportForSourceAndTriggerForUninstall(
8446                         "report0", sources.get(0), triggers.get(0));
8447         EventReport report1 =
8448                 createEventReportForSourceAndTriggerForUninstall(
8449                         "report1", sources.get(0), triggers.get(1));
8450 
8451         AggregateReport aggregateReport0 =
8452                 createAggregateReportForSourceAndTrigger(
8453                         "areport0", sources.get(0), triggers.get(2));
8454 
8455         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8456         sources.forEach(source -> insertSource(source, source.getId()));
8457         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8458 
8459         mDatastoreManager.runInTransaction(
8460                 (dao) -> {
8461                     dao.insertEventReport(report0);
8462                     dao.insertEventReport(report1);
8463                     dao.insertAggregateReport(aggregateReport0);
8464                 });
8465 
8466         // Execution
8467         mDatastoreManager.runInTransaction(
8468                 dao -> {
8469                     Pair<List<String>, List<String>> actualTriggers =
8470                             dao.fetchMatchingTriggersUninstall(
8471                                     TriggerFixture.ValidTriggerParams.REGISTRANT, currentTime);
8472                     // Triggers are ignored
8473                     Truth.assertThat(actualTriggers.first.size()).isEqualTo(0);
8474                     Truth.assertThat(actualTriggers.second.size()).isEqualTo(3);
8475                 });
8476     }
8477 
8478     @Test
fetchMatchingTriggersUninstall_deleteAndIgnoreTriggers()8479     public void fetchMatchingTriggersUninstall_deleteAndIgnoreTriggers() throws Exception {
8480         // Setup
8481         mocker.mockGetFlags(mMockFlags);
8482         doReturn(true).when(mMockFlags).getMeasurementEnableMinReportLifespanForUninstall();
8483         doReturn(TimeUnit.DAYS.toSeconds(1))
8484                 .when(mMockFlags)
8485                 .getMeasurementMinReportLifespanForUninstallSeconds();
8486 
8487         long currentTime = System.currentTimeMillis();
8488         long baseEventTime = currentTime - DAYS.toMillis(3);
8489         long expiryTime = baseEventTime + DAYS.toMillis(30);
8490 
8491         List<Source> sources =
8492                 Arrays.asList(
8493                         SourceFixture.getMinimalValidSourceBuilder()
8494                                 .setEventId(new UnsignedLong(1L))
8495                                 .setId("source1")
8496                                 .setEventTime(baseEventTime)
8497                                 .setExpiryTime(expiryTime)
8498                                 .build(),
8499                         SourceFixture.getMinimalValidSourceBuilder()
8500                                 .setEventId(new UnsignedLong(1L))
8501                                 .setId("source2")
8502                                 .setEventTime(baseEventTime)
8503                                 .setExpiryTime(expiryTime)
8504                                 .build(),
8505                         SourceFixture.getMinimalValidSourceBuilder()
8506                                 .setEventId(new UnsignedLong(1L))
8507                                 .setId("source3")
8508                                 .setEventTime(baseEventTime)
8509                                 .setExpiryTime(expiryTime)
8510                                 .build());
8511 
8512         List<Trigger> triggers =
8513                 Arrays.asList(
8514                         TriggerFixture.getValidTriggerBuilder()
8515                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8516                                 .setId("trigger1")
8517                                 .setTriggerTime(currentTime)
8518                                 .build(),
8519                         TriggerFixture.getValidTriggerBuilder()
8520                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8521                                 .setId("trigger2")
8522                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
8523                                 .build(),
8524                         TriggerFixture.getValidTriggerBuilder()
8525                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8526                                 .setId("trigger3")
8527                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
8528                                 .build());
8529 
8530         EventReport report0 =
8531                 createEventReportForSourceAndTriggerForUninstall(
8532                         "report0", sources.get(0), triggers.get(0));
8533         EventReport report1 =
8534                 createEventReportForSourceAndTriggerForUninstall(
8535                         "report1", sources.get(1), triggers.get(1));
8536 
8537         AggregateReport aggregateReport0 =
8538                 createAggregateReportForSourceAndTrigger(
8539                         "areport0", sources.get(2), triggers.get(2));
8540 
8541         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8542         sources.forEach(source -> insertSource(source, source.getId()));
8543         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8544 
8545         mDatastoreManager.runInTransaction(
8546                 (dao) -> {
8547                     dao.insertEventReport(report0);
8548                     dao.insertEventReport(report1);
8549                     dao.insertAggregateReport(aggregateReport0);
8550                 });
8551 
8552         // Execution
8553         mDatastoreManager.runInTransaction(
8554                 dao -> {
8555                     Pair<List<String>, List<String>> actualTriggers =
8556                             dao.fetchMatchingTriggersUninstall(
8557                                     TriggerFixture.ValidTriggerParams.REGISTRANT, currentTime);
8558                     // Triggers are deleted
8559                     Truth.assertThat(actualTriggers.first.size()).isEqualTo(2);
8560                     Truth.assertThat(actualTriggers.second.size()).isEqualTo(1);
8561                 });
8562     }
8563 
8564     @Test
fetchMatchingSourcesTriggersUninstall_noReportsDelete()8565     public void fetchMatchingSourcesTriggersUninstall_noReportsDelete() throws Exception {
8566         // Setup
8567         mocker.mockGetFlags(mMockFlags);
8568         doReturn(true).when(mMockFlags).getMeasurementEnableMinReportLifespanForUninstall();
8569         doReturn(TimeUnit.DAYS.toSeconds(1))
8570                 .when(mMockFlags)
8571                 .getMeasurementMinReportLifespanForUninstallSeconds();
8572 
8573         long currentTime = System.currentTimeMillis();
8574         long baseEventTime = currentTime - DAYS.toMillis(3);
8575         long expiryTime = baseEventTime + DAYS.toMillis(30);
8576 
8577         List<Source> sources =
8578                 Arrays.asList(
8579                         SourceFixture.getMinimalValidSourceBuilder()
8580                                 .setEventId(new UnsignedLong(1L))
8581                                 .setId("source1")
8582                                 .setEventTime(baseEventTime)
8583                                 .setExpiryTime(expiryTime)
8584                                 .build(),
8585                         SourceFixture.getMinimalValidSourceBuilder()
8586                                 .setEventId(new UnsignedLong(1L))
8587                                 .setId("source2")
8588                                 .setEventTime(baseEventTime)
8589                                 .setExpiryTime(expiryTime)
8590                                 .build());
8591 
8592         List<Trigger> triggers =
8593                 Arrays.asList(
8594                         TriggerFixture.getValidTriggerBuilder()
8595                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8596                                 .setId("trigger1")
8597                                 .setTriggerTime(currentTime)
8598                                 .build(),
8599                         TriggerFixture.getValidTriggerBuilder()
8600                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8601                                 .setId("trigger2")
8602                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
8603                                 .build());
8604 
8605         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8606         sources.forEach(source -> insertSource(source, source.getId()));
8607         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8608 
8609         // Execution
8610         mDatastoreManager.runInTransaction(
8611                 dao -> {
8612                     Pair<List<String>, List<String>> actualTriggers =
8613                             dao.fetchMatchingTriggersUninstall(
8614                                     TriggerFixture.ValidTriggerParams.REGISTRANT, currentTime);
8615                     Pair<List<String>, List<String>> actualSources =
8616                             dao.fetchMatchingSourcesUninstall(
8617                                     SourceFixture.ValidSourceParams.REGISTRANT, currentTime);
8618 
8619                     // All Sources are deleted
8620                     Truth.assertThat(actualSources.first.size()).isEqualTo(2);
8621                     Truth.assertThat(actualSources.second.size()).isEqualTo(0);
8622 
8623                     // All Triggers are deleted
8624                     Truth.assertThat(actualTriggers.first.size()).isEqualTo(2);
8625                     Truth.assertThat(actualTriggers.second.size()).isEqualTo(0);
8626                 });
8627     }
8628 
8629     @Test
deletePendingFakeEventReportsForSources_success()8630     public void deletePendingFakeEventReportsForSources_success() {
8631         // Setup
8632         long baseTime = SOURCE_EVENT_TIME;
8633         // Sources
8634         insertSource(SourceFixture.getValidSource(), "S1");
8635         insertSource(SourceFixture.getValidSource(), "S2");
8636         insertSource(SourceFixture.getValidSource(), "S3");
8637         insertSource(SourceFixture.getValidSource(), "S4");
8638 
8639         mDatastoreManager.runInTransaction(
8640                 (dao) -> {
8641                     // Event reports
8642                     // Should get deleted
8643                     EventReport eventReport1 =
8644                             EventReportFixture.getBaseEventReportBuild()
8645                                     .setId("Event1")
8646                                     .setSourceId("S1")
8647                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8648                                     // trigger time > source event time => fake report
8649                                     .setTriggerTime(baseTime + TimeUnit.HOURS.toMillis(1) + 1L)
8650                                     .setStatus(EventReport.Status.PENDING)
8651                                     .setTriggerId(null)
8652                                     .build();
8653                     dao.insertEventReport(eventReport1);
8654                     dao.insertAttribution(
8655                             createAttribution(
8656                                     "Att1",
8657                                     Attribution.Scope.EVENT,
8658                                     "S1",
8659                                     null,
8660                                     eventReport1.getId()));
8661 
8662                     // Should not get deleted because S2 is not provided
8663                     EventReport eventReport2 =
8664                             EventReportFixture.getBaseEventReportBuild()
8665                                     .setId("Event2")
8666                                     .setSourceId("S2")
8667                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8668                                     // trigger time > source event time => fake report
8669                                     .setTriggerTime(
8670                                             baseTime
8671                                                     + TimeUnit.HOURS.toMillis(1)
8672                                                     + TimeUnit.MINUTES.toMillis(1))
8673                                     .setStatus(EventReport.Status.PENDING)
8674                                     .setTriggerId(null)
8675                                     .build();
8676                     dao.insertEventReport(eventReport2);
8677                     dao.insertAttribution(
8678                             createAttribution(
8679                                     "Att2",
8680                                     Attribution.Scope.EVENT,
8681                                     "S2",
8682                                     null,
8683                                     eventReport2.getId()));
8684 
8685                     // Should not get deleted because it's a real report
8686                     EventReport eventReport3 =
8687                             EventReportFixture.getBaseEventReportBuild()
8688                                     .setId("Event3")
8689                                     .setSourceId("S1")
8690                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8691                                     // trigger time < source event time => real report
8692                                     .setTriggerTime(baseTime - 1L)
8693                                     .setStatus(EventReport.Status.PENDING)
8694                                     .setTriggerId(null)
8695                                     .build();
8696                     dao.insertEventReport(eventReport3);
8697                     dao.insertAttribution(
8698                             createAttribution(
8699                                     "Att3",
8700                                     Attribution.Scope.EVENT,
8701                                     "S1",
8702                                     null,
8703                                     eventReport3.getId()));
8704 
8705                     // Infeasible case, but it should not get deleted because its status is
8706                     // DELIVERED
8707                     EventReport eventReport4 =
8708                             EventReportFixture.getBaseEventReportBuild()
8709                                     .setId("Event4")
8710                                     .setSourceId("S3")
8711                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8712                                     // trigger time > source event time => fake report
8713                                     .setTriggerTime(
8714                                             baseTime
8715                                                     + TimeUnit.HOURS.toMillis(1)
8716                                                     + TimeUnit.SECONDS.toMillis(1))
8717                                     .setStatus(EventReport.Status.DELIVERED)
8718                                     .setTriggerId(null)
8719                                     .build();
8720                     dao.insertEventReport(eventReport4);
8721                     dao.insertAttribution(
8722                             createAttribution(
8723                                     "Att4",
8724                                     Attribution.Scope.EVENT,
8725                                     "S3",
8726                                     null,
8727                                     eventReport4.getId()));
8728 
8729                     // Execution
8730                     dao.deleteFutureFakeEventReportsForSources(List.of("S1", "S3"), baseTime);
8731 
8732                     // Assertion
8733                     assertThrows(DatastoreException.class, () -> dao.getEventReport("Event1"));
8734                     assertEquals(eventReport2, dao.getEventReport("Event2"));
8735                     assertEquals(eventReport3, dao.getEventReport("Event3"));
8736                     assertEquals(eventReport4, dao.getEventReport("Event4"));
8737                 });
8738 
8739         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8740         assertEquals(4, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
8741         Set<String> reportIds = new HashSet<>();
8742         try (Cursor cursor =
8743                 db.rawQuery(
8744                         "SELECT "
8745                                 + AttributionContract.REPORT_ID
8746                                 + " FROM "
8747                                 + AttributionContract.TABLE,
8748                         null)) {
8749             while (cursor.moveToNext()) {
8750                 reportIds.add(cursor.getString(0));
8751             }
8752         }
8753         assertEquals(Set.of("Event1", "Event2", "Event3", "Event4"), reportIds);
8754     }
8755 
getSourceWithDifferentDestinations( int numDestinations, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus)8756     private static Source getSourceWithDifferentDestinations(
8757             int numDestinations,
8758             boolean hasAppDestinations,
8759             boolean hasWebDestinations,
8760             long eventTime,
8761             Uri publisher,
8762             String enrollmentId,
8763             @Source.Status int sourceStatus) {
8764         List<Uri> appDestinations = null;
8765         List<Uri> webDestinations = null;
8766         if (hasAppDestinations) {
8767             appDestinations = new ArrayList<>();
8768             appDestinations.add(Uri.parse("android-app://com.app-destination"));
8769         }
8770         if (hasWebDestinations) {
8771             webDestinations = new ArrayList<>();
8772             for (int i = 0; i < numDestinations; i++) {
8773                 webDestinations.add(
8774                         Uri.parse("https://web-destination-" + String.valueOf(i) + ".com"));
8775             }
8776         }
8777         long expiryTime =
8778                 eventTime
8779                         + TimeUnit.SECONDS.toMillis(
8780                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8781         return new Source.Builder()
8782                 .setEventId(new UnsignedLong(0L))
8783                 .setEventTime(eventTime)
8784                 .setExpiryTime(expiryTime)
8785                 .setPublisher(publisher)
8786                 .setAppDestinations(appDestinations)
8787                 .setWebDestinations(webDestinations)
8788                 .setEnrollmentId(enrollmentId)
8789                 .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
8790                 .setStatus(sourceStatus)
8791                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
8792                 .build();
8793     }
8794 
getSourcesWithDifferentDestinations( int numSources, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus, Uri registrationOrigin)8795     private static List<Source> getSourcesWithDifferentDestinations(
8796             int numSources,
8797             boolean hasAppDestinations,
8798             boolean hasWebDestinations,
8799             long eventTime,
8800             Uri publisher,
8801             String enrollmentId,
8802             @Source.Status int sourceStatus,
8803             Uri registrationOrigin) {
8804         long expiryTime =
8805                 eventTime
8806                         + TimeUnit.SECONDS.toMillis(
8807                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8808         return getSourcesWithDifferentDestinations(
8809                 numSources,
8810                 hasAppDestinations,
8811                 hasWebDestinations,
8812                 eventTime,
8813                 expiryTime,
8814                 publisher,
8815                 enrollmentId,
8816                 sourceStatus,
8817                 registrationOrigin);
8818     }
8819 
getSourcesWithDifferentDestinations( int numSources, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus)8820     private static List<Source> getSourcesWithDifferentDestinations(
8821             int numSources,
8822             boolean hasAppDestinations,
8823             boolean hasWebDestinations,
8824             long eventTime,
8825             Uri publisher,
8826             String enrollmentId,
8827             @Source.Status int sourceStatus) {
8828         long expiryTime =
8829                 eventTime
8830                         + TimeUnit.SECONDS.toMillis(
8831                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8832         return getSourcesWithDifferentDestinations(
8833                 numSources,
8834                 hasAppDestinations,
8835                 hasWebDestinations,
8836                 eventTime,
8837                 expiryTime,
8838                 publisher,
8839                 enrollmentId,
8840                 sourceStatus,
8841                 REGISTRATION_ORIGIN);
8842     }
8843 
getSourcesWithDifferentDestinations( int numSources, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, long expiryTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus, Uri registrationOrigin)8844     private static List<Source> getSourcesWithDifferentDestinations(
8845             int numSources,
8846             boolean hasAppDestinations,
8847             boolean hasWebDestinations,
8848             long eventTime,
8849             long expiryTime,
8850             Uri publisher,
8851             String enrollmentId,
8852             @Source.Status int sourceStatus,
8853             Uri registrationOrigin) {
8854         List<Source> sources = new ArrayList<>();
8855         for (int i = 0; i < numSources; i++) {
8856             Source.Builder sourceBuilder =
8857                     new Source.Builder()
8858                             .setEventId(new UnsignedLong(0L))
8859                             .setEventTime(eventTime)
8860                             .setExpiryTime(expiryTime)
8861                             .setPublisher(publisher)
8862                             .setEnrollmentId(enrollmentId)
8863                             .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
8864                             .setStatus(sourceStatus)
8865                             .setRegistrationOrigin(registrationOrigin);
8866             if (hasAppDestinations) {
8867                 sourceBuilder.setAppDestinations(
8868                         List.of(Uri.parse("android-app://app-destination-" + String.valueOf(i))));
8869             }
8870             if (hasWebDestinations) {
8871                 sourceBuilder.setWebDestinations(
8872                         List.of(
8873                                 Uri.parse(
8874                                         "https://web-destination-" + String.valueOf(i) + ".com")));
8875             }
8876             sources.add(sourceBuilder.build());
8877         }
8878         return sources;
8879     }
8880 
getSourcesWithDifferentRegistrationOrigins( int numSources, List<Uri> appDestinations, List<Uri> webDestinations, long eventTime, Uri publisher, @Source.Status int sourceStatus)8881     private static List<Source> getSourcesWithDifferentRegistrationOrigins(
8882             int numSources,
8883             List<Uri> appDestinations,
8884             List<Uri> webDestinations,
8885             long eventTime,
8886             Uri publisher,
8887             @Source.Status int sourceStatus) {
8888         long expiryTime =
8889                 eventTime
8890                         + TimeUnit.SECONDS.toMillis(
8891                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8892         return getSourcesWithDifferentRegistrationOrigins(
8893                 numSources,
8894                 appDestinations,
8895                 webDestinations,
8896                 eventTime,
8897                 expiryTime,
8898                 publisher,
8899                 sourceStatus);
8900     }
8901 
getSourcesWithDifferentRegistrationOrigins( int numSources, List<Uri> appDestinations, List<Uri> webDestinations, long eventTime, long expiryTime, Uri publisher, @Source.Status int sourceStatus)8902     private static List<Source> getSourcesWithDifferentRegistrationOrigins(
8903             int numSources,
8904             List<Uri> appDestinations,
8905             List<Uri> webDestinations,
8906             long eventTime,
8907             long expiryTime,
8908             Uri publisher,
8909             @Source.Status int sourceStatus) {
8910         List<Source> sources = new ArrayList<>();
8911         for (int i = 0; i < numSources; i++) {
8912             Source.Builder sourceBuilder =
8913                     new Source.Builder()
8914                             .setEventId(new UnsignedLong(0L))
8915                             .setEventTime(eventTime)
8916                             .setExpiryTime(expiryTime)
8917                             .setPublisher(publisher)
8918                             .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
8919                             .setStatus(sourceStatus)
8920                             .setAppDestinations(getNullableUriList(appDestinations))
8921                             .setWebDestinations(getNullableUriList(webDestinations))
8922                             .setEnrollmentId("enrollment-id")
8923                             .setRegistrationOrigin(
8924                                     WebUtil.validUri("https://subdomain" + i + ".example.test"));
8925             sources.add(sourceBuilder.build());
8926         }
8927         return sources;
8928     }
8929 
getAttributionsWithDifferentReportingOrigins( int numAttributions, Uri destinationSite, long triggerTime, Uri sourceSite, String registrant)8930     private static List<Attribution> getAttributionsWithDifferentReportingOrigins(
8931             int numAttributions,
8932             Uri destinationSite,
8933             long triggerTime,
8934             Uri sourceSite,
8935             String registrant) {
8936         List<Attribution> attributions = new ArrayList<>();
8937         for (int i = 0; i < numAttributions; i++) {
8938             Attribution.Builder attributionBuilder =
8939                     new Attribution.Builder()
8940                             .setTriggerTime(triggerTime)
8941                             .setSourceSite(sourceSite.toString())
8942                             .setSourceOrigin(sourceSite.toString())
8943                             .setDestinationSite(destinationSite.toString())
8944                             .setDestinationOrigin(destinationSite.toString())
8945                             .setEnrollmentId("enrollment-id")
8946                             .setRegistrationOrigin(
8947                                     WebUtil.validUri("https://subdomain" + i + ".example.test"))
8948                             .setRegistrant(registrant);
8949             attributions.add(attributionBuilder.build());
8950         }
8951         return attributions;
8952     }
8953 
insertAttribution(Attribution attribution)8954     private static void insertAttribution(Attribution attribution) {
8955         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8956         ContentValues values = new ContentValues();
8957         values.put(AttributionContract.ID, UUID.randomUUID().toString());
8958         values.put(AttributionContract.SOURCE_SITE, attribution.getSourceSite());
8959         values.put(AttributionContract.DESTINATION_SITE, attribution.getDestinationSite());
8960         values.put(AttributionContract.ENROLLMENT_ID, attribution.getEnrollmentId());
8961         values.put(AttributionContract.TRIGGER_TIME, attribution.getTriggerTime());
8962         values.put(AttributionContract.SOURCE_ID, attribution.getSourceId());
8963         values.put(AttributionContract.TRIGGER_ID, attribution.getTriggerId());
8964         values.put(
8965                 AttributionContract.REGISTRATION_ORIGIN,
8966                 attribution.getRegistrationOrigin().toString());
8967         long row = db.insert("msmt_attribution", null, values);
8968         assertNotEquals("Attribution insertion failed", -1, row);
8969     }
8970 
createAttribution( String attributionId, String sourceId, String triggerId)8971     private static Attribution createAttribution(
8972             String attributionId,
8973             String sourceId,
8974             String triggerId) {
8975         return createAttribution(attributionId, Attribution.Scope.EVENT, sourceId, triggerId);
8976     }
8977 
createAttribution( String attributionId, @Attribution.Scope int scope, String sourceId, String triggerId)8978     private static Attribution createAttribution(
8979             String attributionId,
8980             @Attribution.Scope int scope,
8981             String sourceId,
8982             String triggerId) {
8983         return createAttribution(attributionId, scope, sourceId, triggerId, null);
8984     }
8985 
createAttribution( String attributionId, @Attribution.Scope int scope, String sourceId, String triggerId, String reportId)8986     private static Attribution createAttribution(
8987             String attributionId,
8988             @Attribution.Scope int scope,
8989             String sourceId,
8990             String triggerId,
8991             String reportId) {
8992         return new Attribution.Builder()
8993                 .setId(attributionId)
8994                 .setScope(scope)
8995                 .setTriggerTime(0L)
8996                 .setSourceSite("android-app://source.app")
8997                 .setSourceOrigin("android-app://source.app")
8998                 .setDestinationSite("android-app://destination.app")
8999                 .setDestinationOrigin("android-app://destination.app")
9000                 .setEnrollmentId("enrollment-id-")
9001                 .setRegistrant("android-app://registrant.app")
9002                 .setSourceId(sourceId)
9003                 .setTriggerId(triggerId)
9004                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
9005                 .setReportId(reportId)
9006                 .build();
9007     }
9008 
insertSource(Source source)9009     private static void insertSource(Source source) {
9010         insertSource(source, UUID.randomUUID().toString());
9011     }
9012 
9013     // This is needed because MeasurementDao::insertSource inserts a default value for status.
insertSource(Source source, String sourceId)9014     private static void insertSource(Source source, String sourceId) {
9015         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9016         ContentValues values = new ContentValues();
9017         values.put(SourceContract.ID, sourceId);
9018         if (source.getEventId() != null) {
9019             values.put(SourceContract.EVENT_ID, source.getEventId().getValue());
9020         }
9021         values.put(SourceContract.PUBLISHER, source.getPublisher().toString());
9022         values.put(SourceContract.PUBLISHER_TYPE, source.getPublisherType());
9023         values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId());
9024         values.put(SourceContract.EVENT_TIME, source.getEventTime());
9025         values.put(SourceContract.EXPIRY_TIME, source.getExpiryTime());
9026         values.put(SourceContract.PRIORITY, source.getPriority());
9027         values.put(SourceContract.STATUS, source.getStatus());
9028         values.put(SourceContract.SOURCE_TYPE, source.getSourceType().toString());
9029         values.put(SourceContract.REGISTRANT, source.getRegistrant().toString());
9030         values.put(SourceContract.INSTALL_ATTRIBUTION_WINDOW, source.getInstallAttributionWindow());
9031         values.put(SourceContract.INSTALL_COOLDOWN_WINDOW, source.getInstallCooldownWindow());
9032         values.put(SourceContract.ATTRIBUTION_MODE, source.getAttributionMode());
9033         values.put(SourceContract.AGGREGATE_SOURCE, source.getAggregateSource());
9034         values.put(SourceContract.FILTER_DATA, source.getFilterDataString());
9035         values.put(SourceContract.SHARED_FILTER_DATA_KEYS, source.getSharedFilterDataKeys());
9036         values.put(SourceContract.AGGREGATE_CONTRIBUTIONS, source.getAggregateContributions());
9037         values.put(SourceContract.DEBUG_REPORTING, source.isDebugReporting());
9038         values.put(SourceContract.DEBUG_AD_ID, source.getDebugAdId());
9039         values.put(SourceContract.INSTALL_TIME, source.getInstallTime());
9040         values.put(SourceContract.REGISTRATION_ID, source.getRegistrationId());
9041         values.put(SourceContract.SHARED_AGGREGATION_KEYS, source.getSharedAggregationKeys());
9042         values.put(SourceContract.REGISTRATION_ORIGIN, source.getRegistrationOrigin().toString());
9043         values.put(SourceContract.DESTINATION_LIMIT_PRIORITY, source.getDestinationLimitPriority());
9044         values.put(SourceContract.IS_INSTALL_ATTRIBUTED, source.isInstallAttributed());
9045         values.put(
9046                 SourceContract.AGGREGATE_DEBUG_REPORT_CONTRIBUTIONS,
9047                 source.getAggregateDebugReportContributions());
9048         values.put(
9049                 SourceContract.REINSTALL_REATTRIBUTION_WINDOW,
9050                 source.getReinstallReattributionWindow());
9051         if (source.getDebugKey() != null) {
9052             values.put(SourceContract.DEBUG_KEY, source.getDebugKey().getValue());
9053         }
9054         long row = db.insert(SourceContract.TABLE, null, values);
9055         assertNotEquals("Source insertion failed", -1, row);
9056 
9057         maybeInsertSourceDestinations(db, source, sourceId);
9058     }
9059 
getNullableUnsignedLong(@ullable UnsignedLong ulong)9060     private static Long getNullableUnsignedLong(@Nullable UnsignedLong ulong) {
9061         return Optional.ofNullable(ulong).map(UnsignedLong::getValue).orElse(null);
9062     }
9063 
insertTrigger(Trigger trigger, String triggerId)9064     private static void insertTrigger(Trigger trigger, String triggerId) {
9065         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9066         ContentValues values = new ContentValues();
9067         values.put(TriggerContract.ID, triggerId);
9068         values.put(
9069                 TriggerContract.ATTRIBUTION_DESTINATION,
9070                 trigger.getAttributionDestination().toString());
9071         values.put(TriggerContract.DESTINATION_TYPE, trigger.getDestinationType());
9072         values.put(TriggerContract.TRIGGER_TIME, trigger.getTriggerTime());
9073         values.put(TriggerContract.EVENT_TRIGGERS, trigger.getEventTriggers());
9074         values.put(TriggerContract.STATUS, Trigger.Status.PENDING);
9075         values.put(TriggerContract.ENROLLMENT_ID, trigger.getEnrollmentId());
9076         values.put(TriggerContract.REGISTRANT, trigger.getRegistrant().toString());
9077         values.put(TriggerContract.AGGREGATE_TRIGGER_DATA, trigger.getAggregateTriggerData());
9078         values.put(TriggerContract.AGGREGATE_VALUES, trigger.getAggregateValuesString());
9079         values.put(
9080                 TriggerContract.AGGREGATABLE_DEDUPLICATION_KEYS,
9081                 trigger.getAggregateDeduplicationKeys());
9082         values.put(TriggerContract.FILTERS, trigger.getFilters());
9083         values.put(TriggerContract.NOT_FILTERS, trigger.getNotFilters());
9084         values.put(TriggerContract.DEBUG_KEY, getNullableUnsignedLong(trigger.getDebugKey()));
9085         values.put(TriggerContract.DEBUG_REPORTING, trigger.isDebugReporting());
9086         values.put(TriggerContract.AD_ID_PERMISSION, trigger.hasAdIdPermission());
9087         values.put(TriggerContract.AR_DEBUG_PERMISSION, trigger.hasArDebugPermission());
9088         values.put(TriggerContract.ATTRIBUTION_CONFIG, trigger.getAttributionConfig());
9089         values.put(TriggerContract.X_NETWORK_KEY_MAPPING, trigger.getAdtechKeyMapping());
9090         values.put(TriggerContract.DEBUG_JOIN_KEY, trigger.getDebugJoinKey());
9091         values.put(TriggerContract.PLATFORM_AD_ID, trigger.getPlatformAdId());
9092         values.put(TriggerContract.DEBUG_AD_ID, trigger.getDebugAdId());
9093         values.put(TriggerContract.REGISTRATION_ORIGIN, trigger.getRegistrationOrigin().toString());
9094         values.put(
9095                 TriggerContract.AGGREGATION_COORDINATOR_ORIGIN,
9096                 getNullableUriString((List<Uri>) trigger.getAggregationCoordinatorOrigin()));
9097         values.put(
9098                 TriggerContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME_CONFIG,
9099                 trigger.getAggregatableSourceRegistrationTimeConfig().name());
9100         values.put(TriggerContract.TRIGGER_CONTEXT_ID, trigger.getTriggerContextId());
9101         values.put(TriggerContract.ATTRIBUTION_SCOPES, trigger.getAttributionScopesString());
9102         values.put(
9103                 TriggerContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES,
9104                 trigger.getAggregatableFilteringIdMaxBytes());
9105         values.put(
9106                 TriggerContract.AGGREGATE_DEBUG_REPORTING,
9107                 trigger.getAggregateDebugReportingString());
9108         values.put(TriggerContract.NAMED_BUDGETS, trigger.getNamedBudgetsString());
9109         long row = db.insert(TriggerContract.TABLE, null, values);
9110         assertThat(row).isNotEqualTo(-1);
9111     }
9112 
getNullableUriString(List<Uri> uriList)9113     private static String getNullableUriString(List<Uri> uriList) {
9114         return Optional.ofNullable(uriList).map(uris -> uris.get(0).toString()).orElse(null);
9115     }
9116 
9117     /** Test that the AsyncRegistration is inserted correctly. */
9118     @Test
testInsertAsyncRegistration()9119     public void testInsertAsyncRegistration() {
9120         AsyncRegistration validAsyncRegistration =
9121                 AsyncRegistrationFixture.getValidAsyncRegistration();
9122         String validAsyncRegistrationId = validAsyncRegistration.getId();
9123 
9124         mDatastoreManager.runInTransaction(
9125                 (dao) -> dao.insertAsyncRegistration(validAsyncRegistration));
9126 
9127         try (Cursor cursor =
9128                 MeasurementDbHelper.getInstance()
9129                         .getReadableDatabase()
9130                         .query(
9131                                 AsyncRegistrationContract.TABLE,
9132                                 null,
9133                                 AsyncRegistrationContract.ID + " = ? ",
9134                                 new String[] {validAsyncRegistrationId},
9135                                 null,
9136                                 null,
9137                                 null)) {
9138 
9139             assertTrue(cursor.moveToNext());
9140             AsyncRegistration asyncRegistration =
9141                     SqliteObjectMapper.constructAsyncRegistration(cursor);
9142             assertNotNull(asyncRegistration);
9143             assertNotNull(asyncRegistration.getId());
9144             assertEquals(asyncRegistration.getId(), validAsyncRegistration.getId());
9145             assertNotNull(asyncRegistration.getRegistrationUri());
9146             assertNotNull(asyncRegistration.getTopOrigin());
9147             assertEquals(asyncRegistration.getTopOrigin(), validAsyncRegistration.getTopOrigin());
9148             assertNotNull(asyncRegistration.getRegistrant());
9149             assertEquals(asyncRegistration.getRegistrant(), validAsyncRegistration.getRegistrant());
9150             assertNotNull(asyncRegistration.getSourceType());
9151             assertEquals(asyncRegistration.getSourceType(), validAsyncRegistration.getSourceType());
9152             assertEquals(
9153                     asyncRegistration.getDebugKeyAllowed(),
9154                     validAsyncRegistration.getDebugKeyAllowed());
9155             assertEquals(asyncRegistration.getRetryCount(), validAsyncRegistration.getRetryCount());
9156             assertEquals(
9157                     asyncRegistration.getRequestTime(), validAsyncRegistration.getRequestTime());
9158             assertNotNull(asyncRegistration.getOsDestination());
9159             assertEquals(
9160                     asyncRegistration.getOsDestination(),
9161                     validAsyncRegistration.getOsDestination());
9162             assertNotNull(asyncRegistration.getRegistrationUri());
9163             assertEquals(
9164                     asyncRegistration.getRegistrationUri(),
9165                     validAsyncRegistration.getRegistrationUri());
9166             assertEquals(
9167                     asyncRegistration.getDebugKeyAllowed(),
9168                     validAsyncRegistration.getDebugKeyAllowed());
9169             assertEquals(
9170                     asyncRegistration.getPlatformAdId(), validAsyncRegistration.getPlatformAdId());
9171             assertEquals(asyncRegistration.getPostBody(), validAsyncRegistration.getPostBody());
9172             assertEquals(
9173                     asyncRegistration.getRedirectBehavior(),
9174                     validAsyncRegistration.getRedirectBehavior());
9175         }
9176     }
9177 
9178     /** Test that records in AsyncRegistration queue are fetched properly. */
9179     @Test
testFetchNextQueuedAsyncRegistration_validRetryLimit()9180     public void testFetchNextQueuedAsyncRegistration_validRetryLimit() {
9181         AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
9182         String asyncRegistrationId = asyncRegistration.getId();
9183 
9184         mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
9185         mDatastoreManager.runInTransaction(
9186                 (dao) -> {
9187                     AsyncRegistration fetchedAsyncRegistration =
9188                             dao.fetchNextQueuedAsyncRegistration((short) 1, new HashSet<>());
9189                     assertNotNull(fetchedAsyncRegistration);
9190                     assertEquals(fetchedAsyncRegistration.getId(), asyncRegistrationId);
9191                     fetchedAsyncRegistration.incrementRetryCount();
9192                     dao.updateRetryCount(fetchedAsyncRegistration);
9193                 });
9194 
9195         mDatastoreManager.runInTransaction(
9196                 (dao) -> {
9197                     AsyncRegistration fetchedAsyncRegistration =
9198                             dao.fetchNextQueuedAsyncRegistration((short) 1, new HashSet<>());
9199                     assertNull(fetchedAsyncRegistration);
9200                 });
9201     }
9202 
9203     /** Test that records in AsyncRegistration queue are fetched properly. */
9204     @Test
testFetchNextQueuedAsyncRegistration_excludeByOrigin()9205     public void testFetchNextQueuedAsyncRegistration_excludeByOrigin() {
9206         Uri origin1 = Uri.parse("https://adtech1.test");
9207         Uri origin2 = Uri.parse("https://adtech2.test");
9208         Uri regUri1 = origin1.buildUpon().appendPath("/hello").build();
9209         Uri regUri2 = origin2;
9210         AsyncRegistration asyncRegistration1 =
9211                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
9212                         .setRegistrationUri(regUri1)
9213                         .build();
9214         AsyncRegistration asyncRegistration2 =
9215                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
9216                         .setRegistrationUri(regUri2)
9217                         .build();
9218 
9219         mDatastoreManager.runInTransaction(
9220                 (dao) -> {
9221                     dao.insertAsyncRegistration(asyncRegistration1);
9222                     dao.insertAsyncRegistration(asyncRegistration2);
9223                 });
9224         // Should fetch none
9225         Set<Uri> excludedOrigins1 = Set.of(origin1, origin2);
9226         Optional<AsyncRegistration> optAsyncRegistration =
9227                 mDatastoreManager.runInTransactionWithResult(
9228                         (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins1));
9229         assertTrue(optAsyncRegistration.isEmpty());
9230 
9231         // Should fetch only origin1
9232         Set<Uri> excludedOrigins2 = Set.of(origin2);
9233         optAsyncRegistration =
9234                 mDatastoreManager.runInTransactionWithResult(
9235                         (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins2));
9236         assertTrue(optAsyncRegistration.isPresent());
9237         assertEquals(regUri1, optAsyncRegistration.get().getRegistrationUri());
9238 
9239         // Should fetch only origin2
9240         Set<Uri> excludedOrigins3 = Set.of(origin1);
9241         optAsyncRegistration =
9242                 mDatastoreManager.runInTransactionWithResult(
9243                         (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins3));
9244         assertTrue(optAsyncRegistration.isPresent());
9245         assertEquals(regUri2, optAsyncRegistration.get().getRegistrationUri());
9246     }
9247 
9248     /** Test that AsyncRegistration is deleted correctly. */
9249     @Test
testDeleteAsyncRegistration()9250     public void testDeleteAsyncRegistration() {
9251         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9252         AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
9253         String asyncRegistrationID = asyncRegistration.getId();
9254 
9255         mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
9256         try (Cursor cursor =
9257                 MeasurementDbHelper.getInstance()
9258                         .getReadableDatabase()
9259                         .query(
9260                                 AsyncRegistrationContract.TABLE,
9261                                 null,
9262                                 AsyncRegistrationContract.ID + " = ? ",
9263                                 new String[] {asyncRegistration.getId().toString()},
9264                                 null,
9265                                 null,
9266                                 null)) {
9267             assertTrue(cursor.moveToNext());
9268             AsyncRegistration updateAsyncRegistration =
9269                     SqliteObjectMapper.constructAsyncRegistration(cursor);
9270             assertNotNull(updateAsyncRegistration);
9271         }
9272         mDatastoreManager.runInTransaction(
9273                 (dao) -> dao.deleteAsyncRegistration(asyncRegistration.getId()));
9274 
9275         db.query(
9276                 /* table */ AsyncRegistrationContract.TABLE,
9277                 /* columns */ null,
9278                 /* selection */ AsyncRegistrationContract.ID + " = ? ",
9279                 /* selectionArgs */ new String[] {asyncRegistrationID.toString()},
9280                 /* groupBy */ null,
9281                 /* having */ null,
9282                 /* orderedBy */ null);
9283 
9284         assertThat(
9285                         db.query(
9286                                         /* table */ AsyncRegistrationContract.TABLE,
9287                                         /* columns */ null,
9288                                         /* selection */ AsyncRegistrationContract.ID + " = ? ",
9289                                         /* selectionArgs */ new String[] {
9290                                             asyncRegistrationID.toString()
9291                                         },
9292                                         /* groupBy */ null,
9293                                         /* having */ null,
9294                                         /* orderedBy */ null)
9295                                 .getCount())
9296                 .isEqualTo(0);
9297     }
9298 
9299     @Test
testDeleteAsyncRegistration_missingRecord()9300     public void testDeleteAsyncRegistration_missingRecord() {
9301         mDatastoreManager.runInTransaction(
9302                 (dao) ->
9303                         assertThrows(
9304                                 "Async Registration already deleted",
9305                                 DatastoreException.class,
9306                                 () -> dao.deleteAsyncRegistration("missingAsyncRegId")));
9307     }
9308 
9309     @Test
deleteAsyncRegistrations_success()9310     public void deleteAsyncRegistrations_success() {
9311         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9312         AsyncRegistration ar1 =
9313                 new AsyncRegistration.Builder()
9314                         .setId("1")
9315                         .setRegistrant(Uri.parse("android-app://installed-registrant1"))
9316                         .setTopOrigin(Uri.parse("android-app://installed-registrant1"))
9317                         .setAdIdPermission(false)
9318                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
9319                         .setRequestTime(1)
9320                         .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID)
9321                         .build();
9322 
9323         AsyncRegistration ar2 =
9324                 new AsyncRegistration.Builder()
9325                         .setId("2")
9326                         .setRegistrant(Uri.parse("android-app://installed-registrant2"))
9327                         .setTopOrigin(Uri.parse("android-app://installed-registrant2"))
9328                         .setAdIdPermission(false)
9329                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
9330                         .setRequestTime(Long.MAX_VALUE)
9331                         .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID)
9332                         .build();
9333 
9334         AsyncRegistration ar3 =
9335                 new AsyncRegistration.Builder()
9336                         .setId("3")
9337                         .setRegistrant(Uri.parse("android-app://installed-registrant3"))
9338                         .setTopOrigin(Uri.parse("android-app://installed-registrant3"))
9339                         .setAdIdPermission(false)
9340                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
9341                         .setRequestTime(Long.MAX_VALUE)
9342                         .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID)
9343                         .build();
9344 
9345         List<AsyncRegistration> asyncRegistrationList = List.of(ar1, ar2, ar3);
9346         asyncRegistrationList.forEach(
9347                 asyncRegistration -> {
9348                     ContentValues values = new ContentValues();
9349                     values.put(AsyncRegistrationContract.ID, asyncRegistration.getId());
9350                     values.put(
9351                             AsyncRegistrationContract.REQUEST_TIME,
9352                             asyncRegistration.getRequestTime());
9353                     values.put(
9354                             AsyncRegistrationContract.REGISTRANT,
9355                             asyncRegistration.getRegistrant().toString());
9356                     values.put(
9357                             AsyncRegistrationContract.TOP_ORIGIN,
9358                             asyncRegistration.getTopOrigin().toString());
9359                     values.put(
9360                             AsyncRegistrationContract.REGISTRATION_ID,
9361                             asyncRegistration.getRegistrationId());
9362                     db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values);
9363                 });
9364 
9365         mDatastoreManager.runInTransaction(
9366                 (dao) -> dao.deleteAsyncRegistrations(List.of("1", "3")));
9367 
9368         assertThat(
9369                         db.query(
9370                                         /* table */ AsyncRegistrationContract.TABLE,
9371                                         /* columns */ null,
9372                                         /* selection */ null,
9373                                         /* selectionArgs */ null,
9374                                         /* groupBy */ null,
9375                                         /* having */ null,
9376                                         /* orderedBy */ null)
9377                                 .getCount())
9378                 .isEqualTo(1);
9379 
9380         assertThat(
9381                         db.query(
9382                                         /* table */ AsyncRegistrationContract.TABLE,
9383                                         /* columns */ null,
9384                                         /* selection */ AsyncRegistrationContract.ID + " = ? ",
9385                                         /* selectionArgs */ new String[] {"2"},
9386                                         /* groupBy */ null,
9387                                         /* having */ null,
9388                                         /* orderedBy */ null)
9389                                 .getCount())
9390                 .isEqualTo(1);
9391     }
9392 
9393     /** Test that retry count in AsyncRegistration is updated correctly. */
9394     @Test
testUpdateAsyncRegistrationRetryCount()9395     public void testUpdateAsyncRegistrationRetryCount() {
9396         AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
9397         String asyncRegistrationId = asyncRegistration.getId();
9398         long originalRetryCount = asyncRegistration.getRetryCount();
9399 
9400         mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
9401         mDatastoreManager.runInTransaction(
9402                 (dao) -> {
9403                     asyncRegistration.incrementRetryCount();
9404                     dao.updateRetryCount(asyncRegistration);
9405                 });
9406 
9407         try (Cursor cursor =
9408                 MeasurementDbHelper.getInstance()
9409                         .getReadableDatabase()
9410                         .query(
9411                                 AsyncRegistrationContract.TABLE,
9412                                 null,
9413                                 AsyncRegistrationContract.ID + " = ? ",
9414                                 new String[] {asyncRegistrationId},
9415                                 null,
9416                                 null,
9417                                 null)) {
9418             assertTrue(cursor.moveToNext());
9419             AsyncRegistration updateAsyncRegistration =
9420                     SqliteObjectMapper.constructAsyncRegistration(cursor);
9421             assertNotNull(updateAsyncRegistration);
9422             assertTrue(updateAsyncRegistration.getRetryCount() == originalRetryCount + 1);
9423         }
9424     }
9425 
9426     @Test
getSource_fetchesMatchingSourceFromDb()9427     public void getSource_fetchesMatchingSourceFromDb() {
9428         // Setup - insert 2 sources with different IDs
9429         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9430         String sourceId1 = "source1";
9431         Source source1WithoutDestinations =
9432                 SourceFixture.getMinimalValidSourceBuilder()
9433                         .setId(sourceId1)
9434                         .setAppDestinations(null)
9435                         .setWebDestinations(null)
9436                         .build();
9437         Source source1WithDestinations =
9438                 SourceFixture.getMinimalValidSourceBuilder()
9439                         .setId(sourceId1)
9440                         .setAppDestinations(null)
9441                         .setWebDestinations(null)
9442                         .build();
9443         insertInDb(db, source1WithDestinations);
9444         String sourceId2 = "source2";
9445         Source source2WithoutDestinations =
9446                 SourceFixture.getMinimalValidSourceBuilder()
9447                         .setId(sourceId2)
9448                         .setAppDestinations(null)
9449                         .setWebDestinations(null)
9450                         .build();
9451         Source source2WithDestinations =
9452                 SourceFixture.getMinimalValidSourceBuilder()
9453                         .setId(sourceId2)
9454                         .setAppDestinations(null)
9455                         .setWebDestinations(null)
9456                         .build();
9457         insertInDb(db, source2WithDestinations);
9458 
9459         // Execution
9460         mDatastoreManager.runInTransaction(
9461                 (dao) -> {
9462                     assertEquals(source1WithoutDestinations, dao.getSource(sourceId1));
9463                     assertEquals(source2WithoutDestinations, dao.getSource(sourceId2));
9464                 });
9465     }
9466 
9467     @Test
getSourceRegistrant_fetchesMatchingSourceFromDb()9468     public void getSourceRegistrant_fetchesMatchingSourceFromDb() {
9469         // Setup - insert 2 sources with different IDs
9470         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9471         String sourceId1 = "source1";
9472         String registrant1 = "android-app://registrant.app1";
9473         Source source1WithDestinations =
9474                 SourceFixture.getMinimalValidSourceBuilder()
9475                         .setId(sourceId1)
9476                         .setAppDestinations(null)
9477                         .setWebDestinations(null)
9478                         .setRegistrant(Uri.parse(registrant1))
9479                         .build();
9480         insertInDb(db, source1WithDestinations);
9481 
9482         String sourceId2 = "source2";
9483         String registrant2 = "android-app://registrant.app1";
9484         Source source2WithDestinations =
9485                 SourceFixture.getMinimalValidSourceBuilder()
9486                         .setId(sourceId2)
9487                         .setAppDestinations(null)
9488                         .setWebDestinations(null)
9489                         .setRegistrant(Uri.parse(registrant2))
9490                         .build();
9491         insertInDb(db, source2WithDestinations);
9492 
9493         // Execution
9494         mDatastoreManager.runInTransaction(
9495                 (dao) -> {
9496                     assertEquals(registrant1, dao.getSourceRegistrant(sourceId1));
9497                     assertEquals(registrant2, dao.getSourceRegistrant(sourceId2));
9498                 });
9499     }
9500 
9501     @Test
getSource_nonExistingInDb_throwsException()9502     public void getSource_nonExistingInDb_throwsException() {
9503         // Setup - insert 2 sources with different IDs
9504         mocker.mockGetFlags(mMockFlags);
9505         doReturn(true)
9506                 .when(mMockFlags)
9507                 .getMeasurementEnableDatastoreManagerThrowDatastoreException();
9508         doReturn(1.0f).when(mMockFlags).getMeasurementThrowUnknownExceptionSamplingRate();
9509         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9510         String sourceId1 = "source1";
9511         Source source1WithDestinations =
9512                 SourceFixture.getMinimalValidSourceBuilder()
9513                         .setId(sourceId1)
9514                         .setAppDestinations(null)
9515                         .setWebDestinations(null)
9516                         .build();
9517         insertInDb(db, source1WithDestinations);
9518 
9519         // Execution
9520         try {
9521             mDatastoreManager.runInTransaction(
9522                     (dao) -> {
9523                         dao.getSource("random_source_id");
9524                     });
9525             fail();
9526         } catch (IllegalStateException e) {
9527             Throwable cause = e.getCause();
9528             assertEquals(DatastoreException.class, cause.getClass());
9529             assertEquals("Source retrieval failed. Id: random_source_id", cause.getMessage());
9530         }
9531     }
9532 
9533     @Test
getSource_nonExistingInDbNoSampling_swallowException()9534     public void getSource_nonExistingInDbNoSampling_swallowException() {
9535         // Setup - insert 2 sources with different IDs
9536         mocker.mockGetFlags(mMockFlags);
9537         doReturn(true)
9538                 .when(mMockFlags)
9539                 .getMeasurementEnableDatastoreManagerThrowDatastoreException();
9540         doReturn(0.0f).when(mMockFlags).getMeasurementThrowUnknownExceptionSamplingRate();
9541         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9542         String sourceId1 = "source1";
9543         Source source1WithDestinations =
9544                 SourceFixture.getMinimalValidSourceBuilder()
9545                         .setId(sourceId1)
9546                         .setAppDestinations(null)
9547                         .setWebDestinations(null)
9548                         .build();
9549         insertInDb(db, source1WithDestinations);
9550 
9551         // Execution
9552         assertFalse(
9553                 mDatastoreManager.runInTransaction(
9554                         (dao) -> {
9555                             dao.getSource("random_source_id");
9556                         }));
9557     }
9558 
9559     @Test
getSource_nonExistingInDbThrowingDisabled_swallowException()9560     public void getSource_nonExistingInDbThrowingDisabled_swallowException() {
9561         // Setup - insert 2 sources with different IDs
9562         mocker.mockGetFlags(mMockFlags);
9563         doReturn(false)
9564                 .when(mMockFlags)
9565                 .getMeasurementEnableDatastoreManagerThrowDatastoreException();
9566         doReturn(1.0f).when(mMockFlags).getMeasurementThrowUnknownExceptionSamplingRate();
9567         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9568         String sourceId1 = "source1";
9569         Source source1WithDestinations =
9570                 SourceFixture.getMinimalValidSourceBuilder()
9571                         .setId(sourceId1)
9572                         .setAppDestinations(null)
9573                         .setWebDestinations(null)
9574                         .build();
9575         insertInDb(db, source1WithDestinations);
9576 
9577         // Execution
9578         assertFalse(
9579                 mDatastoreManager.runInTransaction(
9580                         (dao) -> {
9581                             dao.getSource("random_source_id");
9582                         }));
9583     }
9584 
9585     @Test
fetchMatchingAggregateReports_returnsMatchingReports()9586     public void fetchMatchingAggregateReports_returnsMatchingReports() {
9587         // setup - create reports for 3*3 combinations of source and trigger
9588         List<Source> sources =
9589                 Arrays.asList(
9590                         SourceFixture.getMinimalValidSourceBuilder()
9591                                 .setEventId(new UnsignedLong(1L))
9592                                 .setId("source1")
9593                                 .build(),
9594                         SourceFixture.getMinimalValidSourceBuilder()
9595                                 .setEventId(new UnsignedLong(2L))
9596                                 .setId("source2")
9597                                 .build(),
9598                         SourceFixture.getMinimalValidSourceBuilder()
9599                                 .setEventId(new UnsignedLong(3L))
9600                                 .setId("source3")
9601                                 .build());
9602         List<Trigger> triggers =
9603                 Arrays.asList(
9604                         TriggerFixture.getValidTriggerBuilder().setId("trigger1").build(),
9605                         TriggerFixture.getValidTriggerBuilder().setId("trigger2").build(),
9606                         TriggerFixture.getValidTriggerBuilder().setId("trigger3").build());
9607         List<AggregateReport> reports =
9608                 ImmutableList.of(
9609                         createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(0)),
9610                         createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(1)),
9611                         createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(2)),
9612                         createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(0)),
9613                         createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(1)),
9614                         createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(2)),
9615                         createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(0)),
9616                         createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(1)),
9617                         createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(2)));
9618 
9619         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9620         sources.forEach(source -> insertSource(source, source.getId()));
9621         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
9622         reports.forEach(
9623                 report ->
9624                         mDatastoreManager.runInTransaction(
9625                                 (dao) -> dao.insertAggregateReport(report)));
9626 
9627         mDatastoreManager.runInTransaction(
9628                 (dao) -> {
9629                     // Execution
9630                     List<AggregateReport> aggregateReports =
9631                             dao.fetchMatchingAggregateReports(
9632                                     Arrays.asList(sources.get(1).getId(), "nonMatchingSource"),
9633                                     Arrays.asList(triggers.get(2).getId(), "nonMatchingTrigger"));
9634                     assertEquals(5, aggregateReports.size());
9635 
9636                     aggregateReports =
9637                             dao.fetchMatchingAggregateReports(
9638                                     Arrays.asList(sources.get(0).getId(), sources.get(1).getId()),
9639                                     Collections.emptyList());
9640                     assertEquals(6, aggregateReports.size());
9641 
9642                     aggregateReports =
9643                             dao.fetchMatchingAggregateReports(
9644                                     Collections.emptyList(),
9645                                     Arrays.asList(
9646                                             triggers.get(0).getId(), triggers.get(2).getId()));
9647                     assertEquals(6, aggregateReports.size());
9648 
9649                     aggregateReports =
9650                             dao.fetchMatchingAggregateReports(
9651                                     Arrays.asList(
9652                                             sources.get(0).getId(),
9653                                             sources.get(1).getId(),
9654                                             sources.get(2).getId()),
9655                                     Arrays.asList(
9656                                             triggers.get(0).getId(),
9657                                             triggers.get(1).getId(),
9658                                             triggers.get(2).getId()));
9659                     assertEquals(9, aggregateReports.size());
9660                 });
9661     }
9662 
9663     @Test
fetchMatchingEventReports_returnsMatchingReports()9664     public void fetchMatchingEventReports_returnsMatchingReports() throws Exception {
9665         // setup - create reports for 3*3 combinations of source and trigger
9666         List<Source> sources =
9667                 Arrays.asList(
9668                         SourceFixture.getMinimalValidSourceBuilder()
9669                                 .setEventId(new UnsignedLong(1L))
9670                                 .setId("source1")
9671                                 .build(),
9672                         SourceFixture.getMinimalValidSourceBuilder()
9673                                 .setEventId(new UnsignedLong(2L))
9674                                 .setId("source2")
9675                                 .build(),
9676                         SourceFixture.getMinimalValidSourceBuilder()
9677                                 .setEventId(new UnsignedLong(3L))
9678                                 .setId("source3")
9679                                 .build());
9680         List<Trigger> triggers =
9681                 Arrays.asList(
9682                         TriggerFixture.getValidTriggerBuilder()
9683                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
9684                                 .setId("trigger1")
9685                                 .build(),
9686                         TriggerFixture.getValidTriggerBuilder()
9687                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
9688                                 .setId("trigger2")
9689                                 .build(),
9690                         TriggerFixture.getValidTriggerBuilder()
9691                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
9692                                 .setId("trigger3")
9693                                 .build());
9694         List<EventReport> reports =
9695                 ImmutableList.of(
9696                         createEventReportForSourceAndTrigger(sources.get(0), triggers.get(0)),
9697                         createEventReportForSourceAndTrigger(sources.get(0), triggers.get(1)),
9698                         createEventReportForSourceAndTrigger(sources.get(0), triggers.get(2)),
9699                         createEventReportForSourceAndTrigger(sources.get(1), triggers.get(0)),
9700                         createEventReportForSourceAndTrigger(sources.get(1), triggers.get(1)),
9701                         createEventReportForSourceAndTrigger(sources.get(1), triggers.get(2)),
9702                         createEventReportForSourceAndTrigger(sources.get(2), triggers.get(0)),
9703                         createEventReportForSourceAndTrigger(sources.get(2), triggers.get(1)),
9704                         createEventReportForSourceAndTrigger(sources.get(2), triggers.get(2)));
9705 
9706         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9707         sources.forEach(source -> insertSource(source, source.getId()));
9708         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
9709         reports.forEach(
9710                 report ->
9711                         mDatastoreManager.runInTransaction((dao) -> dao.insertEventReport(report)));
9712 
9713         mDatastoreManager.runInTransaction(
9714                 (dao) -> {
9715                     // Execution
9716                     List<EventReport> eventReports =
9717                             dao.fetchMatchingEventReports(
9718                                     Arrays.asList(sources.get(1).getId(), "nonMatchingSource"),
9719                                     Arrays.asList(triggers.get(2).getId(), "nonMatchingTrigger"));
9720                     assertEquals(5, eventReports.size());
9721 
9722                     eventReports =
9723                             dao.fetchMatchingEventReports(
9724                                     Arrays.asList(sources.get(0).getId(), sources.get(1).getId()),
9725                                     Collections.emptyList());
9726                     assertEquals(6, eventReports.size());
9727 
9728                     eventReports =
9729                             dao.fetchMatchingEventReports(
9730                                     Collections.emptyList(),
9731                                     Arrays.asList(
9732                                             triggers.get(0).getId(), triggers.get(2).getId()));
9733                     assertEquals(6, eventReports.size());
9734 
9735                     eventReports =
9736                             dao.fetchMatchingEventReports(
9737                                     Arrays.asList(
9738                                             sources.get(0).getId(),
9739                                             sources.get(1).getId(),
9740                                             sources.get(2).getId()),
9741                                     Arrays.asList(
9742                                             triggers.get(0).getId(),
9743                                             triggers.get(1).getId(),
9744                                             triggers.get(2).getId()));
9745                     assertEquals(9, eventReports.size());
9746                 });
9747     }
9748 
9749     @Test
fetchMatchingSources_bringsMatchingSources()9750     public void fetchMatchingSources_bringsMatchingSources() {
9751         // Setup
9752         Source source1 =
9753                 SourceFixture.getMinimalValidSourceBuilder()
9754                         .setEventId(new UnsignedLong(1L))
9755                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9756                         .setEventTime(5000)
9757                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9758                         .setId("source1")
9759                         .build();
9760         Source source2 =
9761                 SourceFixture.getMinimalValidSourceBuilder()
9762                         .setEventId(new UnsignedLong(2L))
9763                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9764                         .setEventTime(10000)
9765                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9766                         .setId("source2")
9767                         .build();
9768         Source source3 =
9769                 SourceFixture.getMinimalValidSourceBuilder()
9770                         .setEventId(new UnsignedLong(3L))
9771                         .setPublisher(WebUtil.validUri("https://subdomain2.site1.test"))
9772                         .setEventTime(15000)
9773                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9774                         .setId("source3")
9775                         .build();
9776         Source source4 =
9777                 SourceFixture.getMinimalValidSourceBuilder()
9778                         .setEventId(new UnsignedLong(4L))
9779                         .setPublisher(WebUtil.validUri("https://subdomain2.site2.test"))
9780                         .setEventTime(15000)
9781                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9782                         .setId("source4")
9783                         .build();
9784         Source source5 =
9785                 SourceFixture.getMinimalValidSourceBuilder()
9786                         .setEventId(new UnsignedLong(5L))
9787                         .setPublisher(WebUtil.validUri("https://subdomain2.site1.test"))
9788                         .setEventTime(20000)
9789                         .setRegistrant(Uri.parse("android-app://com.registrant2"))
9790                         .setId("source5")
9791                         .build();
9792         List<Source> sources = List.of(source1, source2, source3, source4, source5);
9793 
9794         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9795         sources.forEach(source -> insertInDb(db, source));
9796 
9797         // Execution
9798         mDatastoreManager.runInTransaction(
9799                 dao -> {
9800                     // --- DELETE behaviour ---
9801                     // Delete Nothing
9802                     // No matches
9803                     List<String> actualSources =
9804                             dao.fetchMatchingSources(
9805                                     Uri.parse("android-app://com.registrant1"),
9806                                     Instant.ofEpochMilli(0),
9807                                     Instant.ofEpochMilli(50000),
9808                                     List.of(),
9809                                     List.of(),
9810                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9811                     assertEquals(0, actualSources.size());
9812 
9813                     // 1 & 2 match registrant1 and "https://subdomain1.site1.test" publisher
9814                     // origin
9815                     actualSources =
9816                             dao.fetchMatchingSources(
9817                                     Uri.parse("android-app://com.registrant1"),
9818                                     Instant.ofEpochMilli(0),
9819                                     Instant.ofEpochMilli(50000),
9820                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9821                                     List.of(),
9822                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9823                     assertEquals(2, actualSources.size());
9824 
9825                     // Only 2 matches registrant1 and "https://subdomain1.site1.test"
9826                     // publisher origin within
9827                     // the range
9828                     actualSources =
9829                             dao.fetchMatchingSources(
9830                                     Uri.parse("android-app://com.registrant1"),
9831                                     Instant.ofEpochMilli(8000),
9832                                     Instant.ofEpochMilli(50000),
9833                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9834                                     List.of(),
9835                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9836                     assertEquals(1, actualSources.size());
9837 
9838                     // 1,2 & 3 matches registrant1 and "https://site1.test" publisher origin
9839                     actualSources =
9840                             dao.fetchMatchingSources(
9841                                     Uri.parse("android-app://com.registrant1"),
9842                                     Instant.ofEpochMilli(0),
9843                                     Instant.ofEpochMilli(50000),
9844                                     List.of(),
9845                                     List.of(WebUtil.validUri("https://site1.test")),
9846                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9847                     assertEquals(3, actualSources.size());
9848 
9849                     // 3 matches origin and 4 matches domain URI
9850                     actualSources =
9851                             dao.fetchMatchingSources(
9852                                     Uri.parse("android-app://com.registrant1"),
9853                                     Instant.ofEpochMilli(10000),
9854                                     Instant.ofEpochMilli(20000),
9855                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
9856                                     List.of(WebUtil.validUri("https://site2.test")),
9857                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9858                     assertEquals(2, actualSources.size());
9859 
9860                     // --- PRESERVE (anti-match exception registrant) behaviour ---
9861                     // Preserve Nothing
9862                     // 1,2,3 & 4 are match registrant1
9863                     actualSources =
9864                             dao.fetchMatchingSources(
9865                                     Uri.parse("android-app://com.registrant1"),
9866                                     Instant.ofEpochMilli(0),
9867                                     Instant.ofEpochMilli(50000),
9868                                     List.of(),
9869                                     List.of(),
9870                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9871                     assertEquals(4, actualSources.size());
9872 
9873                     // 3 & 4 match registrant1 and don't match
9874                     // "https://subdomain1.site1.test" publisher origin
9875                     actualSources =
9876                             dao.fetchMatchingSources(
9877                                     Uri.parse("android-app://com.registrant1"),
9878                                     Instant.ofEpochMilli(0),
9879                                     Instant.ofEpochMilli(50000),
9880                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9881                                     List.of(),
9882                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9883                     assertEquals(2, actualSources.size());
9884 
9885                     // 3 & 4 match registrant1, in range and don't match
9886                     // "https://subdomain1.site1.test"
9887                     actualSources =
9888                             dao.fetchMatchingSources(
9889                                     Uri.parse("android-app://com.registrant1"),
9890                                     Instant.ofEpochMilli(8000),
9891                                     Instant.ofEpochMilli(50000),
9892                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9893                                     List.of(),
9894                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9895                     assertEquals(2, actualSources.size());
9896 
9897                     // Only 4 matches registrant1, in range and don't match
9898                     // "https://site1.test"
9899                     actualSources =
9900                             dao.fetchMatchingSources(
9901                                     Uri.parse("android-app://com.registrant1"),
9902                                     Instant.ofEpochMilli(0),
9903                                     Instant.ofEpochMilli(50000),
9904                                     List.of(),
9905                                     List.of(WebUtil.validUri("https://site1.test")),
9906                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9907                     assertEquals(1, actualSources.size());
9908 
9909                     // only 2 is registrant1 based, in range and does not match either
9910                     // site2.test or subdomain2.site1.test
9911                     actualSources =
9912                             dao.fetchMatchingSources(
9913                                     Uri.parse("android-app://com.registrant1"),
9914                                     Instant.ofEpochMilli(10000),
9915                                     Instant.ofEpochMilli(20000),
9916                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
9917                                     List.of(WebUtil.validUri("https://site2.test")),
9918                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9919                     assertEquals(1, actualSources.size());
9920                 });
9921     }
9922 
9923     @Test
fetchFlexSourceIdsFor_bringsMatchingSources_expectedSourceReturned()9924     public void fetchFlexSourceIdsFor_bringsMatchingSources_expectedSourceReturned()
9925             throws Exception {
9926         // Setup
9927         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
9928         Source source1 =
9929                 SourceFixture.getMinimalValidSourceBuilder()
9930                         .setEventId(new UnsignedLong(1L))
9931                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9932                         .setEventTime(5000)
9933                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9934                         .setId("source1")
9935                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9936                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9937                         .setPrivacyParameters(
9938                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9939                         .build();
9940         source1.buildTriggerSpecs();
9941         Source source2 =
9942                 SourceFixture.getMinimalValidSourceBuilder()
9943                         .setEventId(new UnsignedLong(2L))
9944                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9945                         .setEventTime(10000)
9946                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9947                         .setId("source2")
9948                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9949                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9950                         .setPrivacyParameters(
9951                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9952                         .build();
9953         source2.buildTriggerSpecs();
9954 
9955         List<TriggerSpecs> triggerSpecsList = List.of(
9956                 source1.getTriggerSpecs(),
9957                 source2.getTriggerSpecs());
9958 
9959         for (TriggerSpecs triggerSpecs : triggerSpecsList) {
9960             insertAttributedTrigger(
9961                     triggerSpecs,
9962                     EventReportFixture.getBaseEventReportBuild()
9963                             .setTriggerId("123456")
9964                             .setTriggerData(new UnsignedLong(2L))
9965                             .setTriggerPriority(1L)
9966                             .setTriggerValue(1L)
9967                             .build());
9968             insertAttributedTrigger(
9969                     triggerSpecs,
9970                     EventReportFixture.getBaseEventReportBuild()
9971                             .setTriggerId("234567")
9972                             .setTriggerData(new UnsignedLong(2L))
9973                             .setTriggerPriority(1L)
9974                             .setTriggerValue(1L)
9975                             .build());
9976             insertAttributedTrigger(
9977                     triggerSpecs,
9978                     EventReportFixture.getBaseEventReportBuild()
9979                             .setTriggerId("345678")
9980                             .setTriggerData(new UnsignedLong(2L))
9981                             .setTriggerPriority(1L)
9982                             .setTriggerValue(1L)
9983                             .build());
9984         }
9985 
9986         Source source3 =
9987                 SourceFixture.getMinimalValidSourceBuilder()
9988                         .setEventId(new UnsignedLong(2L))
9989                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9990                         .setEventTime(10000)
9991                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9992                         .setId("source3")
9993                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9994                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9995                         .setPrivacyParameters(
9996                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9997                         .build();
9998         source3.buildTriggerSpecs();
9999 
10000         insertAttributedTrigger(
10001                 source3.getTriggerSpecs(),
10002                 EventReportFixture.getBaseEventReportBuild()
10003                         .setTriggerId("123456")
10004                         .setTriggerData(new UnsignedLong(2L))
10005                         .setTriggerPriority(1L)
10006                         .setTriggerValue(1L)
10007                         .build());
10008 
10009         List<Source> sources = List.of(source1, source2, source3);
10010 
10011         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
10012         sources.forEach(source -> insertInDb(db, source));
10013 
10014         // Execution
10015         mDatastoreManager.runInTransaction(
10016                 dao -> {
10017                     Set<String> actualSources =
10018                             dao.fetchFlexSourceIdsFor(Collections.singletonList("123456"));
10019                     Set<String> expected = Set.of("source1", "source2", "source3");
10020                     assertEquals(expected, actualSources);
10021                 });
10022 
10023         mDatastoreManager.runInTransaction(
10024                 dao -> {
10025                     Set<String> actualSources =
10026                             dao.fetchFlexSourceIdsFor(Collections.singletonList("234567"));
10027                     Set<String> expected = Set.of("source1", "source2");
10028                     assertEquals(expected, actualSources);
10029                 });
10030 
10031         mDatastoreManager.runInTransaction(
10032                 dao -> {
10033                     Set<String> actualSources =
10034                             dao.fetchFlexSourceIdsFor(Collections.singletonList("23456"));
10035                     assertEquals(Collections.emptySet(), actualSources);
10036                 });
10037 
10038         mDatastoreManager.runInTransaction(
10039                 dao -> {
10040                     Set<String> actualSources = dao.fetchFlexSourceIdsFor(new ArrayList<>());
10041                     assertEquals(Collections.emptySet(), actualSources);
10042                 });
10043     }
10044 
10045     @Test
fetchFlexSourceIdsFor_moreThanSqliteMaxCompoundSelect_expectedSourceReturned()10046     public void fetchFlexSourceIdsFor_moreThanSqliteMaxCompoundSelect_expectedSourceReturned()
10047             throws Exception {
10048         // Setup
10049         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
10050         Source source1 =
10051                 SourceFixture.getMinimalValidSourceBuilder()
10052                         .setEventId(new UnsignedLong(1L))
10053                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10054                         .setEventTime(5000)
10055                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10056                         .setId("source1")
10057                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10058                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10059                         .setPrivacyParameters(
10060                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10061                         .build();
10062         source1.buildTriggerSpecs();
10063         Source source2 =
10064                 SourceFixture.getMinimalValidSourceBuilder()
10065                         .setEventId(new UnsignedLong(2L))
10066                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10067                         .setEventTime(10000)
10068                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10069                         .setId("source2")
10070                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10071                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10072                         .setPrivacyParameters(
10073                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10074                         .build();
10075         source2.buildTriggerSpecs();
10076 
10077         List<TriggerSpecs> triggerSpecsList = List.of(
10078                 source1.getTriggerSpecs(),
10079                 source2.getTriggerSpecs());
10080 
10081         for (TriggerSpecs triggerSpecs : triggerSpecsList) {
10082             insertAttributedTrigger(
10083                     triggerSpecs,
10084                     EventReportFixture.getBaseEventReportBuild()
10085                             .setTriggerId("123456")
10086                             .setTriggerData(new UnsignedLong(2L))
10087                             .setTriggerPriority(1L)
10088                             .setTriggerValue(1L)
10089                             .build());
10090             insertAttributedTrigger(
10091                     triggerSpecs,
10092                     EventReportFixture.getBaseEventReportBuild()
10093                             .setTriggerId("234567")
10094                             .setTriggerData(new UnsignedLong(2L))
10095                             .setTriggerPriority(1L)
10096                             .setTriggerValue(1L)
10097                             .build());
10098             insertAttributedTrigger(
10099                     triggerSpecs,
10100                     EventReportFixture.getBaseEventReportBuild()
10101                             .setTriggerId("345678")
10102                             .setTriggerData(new UnsignedLong(2L))
10103                             .setTriggerPriority(1L)
10104                             .setTriggerValue(1L)
10105                             .build());
10106         }
10107 
10108         Source source3 =
10109                 SourceFixture.getMinimalValidSourceBuilder()
10110                         .setEventId(new UnsignedLong(2L))
10111                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10112                         .setEventTime(10000)
10113                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10114                         .setId("source3")
10115                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10116                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10117                         .setPrivacyParameters(
10118                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10119                         .build();
10120         source3.buildTriggerSpecs();
10121 
10122         insertAttributedTrigger(
10123                 source3.getTriggerSpecs(),
10124                 EventReportFixture.getBaseEventReportBuild()
10125                         .setTriggerId("123456")
10126                         .setTriggerData(new UnsignedLong(2L))
10127                         .setTriggerPriority(1L)
10128                         .setTriggerValue(1L)
10129                         .build());
10130 
10131         List<Source> sources = List.of(source1, source2, source3);
10132 
10133         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
10134         sources.forEach(source -> insertInDb(db, source));
10135 
10136         // Execution
10137         mDatastoreManager.runInTransaction(
10138                 dao -> {
10139                     Set<String> actualSources =
10140                             dao.fetchFlexSourceIdsFor(getRandomIdsWith(1000, "123456"));
10141                     Set<String> expected = Set.of("source1", "source2", "source3");
10142                     assertEquals(expected, actualSources);
10143                 });
10144 
10145         mDatastoreManager.runInTransaction(
10146                 dao -> {
10147                     Set<String> actualSources =
10148                             dao.fetchFlexSourceIdsFor(getRandomIdsWith(1000, "234567"));
10149                     Set<String> expected = Set.of("source1", "source2");
10150                     assertEquals(expected, actualSources);
10151                 });
10152 
10153         mDatastoreManager.runInTransaction(
10154                 dao -> {
10155                     Set<String> actualSources =
10156                             dao.fetchFlexSourceIdsFor(getRandomIdsWith(1000, "23456"));
10157                     assertEquals(Collections.emptySet(), actualSources);
10158                 });
10159 
10160         mDatastoreManager.runInTransaction(
10161                 dao -> {
10162                     Set<String> actualSources = dao.fetchFlexSourceIdsFor(new ArrayList<>());
10163                     assertEquals(Collections.emptySet(), actualSources);
10164                 });
10165     }
10166 
10167     @Test
fetchFlexSourceIdsFor_emptyInputSourceId_noSourceReturned()10168     public void fetchFlexSourceIdsFor_emptyInputSourceId_noSourceReturned() throws Exception {
10169         // Setup
10170         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
10171         Source source1 =
10172                 SourceFixture.getMinimalValidSourceBuilder()
10173                         .setEventId(new UnsignedLong(1L))
10174                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10175                         .setEventTime(5000)
10176                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10177                         .setId("source1")
10178                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10179                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10180                         .setPrivacyParameters(
10181                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10182                         .build();
10183         source1.buildTriggerSpecs();
10184 
10185         Source source2 =
10186                 SourceFixture.getMinimalValidSourceBuilder()
10187                         .setEventId(new UnsignedLong(2L))
10188                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10189                         .setEventTime(10000)
10190                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10191                         .setId("source2")
10192                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10193                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10194                         .setPrivacyParameters(
10195                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10196                         .build();
10197         source2.buildTriggerSpecs();
10198 
10199         List<TriggerSpecs> triggerSpecsList = List.of(
10200                 source1.getTriggerSpecs(),
10201                 source2.getTriggerSpecs());
10202 
10203         for (TriggerSpecs triggerSpecs : triggerSpecsList) {
10204             insertAttributedTrigger(
10205                     triggerSpecs,
10206                     EventReportFixture.getBaseEventReportBuild()
10207                             .setTriggerId("123456")
10208                             .setTriggerData(new UnsignedLong(2L))
10209                             .setTriggerPriority(1L)
10210                             .setTriggerValue(1L)
10211                             .build());
10212             insertAttributedTrigger(
10213                     triggerSpecs,
10214                     EventReportFixture.getBaseEventReportBuild()
10215                             .setTriggerId("234567")
10216                             .setTriggerData(new UnsignedLong(2L))
10217                             .setTriggerPriority(1L)
10218                             .setTriggerValue(1L)
10219                             .build());
10220             insertAttributedTrigger(
10221                     triggerSpecs,
10222                     EventReportFixture.getBaseEventReportBuild()
10223                             .setTriggerId("345678")
10224                             .setTriggerData(new UnsignedLong(2L))
10225                             .setTriggerPriority(1L)
10226                             .setTriggerValue(1L)
10227                             .build());
10228         }
10229 
10230         Source source3 =
10231                 SourceFixture.getMinimalValidSourceBuilder()
10232                         .setEventId(new UnsignedLong(2L))
10233                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10234                         .setEventTime(10000)
10235                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10236                         .setId("source3")
10237                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10238                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10239                         .setPrivacyParameters(
10240                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10241                         .build();
10242         source3.buildTriggerSpecs();
10243 
10244         insertAttributedTrigger(
10245                 source3.getTriggerSpecs(),
10246                 EventReportFixture.getBaseEventReportBuild()
10247                         .setTriggerId("123456")
10248                         .setTriggerData(new UnsignedLong(2L))
10249                         .setTriggerPriority(1L)
10250                         .setTriggerValue(1L)
10251                         .build());
10252 
10253         List<Source> sources = List.of(source1, source2, source3);
10254 
10255         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
10256         sources.forEach(source -> insertInDb(db, source));
10257 
10258         // Execution
10259         mDatastoreManager.runInTransaction(
10260                 dao -> {
10261                     Set<String> actualSources =
10262                             dao.fetchFlexSourceIdsFor(Collections.singletonList("123456"));
10263                     Set<String> expected = Set.of("source1", "source2", "source3");
10264                     assertEquals(expected, actualSources);
10265                 });
10266 
10267         mDatastoreManager.runInTransaction(
10268                 dao -> {
10269                     Set<String> actualSources =
10270                             dao.fetchFlexSourceIdsFor(Collections.singletonList("234567"));
10271                     Set<String> expected = Set.of("source1", "source2");
10272                     assertEquals(expected, actualSources);
10273                 });
10274 
10275         mDatastoreManager.runInTransaction(
10276                 dao -> {
10277                     Set<String> actualSources =
10278                             dao.fetchFlexSourceIdsFor(Collections.singletonList("23456"));
10279                     assertEquals(Collections.emptySet(), actualSources);
10280                 });
10281     }
10282 
10283     @Test
fetchFlexSourceIdsFor_doesNotMatchV1Sources()10284     public void fetchFlexSourceIdsFor_doesNotMatchV1Sources() throws Exception {
10285         // Setup
10286         EventReport eventReport1 =
10287                 EventReportFixture.getBaseEventReportBuild()
10288                         .setTriggerId("123456")
10289                         .setTriggerData(new UnsignedLong(2L))
10290                         .setTriggerPriority(1L)
10291                         .setTriggerValue(1L)
10292                         .build();
10293         EventReport eventReport2 =
10294                 EventReportFixture.getBaseEventReportBuild()
10295                         .setTriggerId("234567")
10296                         .setTriggerData(new UnsignedLong(2L))
10297                         .setTriggerPriority(1L)
10298                         .setTriggerValue(1L)
10299                         .build();
10300         EventReport eventReport3 =
10301                 EventReportFixture.getBaseEventReportBuild()
10302                         .setTriggerId("345678")
10303                         .setTriggerData(new UnsignedLong(2L))
10304                         .setTriggerPriority(1L)
10305                         .setTriggerValue(1L)
10306                         .build();
10307 
10308         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
10309 
10310         // Flex source
10311         Source source1 =
10312                 SourceFixture.getMinimalValidSourceBuilder()
10313                         .setEventId(new UnsignedLong(1L))
10314                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10315                         .setEventTime(5000)
10316                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10317                         .setId("source1")
10318                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10319                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10320                         .setPrivacyParameters(
10321                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10322                         .build();
10323         source1.buildTriggerSpecs();
10324 
10325         insertAttributedTrigger(source1.getTriggerSpecs(), eventReport1);
10326         insertAttributedTrigger(source1.getTriggerSpecs(), eventReport2);
10327         insertAttributedTrigger(source1.getTriggerSpecs(), eventReport3);
10328 
10329         // Non-flex (V1) source
10330         Source source2 =
10331                 SourceFixture.getMinimalValidSourceBuilder()
10332                         .setEventId(new UnsignedLong(2L))
10333                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10334                         .setEventTime(10000)
10335                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10336                         .setId("source2")
10337                         .build();
10338         source2.buildAttributedTriggers();
10339 
10340         insertAttributedTrigger(source2.getAttributedTriggers(), eventReport1);
10341         insertAttributedTrigger(source2.getAttributedTriggers(), eventReport2);
10342         insertAttributedTrigger(source2.getAttributedTriggers(), eventReport3);
10343 
10344         // Flex source
10345         Source source3 =
10346                 SourceFixture.getMinimalValidSourceBuilder()
10347                         .setEventId(new UnsignedLong(2L))
10348                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
10349                         .setEventTime(10000)
10350                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10351                         .setId("source3")
10352                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
10353                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
10354                         .setPrivacyParameters(
10355                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
10356                         .build();
10357         source3.buildTriggerSpecs();
10358 
10359         insertAttributedTrigger(source3.getTriggerSpecs(), eventReport1);
10360 
10361         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
10362         List.of(source1, source2, source3).forEach(source -> insertInDb(db, source));
10363 
10364         // Execution
10365         mDatastoreManager.runInTransaction(
10366                 dao -> {
10367                     Set<String> actualSources =
10368                             dao.fetchFlexSourceIdsFor(Collections.singletonList("123456"));
10369                     Set<String> expected = Set.of("source1", "source3");
10370                     assertEquals(expected, actualSources);
10371                 });
10372 
10373         mDatastoreManager.runInTransaction(
10374                 dao -> {
10375                     Set<String> actualSources =
10376                             dao.fetchFlexSourceIdsFor(
10377                                     new ArrayList<>(Collections.singletonList("234567")));
10378                     Set<String> expected = Set.of("source1");
10379                     assertEquals(expected, actualSources);
10380                 });
10381 
10382         mDatastoreManager.runInTransaction(
10383                 dao -> {
10384                     Set<String> actualSources =
10385                             dao.fetchFlexSourceIdsFor(Collections.singletonList("23456"));
10386                     assertEquals(Collections.emptySet(), actualSources);
10387                 });
10388     }
10389 
10390     @Test
fetchMatchingTriggers_bringsMatchingTriggers()10391     public void fetchMatchingTriggers_bringsMatchingTriggers() {
10392         // Setup
10393         Trigger trigger1 =
10394                 TriggerFixture.getValidTriggerBuilder()
10395                         .setAttributionDestination(
10396                                 WebUtil.validUri("https://subdomain1.site1.test"))
10397                         .setTriggerTime(5000)
10398                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10399                         .setId("trigger1")
10400                         .build();
10401         Trigger trigger2 =
10402                 TriggerFixture.getValidTriggerBuilder()
10403                         .setAttributionDestination(
10404                                 WebUtil.validUri("https://subdomain1.site1.test"))
10405                         .setTriggerTime(10000)
10406                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10407                         .setId("trigger2")
10408                         .build();
10409         Trigger trigger3 =
10410                 TriggerFixture.getValidTriggerBuilder()
10411                         .setAttributionDestination(
10412                                 WebUtil.validUri("https://subdomain2.site1.test"))
10413                         .setTriggerTime(15000)
10414                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10415                         .setId("trigger3")
10416                         .build();
10417         Trigger trigger4 =
10418                 TriggerFixture.getValidTriggerBuilder()
10419                         .setAttributionDestination(
10420                                 WebUtil.validUri("https://subdomain2.site2.test"))
10421                         .setTriggerTime(15000)
10422                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10423                         .setId("trigger4")
10424                         .build();
10425         Trigger trigger5 =
10426                 TriggerFixture.getValidTriggerBuilder()
10427                         .setAttributionDestination(
10428                                 WebUtil.validUri("https://subdomain2.site1.test"))
10429                         .setTriggerTime(20000)
10430                         .setRegistrant(Uri.parse("android-app://com.registrant2"))
10431                         .setId("trigger5")
10432                         .build();
10433         List<Trigger> triggers = List.of(trigger1, trigger2, trigger3, trigger4, trigger5);
10434 
10435         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
10436         triggers.forEach(
10437                 trigger -> {
10438                     ContentValues values = new ContentValues();
10439                     values.put(TriggerContract.ID, trigger.getId());
10440                     values.put(
10441                             TriggerContract.ATTRIBUTION_DESTINATION,
10442                             trigger.getAttributionDestination().toString());
10443                     values.put(TriggerContract.TRIGGER_TIME, trigger.getTriggerTime());
10444                     values.put(TriggerContract.ENROLLMENT_ID, trigger.getEnrollmentId());
10445                     values.put(TriggerContract.REGISTRANT, trigger.getRegistrant().toString());
10446                     values.put(TriggerContract.STATUS, trigger.getStatus());
10447                     db.insert(TriggerContract.TABLE, /* nullColumnHack */ null, values);
10448                 });
10449 
10450         // Execution
10451         mDatastoreManager.runInTransaction(
10452                 dao -> {
10453                     // --- DELETE behaviour ---
10454                     // Delete Nothing
10455                     // No Matches
10456                     Set<String> actualTriggers =
10457                             dao.fetchMatchingTriggers(
10458                                     Uri.parse("android-app://com.registrant1"),
10459                                     Instant.ofEpochMilli(0),
10460                                     Instant.ofEpochMilli(50000),
10461                                     List.of(),
10462                                     List.of(),
10463                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10464                     assertEquals(0, actualTriggers.size());
10465 
10466                     // 1 & 2 match registrant1 and "https://subdomain1.site1.test"
10467                     // destination origin
10468                     actualTriggers =
10469                             dao.fetchMatchingTriggers(
10470                                     Uri.parse("android-app://com.registrant1"),
10471                                     Instant.ofEpochMilli(0),
10472                                     Instant.ofEpochMilli(50000),
10473                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10474                                     List.of(),
10475                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10476                     assertEquals(2, actualTriggers.size());
10477 
10478                     // Only 2 matches registrant1 and "https://subdomain1.site1.test"
10479                     // destination origin within the range
10480                     actualTriggers =
10481                             dao.fetchMatchingTriggers(
10482                                     Uri.parse("android-app://com.registrant1"),
10483                                     Instant.ofEpochMilli(8000),
10484                                     Instant.ofEpochMilli(50000),
10485                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10486                                     List.of(),
10487                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10488                     assertEquals(1, actualTriggers.size());
10489 
10490                     // 1,2 & 3 matches registrant1 and "https://site1.test" destination
10491                     // origin
10492                     actualTriggers =
10493                             dao.fetchMatchingTriggers(
10494                                     Uri.parse("android-app://com.registrant1"),
10495                                     Instant.ofEpochMilli(0),
10496                                     Instant.ofEpochMilli(50000),
10497                                     List.of(),
10498                                     List.of(WebUtil.validUri("https://site1.test")),
10499                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10500                     assertEquals(3, actualTriggers.size());
10501 
10502                     // 3 matches origin and 4 matches domain URI
10503                     actualTriggers =
10504                             dao.fetchMatchingTriggers(
10505                                     Uri.parse("android-app://com.registrant1"),
10506                                     Instant.ofEpochMilli(10000),
10507                                     Instant.ofEpochMilli(20000),
10508                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
10509                                     List.of(WebUtil.validUri("https://site2.test")),
10510                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10511                     assertEquals(2, actualTriggers.size());
10512 
10513                     // --- PRESERVE (anti-match exception registrant) behaviour ---
10514                     // Preserve Nothing
10515                     // 1,2,3 & 4 are match registrant1
10516                     actualTriggers =
10517                             dao.fetchMatchingTriggers(
10518                                     Uri.parse("android-app://com.registrant1"),
10519                                     Instant.ofEpochMilli(0),
10520                                     Instant.ofEpochMilli(50000),
10521                                     List.of(),
10522                                     List.of(),
10523                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10524                     assertEquals(4, actualTriggers.size());
10525 
10526                     // 3 & 4 match registrant1 and don't match
10527                     // "https://subdomain1.site1.test" destination origin
10528                     actualTriggers =
10529                             dao.fetchMatchingTriggers(
10530                                     Uri.parse("android-app://com.registrant1"),
10531                                     Instant.ofEpochMilli(0),
10532                                     Instant.ofEpochMilli(50000),
10533                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10534                                     List.of(),
10535                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10536                     assertEquals(2, actualTriggers.size());
10537 
10538                     // 3 & 4 match registrant1, in range and don't match
10539                     // "https://subdomain1.site1.test"
10540                     actualTriggers =
10541                             dao.fetchMatchingTriggers(
10542                                     Uri.parse("android-app://com.registrant1"),
10543                                     Instant.ofEpochMilli(8000),
10544                                     Instant.ofEpochMilli(50000),
10545                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10546                                     List.of(),
10547                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10548                     assertEquals(2, actualTriggers.size());
10549 
10550                     // Only 4 matches registrant1, in range and don't match
10551                     // "https://site1.test"
10552                     actualTriggers =
10553                             dao.fetchMatchingTriggers(
10554                                     Uri.parse("android-app://com.registrant1"),
10555                                     Instant.ofEpochMilli(0),
10556                                     Instant.ofEpochMilli(50000),
10557                                     List.of(),
10558                                     List.of(WebUtil.validUri("https://site1.test")),
10559                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10560                     assertEquals(1, actualTriggers.size());
10561 
10562                     // only 2 is registrant1 based, in range and does not match either
10563                     // site2.test or subdomain2.site1.test
10564                     actualTriggers =
10565                             dao.fetchMatchingTriggers(
10566                                     Uri.parse("android-app://com.registrant1"),
10567                                     Instant.ofEpochMilli(10000),
10568                                     Instant.ofEpochMilli(20000),
10569                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
10570                                     List.of(WebUtil.validUri("https://site2.test")),
10571                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10572                     assertEquals(1, actualTriggers.size());
10573                 });
10574     }
10575 
10576     @Test
fetchMatchingAsyncRegistrations_bringsMatchingAsyncRegistrations()10577     public void fetchMatchingAsyncRegistrations_bringsMatchingAsyncRegistrations() {
10578         // Setup
10579         AsyncRegistration asyncRegistration1 =
10580                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10581                         .setTopOrigin(
10582                                 WebUtil.validUri("https://subdomain1.site1.test"))
10583                         .setRequestTime(5000)
10584                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10585                         .setId("asyncRegistration1")
10586                         .build();
10587         AsyncRegistration asyncRegistration2 =
10588                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10589                         .setTopOrigin(
10590                                 WebUtil.validUri("https://subdomain1.site1.test"))
10591                         .setRequestTime(10000)
10592                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10593                         .setId("asyncRegistration2")
10594                         .build();
10595         AsyncRegistration asyncRegistration3 =
10596                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10597                         .setTopOrigin(
10598                                 WebUtil.validUri("https://subdomain2.site1.test"))
10599                         .setRequestTime(15000)
10600                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10601                         .setId("asyncRegistration3")
10602                         .build();
10603         AsyncRegistration asyncRegistration4 =
10604                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10605                         .setTopOrigin(
10606                                 WebUtil.validUri("https://subdomain2.site2.test"))
10607                         .setRequestTime(15000)
10608                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10609                         .setId("asyncRegistration4")
10610                         .build();
10611         AsyncRegistration asyncRegistration5 =
10612                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10613                         .setTopOrigin(
10614                                 WebUtil.validUri("https://subdomain2.site1.test"))
10615                         .setRequestTime(20000)
10616                         .setRegistrant(Uri.parse("android-app://com.registrant2"))
10617                         .setId("asyncRegistration5")
10618                         .build();
10619         List<AsyncRegistration> asyncRegistrations = List.of(
10620                 asyncRegistration1, asyncRegistration2, asyncRegistration3, asyncRegistration4,
10621                 asyncRegistration5);
10622 
10623         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
10624         asyncRegistrations.forEach(
10625                 asyncRegistration -> {
10626                     ContentValues values = new ContentValues();
10627                     values.put(AsyncRegistrationContract.ID, asyncRegistration.getId());
10628                     values.put(
10629                             AsyncRegistrationContract.TOP_ORIGIN,
10630                             asyncRegistration.getTopOrigin().toString());
10631                     values.put(AsyncRegistrationContract.REQUEST_TIME,
10632                             asyncRegistration.getRequestTime());
10633                     values.put(AsyncRegistrationContract.REGISTRANT,
10634                             asyncRegistration.getRegistrant().toString());
10635                     values.put(AsyncRegistrationContract.REGISTRATION_ID,
10636                             UUID.randomUUID().toString());
10637                     db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values);
10638                 });
10639 
10640         // Execution
10641         mDatastoreManager.runInTransaction(
10642                 dao -> {
10643                     // --- DELETE behaviour ---
10644                     // Delete Nothing
10645                     // No Matches
10646                     List<String> actualAsyncRegistrations =
10647                             dao.fetchMatchingAsyncRegistrations(
10648                                     Uri.parse("android-app://com.registrant1"),
10649                                     Instant.ofEpochMilli(0),
10650                                     Instant.ofEpochMilli(50000),
10651                                     List.of(),
10652                                     List.of(),
10653                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10654                     assertEquals(0, actualAsyncRegistrations.size());
10655 
10656                     // 1 & 2 match registrant1 and "https://subdomain1.site1.test" top-
10657                     // origin origin
10658                     actualAsyncRegistrations =
10659                             dao.fetchMatchingAsyncRegistrations(
10660                                     Uri.parse("android-app://com.registrant1"),
10661                                     Instant.ofEpochMilli(0),
10662                                     Instant.ofEpochMilli(50000),
10663                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10664                                     List.of(),
10665                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10666                     assertEquals(2, actualAsyncRegistrations.size());
10667 
10668                     // Only 2 matches registrant1 and "https://subdomain1.site1.test"
10669                     // top-origin origin within the range
10670                     actualAsyncRegistrations =
10671                             dao.fetchMatchingAsyncRegistrations(
10672                                     Uri.parse("android-app://com.registrant1"),
10673                                     Instant.ofEpochMilli(8000),
10674                                     Instant.ofEpochMilli(50000),
10675                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10676                                     List.of(),
10677                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10678                     assertEquals(1, actualAsyncRegistrations.size());
10679 
10680                     // 1,2 & 3 matches registrant1 and "https://site1.test" top-origin
10681                     // origin
10682                     actualAsyncRegistrations =
10683                             dao.fetchMatchingAsyncRegistrations(
10684                                     Uri.parse("android-app://com.registrant1"),
10685                                     Instant.ofEpochMilli(0),
10686                                     Instant.ofEpochMilli(50000),
10687                                     List.of(),
10688                                     List.of(WebUtil.validUri("https://site1.test")),
10689                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10690                     assertEquals(3, actualAsyncRegistrations.size());
10691 
10692                     // 3 matches origin and 4 matches domain URI
10693                     actualAsyncRegistrations =
10694                             dao.fetchMatchingAsyncRegistrations(
10695                                     Uri.parse("android-app://com.registrant1"),
10696                                     Instant.ofEpochMilli(10000),
10697                                     Instant.ofEpochMilli(20000),
10698                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
10699                                     List.of(WebUtil.validUri("https://site2.test")),
10700                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10701                     assertEquals(2, actualAsyncRegistrations.size());
10702 
10703                     // --- PRESERVE (anti-match exception registrant) behaviour ---
10704                     // Preserve Nothing
10705                     // 1,2,3 & 4 are match registrant1
10706                     actualAsyncRegistrations =
10707                             dao.fetchMatchingAsyncRegistrations(
10708                                     Uri.parse("android-app://com.registrant1"),
10709                                     Instant.ofEpochMilli(0),
10710                                     Instant.ofEpochMilli(50000),
10711                                     List.of(),
10712                                     List.of(),
10713                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10714                     assertEquals(4, actualAsyncRegistrations.size());
10715 
10716                     // 3 & 4 match registrant1 and don't match
10717                     // "https://subdomain1.site1.test" top-origin origin
10718                     actualAsyncRegistrations =
10719                             dao.fetchMatchingAsyncRegistrations(
10720                                     Uri.parse("android-app://com.registrant1"),
10721                                     Instant.ofEpochMilli(0),
10722                                     Instant.ofEpochMilli(50000),
10723                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10724                                     List.of(),
10725                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10726                     assertEquals(2, actualAsyncRegistrations.size());
10727 
10728                     // 3 & 4 match registrant1, in range and don't match
10729                     // "https://subdomain1.site1.test"
10730                     actualAsyncRegistrations =
10731                             dao.fetchMatchingAsyncRegistrations(
10732                                     Uri.parse("android-app://com.registrant1"),
10733                                     Instant.ofEpochMilli(8000),
10734                                     Instant.ofEpochMilli(50000),
10735                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10736                                     List.of(),
10737                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10738                     assertEquals(2, actualAsyncRegistrations.size());
10739 
10740                     // Only 4 matches registrant1, in range and don't match
10741                     // "https://site1.test"
10742                     actualAsyncRegistrations =
10743                             dao.fetchMatchingAsyncRegistrations(
10744                                     Uri.parse("android-app://com.registrant1"),
10745                                     Instant.ofEpochMilli(0),
10746                                     Instant.ofEpochMilli(50000),
10747                                     List.of(),
10748                                     List.of(WebUtil.validUri("https://site1.test")),
10749                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10750                     assertEquals(1, actualAsyncRegistrations.size());
10751 
10752                     // only 2 is registrant1 based, in range and does not match either
10753                     // site2.test or subdomain2.site1.test
10754                     actualAsyncRegistrations =
10755                             dao.fetchMatchingAsyncRegistrations(
10756                                     Uri.parse("android-app://com.registrant1"),
10757                                     Instant.ofEpochMilli(10000),
10758                                     Instant.ofEpochMilli(20000),
10759                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
10760                                     List.of(WebUtil.validUri("https://site2.test")),
10761                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10762                     assertEquals(1, actualAsyncRegistrations.size());
10763                 });
10764     }
10765 
10766     @Test
testUpdateSourceAggregateReportDedupKeys_updatesKeysInList()10767     public void testUpdateSourceAggregateReportDedupKeys_updatesKeysInList() {
10768         Source validSource = SourceFixture.getValidSource();
10769         assertTrue(validSource.getAggregateReportDedupKeys().equals(new ArrayList<UnsignedLong>()));
10770         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
10771 
10772         String sourceId = getFirstSourceIdFromDatastore();
10773         Source source =
10774                 mDatastoreManager
10775                         .runInTransactionWithResult(
10776                                 measurementDao -> measurementDao.getSource(sourceId))
10777                         .get();
10778 
10779         source.getAggregateReportDedupKeys().add(new UnsignedLong(10L));
10780         mDatastoreManager.runInTransaction(
10781                 (dao) -> dao.updateSourceAggregateReportDedupKeys(source));
10782 
10783         Source sourceAfterUpdate =
10784                 mDatastoreManager
10785                         .runInTransactionWithResult(
10786                                 measurementDao -> measurementDao.getSource(sourceId))
10787                         .get();
10788 
10789         assertTrue(sourceAfterUpdate.getAggregateReportDedupKeys().size() == 1);
10790         assertTrue(sourceAfterUpdate.getAggregateReportDedupKeys().get(0).getValue() == 10L);
10791     }
10792 
10793     @Test
testUpdateSourceAggregateReportDedupKeys_acceptsUnsignedLongValue()10794     public void testUpdateSourceAggregateReportDedupKeys_acceptsUnsignedLongValue() {
10795         Source validSource = SourceFixture.getValidSource();
10796         assertTrue(validSource.getAggregateReportDedupKeys().equals(new ArrayList<UnsignedLong>()));
10797         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
10798 
10799         String sourceId = getFirstSourceIdFromDatastore();
10800         Source source =
10801                 mDatastoreManager
10802                         .runInTransactionWithResult(
10803                                 measurementDao -> measurementDao.getSource(sourceId))
10804                         .get();
10805 
10806         UnsignedLong dedupKeyValue = new UnsignedLong("17293822569102704640");
10807         source.getAggregateReportDedupKeys().add(dedupKeyValue);
10808         mDatastoreManager.runInTransaction(
10809                 (dao) -> dao.updateSourceAggregateReportDedupKeys(source));
10810 
10811         Source sourceAfterUpdate =
10812                 mDatastoreManager
10813                         .runInTransactionWithResult(
10814                                 measurementDao -> measurementDao.getSource(sourceId))
10815                         .get();
10816 
10817         assertEquals(1, sourceAfterUpdate.getAggregateReportDedupKeys().size());
10818         assertEquals(dedupKeyValue, sourceAfterUpdate.getAggregateReportDedupKeys().get(0));
10819     }
10820 
10821     @Test
testUpdateSourceAggregatableNamedBudgetAndContribution_updatesBudgets()10822     public void testUpdateSourceAggregatableNamedBudgetAndContribution_updatesBudgets() {
10823         mocker.mockGetFlags(mMockFlags);
10824         when(mMockFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(true);
10825         when(mMockFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
10826 
10827         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
10828         aggregatableNamedBudgets.createContributionBudget("budget1", 400);
10829         aggregatableNamedBudgets.setContribution("budget1", 150);
10830         Source validSource =
10831                 insertSourceForAggregatableNamedBudgets(
10832                         aggregatableNamedBudgets,
10833                         SOURCE_EVENT_TIME,
10834                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
10835                         List.of(APP_ONE_DESTINATION));
10836         validSource.getAggregatableNamedBudgets().setContribution("budget1", 340);
10837         AggregatableNamedBudgets.BudgetAndContribution validSourceBudgetAndContribution =
10838                 new AggregatableNamedBudgets.BudgetAndContribution(400);
10839         validSourceBudgetAndContribution.setAggregateContribution(340);
10840         mDatastoreManager.runInTransaction(
10841                 (dao) ->
10842                         dao.updateSourceAggregatableNamedBudgetAndContribution(
10843                                 validSource.getId(), "budget1", validSourceBudgetAndContribution));
10844 
10845         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution =
10846                 mDatastoreManager
10847                         .runInTransactionWithResult(
10848                                 measurementDao ->
10849                                         measurementDao
10850                                                 .getSourceAggregatableNamedBudgetAndContribution(
10851                                                         validSource.getId(), "budget1"))
10852                         .get();
10853         AggregatableNamedBudgets.BudgetAndContribution expectedBudgetAndContribution =
10854                 new AggregatableNamedBudgets.BudgetAndContribution(400);
10855         expectedBudgetAndContribution.setAggregateContribution(340);
10856         assertThat(daoBudgetAndContribution).isEqualTo(expectedBudgetAndContribution);
10857     }
10858 
10859     @Test
10860     public void
testUpdateSourceAggregatableNamedBudgetAndContribution_budgetDoesNotExistCreatesRow()10861             testUpdateSourceAggregatableNamedBudgetAndContribution_budgetDoesNotExistCreatesRow() {
10862         mocker.mockGetFlags(mMockFlags);
10863         when(mMockFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(true);
10864         when(mMockFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
10865 
10866         Source validSource = SourceFixture.getValidSource();
10867         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
10868         assertThat(validSource.getAggregatableNamedBudgets()).isNull();
10869 
10870         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
10871         Source finalSource =
10872                 Source.Builder.from(validSource)
10873                         .setAggregatableNamedBudgets(aggregatableNamedBudgets)
10874                         .build();
10875         AggregatableNamedBudgets.BudgetAndContribution sourceBudgetAndContribution =
10876                 new AggregatableNamedBudgets.BudgetAndContribution(100);
10877         sourceBudgetAndContribution.setAggregateContribution(70);
10878         mDatastoreManager.runInTransaction(
10879                 (dao) ->
10880                         dao.updateSourceAggregatableNamedBudgetAndContribution(
10881                                 finalSource.getId(), "budget1", sourceBudgetAndContribution));
10882 
10883         Source sourceAfterUpdate =
10884                 mDatastoreManager
10885                         .runInTransactionWithResult(
10886                                 measurementDao -> measurementDao.getSource(validSource.getId()))
10887                         .get();
10888 
10889         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution =
10890                 mDatastoreManager
10891                         .runInTransactionWithResult(
10892                                 measurementDao ->
10893                                         measurementDao
10894                                                 .getSourceAggregatableNamedBudgetAndContribution(
10895                                                         sourceAfterUpdate.getId(), "budget1"))
10896                         .get();
10897         assertThat(daoBudgetAndContribution).isEqualTo(sourceBudgetAndContribution);
10898     }
10899 
10900     @Test
testPersistAndRetrieveSource_handlesPreExistingNegativeValues()10901     public void testPersistAndRetrieveSource_handlesPreExistingNegativeValues() {
10902         // Setup
10903         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
10904         Source validSource = SourceFixture.getValidSource();
10905         ContentValues values = new ContentValues();
10906         values.put(SourceContract.ID, validSource.getId());
10907         values.put(SourceContract.EVENT_ID, validSource.getEventId().toString());
10908         values.put(SourceContract.ENROLLMENT_ID, validSource.getEnrollmentId());
10909         values.put(SourceContract.REGISTRANT, validSource.getRegistrant().toString());
10910         values.put(SourceContract.PUBLISHER, validSource.getPublisher().toString());
10911         values.put(
10912                 SourceContract.REGISTRATION_ORIGIN, validSource.getRegistrationOrigin().toString());
10913         // Existing invalid values
10914         Long val1 = -123L;
10915         Long val2 = Long.MIN_VALUE;
10916         values.put(
10917                 SourceContract.AGGREGATE_REPORT_DEDUP_KEYS,
10918                 val1.toString() + "," + val2.toString());
10919         db.insert(SourceContract.TABLE, /* nullColumnHack */ null, values);
10920         maybeInsertSourceDestinations(db, validSource, validSource.getId());
10921 
10922         // Execution
10923         Optional<Source> source =
10924                 mDatastoreManager.runInTransactionWithResult(
10925                         measurementDao -> measurementDao.getSource(validSource.getId()));
10926         assertTrue(source.isPresent());
10927         List<UnsignedLong> aggReportDedupKeys = source.get().getAggregateReportDedupKeys();
10928         assertEquals(2, aggReportDedupKeys.size());
10929         assertEquals(val1, (Long) aggReportDedupKeys.get(0).getValue());
10930         assertEquals(val2, (Long) aggReportDedupKeys.get(1).getValue());
10931     }
10932 
10933     @Test
fetchTriggerMatchingSourcesForXna_filtersSourcesCorrectly()10934     public void fetchTriggerMatchingSourcesForXna_filtersSourcesCorrectly() {
10935         // Setup
10936         Uri matchingDestination = APP_ONE_DESTINATION;
10937         Uri nonMatchingDestination = APP_TWO_DESTINATION;
10938         String mmpMatchingEnrollmentId = "mmp1";
10939         String mmpNonMatchingEnrollmentId = "mmpx";
10940         String san1MatchingEnrollmentId = "san1EnrollmentId";
10941         String san2MatchingEnrollmentId = "san2EnrollmentId";
10942         String san3MatchingEnrollmentId = "san3EnrollmentId";
10943         String san4NonMatchingEnrollmentId = "san4EnrollmentId";
10944         String san5MatchingEnrollmentId = "san5EnrollmentId";
10945 
10946         Trigger trigger =
10947                 TriggerFixture.getValidTriggerBuilder()
10948                         .setAttributionDestination(matchingDestination)
10949                         .setEnrollmentId(mmpMatchingEnrollmentId)
10950                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
10951                         .setTriggerTime(TriggerFixture.ValidTriggerParams.TRIGGER_TIME)
10952                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
10953                         .setAggregateTriggerData(
10954                                 TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA)
10955                         .setAggregateValuesString(
10956                                 TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES_STRING)
10957                         .setFilters(TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING)
10958                         .setNotFilters(
10959                                 TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING)
10960                         .build();
10961         Source s1MmpMatchingWithDestinations =
10962                 createSourceBuilder()
10963                         .setId("s1")
10964                         .setEnrollmentId(mmpMatchingEnrollmentId)
10965                         .setAppDestinations(List.of(matchingDestination))
10966                         .build();
10967         Source s1MmpMatchingWithoutDestinations =
10968                 createSourceBuilder()
10969                         .setId("s1")
10970                         .setEnrollmentId(mmpMatchingEnrollmentId)
10971                         .setAppDestinations(null)
10972                         .setWebDestinations(null)
10973                         .setRegistrationId(s1MmpMatchingWithDestinations.getRegistrationId())
10974                         .build();
10975         Source s2MmpDiffDestination =
10976                 createSourceBuilder()
10977                         .setId("s2")
10978                         .setEnrollmentId(mmpMatchingEnrollmentId)
10979                         .setAppDestinations(List.of(nonMatchingDestination))
10980                         .build();
10981         Source s3MmpExpired =
10982                 createSourceBuilder()
10983                         .setId("s3")
10984                         .setEnrollmentId(mmpMatchingEnrollmentId)
10985                         .setAppDestinations(List.of(nonMatchingDestination))
10986                         // expired before trigger time
10987                         .setExpiryTime(trigger.getTriggerTime() - DAYS.toMillis(1))
10988                         .build();
10989         Source s4NonMatchingMmp =
10990                 createSourceBuilder()
10991                         .setId("s4")
10992                         .setEnrollmentId(mmpNonMatchingEnrollmentId)
10993                         .setAppDestinations(List.of(matchingDestination))
10994                         .build();
10995         Source s5MmpMatchingWithDestinations =
10996                 createSourceBuilder()
10997                         .setId("s5")
10998                         .setEnrollmentId(mmpMatchingEnrollmentId)
10999                         .setAppDestinations(List.of(matchingDestination))
11000                         .build();
11001         Source s5MmpMatchingWithoutDestinations =
11002                 createSourceBuilder()
11003                         .setId("s5")
11004                         .setEnrollmentId(mmpMatchingEnrollmentId)
11005                         .setAppDestinations(null)
11006                         .setWebDestinations(null)
11007                         .setRegistrationId(s5MmpMatchingWithDestinations.getRegistrationId())
11008                         .build();
11009         Source s6San1MatchingWithDestinations =
11010                 createSourceBuilder()
11011                         .setId("s6")
11012                         .setEnrollmentId(san1MatchingEnrollmentId)
11013                         .setAppDestinations(List.of(matchingDestination))
11014                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11015                         .build();
11016         Source s6San1MatchingWithoutDestinations =
11017                 createSourceBuilder()
11018                         .setId("s6")
11019                         .setEnrollmentId(san1MatchingEnrollmentId)
11020                         .setAppDestinations(null)
11021                         .setWebDestinations(null)
11022                         .setRegistrationId(s6San1MatchingWithDestinations.getRegistrationId())
11023                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11024                         .build();
11025         Source s7San1DiffDestination =
11026                 createSourceBuilder()
11027                         .setId("s7")
11028                         .setEnrollmentId(san1MatchingEnrollmentId)
11029                         .setAppDestinations(List.of(nonMatchingDestination))
11030                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11031                         .build();
11032         Source s8San2MatchingWithDestinations =
11033                 createSourceBuilder()
11034                         .setId("s8")
11035                         .setEnrollmentId(san2MatchingEnrollmentId)
11036                         .setAppDestinations(List.of(matchingDestination))
11037                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11038                         .build();
11039         Source s8San2MatchingWithoutDestinations =
11040                 createSourceBuilder()
11041                         .setId("s8")
11042                         .setEnrollmentId(san2MatchingEnrollmentId)
11043                         .setAppDestinations(null)
11044                         .setWebDestinations(null)
11045                         .setRegistrationId(s8San2MatchingWithDestinations.getRegistrationId())
11046                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11047                         .build();
11048         Source s9San3XnaIgnored =
11049                 createSourceBuilder()
11050                         .setId("s9")
11051                         .setEnrollmentId(san3MatchingEnrollmentId)
11052                         .setAppDestinations(List.of(matchingDestination))
11053                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11054                         .build();
11055         Source s10San3MatchingWithDestinations =
11056                 createSourceBuilder()
11057                         .setId("s10")
11058                         .setEnrollmentId(san3MatchingEnrollmentId)
11059                         .setAppDestinations(List.of(matchingDestination))
11060                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11061                         .build();
11062         Source s10San3MatchingWithoutDestinations =
11063                 createSourceBuilder()
11064                         .setId("s10")
11065                         .setEnrollmentId(san3MatchingEnrollmentId)
11066                         .setAppDestinations(null)
11067                         .setWebDestinations(null)
11068                         .setRegistrationId(s10San3MatchingWithDestinations.getRegistrationId())
11069                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11070                         .build();
11071         Source s11San4EnrollmentNonMatching =
11072                 createSourceBuilder()
11073                         .setId("s11")
11074                         .setEnrollmentId(san4NonMatchingEnrollmentId)
11075                         .setAppDestinations(List.of(matchingDestination))
11076                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
11077                         .build();
11078         Source s12San1NullSharedAggregationKeys =
11079                 createSourceBuilder()
11080                         .setId("s12")
11081                         .setEnrollmentId(san1MatchingEnrollmentId)
11082                         .setAppDestinations(List.of(matchingDestination))
11083                         .setSharedAggregationKeys(null)
11084                         .build();
11085         Source s13San1Expired =
11086                 createSourceBuilder()
11087                         .setId("s13")
11088                         .setEnrollmentId(san1MatchingEnrollmentId)
11089                         .setAppDestinations(List.of(matchingDestination))
11090                         // expired before trigger time
11091                         .setExpiryTime(trigger.getTriggerTime() - DAYS.toMillis(1))
11092                         .build();
11093         String registrationIdForTriggerAndOtherRegistration = UUID.randomUUID().toString();
11094         Source s14San5RegIdClasesWithMmp =
11095                 createSourceBuilder()
11096                         .setId("s14")
11097                         .setEnrollmentId(san5MatchingEnrollmentId)
11098                         .setAppDestinations(List.of(matchingDestination))
11099                         .setRegistrationId(registrationIdForTriggerAndOtherRegistration)
11100                         .build();
11101         Source s15MmpMatchingWithDestinations =
11102                 createSourceBuilder()
11103                         .setId("s15")
11104                         .setEnrollmentId(mmpMatchingEnrollmentId)
11105                         .setAppDestinations(List.of(matchingDestination))
11106                         .setRegistrationId(registrationIdForTriggerAndOtherRegistration)
11107                         .build();
11108         Source s15MmpMatchingWithoutDestinations =
11109                 createSourceBuilder()
11110                         .setId("s15")
11111                         .setEnrollmentId(mmpMatchingEnrollmentId)
11112                         .setAppDestinations(null)
11113                         .setWebDestinations(null)
11114                         .setRegistrationId(registrationIdForTriggerAndOtherRegistration)
11115                         .build();
11116         List<Source> sources =
11117                 Arrays.asList(
11118                         s1MmpMatchingWithDestinations,
11119                         s2MmpDiffDestination,
11120                         s3MmpExpired,
11121                         s4NonMatchingMmp,
11122                         s5MmpMatchingWithDestinations,
11123                         s6San1MatchingWithDestinations,
11124                         s7San1DiffDestination,
11125                         s8San2MatchingWithDestinations,
11126                         s9San3XnaIgnored,
11127                         s10San3MatchingWithDestinations,
11128                         s11San4EnrollmentNonMatching,
11129                         s12San1NullSharedAggregationKeys,
11130                         s13San1Expired,
11131                         s14San5RegIdClasesWithMmp,
11132                         s15MmpMatchingWithDestinations);
11133         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11134         Objects.requireNonNull(db);
11135         // Insert all sources to the DB
11136         sources.forEach(source -> insertSource(source, source.getId()));
11137 
11138         // Insert XNA ignored sources
11139         ContentValues values = new ContentValues();
11140         values.put(XnaIgnoredSourcesContract.SOURCE_ID, s9San3XnaIgnored.getId());
11141         values.put(XnaIgnoredSourcesContract.ENROLLMENT_ID, mmpMatchingEnrollmentId);
11142         long row = db.insert(XnaIgnoredSourcesContract.TABLE, null, values);
11143         assertEquals(1, row);
11144 
11145         List<Source> expectedMatchingSources =
11146                 Arrays.asList(
11147                         s1MmpMatchingWithoutDestinations,
11148                         s5MmpMatchingWithoutDestinations,
11149                         s6San1MatchingWithoutDestinations,
11150                         s8San2MatchingWithoutDestinations,
11151                         s10San3MatchingWithoutDestinations,
11152                         s15MmpMatchingWithoutDestinations);
11153         Comparator<Source> sortingComparator = Comparator.comparing(Source::getId);
11154         expectedMatchingSources.sort(sortingComparator);
11155 
11156         // Execution
11157         mDatastoreManager.runInTransaction(
11158                 dao -> {
11159                     List<String> matchingSanEnrollmentIds =
11160                             Arrays.asList(
11161                                     san1MatchingEnrollmentId,
11162                                     san2MatchingEnrollmentId,
11163                                     san3MatchingEnrollmentId,
11164                                     san5MatchingEnrollmentId);
11165                     List<Source> actualMatchingSources =
11166                             dao.fetchTriggerMatchingSourcesForXna(
11167                                     trigger, matchingSanEnrollmentIds);
11168                     actualMatchingSources.sort(sortingComparator);
11169                     // Assertion
11170                     assertEquals(expectedMatchingSources, actualMatchingSources);
11171                 });
11172     }
11173 
11174     @Test
insertIgnoredSourceForEnrollment_success()11175     public void insertIgnoredSourceForEnrollment_success() {
11176         // Setup
11177         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11178         // Need to insert sources before, to honor the foreign key constraint
11179         insertSource(createSourceBuilder().setId("s1").build(), "s1");
11180         insertSource(createSourceBuilder().setId("s2").build(), "s2");
11181 
11182         Pair<String, String> entry11 = new Pair<>("s1", "e1");
11183         Pair<String, String> entry21 = new Pair<>("s2", "e1");
11184         Pair<String, String> entry22 = new Pair<>("s2", "e2");
11185 
11186         mDatastoreManager.runInTransaction(
11187                 dao -> {
11188                     // Execution
11189                     dao.insertIgnoredSourceForEnrollment(entry11.first, entry11.second);
11190                     dao.insertIgnoredSourceForEnrollment(entry21.first, entry21.second);
11191                     dao.insertIgnoredSourceForEnrollment(entry22.first, entry22.second);
11192 
11193                     // Assertion
11194                     queryAndAssertSourceEntries(db, "e1", Arrays.asList("s1", "s2"));
11195                     queryAndAssertSourceEntries(db, "e2", Collections.singletonList("s2"));
11196                 });
11197     }
11198 
11199     @Test
countDistinctDebugAdIdsUsedByEnrollment_oneTriggerAndSource()11200     public void countDistinctDebugAdIdsUsedByEnrollment_oneTriggerAndSource() {
11201         // Setup
11202         Source.Builder sourceBuilder =
11203                 new Source.Builder()
11204                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
11205                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
11206                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
11207                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
11208                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
11209         Source s1 =
11210                 sourceBuilder
11211                         .setId("s1")
11212                         .setPublisherType(EventSurfaceType.WEB)
11213                         .setDebugAdId("debug-ad-id-1")
11214                         .setEnrollmentId("enrollment-id-1")
11215                         .build();
11216         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
11217 
11218         Trigger.Builder triggerBuilder =
11219                 new Trigger.Builder()
11220                         .setAttributionDestination(
11221                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
11222                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
11223                         .setRegistrationOrigin(
11224                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
11225                         .setAggregatableSourceRegistrationTimeConfig(
11226                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
11227         Trigger t1 =
11228                 triggerBuilder
11229                         .setId("t1")
11230                         .setDestinationType(EventSurfaceType.WEB)
11231                         .setDebugAdId("debug-ad-id-1")
11232                         .setEnrollmentId("enrollment-id-1")
11233                         .build();
11234         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
11235 
11236         // Assertion
11237         assertTrue(
11238                 mDatastoreManager.runInTransaction(
11239                         dao ->
11240                                 assertEquals(
11241                                         1,
11242                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
11243                                                 "enrollment-id-1"))));
11244     }
11245 
11246     @Test
countDistinctDebugAdIdsUsedByEnrollmentWithinWindow()11247     public void countDistinctDebugAdIdsUsedByEnrollmentWithinWindow() {
11248         // Setup
11249         long startTime = System.currentTimeMillis();
11250         long endTime = System.currentTimeMillis() + DAYS.toMillis(7);
11251         Source.Builder webSourceBuilder =
11252                 SourceFixture.getValidSourceBuilder().setPublisherType(EventSurfaceType.WEB);
11253         Trigger.Builder webTriggerBuilder =
11254                 TriggerFixture.getValidTriggerBuilder().setDestinationType(EventSurfaceType.WEB);
11255 
11256         // Not counted as debug ad id is null
11257         insertSource(webSourceBuilder.setDebugAdId(null).setEventTime(startTime).build(), "s1");
11258         insertTrigger(webTriggerBuilder.setDebugAdId(null).setTriggerTime(startTime).build(), "t1");
11259 
11260         // Not counted as they are outside the window
11261         insertSource(
11262                 webSourceBuilder.setDebugAdId("debug_ad_id_s2").setEventTime(startTime - 1).build(),
11263                 "s2");
11264         insertTrigger(
11265                 webTriggerBuilder
11266                         .setDebugAdId("debug_ad_id_t2")
11267                         .setTriggerTime(startTime - 1)
11268                         .build(),
11269                 "t2");
11270 
11271         // count = 2
11272         insertSource(
11273                 webSourceBuilder.setDebugAdId("debug_ad_id_s3").setEventTime(startTime).build(),
11274                 "s3");
11275         insertTrigger(
11276                 webTriggerBuilder.setDebugAdId("debug_ad_id_t3").setTriggerTime(startTime).build(),
11277                 "t3");
11278 
11279         // count = 4
11280         insertSource(
11281                 webSourceBuilder
11282                         .setDebugAdId("debug_ad_id_s4")
11283                         .setEventTime(startTime + DAYS.toMillis(2))
11284                         .build(),
11285                 "s4");
11286         insertTrigger(
11287                 webTriggerBuilder
11288                         .setDebugAdId("debug_ad_id_t4")
11289                         .setTriggerTime(startTime + DAYS.toMillis(2))
11290                         .build(),
11291                 "t4");
11292 
11293         // count = 5; they share a common debug_ad_id value
11294         insertSource(
11295                 webSourceBuilder.setDebugAdId("debug_ad_id_s5").setEventTime(endTime - 1).build(),
11296                 "s5");
11297         insertTrigger(
11298                 webTriggerBuilder
11299                         .setDebugAdId("debug_ad_id_s5")
11300                         .setTriggerTime(endTime - 1)
11301                         .build(),
11302                 "t5");
11303 
11304         // Not counted as they fall outside the window
11305         insertSource(
11306                 webSourceBuilder.setDebugAdId("debug_ad_id_s6").setEventTime(endTime).build(),
11307                 "s6");
11308         insertTrigger(
11309                 webTriggerBuilder.setDebugAdId("debug_ad_id_s6").setTriggerTime(endTime).build(),
11310                 "t6");
11311 
11312         // Not counted as they belong to a different enrollment
11313         insertSource(
11314                 webSourceBuilder
11315                         .setDebugAdId("debug_ad_id_s7")
11316                         .setEventTime(startTime + DAYS.toMillis(2))
11317                         .setEnrollmentId("otherEnrollment")
11318                         .build(),
11319                 "s7");
11320         insertTrigger(
11321                 webTriggerBuilder
11322                         .setDebugAdId("debug_ad_id_s7")
11323                         .setTriggerTime(startTime + DAYS.toMillis(2))
11324                         .setEnrollmentId("otherEnrollment")
11325                         .build(),
11326                 "t7");
11327 
11328         // Not counted as they have the excluded debug AdId
11329         insertSource(
11330                 webSourceBuilder
11331                         .setDebugAdId("debug_ad_id_s8")
11332                         .setEventTime(startTime + DAYS.toMillis(2))
11333                         .build(),
11334                 "s8");
11335         insertTrigger(
11336                 webTriggerBuilder
11337                         .setDebugAdId("debug_ad_id_s8")
11338                         .setTriggerTime(startTime + DAYS.toMillis(2))
11339                         .build(),
11340                 "t8");
11341 
11342         // Assertion
11343         assertTrue(
11344                 mDatastoreManager.runInTransaction(
11345                         dao ->
11346                                 assertEquals(
11347                                         5,
11348                                         dao.countDistinctDebugAdIdsUsedByEnrollmentInWindow(
11349                                                 SourceFixture.ValidSourceParams.ENROLLMENT_ID,
11350                                                 startTime,
11351                                                 endTime,
11352                                                 "debug_ad_id_s8"))));
11353     }
11354 
11355     @Test
countDistinctDebugAdIdsUsedByEnrollment_nullValuesPresent()11356     public void countDistinctDebugAdIdsUsedByEnrollment_nullValuesPresent() {
11357         // Setup
11358         Source.Builder sourceBuilder =
11359                 new Source.Builder()
11360                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
11361                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
11362                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
11363                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
11364                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
11365         // Source with debug AdId present
11366         Source s1 =
11367                 sourceBuilder
11368                         .setId("s1")
11369                         .setPublisherType(EventSurfaceType.WEB)
11370                         .setDebugAdId("debug-ad-id-1")
11371                         .setEnrollmentId("enrollment-id-1")
11372                         .build();
11373         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
11374         // Source with no debug AdId
11375         Source s2 =
11376                 sourceBuilder
11377                         .setId("s2")
11378                         .setPublisherType(EventSurfaceType.WEB)
11379                         .setEnrollmentId("enrollment-id-1")
11380                         .build();
11381         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
11382 
11383         Trigger.Builder triggerBuilder =
11384                 new Trigger.Builder()
11385                         .setAttributionDestination(
11386                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
11387                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
11388                         .setRegistrationOrigin(
11389                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
11390                         .setAggregatableSourceRegistrationTimeConfig(
11391                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
11392         // Trigger with debug AdId present
11393         Trigger t1 =
11394                 triggerBuilder
11395                         .setId("t1")
11396                         .setDestinationType(EventSurfaceType.WEB)
11397                         .setDebugAdId("debug-ad-id-1")
11398                         .setEnrollmentId("enrollment-id-1")
11399                         .build();
11400         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
11401         // Trigger with no debug AdId
11402         Trigger t2 =
11403                 triggerBuilder
11404                         .setId("t2")
11405                         .setDestinationType(EventSurfaceType.WEB)
11406                         .setEnrollmentId("enrollment-id-1")
11407                         .build();
11408         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
11409 
11410         // Assertion
11411         assertTrue(
11412                 mDatastoreManager.runInTransaction(
11413                         dao ->
11414                                 assertEquals(
11415                                         1,
11416                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
11417                                                 "enrollment-id-1"))));
11418     }
11419 
11420     @Test
countDistinctDebugAdIdsUsedByEnrollment_multipleSourcesAndTriggers()11421     public void countDistinctDebugAdIdsUsedByEnrollment_multipleSourcesAndTriggers() {
11422         // Setup
11423         Source.Builder sourceBuilder =
11424                 new Source.Builder()
11425                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
11426                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
11427                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
11428                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
11429                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
11430         // Multiple sources with same AdId
11431         Source s1 =
11432                 sourceBuilder
11433                         .setId("s1")
11434                         .setPublisherType(EventSurfaceType.WEB)
11435                         .setDebugAdId("debug-ad-id-1")
11436                         .setEnrollmentId("enrollment-id-1")
11437                         .build();
11438         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
11439         Source s2 =
11440                 sourceBuilder
11441                         .setId("s2")
11442                         .setPublisherType(EventSurfaceType.WEB)
11443                         .setDebugAdId("debug-ad-id-1")
11444                         .setEnrollmentId("enrollment-id-1")
11445                         .build();
11446         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
11447         Source s3 =
11448                 sourceBuilder
11449                         .setId("s3")
11450                         .setPublisherType(EventSurfaceType.WEB)
11451                         .setDebugAdId("debug-ad-id-1")
11452                         .setEnrollmentId("enrollment-id-1")
11453                         .build();
11454         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3));
11455 
11456         Trigger.Builder triggerBuilder =
11457                 new Trigger.Builder()
11458                         .setAttributionDestination(
11459                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
11460                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
11461                         .setRegistrationOrigin(
11462                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
11463                         .setAggregatableSourceRegistrationTimeConfig(
11464                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
11465         // Multiple triggers with same AdId
11466         Trigger t1 =
11467                 triggerBuilder
11468                         .setId("t1")
11469                         .setDestinationType(EventSurfaceType.WEB)
11470                         .setDebugAdId("debug-ad-id-1")
11471                         .setEnrollmentId("enrollment-id-1")
11472                         .build();
11473         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
11474         Trigger t2 =
11475                 triggerBuilder
11476                         .setId("t2")
11477                         .setDestinationType(EventSurfaceType.WEB)
11478                         .setDebugAdId("debug-ad-id-1")
11479                         .setEnrollmentId("enrollment-id-1")
11480                         .build();
11481         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
11482         Trigger t3 =
11483                 triggerBuilder
11484                         .setId("t3")
11485                         .setDestinationType(EventSurfaceType.WEB)
11486                         .setDebugAdId("debug-ad-id-1")
11487                         .setEnrollmentId("enrollment-id-1")
11488                         .build();
11489         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3));
11490 
11491         // Assertion
11492         assertTrue(
11493                 mDatastoreManager.runInTransaction(
11494                         dao ->
11495                                 assertEquals(
11496                                         1,
11497                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
11498                                                 "enrollment-id-1"))));
11499     }
11500 
11501     @Test
countDistinctDebugAdIdsUsedByEnrollment_multipleAdIdsPresent()11502     public void countDistinctDebugAdIdsUsedByEnrollment_multipleAdIdsPresent() {
11503         // Setup
11504         Source.Builder sourceBuilder =
11505                 new Source.Builder()
11506                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
11507                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
11508                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
11509                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
11510                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
11511         // Multiple sources with different AdIds but the same enrollmentId
11512         Source s1 =
11513                 sourceBuilder
11514                         .setId("s1")
11515                         .setPublisherType(EventSurfaceType.WEB)
11516                         .setDebugAdId("debug-ad-id-1")
11517                         .setEnrollmentId("enrollment-id-1")
11518                         .build();
11519         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
11520         Source s2 =
11521                 sourceBuilder
11522                         .setId("s2")
11523                         .setPublisherType(EventSurfaceType.WEB)
11524                         .setDebugAdId("debug-ad-id-2")
11525                         .setEnrollmentId("enrollment-id-1")
11526                         .build();
11527         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
11528         Source s3 =
11529                 sourceBuilder
11530                         .setId("s3")
11531                         .setPublisherType(EventSurfaceType.WEB)
11532                         .setDebugAdId("debug-ad-id-3")
11533                         .setEnrollmentId("enrollment-id-1")
11534                         .build();
11535         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3));
11536 
11537         Trigger.Builder triggerBuilder =
11538                 new Trigger.Builder()
11539                         .setAttributionDestination(
11540                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
11541                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
11542                         .setRegistrationOrigin(
11543                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
11544                         .setAggregatableSourceRegistrationTimeConfig(
11545                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
11546         // Multiple triggers with different AdIds but the same enrollmentId
11547         Trigger t1 =
11548                 triggerBuilder
11549                         .setId("t1")
11550                         .setDestinationType(EventSurfaceType.WEB)
11551                         .setDebugAdId("debug-ad-id-4")
11552                         .setEnrollmentId("enrollment-id-1")
11553                         .build();
11554         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
11555         Trigger t2 =
11556                 triggerBuilder
11557                         .setId("t2")
11558                         .setDestinationType(EventSurfaceType.WEB)
11559                         .setDebugAdId("debug-ad-id-5")
11560                         .setEnrollmentId("enrollment-id-1")
11561                         .build();
11562         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
11563         Trigger t3 =
11564                 triggerBuilder
11565                         .setId("t3")
11566                         .setDestinationType(EventSurfaceType.WEB)
11567                         .setDebugAdId("debug-ad-id-6")
11568                         .setEnrollmentId("enrollment-id-1")
11569                         .build();
11570         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3));
11571 
11572         // Assertion
11573         assertTrue(
11574                 mDatastoreManager.runInTransaction(
11575                         dao ->
11576                                 assertEquals(
11577                                         6,
11578                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
11579                                                 "enrollment-id-1"))));
11580     }
11581 
11582     @Test
countDistinctDebugAdIdsUsedByEnrollment_multipleEnrollmentIdsPresent()11583     public void countDistinctDebugAdIdsUsedByEnrollment_multipleEnrollmentIdsPresent() {
11584         // Setup
11585         Source.Builder sourceBuilder =
11586                 new Source.Builder()
11587                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
11588                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
11589                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
11590                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
11591                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
11592         // Multiple sources with different AdIds and differing enrollmentIds
11593         Source s1 =
11594                 sourceBuilder
11595                         .setId("s1")
11596                         .setPublisherType(EventSurfaceType.WEB)
11597                         .setDebugAdId("debug-ad-id-1")
11598                         .setEnrollmentId("enrollment-id-1")
11599                         .build();
11600         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
11601         Source s2 =
11602                 sourceBuilder
11603                         .setId("s2")
11604                         .setPublisherType(EventSurfaceType.WEB)
11605                         .setDebugAdId("debug-ad-id-2")
11606                         .setEnrollmentId("enrollment-id-2")
11607                         .build();
11608         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
11609         Source s3 =
11610                 sourceBuilder
11611                         .setId("s3")
11612                         .setPublisherType(EventSurfaceType.WEB)
11613                         .setDebugAdId("debug-ad-id-3")
11614                         .setEnrollmentId("enrollment-id-2")
11615                         .build();
11616         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3));
11617 
11618         Trigger.Builder triggerBuilder =
11619                 new Trigger.Builder()
11620                         .setAttributionDestination(
11621                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
11622                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
11623                         .setRegistrationOrigin(
11624                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
11625                         .setAggregatableSourceRegistrationTimeConfig(
11626                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
11627         // Multiple triggers with different AdIds and differing enrollmentIds
11628         Trigger t1 =
11629                 triggerBuilder
11630                         .setId("t1")
11631                         .setDestinationType(EventSurfaceType.WEB)
11632                         .setDebugAdId("debug-ad-id-4")
11633                         .setEnrollmentId("enrollment-id-1")
11634                         .build();
11635         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
11636         Trigger t2 =
11637                 triggerBuilder
11638                         .setId("t2")
11639                         .setDestinationType(EventSurfaceType.WEB)
11640                         .setDebugAdId("debug-ad-id-5")
11641                         .setEnrollmentId("enrollment-id-2")
11642                         .build();
11643         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
11644         Trigger t3 =
11645                 triggerBuilder
11646                         .setId("t3")
11647                         .setDestinationType(EventSurfaceType.WEB)
11648                         .setDebugAdId("debug-ad-id-6")
11649                         .setEnrollmentId("enrollment-id-2")
11650                         .build();
11651         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3));
11652 
11653         // Assertion
11654         assertTrue(
11655                 mDatastoreManager.runInTransaction(
11656                         dao ->
11657                                 assertEquals(
11658                                         2,
11659                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
11660                                                 "enrollment-id-1"))));
11661         assertTrue(
11662                 mDatastoreManager.runInTransaction(
11663                         dao ->
11664                                 assertEquals(
11665                                         4,
11666                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
11667                                                 "enrollment-id-2"))));
11668     }
11669 
11670     @Test
getPendingAggregateReportIdsByCoordinatorInWindow()11671     public void getPendingAggregateReportIdsByCoordinatorInWindow() {
11672         AggregateReport ar11 =
11673                 AggregateReportFixture.getValidAggregateReportBuilder()
11674                         .setId("11")
11675                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11676                         .build();
11677         AggregateReport ar12 =
11678                 AggregateReportFixture.getValidAggregateReportBuilder()
11679                         .setId("12")
11680                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11681                         .build();
11682         AggregateReport ar21 =
11683                 AggregateReportFixture.getValidAggregateReportBuilder()
11684                         .setId("21")
11685                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11686                         .build();
11687         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11688         AbstractDbIntegrationTest.insertToDb(ar11, db);
11689         AbstractDbIntegrationTest.insertToDb(ar12, db);
11690         AbstractDbIntegrationTest.insertToDb(ar21, db);
11691 
11692         Optional<Map<String, List<String>>> resOpt =
11693                 mDatastoreManager.runInTransactionWithResult(
11694                         (dao) ->
11695                                 dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11696                                         AggregateReportFixture.ValidAggregateReportParams
11697                                                 .TRIGGER_TIME,
11698                                         AggregateReportFixture.ValidAggregateReportParams
11699                                                         .TRIGGER_TIME
11700                                                 + DAYS.toMillis(30)));
11701         assertTrue(resOpt.isPresent());
11702         Map<String, List<String>> res = resOpt.get();
11703         assertEquals(2, res.size());
11704 
11705         // URL 1
11706         List<String> url1Ids = res.get("https://url1.test");
11707         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11708         assertEquals(2, url1Ids.size());
11709         assertEquals("11", url1Ids.get(0));
11710         assertEquals("12", url1Ids.get(1));
11711         // URL 2
11712         List<String> url2Ids = res.get("https://url2.test");
11713         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11714         assertEquals(1, url2Ids.size());
11715         assertEquals("21", url2Ids.get(0));
11716     }
11717 
11718     @Test
getPendingAggregateReportIdsByCoordinatorInWindowWithRetryLimit()11719     public void getPendingAggregateReportIdsByCoordinatorInWindowWithRetryLimit() {
11720         // Mocking that the flags return a Max Retry of 1
11721         Flags mockFlags = Mockito.mock(Flags.class);
11722         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11723         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11724         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11725 
11726         AggregateReport ar11 =
11727                 AggregateReportFixture.getValidAggregateReportBuilder()
11728                         .setId("11")
11729                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11730                         .build();
11731         AggregateReport ar12 =
11732                 AggregateReportFixture.getValidAggregateReportBuilder()
11733                         .setId("12")
11734                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11735                         .build();
11736         AggregateReport ar21 =
11737                 AggregateReportFixture.getValidAggregateReportBuilder()
11738                         .setId("21")
11739                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11740                         .build();
11741         AggregateReport ar31 =
11742                 AggregateReportFixture.getValidAggregateReportBuilder()
11743                         .setId("31")
11744                         .setAggregationCoordinatorOrigin(Uri.parse("https://url3.test"))
11745                         .build();
11746         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11747         AbstractDbIntegrationTest.insertToDb(ar11, db);
11748         AbstractDbIntegrationTest.insertToDb(ar12, db);
11749         AbstractDbIntegrationTest.insertToDb(ar21, db);
11750         AbstractDbIntegrationTest.insertToDb(ar31, db);
11751 
11752         Optional<Map<String, List<String>>> resOpt =
11753                 mDatastoreManager.runInTransactionWithResult(
11754                         (dao) -> {
11755                             return dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11756                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME,
11757                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME
11758                                             + DAYS.toMillis(30));
11759                         });
11760         assertTrue(resOpt.isPresent());
11761         Map<String, List<String>> res = resOpt.get();
11762         assertEquals(3, res.size());
11763 
11764         // URL 1
11765         List<String> url1Ids = res.get("https://url1.test");
11766         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11767         assertEquals(2, url1Ids.size());
11768         assertEquals("11", url1Ids.get(0));
11769         assertEquals("12", url1Ids.get(1));
11770         // URL 2
11771         List<String> url2Ids = res.get("https://url2.test");
11772         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11773         assertEquals(1, url2Ids.size());
11774         assertEquals("21", url2Ids.get(0));
11775 
11776         resOpt =
11777                 mDatastoreManager.runInTransactionWithResult(
11778                         (dao) -> {
11779                             // Adds records to KeyValueData table for Retry Count.
11780                             dao.incrementAndGetReportingRetryCount(
11781                                     ar31.getId(), DataType.AGGREGATE_REPORT_RETRY_COUNT);
11782                             return dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11783                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME,
11784                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME
11785                                             + DAYS.toMillis(30));
11786                         });
11787         res = resOpt.get();
11788 
11789         assertEquals(2, res.size());
11790 
11791         // URL 1
11792         url1Ids = res.get("https://url1.test");
11793         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11794         assertEquals(2, url1Ids.size());
11795         assertEquals("11", url1Ids.get(0));
11796         assertEquals("12", url1Ids.get(1));
11797         // URL 2
11798         url2Ids = res.get("https://url2.test");
11799         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11800         assertEquals(1, url2Ids.size());
11801         assertEquals("21", url2Ids.get(0));
11802     }
11803 
11804     @Test
getPendingAggregateDebugReportIdsByCoordinator()11805     public void getPendingAggregateDebugReportIdsByCoordinator() {
11806         AggregateReport ar11 =
11807                 AggregateReportFixture.getValidAggregateReportBuilder()
11808                         .setId("11")
11809                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11810                         .build();
11811         AggregateReport ar12 =
11812                 AggregateReportFixture.getValidAggregateReportBuilder()
11813                         .setId("12")
11814                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11815                         .build();
11816         AggregateReport ar21 =
11817                 AggregateReportFixture.getValidAggregateReportBuilder()
11818                         .setId("21")
11819                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11820                         .build();
11821         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11822         AbstractDbIntegrationTest.insertToDb(ar11, db);
11823         AbstractDbIntegrationTest.insertToDb(ar12, db);
11824         AbstractDbIntegrationTest.insertToDb(ar21, db);
11825 
11826         Optional<Map<String, List<String>>> resOpt =
11827                 mDatastoreManager.runInTransactionWithResult(
11828                         (dao) ->
11829                                 dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11830                                         AggregateReportFixture.ValidAggregateReportParams
11831                                                 .TRIGGER_TIME,
11832                                         AggregateReportFixture.ValidAggregateReportParams
11833                                                         .TRIGGER_TIME
11834                                                 + DAYS.toMillis(30)));
11835         assertTrue(resOpt.isPresent());
11836         Map<String, List<String>> res = resOpt.get();
11837         assertEquals(2, res.size());
11838 
11839         // URL 1
11840         List<String> url1Ids = res.get("https://url1.test");
11841         assertEquals(2, url1Ids.size());
11842         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11843         assertEquals("11", url1Ids.get(0));
11844         assertEquals("12", url1Ids.get(1));
11845         // URL 2
11846         List<String> url2Ids = res.get("https://url2.test");
11847         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11848         assertEquals(1, url2Ids.size());
11849         assertEquals("21", url2Ids.get(0));
11850     }
11851 
11852     @Test
getPendingAggregateDebugReportIdsByCoordinatorWithRetryLimit()11853     public void getPendingAggregateDebugReportIdsByCoordinatorWithRetryLimit() {
11854         // Mocking that the flags return a Max Retry of 1
11855         Flags mockFlags = Mockito.mock(Flags.class);
11856         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11857         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11858         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11859 
11860         AggregateReport ar11 =
11861                 AggregateReportFixture.getValidAggregateReportBuilder()
11862                         .setId("11")
11863                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11864                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11865                         .build();
11866         AggregateReport ar12 =
11867                 AggregateReportFixture.getValidAggregateReportBuilder()
11868                         .setId("12")
11869                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11870                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11871                         .build();
11872         AggregateReport ar21 =
11873                 AggregateReportFixture.getValidAggregateReportBuilder()
11874                         .setId("21")
11875                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11876                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11877                         .build();
11878         AggregateReport ar31 =
11879                 AggregateReportFixture.getValidAggregateReportBuilder()
11880                         .setId("31")
11881                         .setAggregationCoordinatorOrigin(Uri.parse("https://url3.test"))
11882                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11883                         .build();
11884         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11885         AbstractDbIntegrationTest.insertToDb(ar11, db);
11886         AbstractDbIntegrationTest.insertToDb(ar12, db);
11887         AbstractDbIntegrationTest.insertToDb(ar21, db);
11888         AbstractDbIntegrationTest.insertToDb(ar31, db);
11889 
11890         Optional<Map<String, List<String>>> resOpt =
11891                 mDatastoreManager.runInTransactionWithResult(
11892                         (dao) -> dao.getPendingAggregateDebugReportIdsByCoordinator());
11893         assertTrue(resOpt.isPresent());
11894         Map<String, List<String>> res = resOpt.get();
11895         assertEquals(3, res.size());
11896 
11897         resOpt =
11898                 mDatastoreManager.runInTransactionWithResult(
11899                         (dao) -> {
11900                             // Adds records to KeyValueData table for Retry Count.
11901                             dao.incrementAndGetReportingRetryCount(
11902                                     ar31.getId(), DataType.AGGREGATE_REPORT_RETRY_COUNT);
11903                             return dao.getPendingAggregateDebugReportIdsByCoordinator();
11904                         });
11905         assertTrue(resOpt.isPresent());
11906         res = resOpt.get();
11907         assertEquals(2, res.size());
11908 
11909         // URL 1
11910         List<String> url1Ids = res.get("https://url1.test");
11911         assertEquals(2, url1Ids.size());
11912         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11913         assertEquals("11", url1Ids.get(0));
11914         assertEquals("12", url1Ids.get(1));
11915         // URL 2
11916         List<String> url2Ids = res.get("https://url2.test");
11917         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11918         assertEquals(1, url2Ids.size());
11919         assertEquals("21", url2Ids.get(0));
11920     }
11921 
11922     @Test
getPendingEventReportIdsInWindowWithRetry()11923     public void getPendingEventReportIdsInWindowWithRetry() {
11924         // Mocking that the flags return a Max Retry of 1
11925         Flags mockFlags = Mockito.mock(Flags.class);
11926         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11927         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11928         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11929 
11930         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11931 
11932         EventReport er1 =
11933                 generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1);
11934         EventReport er2 =
11935                 generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2);
11936         List.of(er1, er2)
11937                 .forEach(
11938                         eventReport -> {
11939                             ContentValues values = new ContentValues();
11940                             values.put(EventReportContract.ID, eventReport.getId());
11941                             values.put(
11942                                     EventReportContract.ATTRIBUTION_DESTINATION,
11943                                     eventReport.getAttributionDestinations().get(0).toString());
11944                             values.put(
11945                                     EventReportContract.REPORT_TIME,
11946                                     EventReportFixture.ValidEventReportParams.TRIGGER_TIME
11947                                             + DAYS.toMillis(15));
11948                             values.put(EventReportContract.STATUS, EventReport.Status.PENDING);
11949                             db.insert(EventReportContract.TABLE, null, values);
11950                         });
11951         Optional<List<String>> resOpt =
11952                 mDatastoreManager.runInTransactionWithResult(
11953                         (dao) ->
11954                                 dao.getPendingEventReportIdsInWindow(
11955                                         EventReportFixture.ValidEventReportParams.TRIGGER_TIME,
11956                                         EventReportFixture.ValidEventReportParams.TRIGGER_TIME
11957                                                 + DAYS.toMillis(30)));
11958         assertTrue(resOpt.isPresent());
11959         List<String> res = resOpt.get();
11960         assertEquals(2, res.size());
11961         assertTrue(res.containsAll(List.of("1", "2")));
11962         resOpt =
11963                 mDatastoreManager.runInTransactionWithResult(
11964                         (dao) -> {
11965                             // Adds records to KeyValueData table for Retry Count.
11966                             dao.incrementAndGetReportingRetryCount(
11967                                     "1", DataType.EVENT_REPORT_RETRY_COUNT);
11968                             return dao.getPendingEventReportIdsInWindow(
11969                                     EventReportFixture.ValidEventReportParams.TRIGGER_TIME,
11970                                     EventReportFixture.ValidEventReportParams.TRIGGER_TIME
11971                                             + DAYS.toMillis(30));
11972                         });
11973         res = resOpt.get();
11974         assertEquals(1, res.size());
11975         assertEquals(List.of("2"), res);
11976     }
11977 
11978     @Test
getPendingEventDebugReportIdsWithRetryLimit()11979     public void getPendingEventDebugReportIdsWithRetryLimit() {
11980         // Mocking that the flags return a Max Retry of 1
11981         Flags mockFlags = Mockito.mock(Flags.class);
11982         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11983         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11984         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11985 
11986         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11987 
11988         EventReport er1 =
11989                 generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1);
11990         EventReport er2 =
11991                 generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2);
11992         List.of(er1, er2)
11993                 .forEach(
11994                         eventReport -> {
11995                             ContentValues values = new ContentValues();
11996                             values.put(EventReportContract.ID, eventReport.getId());
11997                             values.put(
11998                                     EventReportContract.ATTRIBUTION_DESTINATION,
11999                                     eventReport.getAttributionDestinations().get(0).toString());
12000                             values.put(
12001                                     EventReportContract.DEBUG_REPORT_STATUS,
12002                                     EventReport.DebugReportStatus.PENDING);
12003                             db.insert(EventReportContract.TABLE, null, values);
12004                         });
12005 
12006         Optional<List<String>> resOpt =
12007                 mDatastoreManager.runInTransactionWithResult(
12008                         (dao) -> dao.getPendingDebugEventReportIds());
12009         assertTrue(resOpt.isPresent());
12010         List<String> res = resOpt.get();
12011         assertEquals(2, res.size());
12012         assertTrue(res.containsAll(List.of("1", "2")));
12013         resOpt =
12014                 mDatastoreManager.runInTransactionWithResult(
12015                         (dao) -> {
12016                             // Adds records to KeyValueData table for Retry Count.
12017                             dao.incrementAndGetReportingRetryCount(
12018                                     "1", DataType.DEBUG_EVENT_REPORT_RETRY_COUNT);
12019                             return dao.getPendingDebugEventReportIds();
12020                         });
12021         res = resOpt.get();
12022         assertEquals(1, res.size());
12023         assertEquals(List.of("2"), res);
12024     }
12025 
12026     @Test
getNonExpiredAggregateEncryptionKeys()12027     public void getNonExpiredAggregateEncryptionKeys() {
12028         AggregateEncryptionKey ek11 =
12029                 new AggregateEncryptionKey.Builder()
12030                         .setId("11")
12031                         .setKeyId("11")
12032                         .setPublicKey("11")
12033                         .setExpiry(11)
12034                         .setAggregationCoordinatorOrigin(Uri.parse("https://1coordinator.test"))
12035                         .build();
12036         // ek12 will not be fetched because expiry (5) < 10
12037         AggregateEncryptionKey ek12 =
12038                 new AggregateEncryptionKey.Builder()
12039                         .setId("12")
12040                         .setKeyId("12")
12041                         .setPublicKey("12")
12042                         .setExpiry(5)
12043                         .setAggregationCoordinatorOrigin(Uri.parse("https://1coordinator.test"))
12044                         .build();
12045 
12046         AggregateEncryptionKey ek21 =
12047                 new AggregateEncryptionKey.Builder()
12048                         .setId("21")
12049                         .setKeyId("21")
12050                         .setPublicKey("21")
12051                         .setExpiry(10)
12052                         .setAggregationCoordinatorOrigin(Uri.parse("https://2coordinator.test"))
12053                         .build();
12054         AggregateEncryptionKey ek22 =
12055                 new AggregateEncryptionKey.Builder()
12056                         .setId("22")
12057                         .setKeyId("22")
12058                         .setPublicKey("22")
12059                         .setExpiry(15)
12060                         .setAggregationCoordinatorOrigin(Uri.parse("https://2coordinator.test"))
12061                         .build();
12062 
12063         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12064         AbstractDbIntegrationTest.insertToDb(ek11, db);
12065         AbstractDbIntegrationTest.insertToDb(ek12, db);
12066         AbstractDbIntegrationTest.insertToDb(ek21, db);
12067         AbstractDbIntegrationTest.insertToDb(ek22, db);
12068 
12069         List<AggregateEncryptionKey> res1 =
12070                 mDatastoreManager
12071                         .runInTransactionWithResult(
12072                                 (dao) -> {
12073                                     return dao.getNonExpiredAggregateEncryptionKeys(
12074                                             Uri.parse("https://1coordinator.test"), 10);
12075                                 })
12076                         .orElseThrow();
12077         // ek12 will not be fetched because expiry (5) < 10
12078         assertEquals(1, res1.size());
12079         assertEquals(ek11, res1.get(0));
12080 
12081         List<AggregateEncryptionKey> res2 =
12082                 mDatastoreManager
12083                         .runInTransactionWithResult(
12084                                 (dao) -> {
12085                                     return dao.getNonExpiredAggregateEncryptionKeys(
12086                                             Uri.parse("https://2coordinator.test"), 10);
12087                                 })
12088                         .orElseThrow();
12089         res1.sort((x, y) -> String.CASE_INSENSITIVE_ORDER.compare(x.getId(), y.getId()));
12090         assertEquals(2, res2.size());
12091         assertEquals(ek21, res2.get(0));
12092         assertEquals(ek22, res2.get(1));
12093     }
12094 
12095     @Test
incrementReportRetryIncrements()12096     public void incrementReportRetryIncrements() {
12097         final String eventId = "TestIdEvent";
12098         final String aggregateId = "TestIdAggregate";
12099         final String debugId = "TestIdDebug";
12100 
12101         mDatastoreManager.runInTransaction(
12102                 (dao) -> {
12103                     dao.incrementAndGetReportingRetryCount(
12104                             eventId, DataType.EVENT_REPORT_RETRY_COUNT);
12105                     dao.incrementAndGetReportingRetryCount(
12106                             eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT);
12107                     dao.incrementAndGetReportingRetryCount(
12108                             aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT);
12109                     dao.incrementAndGetReportingRetryCount(
12110                             aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT);
12111                     dao.incrementAndGetReportingRetryCount(
12112                             debugId, DataType.DEBUG_REPORT_RETRY_COUNT);
12113                 });
12114         Optional<KeyValueData> eventCount =
12115                 mDatastoreManager.runInTransactionWithResult(
12116                         (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT));
12117         Optional<KeyValueData> debugEventCount =
12118                 mDatastoreManager.runInTransactionWithResult(
12119                         (dao) ->
12120                                 dao.getKeyValueData(
12121                                         eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT));
12122         Optional<KeyValueData> aggregateCount =
12123                 mDatastoreManager.runInTransactionWithResult(
12124                         (dao) ->
12125                                 dao.getKeyValueData(
12126                                         aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT));
12127         Optional<KeyValueData> debugAggregateCount =
12128                 mDatastoreManager.runInTransactionWithResult(
12129                         (dao) ->
12130                                 dao.getKeyValueData(
12131                                         aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT));
12132         Optional<KeyValueData> debugCount =
12133                 mDatastoreManager.runInTransactionWithResult(
12134                         (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT));
12135 
12136         assertTrue(eventCount.isPresent());
12137         assertEquals(1, (eventCount.get().getReportRetryCount()));
12138         assertTrue(debugEventCount.isPresent());
12139         assertEquals(1, (debugEventCount.get().getReportRetryCount()));
12140         assertTrue(aggregateCount.isPresent());
12141         assertEquals(1, (aggregateCount.get().getReportRetryCount()));
12142         assertTrue(debugAggregateCount.isPresent());
12143         assertEquals(1, (debugAggregateCount.get().getReportRetryCount()));
12144         assertTrue(debugCount.isPresent());
12145         assertEquals(1, (debugCount.get().getReportRetryCount()));
12146 
12147         mDatastoreManager.runInTransaction(
12148                 (dao) -> {
12149                     dao.incrementAndGetReportingRetryCount(
12150                             eventId, DataType.EVENT_REPORT_RETRY_COUNT);
12151                     dao.incrementAndGetReportingRetryCount(
12152                             aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT);
12153                 });
12154         eventCount =
12155                 mDatastoreManager.runInTransactionWithResult(
12156                         (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT));
12157         aggregateCount =
12158                 mDatastoreManager.runInTransactionWithResult(
12159                         (dao) ->
12160                                 dao.getKeyValueData(
12161                                         aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT));
12162         debugEventCount =
12163                 mDatastoreManager.runInTransactionWithResult(
12164                         (dao) ->
12165                                 dao.getKeyValueData(
12166                                         eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT));
12167         debugAggregateCount =
12168                 mDatastoreManager.runInTransactionWithResult(
12169                         (dao) ->
12170                                 dao.getKeyValueData(
12171                                         aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT));
12172         debugCount =
12173                 mDatastoreManager.runInTransactionWithResult(
12174                         (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT));
12175 
12176         assertTrue(eventCount.isPresent());
12177         assertEquals(2, (eventCount.get().getReportRetryCount()));
12178         assertTrue(debugEventCount.isPresent());
12179         assertEquals(1, (debugEventCount.get().getReportRetryCount()));
12180         assertTrue(aggregateCount.isPresent());
12181         assertEquals(2, (aggregateCount.get().getReportRetryCount()));
12182         assertTrue(debugAggregateCount.isPresent());
12183         assertEquals(1, (debugAggregateCount.get().getReportRetryCount()));
12184         assertTrue(debugCount.isPresent());
12185         assertEquals(1, (debugCount.get().getReportRetryCount()));
12186 
12187         mDatastoreManager.runInTransaction(
12188                 (dao) -> {
12189                     dao.incrementAndGetReportingRetryCount(
12190                             eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT);
12191                     dao.incrementAndGetReportingRetryCount(
12192                             aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT);
12193                     dao.incrementAndGetReportingRetryCount(
12194                             debugId, DataType.DEBUG_REPORT_RETRY_COUNT);
12195                 });
12196         eventCount =
12197                 mDatastoreManager.runInTransactionWithResult(
12198                         (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT));
12199         aggregateCount =
12200                 mDatastoreManager.runInTransactionWithResult(
12201                         (dao) ->
12202                                 dao.getKeyValueData(
12203                                         aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT));
12204         debugEventCount =
12205                 mDatastoreManager.runInTransactionWithResult(
12206                         (dao) ->
12207                                 dao.getKeyValueData(
12208                                         eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT));
12209         debugAggregateCount =
12210                 mDatastoreManager.runInTransactionWithResult(
12211                         (dao) ->
12212                                 dao.getKeyValueData(
12213                                         aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT));
12214         debugCount =
12215                 mDatastoreManager.runInTransactionWithResult(
12216                         (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT));
12217 
12218         assertTrue(eventCount.isPresent());
12219         assertEquals(2, (eventCount.get().getReportRetryCount()));
12220         assertTrue(debugEventCount.isPresent());
12221         assertEquals(2, (debugEventCount.get().getReportRetryCount()));
12222         assertTrue(aggregateCount.isPresent());
12223         assertEquals(2, (aggregateCount.get().getReportRetryCount()));
12224         assertTrue(debugAggregateCount.isPresent());
12225         assertEquals(2, (debugAggregateCount.get().getReportRetryCount()));
12226         assertTrue(debugCount.isPresent());
12227         assertEquals(2, (debugCount.get().getReportRetryCount()));
12228     }
12229 
12230     @Test
countNavigationSourcesPerReportingOriginQuery()12231     public void countNavigationSourcesPerReportingOriginQuery() {
12232         final String registrationId1 = "registrationId1";
12233         final String registrationId2 = "registrationId2";
12234         Source source1 =
12235                 SourceFixture.getValidSourceBuilder()
12236                         .setRegistrationId(registrationId1)
12237                         .setSourceType(Source.SourceType.NAVIGATION)
12238                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
12239                         .build();
12240         Source source2 =
12241                 SourceFixture.getValidSourceBuilder()
12242                         .setRegistrationId(registrationId1)
12243                         .setSourceType(Source.SourceType.EVENT)
12244                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
12245                         .build();
12246         Source source3 =
12247                 SourceFixture.getValidSourceBuilder()
12248                         .setRegistrationId(registrationId1)
12249                         .setSourceType(Source.SourceType.NAVIGATION)
12250                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
12251                         .build();
12252         Source source4 =
12253                 SourceFixture.getValidSourceBuilder()
12254                         .setRegistrationId(registrationId2)
12255                         .setSourceType(Source.SourceType.EVENT)
12256                         .setRegistrationOrigin(REGISTRATION_ORIGIN_2)
12257                         .build();
12258         Source source5 =
12259                 SourceFixture.getValidSourceBuilder()
12260                         .setRegistrationId(registrationId2)
12261                         .setSourceType(Source.SourceType.NAVIGATION)
12262                         .setRegistrationOrigin(REGISTRATION_ORIGIN_2)
12263                         .build();
12264         Arrays.asList(source1, source2, source3, source4, source5).stream()
12265                 .forEach(source -> insertSource(source));
12266         assertThat(
12267                         mDatastoreManager.runInTransactionWithResult(
12268                                 (dao) ->
12269                                         dao.countNavigationSourcesPerReportingOrigin(
12270                                                 REGISTRATION_ORIGIN, registrationId1)))
12271                 .isEqualTo(Optional.of(2L));
12272         assertThat(
12273                         mDatastoreManager.runInTransactionWithResult(
12274                                 (dao) ->
12275                                         dao.countNavigationSourcesPerReportingOrigin(
12276                                                 REGISTRATION_ORIGIN_2, registrationId2)))
12277                 .isEqualTo(Optional.of(1L));
12278         assertThat(
12279                         mDatastoreManager.runInTransactionWithResult(
12280                                 (dao) ->
12281                                         dao.countNavigationSourcesPerReportingOrigin(
12282                                                 REGISTRATION_ORIGIN, registrationId2)))
12283                 .isEqualTo(Optional.of(0L));
12284     }
12285 
verifySourceStatus(@onNull Source source, @Source.Status int status)12286     private void verifySourceStatus(@NonNull Source source, @Source.Status int status) {
12287         assertThat(
12288                         mDatastoreManager
12289                                 .runInTransactionWithResult(
12290                                         measurementDao -> measurementDao.getSource(source.getId()))
12291                                 .get()
12292                                 .getStatus())
12293                 .isEqualTo(status);
12294     }
12295 
12296     @Test
12297     public void
testUpdateSourcesForAttributionScope_diffMaxViewStates_ignoresSourcesDeletesReports()12298             testUpdateSourcesForAttributionScope_diffMaxViewStates_ignoresSourcesDeletesReports() {
12299         mLegacyFlags = mMockFlags;
12300         mocker.mockGetFlags(mMockFlags);
12301         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
12302         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
12303 
12304         Source source1 =
12305                 insertSourceForAttributionScope(
12306                         List.of("1"),
12307                         ATTRIBUTION_SCOPE_LIMIT,
12308                         MAX_EVENT_STATES,
12309                         SOURCE_EVENT_TIME,
12310                         List.of(WEB_ONE_DESTINATION),
12311                         null);
12312         EventReport pastFakeEventReport =
12313                 new EventReport.Builder()
12314                         .setId("1")
12315                         .setSourceId(source1.getId())
12316                         .setSourceEventId(source1.getEventId())
12317                         .setReportTime(SOURCE_EVENT_TIME)
12318                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12319                         .setTriggerTime(SOURCE_EVENT_TIME)
12320                         .setSourceType(source1.getSourceType())
12321                         .setStatus(EventReport.Status.PENDING)
12322                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12323                         .build();
12324         EventReport fakeEventReport1 =
12325                 new EventReport.Builder()
12326                         .setId("2")
12327                         .setSourceId(source1.getId())
12328                         .setSourceEventId(source1.getEventId())
12329                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12330                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12331                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12332                         .setSourceType(source1.getSourceType())
12333                         .setStatus(EventReport.Status.PENDING)
12334                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12335                         .build();
12336         // Deleted fake event report for comparison.
12337         EventReport deletedFakeEventReport1 =
12338                 new EventReport.Builder()
12339                         .setId("3")
12340                         .setSourceId(source1.getId())
12341                         .setSourceEventId(source1.getEventId())
12342                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12343                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12344                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12345                         .setSourceType(source1.getSourceType())
12346                         .setStatus(EventReport.Status.MARKED_TO_DELETE)
12347                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12348                         .build();
12349         mDatastoreManager.runInTransaction(
12350                 (dao) -> {
12351                     dao.insertEventReport(pastFakeEventReport);
12352                     dao.insertEventReport(fakeEventReport1);
12353                     dao.updateSourcesForAttributionScope(source1);
12354                 });
12355         Arrays.asList(source1).stream()
12356                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12357         assertThat(
12358                         mDatastoreManager
12359                                 .runInTransactionWithResult(
12360                                         measurementDao ->
12361                                                 measurementDao.getSourceEventReports(source1))
12362                                 .get())
12363                 .containsExactly(pastFakeEventReport, fakeEventReport1);
12364 
12365         Source source2 =
12366                 insertSourceForAttributionScope(
12367                         List.of("2"),
12368                         ATTRIBUTION_SCOPE_LIMIT,
12369                         MAX_EVENT_STATES + 1,
12370                         SOURCE_EVENT_TIME + 1,
12371                         List.of(WEB_ONE_DESTINATION),
12372                         null);
12373         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12374         Arrays.asList(source1).stream()
12375                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12376         Arrays.asList(source2).stream()
12377                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12378         assertThat(
12379                         mDatastoreManager
12380                                 .runInTransactionWithResult(
12381                                         measurementDao ->
12382                                                 measurementDao.getSourceEventReports(source1))
12383                                 .get())
12384                 .containsExactly(pastFakeEventReport, deletedFakeEventReport1);
12385 
12386         Source source3 =
12387                 insertSourceForAttributionScope(
12388                         List.of("3"),
12389                         ATTRIBUTION_SCOPE_LIMIT,
12390                         MAX_EVENT_STATES,
12391                         SOURCE_EVENT_TIME + 2,
12392                         List.of(WEB_TWO_DESTINATION),
12393                         null);
12394         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12395         Arrays.asList(source1).stream()
12396                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12397         Arrays.asList(source2, source3).stream()
12398                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12399 
12400         Source source4 =
12401                 insertSourceForAttributionScope(
12402                         List.of("5"),
12403                         ATTRIBUTION_SCOPE_LIMIT,
12404                         MAX_EVENT_STATES,
12405                         SOURCE_EVENT_TIME + 3,
12406                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12407                         null);
12408         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
12409         Arrays.asList(source1, source2).stream()
12410                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12411         Arrays.asList(source3, source4).stream()
12412                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12413 
12414         Source source5 =
12415                 insertSourceForAttributionScope(
12416                         List.of("4"),
12417                         ATTRIBUTION_SCOPE_LIMIT,
12418                         MAX_EVENT_STATES + 1,
12419                         SOURCE_EVENT_TIME + 4,
12420                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12421                         null);
12422         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source5));
12423         Arrays.asList(source1, source2, source3, source4).stream()
12424                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12425         Arrays.asList(source5).stream()
12426                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12427 
12428         // Sources for different reporting origin with different max event states.
12429         Source source6 =
12430                 insertSourceForAttributionScope(
12431                         List.of("4"),
12432                         ATTRIBUTION_SCOPE_LIMIT,
12433                         MAX_EVENT_STATES,
12434                         SOURCE_EVENT_TIME + 5,
12435                         List.of(WEB_ONE_DESTINATION),
12436                         null,
12437                         REGISTRATION_ORIGIN_2);
12438         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source6));
12439         Arrays.asList(source1, source2, source3, source4).stream()
12440                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12441         Arrays.asList(source5, source6).stream()
12442                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12443 
12444         // Sources for different reporting origin with the same max event states.
12445         Source source7 =
12446                 insertSourceForAttributionScope(
12447                         List.of("4"),
12448                         ATTRIBUTION_SCOPE_LIMIT + 1,
12449                         MAX_EVENT_STATES,
12450                         SOURCE_EVENT_TIME + 5,
12451                         List.of(WEB_TWO_DESTINATION),
12452                         null,
12453                         REGISTRATION_ORIGIN_2);
12454         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source7));
12455         Arrays.asList(source1, source2, source3, source4).stream()
12456                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12457         Arrays.asList(source5, source6, source7).stream()
12458                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12459 
12460         Source source8 =
12461                 insertSourceForAttributionScope(
12462                         List.of("4"),
12463                         ATTRIBUTION_SCOPE_LIMIT,
12464                         MAX_EVENT_STATES + 1,
12465                         SOURCE_EVENT_TIME + 5,
12466                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12467                         null);
12468         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source8));
12469         Arrays.asList(source1, source2, source3, source4).stream()
12470                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12471         Arrays.asList(source5, source6, source7, source8).stream()
12472                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12473     }
12474 
12475     @Test
testUpdateSourcesForAttributionScope_smallerLimit_ignoresSourcesDeletesReports()12476     public void testUpdateSourcesForAttributionScope_smallerLimit_ignoresSourcesDeletesReports() {
12477         mLegacyFlags = mMockFlags;
12478         mocker.mockGetFlags(mMockFlags);
12479         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
12480         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
12481         Source source1 =
12482                 insertSourceForAttributionScope(
12483                         List.of("1"),
12484                         ATTRIBUTION_SCOPE_LIMIT,
12485                         MAX_EVENT_STATES,
12486                         SOURCE_EVENT_TIME,
12487                         List.of(WEB_ONE_DESTINATION),
12488                         null);
12489         EventReport pastFakeEventReport =
12490                 new EventReport.Builder()
12491                         .setId("1")
12492                         .setSourceId(source1.getId())
12493                         .setSourceEventId(source1.getEventId())
12494                         .setReportTime(SOURCE_EVENT_TIME)
12495                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12496                         .setTriggerTime(SOURCE_EVENT_TIME)
12497                         .setSourceType(source1.getSourceType())
12498                         .setStatus(EventReport.Status.PENDING)
12499                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12500                         .build();
12501         EventReport fakeEventReport1 =
12502                 new EventReport.Builder()
12503                         .setId("2")
12504                         .setSourceId(source1.getId())
12505                         .setSourceEventId(source1.getEventId())
12506                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12507                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12508                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12509                         .setSourceType(source1.getSourceType())
12510                         .setStatus(EventReport.Status.PENDING)
12511                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12512                         .build();
12513         // Delete fake event report for comparison.
12514         EventReport deletedFakeEventReport1 =
12515                 new EventReport.Builder()
12516                         .setId("3")
12517                         .setSourceId(source1.getId())
12518                         .setSourceEventId(source1.getEventId())
12519                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12520                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12521                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12522                         .setSourceType(source1.getSourceType())
12523                         .setStatus(EventReport.Status.MARKED_TO_DELETE)
12524                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12525                         .build();
12526         mDatastoreManager.runInTransaction(
12527                 (dao) -> {
12528                     dao.insertEventReport(pastFakeEventReport);
12529                     dao.insertEventReport(fakeEventReport1);
12530                     dao.updateSourcesForAttributionScope(source1);
12531                 });
12532         Arrays.asList(source1).stream()
12533                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12534         assertThat(
12535                         mDatastoreManager
12536                                 .runInTransactionWithResult(
12537                                         measurementDao ->
12538                                                 measurementDao.getSourceEventReports(source1))
12539                                 .get())
12540                 .containsExactly(pastFakeEventReport, fakeEventReport1);
12541 
12542         Source source2 =
12543                 insertSourceForAttributionScope(
12544                         List.of("2"),
12545                         /* attributionScopeLimit= */ 8L,
12546                         MAX_EVENT_STATES,
12547                         SOURCE_EVENT_TIME + 1,
12548                         List.of(WEB_ONE_DESTINATION),
12549                         null);
12550         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12551         Arrays.asList(source1).stream()
12552                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12553         Arrays.asList(source2).stream()
12554                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12555         assertThat(
12556                         mDatastoreManager
12557                                 .runInTransactionWithResult(
12558                                         measurementDao ->
12559                                                 measurementDao.getSourceEventReports(source1))
12560                                 .get())
12561                 .containsExactly(pastFakeEventReport, deletedFakeEventReport1);
12562 
12563         Source source3 =
12564                 insertSourceForAttributionScope(
12565                         List.of("3"),
12566                         /* attributionScopeLimit= */ 4L,
12567                         MAX_EVENT_STATES,
12568                         SOURCE_EVENT_TIME + 2,
12569                         List.of(WEB_TWO_DESTINATION),
12570                         null);
12571         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12572         Arrays.asList(source1).stream()
12573                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12574         Arrays.asList(source2, source3).stream()
12575                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12576 
12577         Source source4 =
12578                 insertSourceForAttributionScope(
12579                         List.of("3"),
12580                         /* attributionScopeLimit= */ 7L,
12581                         MAX_EVENT_STATES,
12582                         SOURCE_EVENT_TIME + 3,
12583                         List.of(WEB_TWO_DESTINATION),
12584                         null);
12585         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
12586         Arrays.asList(source1, source3).stream()
12587                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12588         Arrays.asList(source2, source4).stream()
12589                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12590 
12591         Source source5 =
12592                 insertSourceForAttributionScope(
12593                         List.of("3"),
12594                         /* attributionScopeLimit= */ 4L,
12595                         MAX_EVENT_STATES,
12596                         SOURCE_EVENT_TIME + 2,
12597                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12598                         null);
12599         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source5));
12600         Arrays.asList(source1, source3).stream()
12601                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12602         Arrays.asList(source2, source4, source5).stream()
12603                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12604 
12605         Source source6 =
12606                 insertSourceForAttributionScope(
12607                         List.of("3"),
12608                         /* attributionScopeLimit= */ 6L,
12609                         MAX_EVENT_STATES,
12610                         SOURCE_EVENT_TIME + 2,
12611                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12612                         null);
12613         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source6));
12614         Arrays.asList(source1, source3, source5).stream()
12615                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12616         Arrays.asList(source2, source4, source6).stream()
12617                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12618 
12619         // Sources for different reporting origin with different max event states.
12620         Source source7 =
12621                 insertSourceForAttributionScope(
12622                         List.of("4"),
12623                         /* attributionScopeLimit= */ 6L,
12624                         MAX_EVENT_STATES,
12625                         SOURCE_EVENT_TIME + 3,
12626                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12627                         null,
12628                         REGISTRATION_ORIGIN_2);
12629         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source7));
12630         Arrays.asList(source1, source3, source5).stream()
12631                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12632         Arrays.asList(source2, source4, source6, source7).stream()
12633                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12634 
12635         // Sources for different reporting origin with the same max event states.
12636         Source source8 =
12637                 insertSourceForAttributionScope(
12638                         List.of("4"),
12639                         /* attributionScopeLimit= */ 4L,
12640                         MAX_EVENT_STATES,
12641                         SOURCE_EVENT_TIME + 3,
12642                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12643                         null,
12644                         REGISTRATION_ORIGIN_2);
12645         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source8));
12646         Arrays.asList(source1, source3, source5).stream()
12647                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12648         Arrays.asList(source2, source4, source6, source7, source8).stream()
12649                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12650 
12651         Source source9 =
12652                 insertSourceForAttributionScope(
12653                         List.of("4"),
12654                         /* attributionScopeLimit= */ 5L,
12655                         MAX_EVENT_STATES,
12656                         SOURCE_EVENT_TIME + 4,
12657                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
12658                         null);
12659         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source9));
12660         Arrays.stream(new Source[] {source1, source3, source5})
12661                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12662         Arrays.stream(new Source[] {source2, source4, source6, source7, source8, source9})
12663                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12664     }
12665 
12666     @Test
12667     public void
testUpdateSourcesForAttributionScope_scopedSource_ignoresNonScopedAndDeletesReports()12668             testUpdateSourcesForAttributionScope_scopedSource_ignoresNonScopedAndDeletesReports() {
12669         mLegacyFlags = mMockFlags;
12670         mocker.mockGetFlags(mMockFlags);
12671         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
12672         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
12673 
12674         Source source1 =
12675                 insertSourceForAttributionScope(
12676                         null, null, null, SOURCE_EVENT_TIME, List.of(WEB_ONE_DESTINATION), null);
12677         EventReport pastFakeEventReport =
12678                 new EventReport.Builder()
12679                         .setId("1")
12680                         .setSourceId(source1.getId())
12681                         .setSourceEventId(source1.getEventId())
12682                         .setReportTime(SOURCE_EVENT_TIME)
12683                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12684                         .setTriggerTime(SOURCE_EVENT_TIME)
12685                         .setSourceType(source1.getSourceType())
12686                         .setStatus(EventReport.Status.PENDING)
12687                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12688                         .build();
12689         EventReport fakeEventReport1 =
12690                 new EventReport.Builder()
12691                         .setId("2")
12692                         .setSourceId(source1.getId())
12693                         .setSourceEventId(source1.getEventId())
12694                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12695                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12696                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12697                         .setSourceType(source1.getSourceType())
12698                         .setStatus(EventReport.Status.PENDING)
12699                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12700                         .build();
12701         // Deleted fake event report for comparison.
12702         EventReport deletedFakeEventReport1 =
12703                 new EventReport.Builder()
12704                         .setId("3")
12705                         .setSourceId(source1.getId())
12706                         .setSourceEventId(source1.getEventId())
12707                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12708                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12709                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12710                         .setSourceType(source1.getSourceType())
12711                         .setStatus(EventReport.Status.MARKED_TO_DELETE)
12712                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12713                         .build();
12714         mDatastoreManager.runInTransaction(
12715                 (dao) -> {
12716                     dao.insertEventReport(pastFakeEventReport);
12717                     dao.insertEventReport(fakeEventReport1);
12718                     dao.updateSourcesForAttributionScope(source1);
12719                 });
12720         Arrays.asList(source1).stream()
12721                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12722         assertThat(
12723                         mDatastoreManager
12724                                 .runInTransactionWithResult(
12725                                         measurementDao ->
12726                                                 measurementDao.getSourceEventReports(source1))
12727                                 .get())
12728                 .containsExactly(pastFakeEventReport, fakeEventReport1);
12729 
12730         Source source2 =
12731                 insertSourceForAttributionScope(
12732                         List.of("2"),
12733                         ATTRIBUTION_SCOPE_LIMIT,
12734                         MAX_EVENT_STATES,
12735                         SOURCE_EVENT_TIME + 1,
12736                         List.of(WEB_ONE_DESTINATION),
12737                         null);
12738         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12739         Arrays.asList(source1).stream()
12740                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12741         Arrays.asList(source2).stream()
12742                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12743         assertThat(
12744                         mDatastoreManager
12745                                 .runInTransactionWithResult(
12746                                         measurementDao ->
12747                                                 measurementDao.getSourceEventReports(source1))
12748                                 .get())
12749                 .containsExactly(pastFakeEventReport, deletedFakeEventReport1);
12750 
12751         Source source3 =
12752                 insertSourceForAttributionScope(
12753                         List.of("3"),
12754                         ATTRIBUTION_SCOPE_LIMIT,
12755                         MAX_EVENT_STATES,
12756                         SOURCE_EVENT_TIME + 2,
12757                         List.of(WEB_TWO_DESTINATION),
12758                         null);
12759         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12760         Arrays.asList(source1).stream()
12761                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12762         Arrays.asList(source2, source3).stream()
12763                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12764     }
12765 
12766     @Test
testUpdateSourcesForAttributionScope_newNonScopedSource_removesScopes()12767     public void testUpdateSourcesForAttributionScope_newNonScopedSource_removesScopes() {
12768         mLegacyFlags = mMockFlags;
12769         mocker.mockGetFlags(mMockFlags);
12770         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
12771         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
12772         Consumer<? super Source> verifyAttributionScopeEmptyFn =
12773                 source -> {
12774                     assertThat(
12775                                     mDatastoreManager
12776                                             .runInTransactionWithResult(
12777                                                     measurementDao ->
12778                                                             measurementDao
12779                                                                     .getSourceAttributionScopes(
12780                                                                             source.getId()))
12781                                             .get())
12782                             .isEmpty();
12783                     Source savedSource =
12784                             mDatastoreManager
12785                                     .runInTransactionWithResult(
12786                                             measurementDao ->
12787                                                     measurementDao.getSource(source.getId()))
12788                                     .get();
12789                     assertThat(savedSource.getAttributionScopeLimit()).isNull();
12790                     assertThat(savedSource.getMaxEventStates()).isNull();
12791                 };
12792 
12793         Consumer<? super Source> verifyAttributionScopeUnchangedFn =
12794                 source -> {
12795                     assertThat(
12796                                     mDatastoreManager
12797                                             .runInTransactionWithResult(
12798                                                     measurementDao ->
12799                                                             measurementDao
12800                                                                     .getSourceAttributionScopes(
12801                                                                             source.getId()))
12802                                             .get())
12803                             .containsExactlyElementsIn(source.getAttributionScopes());
12804                     Source savedSource =
12805                             mDatastoreManager
12806                                     .runInTransactionWithResult(
12807                                             measurementDao ->
12808                                                     measurementDao.getSource(source.getId()))
12809                                     .get();
12810                     assertThat(savedSource.getAttributionScopeLimit())
12811                             .isEqualTo(savedSource.getAttributionScopeLimit());
12812                     assertThat(savedSource.getMaxEventStates())
12813                             .isEqualTo(savedSource.getMaxEventStates());
12814                 };
12815 
12816         Source source1 =
12817                 insertSourceForAttributionScope(
12818                         List.of("1"),
12819                         ATTRIBUTION_SCOPE_LIMIT,
12820                         MAX_EVENT_STATES,
12821                         SOURCE_EVENT_TIME,
12822                         List.of(WEB_ONE_DESTINATION),
12823                         null);
12824         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source1));
12825         Arrays.asList(source1).stream()
12826                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12827         Arrays.asList(source1).stream().forEach(verifyAttributionScopeUnchangedFn);
12828 
12829         Source source2 =
12830                 insertSourceForAttributionScope(
12831                         List.of("2"),
12832                         ATTRIBUTION_SCOPE_LIMIT,
12833                         MAX_EVENT_STATES,
12834                         SOURCE_EVENT_TIME + 1,
12835                         List.of(WEB_ONE_DESTINATION),
12836                         null);
12837         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12838         Arrays.asList(source1, source2).stream()
12839                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12840         Arrays.asList(source1, source2).stream().forEach(verifyAttributionScopeUnchangedFn);
12841 
12842         Source source3 =
12843                 insertSourceForAttributionScope(
12844                         null,
12845                         null,
12846                         null,
12847                         SOURCE_EVENT_TIME + 2,
12848                         List.of(WEB_TWO_DESTINATION),
12849                         null);
12850         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12851         Arrays.asList(source1, source2, source3).stream()
12852                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12853         // Source3 is for a different destination, attribution scopes should not be cleared.
12854         Arrays.asList(source1, source2).stream().forEach(verifyAttributionScopeUnchangedFn);
12855 
12856         Source source4 =
12857                 insertSourceForAttributionScope(
12858                         null,
12859                         null,
12860                         null,
12861                         SOURCE_EVENT_TIME + 3,
12862                         List.of(WEB_ONE_DESTINATION),
12863                         null);
12864         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
12865         Arrays.asList(source1, source2, source3, source4).stream()
12866                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12867         // Source4 is the same destination, clear attribution scopes for older sources.
12868         Arrays.asList(source1, source2).stream().forEach(verifyAttributionScopeEmptyFn);
12869     }
12870 
12871     @Test
testUpdateSourcesForAttributionScope_scopesNotSelected_ignoreSources()12872     public void testUpdateSourcesForAttributionScope_scopesNotSelected_ignoreSources() {
12873         mLegacyFlags = mMockFlags;
12874         mocker.mockGetFlags(mMockFlags);
12875         doReturn(true).when(mMockFlags).getMeasurementEnableAttributionScope();
12876         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
12877         // Below are the sources registered with attribution scopes and destinations.
12878         // For each destination, two sources are registered, and only one's scopes are to be
12879         // deleted.
12880         // For registration R1:
12881         // S1: attribution scopes -> [0, ""], destinations -> [D1]
12882         // S2: attribution scopes -> [3, 4, 5], destinations -> [D1]
12883         // S3: attribution scopes -> [0, 1], destinations -> [D2]
12884         // S4: attribution scopes -> [1, 3], destinations -> [D2]
12885         // S5: attribution scopes -> [1, 2], destinations -> [D3]
12886         // S6: attribution scopes -> [2, 3, 4], destinations -> [D3]
12887         // S7: attribution scopes -> [2], destinations -> [D4]
12888         // S8: attribution scopes -> [1, 2], destinations -> [D4], shares same timestamp as S7.
12889         // S12: attribution scopes -> [3, 4], destinations -> [D1, D2, D3, D4]
12890         // For registration R2 to test interplay cross reporting origin:
12891         // If the reporting origin were R1, the attribution scopes for S9, S10, and S11 would
12892         // have been removed.
12893         // S9: attribution scopes -> [0], destinations -> [D1]
12894         // S10: attribution scopes -> [0, 1], destinations -> [D2]
12895         // S11: attribution scopes -> [1, 2], destinations -> [D3]
12896         // The selected attribution scopes for each destination are:
12897         // D1: [3, 4, 5] => Scope for S1 to be removed.
12898         // D2: [1, 3, 4] => Scope for S3 to be removed.
12899         // D3: [2, 3, 4] => Scope for S5 to be removed.
12900         // D4: [2, 3, 4] => Scope for S8 to be removed. S7 and S8 share the same timestamp; the
12901         // attribution scope with the higher value will be selected.
12902 
12903         // S1: attribution scopes -> [0], destinations -> [D1]
12904         Source source1 =
12905                 insertSourceForAttributionScope(
12906                         List.of("0", ""),
12907                         ATTRIBUTION_SCOPE_LIMIT,
12908                         MAX_EVENT_STATES,
12909                         SOURCE_EVENT_TIME,
12910                         null,
12911                         List.of(APP_ONE_DESTINATION));
12912         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source1));
12913         Arrays.stream(new Source[] {source1})
12914                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12915 
12916         // S2: attribution scopes -> [3, 4, 5], destinations -> [D1]
12917         Source source2 =
12918                 insertSourceForAttributionScope(
12919                         List.of("3", "4", "5"),
12920                         ATTRIBUTION_SCOPE_LIMIT,
12921                         MAX_EVENT_STATES,
12922                         SOURCE_EVENT_TIME + 1,
12923                         null,
12924                         List.of(APP_ONE_DESTINATION));
12925         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12926         Arrays.stream(new Source[] {source1})
12927                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12928         Arrays.stream(new Source[] {source2})
12929                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12930 
12931         // S3: attribution scopes -> [0, 1], destinations -> [D2]
12932         Source source3 =
12933                 insertSourceForAttributionScope(
12934                         List.of("0", "1"),
12935                         ATTRIBUTION_SCOPE_LIMIT,
12936                         MAX_EVENT_STATES,
12937                         SOURCE_EVENT_TIME + 2,
12938                         List.of(WEB_ONE_DESTINATION),
12939                         null);
12940         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12941         Arrays.stream(new Source[] {source1})
12942                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12943         Arrays.stream(new Source[] {source2, source3})
12944                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12945 
12946         // S4: attribution scopes -> [1, 3], destinations -> [D2]
12947         Source source4 =
12948                 insertSourceForAttributionScope(
12949                         List.of("1", "3"),
12950                         ATTRIBUTION_SCOPE_LIMIT,
12951                         MAX_EVENT_STATES,
12952                         SOURCE_EVENT_TIME + 3,
12953                         List.of(WEB_ONE_DESTINATION),
12954                         null);
12955         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
12956         Arrays.stream(new Source[] {source1})
12957                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12958         Arrays.stream(new Source[] {source2, source3, source4})
12959                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12960 
12961         // S5: attribution scopes -> [1, 2], destinations -> [D3]
12962         Source source5 =
12963                 insertSourceForAttributionScope(
12964                         List.of("1", "2"),
12965                         ATTRIBUTION_SCOPE_LIMIT,
12966                         MAX_EVENT_STATES,
12967                         SOURCE_EVENT_TIME + 4,
12968                         List.of(WEB_TWO_DESTINATION),
12969                         null);
12970         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source5));
12971         Arrays.stream(new Source[] {source1})
12972                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12973         Arrays.stream(new Source[] {source2, source3, source4, source5})
12974                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12975 
12976         // S6: attribution scopes -> [2, 3, 4], destinations -> [D3]
12977         Source source6 =
12978                 insertSourceForAttributionScope(
12979                         List.of("2", "3", "4"),
12980                         ATTRIBUTION_SCOPE_LIMIT,
12981                         MAX_EVENT_STATES,
12982                         SOURCE_EVENT_TIME + 5,
12983                         List.of(WEB_TWO_DESTINATION),
12984                         null);
12985         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source6));
12986         Arrays.stream(new Source[] {source1, source5})
12987                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12988         Arrays.stream(new Source[] {source2, source3, source4, source6})
12989                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12990 
12991         // S7: attribution scopes -> [2], destinations -> [D4]
12992         Source source7 =
12993                 insertSourceForAttributionScope(
12994                         List.of("2"),
12995                         ATTRIBUTION_SCOPE_LIMIT,
12996                         MAX_EVENT_STATES,
12997                         SOURCE_EVENT_TIME + 6,
12998                         List.of(WEB_THREE_DESTINATION),
12999                         null);
13000         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source7));
13001         Arrays.stream(new Source[] {source1, source5})
13002                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
13003         Arrays.stream(new Source[] {source2, source3, source4, source6, source7})
13004                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
13005 
13006         // S8: attribution scopes -> [1, 2], destinations -> [D4], shares same timestamp as S7.
13007         Source source8 =
13008                 insertSourceForAttributionScope(
13009                         List.of("1", "2"),
13010                         ATTRIBUTION_SCOPE_LIMIT,
13011                         MAX_EVENT_STATES,
13012                         SOURCE_EVENT_TIME + 6,
13013                         List.of(WEB_THREE_DESTINATION),
13014                         null);
13015         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source8));
13016         Arrays.stream(new Source[] {source1, source5})
13017                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
13018         Arrays.stream(new Source[] {source2, source3, source4, source6, source7, source8})
13019                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
13020 
13021         // S9: attribution scopes -> [0], destinations -> [D1], reporting origin -> R2
13022         Source source9 =
13023                 insertSourceForAttributionScope(
13024                         List.of("0"),
13025                         ATTRIBUTION_SCOPE_LIMIT,
13026                         MAX_EVENT_STATES,
13027                         SOURCE_EVENT_TIME,
13028                         null,
13029                         List.of(APP_ONE_DESTINATION),
13030                         REGISTRATION_ORIGIN_2);
13031         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source9));
13032         Arrays.stream(new Source[] {source1, source5})
13033                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
13034         Arrays.stream(new Source[] {source2, source3, source4, source6, source7, source8, source9})
13035                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
13036 
13037         // S10: attribution scopes -> [0, 1], destinations -> [D2], reporting origin -> R2
13038         Source source10 =
13039                 insertSourceForAttributionScope(
13040                         List.of("0", "1"),
13041                         ATTRIBUTION_SCOPE_LIMIT,
13042                         MAX_EVENT_STATES,
13043                         SOURCE_EVENT_TIME + 2,
13044                         List.of(WEB_ONE_DESTINATION),
13045                         null,
13046                         REGISTRATION_ORIGIN_2);
13047         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source10));
13048         Arrays.stream(new Source[] {source1, source5})
13049                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
13050         Arrays.stream(
13051                         new Source[] {
13052                             source2, source3, source4, source6, source7, source8, source9, source10
13053                         })
13054                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
13055 
13056         // S11: attribution scopes -> [1, 2], destinations -> [D3], reporting origin -> R2
13057         Source source11 =
13058                 insertSourceForAttributionScope(
13059                         List.of("1", "2"),
13060                         ATTRIBUTION_SCOPE_LIMIT,
13061                         MAX_EVENT_STATES,
13062                         SOURCE_EVENT_TIME + 4,
13063                         List.of(WEB_TWO_DESTINATION),
13064                         null,
13065                         REGISTRATION_ORIGIN_2);
13066         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source11));
13067         Arrays.stream(new Source[] {source1, source5})
13068                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
13069         Arrays.stream(
13070                         new Source[] {
13071                             source2, source3, source4, source6, source7, source8, source9, source10,
13072                             source11
13073                         })
13074                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
13075 
13076         // S12: attribution scopes -> [3, 4], destinations -> [D1, D2, D3, D4]
13077         Source source12 =
13078                 insertSourceForAttributionScope(
13079                         List.of("3", "4"),
13080                         ATTRIBUTION_SCOPE_LIMIT,
13081                         MAX_EVENT_STATES,
13082                         SOURCE_EVENT_TIME + 8,
13083                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION, WEB_THREE_DESTINATION),
13084                         List.of(APP_ONE_DESTINATION));
13085         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source12));
13086         Arrays.stream(new Source[] {source1, source3, source5, source8})
13087                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
13088         Arrays.stream(
13089                         new Source[] {
13090                             source2, source4, source6, source7, source9, source10, source11,
13091                             source12
13092                         })
13093                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
13094     }
13095 
13096     @Test
getLatestReportTimeInBatchWindow_singleAggregateReport_returnsSingleReportTime()13097     public void getLatestReportTimeInBatchWindow_singleAggregateReport_returnsSingleReportTime() {
13098         Source source =
13099                 SourceFixture.getMinimalValidSourceBuilder()
13100                         .setEventId(new UnsignedLong(1L))
13101                         .setId("source1")
13102                         .build();
13103 
13104         long scheduledReportTime = 1L;
13105         AggregateReport report =
13106                 generateMockAggregateReport(
13107                         WebUtil.validUrl("https://destination-1.test"),
13108                         1,
13109                         "source1",
13110                         scheduledReportTime);
13111 
13112         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13113         Objects.requireNonNull(db);
13114         insertSource(source, source.getId());
13115         getAggregateReportConsumer(db).accept(report);
13116 
13117         Long result =
13118                 mDatastoreManager
13119                         .runInTransactionWithResult(
13120                                 measurementDao ->
13121                                         measurementDao.getLatestReportTimeInBatchWindow(
13122                                                 mLegacyFlags
13123                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13124                         .orElseThrow();
13125 
13126         assertEquals(scheduledReportTime, result.longValue());
13127     }
13128 
13129     @Test
testGetLatestReportTimeInBatchWindow_singleEventReport_returnsSingleReportTime()13130     public void testGetLatestReportTimeInBatchWindow_singleEventReport_returnsSingleReportTime() {
13131         long reportTime = 1L;
13132         EventReport report =
13133                 generateMockEventReport(
13134                         WebUtil.validUrl("https://destination-1.test"), 1, reportTime);
13135 
13136         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13137         Objects.requireNonNull(db);
13138         getEventReportConsumer(db).accept(report);
13139 
13140         Long result =
13141                 mDatastoreManager
13142                         .runInTransactionWithResult(
13143                                 measurementDao ->
13144                                         measurementDao.getLatestReportTimeInBatchWindow(
13145                                                 mLegacyFlags
13146                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13147                         .orElseThrow();
13148 
13149         assertEquals(reportTime, result.longValue());
13150     }
13151 
13152     @Test
13153     public void
testGetLatestReportTimeInBatchWindow_twoAggregateReport_bothInBatchWindow_returnsTwoReportTimes()13154             testGetLatestReportTimeInBatchWindow_twoAggregateReport_bothInBatchWindow_returnsTwoReportTimes() {
13155         String sourceId = "source1";
13156         Source source =
13157                 SourceFixture.getMinimalValidSourceBuilder()
13158                         .setEventId(new UnsignedLong(1L))
13159                         .setId(sourceId)
13160                         .build();
13161 
13162         long firstScheduledReportTime = 1L;
13163         long secondScheduledReportTime =
13164                 firstScheduledReportTime
13165                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
13166                         - 1L;
13167 
13168         String destination = WebUtil.validUrl("https://destination-1.test");
13169         List<AggregateReport> reports =
13170                 Arrays.asList(
13171                         generateMockAggregateReport(
13172                                 destination, 1, sourceId, firstScheduledReportTime),
13173                         generateMockAggregateReport(
13174                                 destination, 2, sourceId, secondScheduledReportTime));
13175 
13176         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13177         Objects.requireNonNull(db);
13178         insertSource(source, source.getId());
13179         reports.forEach(getAggregateReportConsumer(db));
13180 
13181         Long result =
13182                 mDatastoreManager
13183                         .runInTransactionWithResult(
13184                                 measurementDao ->
13185                                         measurementDao.getLatestReportTimeInBatchWindow(
13186                                                 mLegacyFlags
13187                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13188                         .orElseThrow();
13189 
13190         assertEquals(secondScheduledReportTime, result.longValue());
13191     }
13192 
13193     @Test
13194     public void
testGetLatestReportTimeInBatchWindow_twoAggReport_oneAfterBatchWindow_returnOneReportTime()13195             testGetLatestReportTimeInBatchWindow_twoAggReport_oneAfterBatchWindow_returnOneReportTime() {
13196         String sourceId = "source1";
13197         Source source =
13198                 SourceFixture.getMinimalValidSourceBuilder()
13199                         .setEventId(new UnsignedLong(1L))
13200                         .setId(sourceId)
13201                         .build();
13202 
13203         long firstScheduledReportTime = 1L;
13204         long secondScheduledReportTime =
13205                 firstScheduledReportTime
13206                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
13207                         + 1L;
13208 
13209         String destination = WebUtil.validUrl("https://destination-1.test");
13210         List<AggregateReport> reports =
13211                 Arrays.asList(
13212                         generateMockAggregateReport(
13213                                 destination, 1, sourceId, firstScheduledReportTime),
13214                         generateMockAggregateReport(
13215                                 destination, 2, sourceId, secondScheduledReportTime));
13216 
13217         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13218         Objects.requireNonNull(db);
13219         insertSource(source, source.getId());
13220         reports.forEach(getAggregateReportConsumer(db));
13221 
13222         Long result =
13223                 mDatastoreManager
13224                         .runInTransactionWithResult(
13225                                 measurementDao ->
13226                                         measurementDao.getLatestReportTimeInBatchWindow(
13227                                                 mLegacyFlags
13228                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13229                         .orElseThrow();
13230 
13231         // secondScheduledReportTime should not be returned as it was outside the batch window.
13232         assertEquals(firstScheduledReportTime, result.longValue());
13233     }
13234 
13235     @Test
13236     public void
testGetLatestReportTimeInBatchWindow_twoEventReport_bothInBatchWindow_returnSecondReportTime()13237             testGetLatestReportTimeInBatchWindow_twoEventReport_bothInBatchWindow_returnSecondReportTime() {
13238         long firstScheduledReportTime = 1L;
13239         long secondScheduledReportTime =
13240                 firstScheduledReportTime
13241                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
13242                         - 1L;
13243 
13244         String destination = WebUtil.validUrl("https://destination-1.test");
13245         List<EventReport> reports =
13246                 Arrays.asList(
13247                         generateMockEventReport(destination, 1, firstScheduledReportTime),
13248                         generateMockEventReport(destination, 2, secondScheduledReportTime));
13249 
13250         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13251         Objects.requireNonNull(db);
13252 
13253         Consumer<EventReport> eventReportConsumer = getEventReportConsumer(db);
13254         reports.forEach(eventReportConsumer);
13255 
13256         Long result =
13257                 mDatastoreManager
13258                         .runInTransactionWithResult(
13259                                 measurementDao ->
13260                                         measurementDao.getLatestReportTimeInBatchWindow(
13261                                                 mLegacyFlags
13262                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13263                         .orElseThrow();
13264 
13265         assertEquals(secondScheduledReportTime, result.longValue());
13266     }
13267 
13268     @Test
13269     public void
testGetLatestReportTimeInBatchWindow_twoEventReport_oneAfterBatchWindow_returnFirstReportTime()13270             testGetLatestReportTimeInBatchWindow_twoEventReport_oneAfterBatchWindow_returnFirstReportTime() {
13271         long firstScheduledReportTime = 1L;
13272         long secondScheduledReportTime =
13273                 firstScheduledReportTime
13274                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
13275                         + 1L;
13276 
13277         String destination = WebUtil.validUrl("https://destination-1.test");
13278         List<EventReport> reports =
13279                 Arrays.asList(
13280                         generateMockEventReport(destination, 1, firstScheduledReportTime),
13281                         generateMockEventReport(destination, 2, secondScheduledReportTime));
13282 
13283         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13284         Objects.requireNonNull(db);
13285 
13286         Consumer<EventReport> eventReportConsumer = getEventReportConsumer(db);
13287         reports.forEach(eventReportConsumer);
13288 
13289         Long result =
13290                 mDatastoreManager
13291                         .runInTransactionWithResult(
13292                                 measurementDao ->
13293                                         measurementDao.getLatestReportTimeInBatchWindow(
13294                                                 mLegacyFlags
13295                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13296                         .orElseThrow();
13297 
13298         // secondScheduledReportTime should not be returned as it was outside the batch window.
13299         assertEquals(firstScheduledReportTime, result.longValue());
13300     }
13301 
13302     @Test
13303     public void
testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_bothInBatchWindow_returnSecondReportTime()13304             testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_bothInBatchWindow_returnSecondReportTime() {
13305         long firstScheduledReportTime = 1L;
13306         long secondScheduledReportTime =
13307                 firstScheduledReportTime
13308                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
13309                         - 1L;
13310 
13311         Source source =
13312                 SourceFixture.getMinimalValidSourceBuilder()
13313                         .setEventId(new UnsignedLong(1L))
13314                         .setId("source1")
13315                         .build();
13316 
13317         String destination = WebUtil.validUrl("https://destination-1.test");
13318         AggregateReport aggregateReport =
13319                 generateMockAggregateReport(destination, 1, "source1", firstScheduledReportTime);
13320         EventReport eventReport =
13321                 generateMockEventReport(destination, 1, secondScheduledReportTime);
13322 
13323         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13324         Objects.requireNonNull(db);
13325         insertSource(source, source.getId());
13326         getEventReportConsumer(db).accept(eventReport);
13327         getAggregateReportConsumer(db).accept(aggregateReport);
13328 
13329         Long result =
13330                 mDatastoreManager
13331                         .runInTransactionWithResult(
13332                                 measurementDao ->
13333                                         measurementDao.getLatestReportTimeInBatchWindow(
13334                                                 mLegacyFlags
13335                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13336                         .orElseThrow();
13337 
13338         assertEquals(secondScheduledReportTime, result.longValue());
13339     }
13340 
13341     @Test
13342     public void
testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_oneAfterBatchWindow_returnFirstReportTime()13343             testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_oneAfterBatchWindow_returnFirstReportTime() {
13344         long firstScheduledReportTime = 1L;
13345         long secondScheduledReportTime =
13346                 firstScheduledReportTime
13347                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
13348                         + 1L;
13349 
13350         String sourceId = "source1";
13351         Source source =
13352                 SourceFixture.getMinimalValidSourceBuilder()
13353                         .setEventId(new UnsignedLong(1L))
13354                         .setId(sourceId)
13355                         .build();
13356 
13357         String destination = WebUtil.validUrl("https://destination-1.test");
13358         AggregateReport aggregateReport =
13359                 generateMockAggregateReport(destination, 1, sourceId, firstScheduledReportTime);
13360         EventReport eventReport =
13361                 generateMockEventReport(destination, 1, secondScheduledReportTime);
13362 
13363         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13364         Objects.requireNonNull(db);
13365         insertSource(source, source.getId());
13366         getEventReportConsumer(db).accept(eventReport);
13367         getAggregateReportConsumer(db).accept(aggregateReport);
13368 
13369         Long result =
13370                 mDatastoreManager
13371                         .runInTransactionWithResult(
13372                                 measurementDao ->
13373                                         measurementDao.getLatestReportTimeInBatchWindow(
13374                                                 mLegacyFlags
13375                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13376                         .orElseThrow();
13377 
13378         assertEquals(firstScheduledReportTime, result.longValue());
13379     }
13380 
13381     @Test
13382     public void
testGetLatestReportTimeInBatchWindow_manyAggReport_manyEventReport_returnLatestReportTime()13383             testGetLatestReportTimeInBatchWindow_manyAggReport_manyEventReport_returnLatestReportTime() {
13384         String sourceId = "source1";
13385         Source source =
13386                 SourceFixture.getMinimalValidSourceBuilder()
13387                         .setEventId(new UnsignedLong(1L))
13388                         .setId(sourceId)
13389                         .build();
13390         List<EventReport> eventReports = new ArrayList<>();
13391         List<AggregateReport> aggregateReports = new ArrayList<>();
13392         int firstReportTime = 1;
13393         String destination = WebUtil.validUrl("https://destination-1.test");
13394         for (int i = firstReportTime; i < MEASUREMENT_MAX_EVENT_REPORTS_PER_DESTINATION; i++) {
13395             eventReports.add(
13396                     generateMockEventReport(destination, /* id= */ i, /* reportTime= */ i));
13397         }
13398 
13399         for (int i = firstReportTime; i < MEASUREMENT_MAX_AGGREGATE_REPORTS_PER_SOURCE; i++) {
13400             aggregateReports.add(
13401                     generateMockAggregateReport(
13402                             destination, /* id= */ i, sourceId, /* reportTime= */ i));
13403         }
13404 
13405         // Add one more aggregate report that is scheduled at the very edge of the batch window.
13406         // The report time for this report should be returned as the report time.
13407         long lastReportTime = MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS;
13408         int lastId = MEASUREMENT_MAX_AGGREGATE_REPORTS_PER_SOURCE;
13409         aggregateReports.add(
13410                 generateMockAggregateReport(destination, lastId, sourceId, lastReportTime));
13411 
13412         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13413         Objects.requireNonNull(db);
13414         insertSource(source, source.getId());
13415         eventReports.forEach(getEventReportConsumer(db));
13416         aggregateReports.forEach(getAggregateReportConsumer(db));
13417 
13418         Long result =
13419                 mDatastoreManager
13420                         .runInTransactionWithResult(
13421                                 measurementDao ->
13422                                         measurementDao.getLatestReportTimeInBatchWindow(
13423                                                 mLegacyFlags
13424                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
13425                         .orElseThrow();
13426 
13427         assertEquals(lastReportTime, result.longValue());
13428     }
13429 
13430     @Test
testGetLatestReportTimeInBatchWindow_noReports_returnNull()13431     public void testGetLatestReportTimeInBatchWindow_noReports_returnNull() {
13432         Optional<Long> results =
13433                 mDatastoreManager.runInTransactionWithResult(
13434                         measurementDao ->
13435                                 measurementDao.getLatestReportTimeInBatchWindow(
13436                                         mLegacyFlags
13437                                                 .getMeasurementReportingJobServiceBatchWindowMillis()));
13438 
13439         assertTrue(results.isEmpty());
13440     }
13441 
13442     @Test
testInsertSource_withDestinationLimitPriorityEnabled_fetchesTheSetValue()13443     public void testInsertSource_withDestinationLimitPriorityEnabled_fetchesTheSetValue() {
13444         // Setup
13445         mocker.mockGetFlags(mMockFlags);
13446         doReturn(true).when(mMockFlags).getMeasurementEnableSourceDestinationLimitPriority();
13447         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
13448 
13449         // Execution
13450         Source validSource =
13451                 SourceFixture.getValidSourceBuilder()
13452                         .setDestinationLimitPriority(
13453                                 SourceFixture.ValidSourceParams.DESTINATION_LIMIT_PRIORITY)
13454                         .build();
13455         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
13456 
13457         // Assertion
13458         String sourceId = getFirstSourceIdFromDatastore();
13459         Source source =
13460                 mDatastoreManager
13461                         .runInTransactionWithResult(
13462                                 measurementDao -> measurementDao.getSource(sourceId))
13463                         .orElseThrow(() -> new IllegalStateException("Source is null"));
13464         assertEquals(
13465                 SourceFixture.ValidSourceParams.DESTINATION_LIMIT_PRIORITY,
13466                 source.getDestinationLimitPriority());
13467     }
13468 
13469     @Test
testInsertAggregateDebugReportRecords_sourceAndTriggerIdPresent_succeeds()13470     public void testInsertAggregateDebugReportRecords_sourceAndTriggerIdPresent_succeeds() {
13471         // insert source & trigger to honor the foreign key constraint
13472         Source source =
13473                 SourceFixture.getMinimalValidSourceBuilder()
13474                         .setId("S1")
13475                         .setEventId(new UnsignedLong(3L))
13476                         .setEnrollmentId("1")
13477                         .build();
13478         Trigger trigger =
13479                 TriggerFixture.getValidTriggerBuilder().setId("T1").setEnrollmentId("2").build();
13480         AggregateDebugReportRecord validAggregateDebugReportRecord =
13481                 new AggregateDebugReportRecord.Builder(
13482                                 /* reportGenerationTime= */ 1000L,
13483                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13484                                 /* registrantApp= */ Uri.parse("com.test1.myapp"),
13485                                 /* registrationOrigin= */ Uri.parse("https://destination.test"),
13486                                 /* contributions= */ 9)
13487                         .setSourceId("S1")
13488                         .setTriggerId("T1")
13489                         .build();
13490 
13491         // Execution
13492         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
13493         Objects.requireNonNull(db);
13494         insertSource(source, source.getId());
13495         AbstractDbIntegrationTest.insertToDb(trigger, db);
13496         mDatastoreManager.runInTransaction(
13497                 (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
13498 
13499         // Assertion
13500         try (Cursor cursor =
13501                 MeasurementDbHelper.getInstance()
13502                         .getReadableDatabase()
13503                         .query(
13504                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
13505                                 null,
13506                                 null,
13507                                 null,
13508                                 null,
13509                                 null,
13510                                 null)) {
13511             assertThat(cursor.moveToNext()).isTrue();
13512             assertThat(validAggregateDebugReportRecord.getReportGenerationTime())
13513                     .isEqualTo(
13514                             cursor.getInt(
13515                                     cursor.getColumnIndex(
13516                                             AggregatableDebugReportBudgetTrackerContract
13517                                                     .REPORT_GENERATION_TIME)));
13518             assertThat(validAggregateDebugReportRecord.getTopLevelRegistrant().toString())
13519                     .isEqualTo(
13520                             cursor.getString(
13521                                     cursor.getColumnIndex(
13522                                             AggregatableDebugReportBudgetTrackerContract
13523                                                     .TOP_LEVEL_REGISTRANT)));
13524             assertThat(validAggregateDebugReportRecord.getRegistrantApp().toString())
13525                     .isEqualTo(
13526                             cursor.getString(
13527                                     cursor.getColumnIndex(
13528                                             AggregatableDebugReportBudgetTrackerContract
13529                                                     .REGISTRANT_APP)));
13530             assertThat(validAggregateDebugReportRecord.getRegistrationOrigin().toString())
13531                     .isEqualTo(
13532                             cursor.getString(
13533                                     cursor.getColumnIndex(
13534                                             AggregatableDebugReportBudgetTrackerContract
13535                                                     .REGISTRATION_ORIGIN)));
13536             assertThat(validAggregateDebugReportRecord.getSourceId())
13537                     .isEqualTo(
13538                             cursor.getString(
13539                                     cursor.getColumnIndex(
13540                                             AggregatableDebugReportBudgetTrackerContract
13541                                                     .SOURCE_ID)));
13542             assertThat(validAggregateDebugReportRecord.getTriggerId())
13543                     .isEqualTo(
13544                             cursor.getString(
13545                                     cursor.getColumnIndex(
13546                                             AggregatableDebugReportBudgetTrackerContract
13547                                                     .TRIGGER_ID)));
13548             assertThat(validAggregateDebugReportRecord.getContributions())
13549                     .isEqualTo(
13550                             cursor.getInt(
13551                                     cursor.getColumnIndex(
13552                                             AggregatableDebugReportBudgetTrackerContract
13553                                                     .CONTRIBUTIONS)));
13554         }
13555     }
13556 
13557     @Test
testInsertAggregateDebugReportRecords_nullTriggerId_succeeds()13558     public void testInsertAggregateDebugReportRecords_nullTriggerId_succeeds() {
13559         // insert source & trigger to honor the foreign key constraint
13560         Source source =
13561                 SourceFixture.getMinimalValidSourceBuilder()
13562                         .setId("S1")
13563                         .setEventId(new UnsignedLong(3L))
13564                         .setEnrollmentId("1")
13565                         .build();
13566         AggregateDebugReportRecord validAggregateDebugReportRecord =
13567                 new AggregateDebugReportRecord.Builder(
13568                                 /* reportGenerationTime= */ 1000L,
13569                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13570                                 /* registrantApp= */ Uri.parse("com.test1.myapp"),
13571                                 /* registrationOrigin= */ Uri.parse("https://destination.test"),
13572                                 /* contributions= */ 9)
13573                         .setSourceId("S1")
13574                         .build();
13575 
13576         // Execution
13577         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
13578         Objects.requireNonNull(db);
13579         insertSource(source, source.getId());
13580         mDatastoreManager.runInTransaction(
13581                 (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
13582 
13583         // Assertion
13584         try (Cursor cursor =
13585                 MeasurementDbHelper.getInstance()
13586                         .getReadableDatabase()
13587                         .query(
13588                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
13589                                 null,
13590                                 null,
13591                                 null,
13592                                 null,
13593                                 null,
13594                                 null)) {
13595             assertThat(cursor.moveToNext()).isTrue();
13596             assertThat(validAggregateDebugReportRecord.getReportGenerationTime())
13597                     .isEqualTo(
13598                             cursor.getInt(
13599                                     cursor.getColumnIndex(
13600                                             AggregatableDebugReportBudgetTrackerContract
13601                                                     .REPORT_GENERATION_TIME)));
13602             assertThat(validAggregateDebugReportRecord.getTopLevelRegistrant().toString())
13603                     .isEqualTo(
13604                             cursor.getString(
13605                                     cursor.getColumnIndex(
13606                                             AggregatableDebugReportBudgetTrackerContract
13607                                                     .TOP_LEVEL_REGISTRANT)));
13608             assertThat(validAggregateDebugReportRecord.getRegistrantApp().toString())
13609                     .isEqualTo(
13610                             cursor.getString(
13611                                     cursor.getColumnIndex(
13612                                             AggregatableDebugReportBudgetTrackerContract
13613                                                     .REGISTRANT_APP)));
13614             assertThat(validAggregateDebugReportRecord.getRegistrationOrigin().toString())
13615                     .isEqualTo(
13616                             cursor.getString(
13617                                     cursor.getColumnIndex(
13618                                             AggregatableDebugReportBudgetTrackerContract
13619                                                     .REGISTRATION_ORIGIN)));
13620             assertThat(validAggregateDebugReportRecord.getSourceId())
13621                     .isEqualTo(
13622                             cursor.getString(
13623                                     cursor.getColumnIndex(
13624                                             AggregatableDebugReportBudgetTrackerContract
13625                                                     .SOURCE_ID)));
13626             assertThat(validAggregateDebugReportRecord.getTriggerId())
13627                     .isEqualTo(
13628                             cursor.getString(
13629                                     cursor.getColumnIndex(
13630                                             AggregatableDebugReportBudgetTrackerContract
13631                                                     .TRIGGER_ID)));
13632             assertThat(validAggregateDebugReportRecord.getContributions())
13633                     .isEqualTo(
13634                             cursor.getInt(
13635                                     cursor.getColumnIndex(
13636                                             AggregatableDebugReportBudgetTrackerContract
13637                                                     .CONTRIBUTIONS)));
13638         }
13639     }
13640 
13641     @Test
testInsertAggregateDebugReportRecords_nullSourceAndTriggerId_succeeds()13642     public void testInsertAggregateDebugReportRecords_nullSourceAndTriggerId_succeeds() {
13643         AggregateDebugReportRecord validAggregateDebugReportRecord =
13644                 new AggregateDebugReportRecord.Builder(
13645                                 /* reportGenerationTime= */ 1000L,
13646                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13647                                 /* registrantApp= */ Uri.parse("com.test1.myapp"),
13648                                 /* registrationOrigin= */ Uri.parse("https://destination.test"),
13649                                 /* contributions= */ 9)
13650                         .build();
13651 
13652         // Execution
13653         mDatastoreManager.runInTransaction(
13654                 (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
13655 
13656         // Assertion
13657         try (Cursor cursor =
13658                 MeasurementDbHelper.getInstance()
13659                         .getReadableDatabase()
13660                         .query(
13661                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
13662                                 null,
13663                                 null,
13664                                 null,
13665                                 null,
13666                                 null,
13667                                 null)) {
13668             assertThat(cursor.moveToNext()).isTrue();
13669             assertThat(validAggregateDebugReportRecord.getReportGenerationTime())
13670                     .isEqualTo(
13671                             cursor.getInt(
13672                                     cursor.getColumnIndex(
13673                                             AggregatableDebugReportBudgetTrackerContract
13674                                                     .REPORT_GENERATION_TIME)));
13675             assertThat(validAggregateDebugReportRecord.getTopLevelRegistrant().toString())
13676                     .isEqualTo(
13677                             cursor.getString(
13678                                     cursor.getColumnIndex(
13679                                             AggregatableDebugReportBudgetTrackerContract
13680                                                     .TOP_LEVEL_REGISTRANT)));
13681             assertThat(validAggregateDebugReportRecord.getRegistrantApp().toString())
13682                     .isEqualTo(
13683                             cursor.getString(
13684                                     cursor.getColumnIndex(
13685                                             AggregatableDebugReportBudgetTrackerContract
13686                                                     .REGISTRANT_APP)));
13687             assertThat(validAggregateDebugReportRecord.getRegistrationOrigin().toString())
13688                     .isEqualTo(
13689                             cursor.getString(
13690                                     cursor.getColumnIndex(
13691                                             AggregatableDebugReportBudgetTrackerContract
13692                                                     .REGISTRATION_ORIGIN)));
13693             assertThat(validAggregateDebugReportRecord.getSourceId())
13694                     .isEqualTo(
13695                             cursor.getString(
13696                                     cursor.getColumnIndex(
13697                                             AggregatableDebugReportBudgetTrackerContract
13698                                                     .SOURCE_ID)));
13699             assertThat(validAggregateDebugReportRecord.getTriggerId())
13700                     .isEqualTo(
13701                             cursor.getString(
13702                                     cursor.getColumnIndex(
13703                                             AggregatableDebugReportBudgetTrackerContract
13704                                                     .TRIGGER_ID)));
13705             assertThat(validAggregateDebugReportRecord.getContributions())
13706                     .isEqualTo(
13707                             cursor.getInt(
13708                                     cursor.getColumnIndex(
13709                                             AggregatableDebugReportBudgetTrackerContract
13710                                                     .CONTRIBUTIONS)));
13711         }
13712     }
13713 
13714     @Test
testGetTotalAggregateDebugReportBudget()13715     public void testGetTotalAggregateDebugReportBudget() {
13716         // insert source & trigger to honor the foreign key constraint
13717         Source source =
13718                 SourceFixture.getMinimalValidSourceBuilder()
13719                         .setId("S1")
13720                         .setEventId(new UnsignedLong(3L))
13721                         .setEnrollmentId("1")
13722                         .build();
13723         Trigger trigger =
13724                 TriggerFixture.getValidTriggerBuilder().setId("T1").setEnrollmentId("2").build();
13725         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
13726         Objects.requireNonNull(db);
13727         insertSource(source, source.getId());
13728         AbstractDbIntegrationTest.insertToDb(trigger, db);
13729 
13730         // test case 1 (publisher query): outside time window + same publisher
13731         AggregateDebugReportRecord validAggregateDebugReportRecord1 =
13732                 new AggregateDebugReportRecord.Builder(
13733                                 /* reportGenerationTime= */ 1000L,
13734                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13735                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13736                                 /* registrationOrigin= */ Uri.parse("https://destination0.test"),
13737                                 /* contributions= */ 9)
13738                         .setSourceId("S1")
13739                         .setTriggerId("T1")
13740                         .build();
13741 
13742         // test case 2 (publisher query): inside time window + same publisher + different origin
13743         AggregateDebugReportRecord validAggregateDebugReportRecord2 =
13744                 new AggregateDebugReportRecord.Builder(
13745                                 /* reportGenerationTime= */ 2000L,
13746                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13747                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13748                                 /* registrationOrigin= */ Uri.parse("https://destination1.test"),
13749                                 /* contributions= */ 16)
13750                         .setSourceId("S1")
13751                         .setTriggerId("T1")
13752                         .build();
13753         AggregateDebugReportRecord validAggregateDebugReportRecord3 =
13754                 new AggregateDebugReportRecord.Builder(
13755                                 /* reportGenerationTime= */ 3000L,
13756                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13757                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13758                                 /* registrationOrigin= */ Uri.parse("https://destination2.test"),
13759                                 /* contributions= */ 25)
13760                         .setSourceId("S1")
13761                         .setTriggerId("T1")
13762                         .build();
13763         AggregateDebugReportRecord validAggregateDebugReportRecord4 =
13764                 new AggregateDebugReportRecord.Builder(
13765                                 /* reportGenerationTime= */ 4000L,
13766                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13767                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13768                                 /* registrationOrigin= */ Uri.parse("https://destination3.test"),
13769                                 /* contributions= */ 36)
13770                         .setSourceId("S1")
13771                         .setTriggerId("T1")
13772                         .build();
13773 
13774         // test case 3 (publisher + origin query): inside time window + same publisher + same origin
13775         AggregateDebugReportRecord validAggregateDebugReportRecord5 =
13776                 new AggregateDebugReportRecord.Builder(
13777                                 /* reportGenerationTime= */ 5000L,
13778                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_2,
13779                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13780                                 /* registrationOrigin= */ Uri.parse("https://destination4.test"),
13781                                 /* contributions= */ 49)
13782                         .setSourceId("S1")
13783                         .setTriggerId("T1")
13784                         .build();
13785         AggregateDebugReportRecord validAggregateDebugReportRecord6 =
13786                 new AggregateDebugReportRecord.Builder(
13787                                 /* reportGenerationTime= */ 6000L,
13788                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_2,
13789                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13790                                 /* registrationOrigin= */ Uri.parse("https://destination4.test"),
13791                                 /* contributions= */ 64)
13792                         .setSourceId("S1")
13793                         .setTriggerId("T1")
13794                         .build();
13795         List<AggregateDebugReportRecord> validAggregateDebugReportRecords =
13796                 Arrays.asList(
13797                         validAggregateDebugReportRecord1,
13798                         validAggregateDebugReportRecord2,
13799                         validAggregateDebugReportRecord3,
13800                         validAggregateDebugReportRecord4,
13801                         validAggregateDebugReportRecord5,
13802                         validAggregateDebugReportRecord6);
13803 
13804         for (AggregateDebugReportRecord validAggregateDebugReportRecord :
13805                 validAggregateDebugReportRecords) {
13806             mDatastoreManager.runInTransaction(
13807                     (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
13808         }
13809 
13810         // test case 1
13811         int budget1 =
13812                 mDatastoreManager
13813                         .runInTransactionWithResult(
13814                                 (dao) ->
13815                                         dao.sumAggregateDebugReportBudgetXPublisherXWindow(
13816                                                 /* publisher= */ TOP_LEVEL_REGISTRANT_1,
13817                                                 /* publisherType= */ EventSurfaceType.APP,
13818                                                 /* windowStartTime= */ 1000L))
13819                         .get();
13820         assertThat(budget1).isEqualTo((16 + 25 + 36));
13821 
13822         // test case 2
13823         int budget2 =
13824                 mDatastoreManager
13825                         .runInTransactionWithResult(
13826                                 (dao) ->
13827                                         dao.sumAggregateDebugReportBudgetXPublisherXWindow(
13828                                                 /* publisher= */ TOP_LEVEL_REGISTRANT_1,
13829                                                 /* publisherType= */ EventSurfaceType.APP,
13830                                                 /* windowStartTime= */ 1001L))
13831                         .get();
13832         assertThat(budget2).isEqualTo((16 + 25 + 36));
13833 
13834         // test case 3
13835         int budget3 =
13836                 mDatastoreManager
13837                         .runInTransactionWithResult(
13838                                 (dao) ->
13839                                         dao.sumAggregateDebugReportBudgetXOriginXPublisherXWindow(
13840                                                 /* publisher= */ TOP_LEVEL_REGISTRANT_2,
13841                                                 /* publisherType= */ EventSurfaceType.APP,
13842                                                 /* origin= */ Uri.parse(
13843                                                         "https://destination4.test"),
13844                                                 /* windowStartTime= */ 1001L))
13845                         .get();
13846         assertThat(budget3).isEqualTo((49 + 64));
13847     }
13848 
13849     @Test
updateSourceAggregateDebugContributions_updateFromPreValue_success()13850     public void updateSourceAggregateDebugContributions_updateFromPreValue_success() {
13851         // Setup
13852         mocker.mockGetFlags(mMockFlags);
13853         doReturn(true).when(mMockFlags).getMeasurementEnableAggregateDebugReporting();
13854         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mMockFlags).getMeasurementDbSizeLimit();
13855         int initialContributions = 1024;
13856         Source source =
13857                 SourceFixture.getValidSourceBuilder()
13858                         .setAggregateDebugReportContributions(initialContributions)
13859                         .build();
13860         insertSource(source, source.getId());
13861 
13862         // Verify persisted value
13863         assertThat(getFirstSourceFromDb().getAggregateDebugReportContributions())
13864                 .isEqualTo(initialContributions);
13865 
13866         // Execution
13867         // Update the value
13868         int expectedUpdatedContributions = 65536;
13869         source.setAggregateDebugContributions(expectedUpdatedContributions);
13870         mDatastoreManager.runInTransaction(
13871                 measurementDao -> measurementDao.updateSourceAggregateDebugContributions(source));
13872 
13873         // Verification
13874         assertThat(getFirstSourceFromDb().getAggregateDebugReportContributions())
13875                 .isEqualTo(expectedUpdatedContributions);
13876     }
13877 
13878     /** Test that records in SourceContract Table are fetched properly. */
13879     @Test
testFetchAllSourceRegistrations_pass()13880     public void testFetchAllSourceRegistrations_pass() {
13881         Source source1 =
13882                 SourceFixture.getMinimalValidSourceBuilder()
13883                         .setId("1")
13884                         .setEventTime(8640000000L)
13885                         .setExpiryTime(8640000010L)
13886                         .setDebugKey(new UnsignedLong(7834690L))
13887                         .build();
13888         insertSource(source1, source1.getId());
13889 
13890         Source source2 =
13891                 SourceFixture.getMinimalValidSourceBuilder()
13892                         .setId("2")
13893                         .setEventTime(8640000000L)
13894                         .setExpiryTime(8640000010L)
13895                         .setDebugKey(new UnsignedLong(7834690L))
13896                         .build();
13897         insertSource(source2, source2.getId());
13898 
13899         List<Source> fetchedAllSourceRegistration =
13900                 mDatastoreManager
13901                         .runInTransactionWithResult(dao -> dao.fetchAllSourceRegistrations())
13902                         .orElseThrow();
13903 
13904         assertThat(fetchedAllSourceRegistration).isNotNull();
13905         assertThat(fetchedAllSourceRegistration.size()).isEqualTo(2);
13906 
13907         assertThat(fetchedAllSourceRegistration.get(0)).isEqualTo(source1);
13908         assertThat(fetchedAllSourceRegistration.get(1)).isEqualTo(source2);
13909     }
13910 
13911     @Test
testFetchAllSourceRegistrations_passMultipleDestinations()13912     public void testFetchAllSourceRegistrations_passMultipleDestinations() {
13913         List<Uri> webDestinations1 =
13914                 List.of(
13915                         Uri.parse("https://first-place.test"),
13916                         Uri.parse("https://second-place.test"),
13917                         Uri.parse("https://third-place.test"));
13918 
13919         Source source1 =
13920                 SourceFixture.getMinimalValidSourceBuilder()
13921                         .setId("1")
13922                         .setWebDestinations(webDestinations1)
13923                         .build();
13924         insertSource(source1, source1.getId());
13925 
13926         List<Uri> webDestinations2 =
13927                 List.of(
13928                         Uri.parse("https://not-first-place.test"),
13929                         Uri.parse("https://not-second-place.test"),
13930                         Uri.parse("https://third-place.test"));
13931         Source source2 =
13932                 SourceFixture.getMinimalValidSourceBuilder()
13933                         .setId("2")
13934                         .setWebDestinations(webDestinations2)
13935                         .build();
13936         insertSource(source2, source2.getId());
13937 
13938         List<Source> fetchedAllSourceRegistration =
13939                 mDatastoreManager
13940                         .runInTransactionWithResult(dao -> dao.fetchAllSourceRegistrations())
13941                         .orElseThrow();
13942 
13943         assertThat(fetchedAllSourceRegistration).isNotNull();
13944         assertThat(fetchedAllSourceRegistration.size()).isEqualTo(2);
13945 
13946         assertThat(fetchedAllSourceRegistration.get(0)).isEqualTo(source1);
13947         assertThat(fetchedAllSourceRegistration.get(1)).isEqualTo(source2);
13948     }
13949 
13950     /** Test that records in TriggerContract Table are fetched properly. */
13951     @Test
testFetchAllTriggerRegistrations_pass()13952     public void testFetchAllTriggerRegistrations_pass() {
13953         Trigger trigger1 =
13954                 TriggerFixture.getValidTriggerBuilder()
13955                         .setId("trigger1")
13956                         .setTriggerTime(TriggerFixture.ValidTriggerParams.TRIGGER_TIME)
13957                         .setDebugKey(TriggerFixture.ValidTriggerParams.DEBUG_KEY)
13958                         .build();
13959 
13960         insertTrigger(trigger1, "trigger1");
13961 
13962         Trigger trigger2 =
13963                 TriggerFixture.getValidTriggerBuilder()
13964                         .setId("trigger2")
13965                         .setTriggerTime(TriggerFixture.ValidTriggerParams.TRIGGER_TIME)
13966                         .setDebugKey(TriggerFixture.ValidTriggerParams.DEBUG_KEY)
13967                         .build();
13968 
13969         insertTrigger(trigger2, "trigger2");
13970 
13971         List<Trigger> fetchedAllTriggerRegistration =
13972                 mDatastoreManager
13973                         .runInTransactionWithResult(dao -> dao.fetchAllTriggerRegistrations())
13974                         .orElseThrow();
13975 
13976         assertThat(fetchedAllTriggerRegistration).isNotNull();
13977         assertThat(fetchedAllTriggerRegistration.size()).isEqualTo(2);
13978 
13979         assertThat(fetchedAllTriggerRegistration.get(0)).isEqualTo(trigger1);
13980         assertThat(fetchedAllTriggerRegistration.get(1)).isEqualTo(trigger2);
13981     }
13982 
13983     /** Test that records in EventReport Table are fetched properly. */
13984     @Test
testFetchAllEventReports_pass()13985     public void testFetchAllEventReports_pass() {
13986         Source source1 =
13987                 SourceFixture.getMinimalValidSourceBuilder()
13988                         .setId("S1")
13989                         .setAttributionMode(Source.AttributionMode.FALSELY)
13990                         .build();
13991         insertSource(source1, source1.getId());
13992         Source source2 =
13993                 SourceFixture.getMinimalValidSourceBuilder()
13994                         .setId("S2")
13995                         .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
13996                         .build();
13997         insertSource(source2, source2.getId());
13998 
13999         Trigger trigger1 = TriggerFixture.getValidTriggerBuilder().setId("T1").build();
14000         insertTrigger(trigger1, trigger1.getId());
14001 
14002         EventReport eventReport1 =
14003                 EventReportFixture.getBaseEventReportBuild()
14004                         .setId("Event1")
14005                         .setSourceId("S1")
14006                         .setTriggerId(null)
14007                         .build();
14008 
14009         EventReport eventReport2 =
14010                 EventReportFixture.getBaseEventReportBuild()
14011                         .setId("Event2")
14012                         .setSourceId("S2")
14013                         .setTriggerId("T1")
14014                         .build();
14015 
14016         mDatastoreManager.runInTransaction(
14017                 (dao) -> {
14018                     dao.insertEventReport(eventReport1);
14019                     dao.insertEventReport(eventReport2);
14020                 });
14021 
14022         List<EventReport> fetchedAllEventReports =
14023                 mDatastoreManager
14024                         .runInTransactionWithResult(dao -> dao.fetchAllEventReports())
14025                         .orElseThrow();
14026         ;
14027 
14028         assertThat(fetchedAllEventReports).isNotNull();
14029         assertThat(fetchedAllEventReports.size()).isEqualTo(2);
14030 
14031         assertThat(fetchedAllEventReports.get(0)).isEqualTo(eventReport1);
14032         assertThat(fetchedAllEventReports.get(1)).isEqualTo(eventReport2);
14033     }
14034 
14035     /** Test that records in AggregateReport Table are fetched properly. */
14036     @Test
testFetchAllAggregatableReports_pass()14037     public void testFetchAllAggregatableReports_pass() {
14038         AggregateReport aggregatableReport1 =
14039                 AggregateReportFixture.getValidAggregateReportBuilder()
14040                         .setId("report1")
14041                         .setPublisher(Uri.parse("android-app://com.registrant1"))
14042                         .setAttributionDestination(Uri.parse("android-app://com.destination1"))
14043                         .build();
14044 
14045         AggregateReport aggregatableReport2 =
14046                 AggregateReportFixture.getValidAggregateReportBuilder()
14047                         .setId("report2")
14048                         .setPublisher(Uri.parse("android-app://com.registrant2"))
14049                         .setAttributionDestination(
14050                                 Uri.parse("android-app://com.destination2"))
14051                         .build();
14052 
14053         mDatastoreManager.runInTransaction(
14054                 (dao) -> {
14055                     dao.insertAggregateReport(aggregatableReport1);
14056                     dao.insertAggregateReport(aggregatableReport2);
14057                 });
14058 
14059         List<AggregateReport> fetchedAllAggregatableReports =
14060                 mDatastoreManager
14061                         .runInTransactionWithResult(dao -> dao.fetchAllAggregatableReports())
14062                         .orElseThrow();
14063 
14064         assertThat(fetchedAllAggregatableReports).isNotNull();
14065         assertThat(fetchedAllAggregatableReports.size()).isEqualTo(2);
14066 
14067         assertThat(fetchedAllAggregatableReports.get(0)).isEqualTo(aggregatableReport1);
14068         assertThat(fetchedAllAggregatableReports.get(1)).isEqualTo(aggregatableReport2);
14069     }
14070 
14071     @Test
testFetchAllDebugReports_pass()14072     public void testFetchAllDebugReports_pass() {
14073         DebugReport debugReport1 =
14074                 createDebugReport(/* id= */ "1", Uri.parse("android-app://debug1"), 1701206853050L);
14075 
14076         DebugReport debugReport2 =
14077                 createDebugReport(/* id= */ "2", Uri.parse("android-app://debug2"), 1701206853050L);
14078 
14079         mDatastoreManager.runInTransaction(
14080                 (dao) -> {
14081                     dao.insertDebugReport(debugReport1);
14082                     dao.insertDebugReport(debugReport2);
14083                 });
14084 
14085         List<DebugReport> fetchedAllDebugReports =
14086                 mDatastoreManager
14087                         .runInTransactionWithResult(dao -> dao.fetchAllDebugReports())
14088                         .orElseThrow();
14089 
14090         assertThat(fetchedAllDebugReports).isNotNull();
14091         assertThat(fetchedAllDebugReports.size()).isEqualTo(2);
14092 
14093         assertThat(fetchedAllDebugReports.get(0)).isEqualTo(debugReport1);
14094         assertThat(fetchedAllDebugReports.get(1)).isEqualTo(debugReport2);
14095     }
14096 
14097     @Test
testGetPendingCountUniqueReportIds_getsPendingReports()14098     public void testGetPendingCountUniqueReportIds_getsPendingReports() {
14099         CountUniqueReport report1 =
14100                 CountUniqueReportFixture.getValidCountUniqueReportBuilder()
14101                         .setReportId("CU1")
14102                         .setStatus(CountUniqueReport.ReportDeliveryStatus.PENDING)
14103                         .build();
14104         CountUniqueReport report2 =
14105                 CountUniqueReportFixture.getValidCountUniqueReportBuilder()
14106                         .setReportId("CU2")
14107                         .setStatus(CountUniqueReport.ReportDeliveryStatus.PENDING)
14108                         .build();
14109 
14110         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
14111 
14112         AbstractDbIntegrationTest.insertToDb(report1, db);
14113         AbstractDbIntegrationTest.insertToDb(report2, db);
14114 
14115         Optional<List<String>> idsOpt =
14116                 mDatastoreManager.runInTransactionWithResult(
14117                         (dao) -> dao.getPendingCountUniqueReportIds());
14118 
14119         assertThat(idsOpt.isPresent()).isTrue();
14120         List<String> ids = idsOpt.get();
14121         assertThat(ids.size()).isEqualTo(2);
14122 
14123         assertThat(ids.contains(report1.getReportId())).isTrue();
14124         assertThat(ids.contains(report2.getReportId())).isTrue();
14125     }
14126 
14127     @Test
testGetPendingCountUniqueReportIds_ignoresDeliveredReports()14128     public void testGetPendingCountUniqueReportIds_ignoresDeliveredReports() {
14129         CountUniqueReport report1 =
14130                 CountUniqueReportFixture.getValidCountUniqueReportBuilder()
14131                         .setReportId("CU1")
14132                         .setStatus(CountUniqueReport.ReportDeliveryStatus.DELIVERED)
14133                         .build();
14134         CountUniqueReport report2 =
14135                 CountUniqueReportFixture.getValidCountUniqueReportBuilder()
14136                         .setReportId("CU2")
14137                         .setStatus(CountUniqueReport.ReportDeliveryStatus.PENDING)
14138                         .build();
14139 
14140         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
14141 
14142         AbstractDbIntegrationTest.insertToDb(report1, db);
14143         AbstractDbIntegrationTest.insertToDb(report2, db);
14144 
14145         Optional<List<String>> idsOpt =
14146                 mDatastoreManager.runInTransactionWithResult(
14147                         (dao) -> dao.getPendingCountUniqueReportIds());
14148 
14149         assertThat(idsOpt.isPresent()).isTrue();
14150         List<String> ids = idsOpt.get();
14151         assertThat(ids.size()).isEqualTo(1);
14152 
14153         assertThat(ids.contains(report1.getReportId())).isFalse();
14154         assertThat(ids.contains(report2.getReportId())).isTrue();
14155     }
14156 
getFirstSourceFromDb()14157     private Source getFirstSourceFromDb() {
14158         return mDatastoreManager
14159                 .runInTransactionWithResult(
14160                         measurementDao ->
14161                                 measurementDao.getSource(
14162                                         getFirstIdFromDatastore(
14163                                                 SourceContract.TABLE, SourceContract.ID)))
14164                 .orElseThrow();
14165     }
14166 
getAggregateReportConsumer(SQLiteDatabase db)14167     private static Consumer<AggregateReport> getAggregateReportConsumer(SQLiteDatabase db) {
14168         Consumer<AggregateReport> aggregateReportConsumer =
14169                 aggregateReport -> {
14170                     ContentValues values = new ContentValues();
14171                     values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId());
14172                     values.put(
14173                             MeasurementTables.AggregateReport.SOURCE_ID,
14174                             aggregateReport.getSourceId());
14175                     values.put(
14176                             MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
14177                             aggregateReport.getAttributionDestination().toString());
14178                     values.put(
14179                             MeasurementTables.AggregateReport.SCHEDULED_REPORT_TIME,
14180                             aggregateReport.getScheduledReportTime());
14181                     values.put(
14182                             MeasurementTables.AggregateReport.STATUS, aggregateReport.getStatus());
14183                     db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
14184                 };
14185         return aggregateReportConsumer;
14186     }
14187 
getEventReportConsumer(SQLiteDatabase db)14188     private static Consumer<EventReport> getEventReportConsumer(SQLiteDatabase db) {
14189         Consumer<EventReport> eventReportConsumer =
14190                 eventReport -> {
14191                     ContentValues values = new ContentValues();
14192                     values.put(EventReportContract.ID, eventReport.getId());
14193                     values.put(
14194                             EventReportContract.ATTRIBUTION_DESTINATION,
14195                             eventReport.getAttributionDestinations().get(0).toString());
14196                     values.put(EventReportContract.REPORT_TIME, eventReport.getReportTime());
14197                     values.put(EventReportContract.STATUS, EventReport.Status.PENDING);
14198 
14199                     db.insert(EventReportContract.TABLE, null, values);
14200                 };
14201         return eventReportConsumer;
14202     }
14203 
insertInDb(SQLiteDatabase db, Source source)14204     private void insertInDb(SQLiteDatabase db, Source source) {
14205         ContentValues values = new ContentValues();
14206         values.put(SourceContract.ID, source.getId());
14207         values.put(SourceContract.STATUS, Source.Status.ACTIVE);
14208         values.put(SourceContract.EVENT_TIME, source.getEventTime());
14209         values.put(SourceContract.EXPIRY_TIME, source.getExpiryTime());
14210         values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId());
14211         values.put(SourceContract.PUBLISHER, source.getPublisher().toString());
14212         values.put(SourceContract.REGISTRANT, source.getRegistrant().toString());
14213         values.put(SourceContract.REGISTRATION_ORIGIN, source.getRegistrationOrigin().toString());
14214         if (source.getAttributedTriggers() != null) {
14215             values.put(SourceContract.EVENT_ATTRIBUTION_STATUS, source.attributedTriggersToJson());
14216         }
14217         if (source.getTriggerSpecs() != null) {
14218             values.put(SourceContract.TRIGGER_SPECS, source.getTriggerSpecs().encodeToJson());
14219         }
14220         db.insert(SourceContract.TABLE, null, values);
14221 
14222         // Insert source destinations
14223         if (source.getAppDestinations() != null) {
14224             for (Uri appDestination : source.getAppDestinations()) {
14225                 ContentValues destinationValues = new ContentValues();
14226                 destinationValues.put(
14227                         MeasurementTables.SourceDestination.SOURCE_ID, source.getId());
14228                 destinationValues.put(
14229                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.APP);
14230                 destinationValues.put(
14231                         MeasurementTables.SourceDestination.DESTINATION, appDestination.toString());
14232                 db.insert(MeasurementTables.SourceDestination.TABLE, null, destinationValues);
14233             }
14234         }
14235 
14236         if (source.getWebDestinations() != null) {
14237             for (Uri webDestination : source.getWebDestinations()) {
14238                 ContentValues destinationValues = new ContentValues();
14239                 destinationValues.put(
14240                         MeasurementTables.SourceDestination.SOURCE_ID, source.getId());
14241                 destinationValues.put(
14242                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.WEB);
14243                 destinationValues.put(
14244                         MeasurementTables.SourceDestination.DESTINATION, webDestination.toString());
14245                 db.insert(MeasurementTables.SourceDestination.TABLE, null, destinationValues);
14246             }
14247         }
14248     }
14249 
queryAndAssertSourceEntries( SQLiteDatabase db, String enrollmentId, List<String> expectedSourceIds)14250     private void queryAndAssertSourceEntries(
14251             SQLiteDatabase db, String enrollmentId, List<String> expectedSourceIds) {
14252         try (Cursor cursor =
14253                 db.query(
14254                         XnaIgnoredSourcesContract.TABLE,
14255                         new String[] {XnaIgnoredSourcesContract.SOURCE_ID},
14256                         XnaIgnoredSourcesContract.ENROLLMENT_ID + " = ?",
14257                         new String[] {enrollmentId},
14258                         null,
14259                         null,
14260                         null)) {
14261             assertEquals(expectedSourceIds.size(), cursor.getCount());
14262             for (int i = 0; i < expectedSourceIds.size() && cursor.moveToNext(); i++) {
14263                 assertEquals(expectedSourceIds.get(i), cursor.getString(0));
14264             }
14265         }
14266     }
14267 
createSourceBuilder()14268     private Source.Builder createSourceBuilder() {
14269         return new Source.Builder()
14270                 .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
14271                 .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
14272                 .setAppDestinations(SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATIONS)
14273                 .setWebDestinations(SourceFixture.ValidSourceParams.WEB_DESTINATIONS)
14274                 .setEnrollmentId(SourceFixture.ValidSourceParams.ENROLLMENT_ID)
14275                 .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
14276                 .setEventTime(SOURCE_EVENT_TIME)
14277                 .setExpiryTime(SourceFixture.ValidSourceParams.EXPIRY_TIME)
14278                 .setPriority(SourceFixture.ValidSourceParams.PRIORITY)
14279                 .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
14280                 .setInstallAttributionWindow(
14281                         SourceFixture.ValidSourceParams.INSTALL_ATTRIBUTION_WINDOW)
14282                 .setInstallCooldownWindow(SourceFixture.ValidSourceParams.INSTALL_COOLDOWN_WINDOW)
14283                 .setAttributionMode(SourceFixture.ValidSourceParams.ATTRIBUTION_MODE)
14284                 .setAggregateSource(SourceFixture.ValidSourceParams.buildAggregateSource())
14285                 .setFilterDataString(SourceFixture.ValidSourceParams.buildFilterDataString())
14286                 .setSharedFilterDataKeys(SourceFixture.ValidSourceParams.SHARED_FILTER_DATA_KEYS)
14287                 .setIsDebugReporting(true)
14288                 .setRegistrationId(UUID.randomUUID().toString())
14289                 .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
14290                 .setInstallTime(SourceFixture.ValidSourceParams.INSTALL_TIME)
14291                 .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
14292     }
14293 
createAggregateReportForSourceAndTrigger( Source source, Trigger trigger)14294     private AggregateReport createAggregateReportForSourceAndTrigger(
14295             Source source, Trigger trigger) {
14296         return createAggregateReportForSourceAndTrigger(
14297                 UUID.randomUUID().toString(), source, trigger);
14298     }
14299 
createEventReportForSourceAndTrigger(Source source, Trigger trigger)14300     private EventReport createEventReportForSourceAndTrigger(Source source, Trigger trigger)
14301             throws JSONException {
14302         return createEventReportForSourceAndTrigger(UUID.randomUUID().toString(), source, trigger);
14303     }
14304 
createAggregateReportForSourceAndTrigger( String reportId, Source source, Trigger trigger)14305     private AggregateReport createAggregateReportForSourceAndTrigger(
14306             String reportId, Source source, Trigger trigger) {
14307         return AggregateReportFixture.getValidAggregateReportBuilder()
14308                 .setId(reportId)
14309                 .setSourceId(source.getId())
14310                 .setTriggerId(trigger.getId())
14311                 .setTriggerTime(trigger.getTriggerTime())
14312                 .build();
14313     }
14314 
createEventReportForSourceAndTrigger( String reportId, Source source, Trigger trigger)14315     private EventReport createEventReportForSourceAndTrigger(
14316             String reportId, Source source, Trigger trigger) throws JSONException {
14317         EventTrigger eventTrigger = trigger.parseEventTriggers(mFakeFlags).get(0);
14318         return new EventReport.Builder()
14319                 .populateFromSourceAndTrigger(
14320                         source,
14321                         trigger,
14322                         eventTrigger.getTriggerData(),
14323                         eventTrigger,
14324                         new Pair<>(null, null),
14325                         new EventReportWindowCalcDelegate(mLegacyFlags),
14326                         new SourceNoiseHandler(mLegacyFlags),
14327                         source.getAttributionDestinations(trigger.getDestinationType()))
14328                 .setId(reportId)
14329                 .setSourceEventId(source.getEventId())
14330                 .setSourceId(source.getId())
14331                 .setTriggerId(trigger.getId())
14332                 .build();
14333     }
14334 
createEventReportForSourceAndTriggerForUninstall( String reportId, Source source, Trigger trigger)14335     private EventReport createEventReportForSourceAndTriggerForUninstall(
14336             String reportId, Source source, Trigger trigger) throws JSONException {
14337         EventTrigger eventTrigger = trigger.parseEventTriggers(mFakeFlags).get(0);
14338         return new EventReport.Builder()
14339                 .setTriggerTime(trigger.getTriggerTime())
14340                 .setSourceEventId(source.getEventId())
14341                 .setEnrollmentId(source.getEnrollmentId())
14342                 .setStatus(EventReport.Status.PENDING)
14343                 .setSourceType(source.getSourceType())
14344                 .setDebugReportStatus(EventReport.DebugReportStatus.NONE)
14345                 .setRegistrationOrigin(trigger.getRegistrationOrigin())
14346                 .setTriggerPriority(eventTrigger.getTriggerPriority())
14347                 .setTriggerData(eventTrigger.getTriggerData())
14348                 .setId(reportId)
14349                 .setAttributionDestinations(
14350                         source.getAttributionDestinations(trigger.getDestinationType()))
14351                 .setSourceId(source.getId())
14352                 .setTriggerId(trigger.getId())
14353                 .build();
14354     }
14355 
createDebugReport()14356     private DebugReport createDebugReport() {
14357         return createDebugReport(UUID.randomUUID().toString(), REGISTRANT, INSERTION_TIME);
14358     }
14359 
createDebugReport(String id, Uri registrant, long insertionTime)14360     private DebugReport createDebugReport(String id, Uri registrant, long insertionTime) {
14361         return new DebugReport.Builder()
14362                 .setId(id)
14363                 .setType("trigger-event-deduplicated")
14364                 .setBody(
14365                         " {\n"
14366                                 + "      \"attribution_destination\":"
14367                                 + " \"https://destination.example\",\n"
14368                                 + "      \"source_event_id\": \"45623\"\n"
14369                                 + "    }")
14370                 .setEnrollmentId("1")
14371                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
14372                 .setRegistrant(registrant)
14373                 .setInsertionTime(insertionTime)
14374                 .build();
14375     }
14376 
buildDebugReportWithInstalledRegistrant(String id)14377     private DebugReport buildDebugReportWithInstalledRegistrant(String id) {
14378         return new DebugReport.Builder()
14379                 .setId(id)
14380                 .setType("trigger-event-deduplicated")
14381                 .setBody(
14382                         " {\n"
14383                                 + "      \"attribution_destination\":"
14384                                 + " \"https://destination.example\",\n"
14385                                 + "      \"source_event_id\": \"45623\"\n"
14386                                 + "    }")
14387                 .setEnrollmentId("1")
14388                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
14389                 .setRegistrant(INSTALLED_REGISTRANT)
14390                 .setInsertionTime(INSERTION_TIME)
14391                 .build();
14392     }
14393 
buildDebugReportWithNotInstalledRegistrant(String id)14394     private DebugReport buildDebugReportWithNotInstalledRegistrant(String id) {
14395         return new DebugReport.Builder()
14396                 .setId(id)
14397                 .setType("trigger-event-deduplicated")
14398                 .setBody(
14399                         " {\n"
14400                                 + "      \"attribution_destination\":"
14401                                 + " \"https://destination.example\",\n"
14402                                 + "      \"source_event_id\": \"45623\"\n"
14403                                 + "    }")
14404                 .setEnrollmentId("1")
14405                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
14406                 .setRegistrant(NOT_INSTALLED_REGISTRANT)
14407                 .setInsertionTime(INSERTION_TIME)
14408                 .build();
14409     }
14410 
buildRegistrant(String appName)14411     private Uri buildRegistrant(String appName) {
14412         return Uri.parse("android-app://" + appName);
14413     }
14414 
insertAsyncRecordForPackageName(Uri... registrants)14415     private void insertAsyncRecordForPackageName(Uri... registrants) {
14416         for (Uri registrant : registrants) {
14417             AsyncRegistration validRecord =
14418                     AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
14419                             .setRegistrant(registrant)
14420                             .build();
14421 
14422             mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(validRecord));
14423         }
14424     }
14425 
insertSourceForPackageName(Uri... registrants)14426     private void insertSourceForPackageName(Uri... registrants) {
14427         for (Uri registrant : registrants) {
14428             Source validSource =
14429                     SourceFixture.getValidSourceBuilder().setRegistrant(registrant).build();
14430 
14431             mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
14432         }
14433     }
14434 
insertSourceForAttributionScope( List<String> attributionScopes, Long attributionScopeLimit, Long maxEventStates, long eventTime, List<Uri> webDestinations, List<Uri> appDestinations)14435     private Source insertSourceForAttributionScope(
14436             List<String> attributionScopes,
14437             Long attributionScopeLimit,
14438             Long maxEventStates,
14439             long eventTime,
14440             List<Uri> webDestinations,
14441             List<Uri> appDestinations) {
14442         return insertSourceForAttributionScope(
14443                 attributionScopes,
14444                 attributionScopeLimit,
14445                 maxEventStates,
14446                 eventTime,
14447                 webDestinations,
14448                 appDestinations,
14449                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
14450     }
14451 
insertSourceForAttributionScope( List<String> attributionScopes, Long attributionScopeLimit, Long maxEventStates, long eventTime, List<Uri> webDestinations, List<Uri> appDestinations, @NonNull Uri reportingOrigin)14452     private Source insertSourceForAttributionScope(
14453             List<String> attributionScopes,
14454             Long attributionScopeLimit,
14455             Long maxEventStates,
14456             long eventTime,
14457             List<Uri> webDestinations,
14458             List<Uri> appDestinations,
14459             @NonNull Uri reportingOrigin) {
14460         return insertSourceForAttributionScope(
14461                 attributionScopes,
14462                 attributionScopeLimit,
14463                 maxEventStates,
14464                 eventTime,
14465                 webDestinations,
14466                 appDestinations,
14467                 reportingOrigin,
14468                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
14469                 Source.SourceType.EVENT,
14470                 Source.Status.ACTIVE);
14471     }
14472 
insertSourceForAttributionScope( List<String> attributionScopes, Long attributionScopeLimit, Long maxEventStates, long eventTime, List<Uri> webDestinations, List<Uri> appDestinations, @NonNull Uri reportingOrigin, String registrationId, Source.SourceType sourceType, @Source.Status int status)14473     private Source insertSourceForAttributionScope(
14474             List<String> attributionScopes,
14475             Long attributionScopeLimit,
14476             Long maxEventStates,
14477             long eventTime,
14478             List<Uri> webDestinations,
14479             List<Uri> appDestinations,
14480             @NonNull Uri reportingOrigin,
14481             String registrationId,
14482             Source.SourceType sourceType,
14483             @Source.Status int status) {
14484         Source validSource =
14485                 SourceFixture.getValidSourceBuilder()
14486                         .setEventTime(eventTime)
14487                         .setAttributionScopeLimit(attributionScopeLimit)
14488                         .setMaxEventStates(maxEventStates)
14489                         .setWebDestinations(webDestinations)
14490                         .setAppDestinations(appDestinations)
14491                         .setAttributionScopes(attributionScopes)
14492                         .setRegistrationOrigin(reportingOrigin)
14493                         .setSourceType(sourceType)
14494                         .setStatus(status)
14495                         .setRegistrationId(registrationId)
14496                         .build();
14497 
14498         mDatastoreManager.runInTransaction(
14499                 (dao) -> {
14500                     dao.insertSource(validSource);
14501                     Source insertedSource = dao.getSource(validSource.getId());
14502                     boolean attributionScopeEnabled =
14503                             mLegacyFlags.getMeasurementEnableAttributionScope();
14504                     assertThat(insertedSource.getMaxEventStates())
14505                             .isEqualTo(attributionScopeEnabled ? maxEventStates : null);
14506                     assertThat(insertedSource.getAttributionScopeLimit())
14507                             .isEqualTo(attributionScopeEnabled ? attributionScopeLimit : null);
14508                     assertThat(dao.getSourceAttributionScopes(validSource.getId()))
14509                             .containsExactlyElementsIn(
14510                                     (!attributionScopeEnabled || attributionScopes == null)
14511                                             ? List.of()
14512                                             : attributionScopes);
14513                 });
14514         return validSource;
14515     }
14516 
insertSourceForAggregatableNamedBudgets( AggregatableNamedBudgets aggregatableNamedBudgets, long eventTime, List<Uri> webDestinations, List<Uri> appDestinations)14517     private Source insertSourceForAggregatableNamedBudgets(
14518             AggregatableNamedBudgets aggregatableNamedBudgets,
14519             long eventTime,
14520             List<Uri> webDestinations,
14521             List<Uri> appDestinations) {
14522         Source validSource =
14523                 SourceFixture.getValidSourceBuilder()
14524                         .setEventTime(eventTime)
14525                         .setWebDestinations(webDestinations)
14526                         .setAppDestinations(appDestinations)
14527                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN)
14528                         .setSourceType(Source.SourceType.EVENT)
14529                         .setStatus(Source.Status.ACTIVE)
14530                         .setRegistrationId(SourceFixture.ValidSourceParams.REGISTRATION_ID)
14531                         .setAggregatableNamedBudgets(aggregatableNamedBudgets)
14532                         .build();
14533 
14534         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
14535         return validSource;
14536     }
14537 
insertSourceForPopulatingAggregatableNamedBudgets( long eventTime, List<Uri> webDestinations, List<Uri> appDestinations)14538     private Source insertSourceForPopulatingAggregatableNamedBudgets(
14539             long eventTime, List<Uri> webDestinations, List<Uri> appDestinations) {
14540         Source validSource =
14541                 SourceFixture.getValidSourceBuilder()
14542                         .setEventTime(eventTime)
14543                         .setWebDestinations(webDestinations)
14544                         .setAppDestinations(appDestinations)
14545                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN)
14546                         .setSourceType(Source.SourceType.EVENT)
14547                         .setStatus(Source.Status.ACTIVE)
14548                         .setRegistrationId(SourceFixture.ValidSourceParams.REGISTRATION_ID)
14549                         .build();
14550 
14551         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
14552         return validSource;
14553     }
14554 
insertTriggerForPackageName(Uri... registrants)14555     private void insertTriggerForPackageName(Uri... registrants) {
14556         for (Uri registrant : registrants) {
14557             Trigger validTrigger =
14558                     TriggerFixture.getValidTriggerBuilder()
14559                             .setRegistrant(registrant)
14560                             .setId(UUID.randomUUID().toString())
14561                             .build();
14562 
14563             mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger));
14564         }
14565     }
14566 
setupSourceAndTriggerData()14567     private void setupSourceAndTriggerData() {
14568         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
14569         List<Source> sourcesList = new ArrayList<>();
14570         sourcesList.add(
14571                 SourceFixture.getMinimalValidSourceBuilder()
14572                         .setId("S1")
14573                         .setRegistrant(APP_TWO_SOURCES)
14574                         .setPublisher(APP_TWO_PUBLISHER)
14575                         .setPublisherType(EventSurfaceType.APP)
14576                         .build());
14577         sourcesList.add(
14578                 SourceFixture.getMinimalValidSourceBuilder()
14579                         .setId("S2")
14580                         .setRegistrant(APP_TWO_SOURCES)
14581                         .setPublisher(APP_TWO_PUBLISHER)
14582                         .setPublisherType(EventSurfaceType.APP)
14583                         .build());
14584         sourcesList.add(
14585                 SourceFixture.getMinimalValidSourceBuilder()
14586                         .setId("S3")
14587                         .setRegistrant(APP_ONE_SOURCE)
14588                         .setPublisher(APP_ONE_PUBLISHER)
14589                         .setPublisherType(EventSurfaceType.APP)
14590                         .build());
14591         sourcesList.add(
14592                 SourceFixture.getMinimalValidSourceBuilder()
14593                         .setId("S4")
14594                         .setRegistrant(APP_ONE_SOURCE)
14595                         .setPublisher(APP_ONE_PUBLISHER)
14596                         .setPublisherType(EventSurfaceType.APP)
14597                         .setStatus(Source.Status.MARKED_TO_DELETE)
14598                         .build());
14599         sourcesList.add(
14600                 SourceFixture.getMinimalValidSourceBuilder()
14601                         .setId("S5")
14602                         .setRegistrant(APP_TWO_SOURCES)
14603                         .setPublisher(APP_TWO_PUBLISHER)
14604                         .setPublisherType(EventSurfaceType.APP)
14605                         .setStatus(Source.Status.MARKED_TO_DELETE)
14606                         .build());
14607         sourcesList.forEach(source -> insertSource(source, source.getId()));
14608         List<Trigger> triggersList = new ArrayList<>();
14609         triggersList.add(
14610                 TriggerFixture.getValidTriggerBuilder()
14611                         .setId("T1")
14612                         .setRegistrant(APP_TWO_DESTINATION)
14613                         .build());
14614         triggersList.add(
14615                 TriggerFixture.getValidTriggerBuilder()
14616                         .setId("T2")
14617                         .setRegistrant(APP_TWO_DESTINATION)
14618                         .build());
14619         triggersList.add(
14620                 TriggerFixture.getValidTriggerBuilder()
14621                         .setId("T3")
14622                         .setRegistrant(APP_ONE_DESTINATION)
14623                         .build());
14624 
14625         // Add web triggers.
14626         triggersList.add(
14627                 TriggerFixture.getValidTriggerBuilder()
14628                         .setId("T4")
14629                         .setRegistrant(APP_BROWSER)
14630                         .setAttributionDestination(WEB_ONE_DESTINATION)
14631                         .build());
14632         triggersList.add(
14633                 TriggerFixture.getValidTriggerBuilder()
14634                         .setId("T5")
14635                         .setRegistrant(APP_BROWSER)
14636                         .setAttributionDestination(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN)
14637                         .build());
14638         triggersList.add(
14639                 TriggerFixture.getValidTriggerBuilder()
14640                         .setId("T7")
14641                         .setRegistrant(APP_BROWSER)
14642                         .setAttributionDestination(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2)
14643                         .build());
14644         triggersList.add(
14645                 TriggerFixture.getValidTriggerBuilder()
14646                         .setId("T8")
14647                         .setRegistrant(APP_BROWSER)
14648                         .setAttributionDestination(WEB_TWO_DESTINATION)
14649                         .build());
14650         triggersList.add(
14651                 TriggerFixture.getValidTriggerBuilder()
14652                         .setId("T9")
14653                         .setRegistrant(APP_BROWSER)
14654                         .setAttributionDestination(WEB_TWO_DESTINATION_WITH_PATH)
14655                         .build());
14656 
14657         for (Trigger trigger : triggersList) {
14658             ContentValues values = new ContentValues();
14659             values.put("_id", trigger.getId());
14660             values.put("registrant", trigger.getRegistrant().toString());
14661             values.put("attribution_destination", trigger.getAttributionDestination().toString());
14662             long row = db.insert("msmt_trigger", null, values);
14663             assertNotEquals("Trigger insertion failed", -1, row);
14664         }
14665     }
14666 
createWebTrigger(Uri attributionDestination)14667     private Trigger createWebTrigger(Uri attributionDestination) {
14668         return TriggerFixture.getValidTriggerBuilder()
14669                 .setId("ID" + mValueId++)
14670                 .setAttributionDestination(attributionDestination)
14671                 .setRegistrant(APP_BROWSER)
14672                 .build();
14673     }
14674 
createAppTrigger(Uri registrant, Uri destination)14675     private Trigger createAppTrigger(Uri registrant, Uri destination) {
14676         return TriggerFixture.getValidTriggerBuilder()
14677                 .setId("ID" + mValueId++)
14678                 .setAttributionDestination(destination)
14679                 .setRegistrant(registrant)
14680                 .build();
14681     }
14682 
addTriggersToDatabase(List<Trigger> triggersList)14683     private void addTriggersToDatabase(List<Trigger> triggersList) {
14684         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
14685 
14686         for (Trigger trigger : triggersList) {
14687             ContentValues values = new ContentValues();
14688             values.put("_id", trigger.getId());
14689             values.put("registrant", trigger.getRegistrant().toString());
14690             values.put("attribution_destination", trigger.getAttributionDestination().toString());
14691             long row = db.insert("msmt_trigger", null, values);
14692             assertNotEquals("Trigger insertion failed", -1, row);
14693         }
14694     }
14695 
setupSourceDataForPublisherTypeWeb()14696     private void setupSourceDataForPublisherTypeWeb() {
14697         List<Source> sourcesList = new ArrayList<>();
14698         sourcesList.add(
14699                 SourceFixture.getMinimalValidSourceBuilder()
14700                         .setId("W1")
14701                         .setPublisher(WEB_PUBLISHER_ONE)
14702                         .setPublisherType(EventSurfaceType.WEB)
14703                         .build());
14704         sourcesList.add(
14705                 SourceFixture.getMinimalValidSourceBuilder()
14706                         .setId("W21")
14707                         .setPublisher(WEB_PUBLISHER_TWO)
14708                         .setPublisherType(EventSurfaceType.WEB)
14709                         .build());
14710         sourcesList.add(
14711                 SourceFixture.getMinimalValidSourceBuilder()
14712                         .setId("W22")
14713                         .setPublisher(WEB_PUBLISHER_TWO)
14714                         .setPublisherType(EventSurfaceType.WEB)
14715                         .build());
14716         sourcesList.add(
14717                 SourceFixture.getMinimalValidSourceBuilder()
14718                         .setId("W23")
14719                         .setPublisher(WEB_PUBLISHER_TWO)
14720                         .setPublisherType(EventSurfaceType.WEB)
14721                         .setStatus(Source.Status.MARKED_TO_DELETE)
14722                         .build());
14723         sourcesList.add(
14724                 SourceFixture.getMinimalValidSourceBuilder()
14725                         .setId("S3")
14726                         .setPublisher(WEB_PUBLISHER_THREE)
14727                         .setPublisherType(EventSurfaceType.WEB)
14728                         .build());
14729         sourcesList.forEach(source -> insertSource(source, source.getId()));
14730     }
14731 
createSourceForIATest( String id, long currentTime, long priority, int eventTimePastDays, boolean expiredIAWindow, String enrollmentId)14732     private Source.Builder createSourceForIATest(
14733             String id,
14734             long currentTime,
14735             long priority,
14736             int eventTimePastDays,
14737             boolean expiredIAWindow,
14738             String enrollmentId) {
14739         return createSourceForIATest(
14740                 id,
14741                 currentTime,
14742                 priority,
14743                 eventTimePastDays,
14744                 expiredIAWindow,
14745                 enrollmentId,
14746                 REGISTRATION_ORIGIN);
14747     }
14748 
createSourceForIATest( String id, long currentTime, long priority, int eventTimePastDays, boolean expiredIAWindow, String enrollmentId, Uri registrationOrigin)14749     private Source.Builder createSourceForIATest(
14750             String id,
14751             long currentTime,
14752             long priority,
14753             int eventTimePastDays,
14754             boolean expiredIAWindow,
14755             String enrollmentId,
14756             Uri registrationOrigin) {
14757         return new Source.Builder()
14758                 .setId(id)
14759                 .setPublisher(Uri.parse("android-app://com.example.sample"))
14760                 .setRegistrant(Uri.parse("android-app://com.example.sample"))
14761                 .setEnrollmentId(enrollmentId)
14762                 .setExpiryTime(currentTime + DAYS.toMillis(30))
14763                 .setInstallAttributionWindow(DAYS.toMillis(expiredIAWindow ? 0 : 30))
14764                 .setAppDestinations(List.of(INSTALLED_PACKAGE))
14765                 .setEventTime(
14766                         currentTime
14767                                 - DAYS.toMillis(eventTimePastDays == -1 ? 10 : eventTimePastDays))
14768                 .setPriority(priority == -1 ? 100 : priority)
14769                 .setRegistrationOrigin(registrationOrigin);
14770     }
14771 
generateMockAggregateReport(String attributionDestination, int id)14772     private AggregateReport generateMockAggregateReport(String attributionDestination, int id) {
14773         return new AggregateReport.Builder()
14774                 .setId(String.valueOf(id))
14775                 .setAttributionDestination(Uri.parse(attributionDestination))
14776                 .build();
14777     }
14778 
generateMockAggregateReportBuilder( String attributionDestination, int id, String sourceId, String api)14779     private AggregateReport.Builder generateMockAggregateReportBuilder(
14780             String attributionDestination, int id, String sourceId, String api) {
14781         return new AggregateReport.Builder()
14782                 .setId(String.valueOf(id))
14783                 .setSourceId(sourceId)
14784                 .setAttributionDestination(Uri.parse(attributionDestination))
14785                 .setApi(api);
14786     }
14787 
generateMockAggregateReport( String attributionDestination, int id, String sourceId, String api)14788     private AggregateReport generateMockAggregateReport(
14789             String attributionDestination, int id, String sourceId, String api) {
14790         return generateMockAggregateReportBuilder(
14791                 attributionDestination, id, sourceId, api).build();
14792     }
14793 
generateMockAggregateReport( String attributionDestination, int id, String sourceId, long reportTime)14794     private AggregateReport generateMockAggregateReport(
14795             String attributionDestination, int id, String sourceId, long reportTime) {
14796         return new AggregateReport.Builder()
14797                 .setId(String.valueOf(id))
14798                 .setSourceId(sourceId)
14799                 .setAttributionDestination(Uri.parse(attributionDestination))
14800                 .setScheduledReportTime(reportTime)
14801                 .build();
14802     }
14803 
generateMockEventReport(String attributionDestination, int id)14804     private EventReport generateMockEventReport(String attributionDestination, int id) {
14805         return new EventReport.Builder()
14806                 .setId(String.valueOf(id))
14807                 .setAttributionDestinations(List.of(Uri.parse(attributionDestination)))
14808                 .build();
14809     }
14810 
generateMockEventReport( String attributionDestination, int id, long reportTime)14811     private EventReport generateMockEventReport(
14812             String attributionDestination, int id, long reportTime) {
14813         return new EventReport.Builder()
14814                 .setId(String.valueOf(id))
14815                 .setAttributionDestinations(List.of(Uri.parse(attributionDestination)))
14816                 .setReportTime(reportTime)
14817                 .build();
14818     }
14819 
assertAggregateReportCount( List<String> attributionDestinations, int destinationType, List<Integer> expectedCounts)14820     private void assertAggregateReportCount(
14821             List<String> attributionDestinations,
14822             int destinationType,
14823             List<Integer> expectedCounts) {
14824         IntStream.range(0, attributionDestinations.size())
14825                 .forEach(
14826                         i -> {
14827                             DatastoreManager.ThrowingCheckedFunction<Integer>
14828                                     aggregateReportCountPerDestination =
14829                                             measurementDao ->
14830                                                     measurementDao
14831                                                             .getNumAggregateReportsPerDestination(
14832                                                                     Uri.parse(
14833                                                                             attributionDestinations
14834                                                                                     .get(i)),
14835                                                                     destinationType);
14836                             assertEquals(
14837                                     expectedCounts.get(i),
14838                                     mDatastoreManager
14839                                             .runInTransactionWithResult(
14840                                                     aggregateReportCountPerDestination)
14841                                             .orElseThrow());
14842                         });
14843     }
14844 
assertEventReportCount( List<String> attributionDestinations, int destinationType, List<Integer> expectedCounts)14845     private void assertEventReportCount(
14846             List<String> attributionDestinations,
14847             int destinationType,
14848             List<Integer> expectedCounts) {
14849         IntStream.range(0, attributionDestinations.size())
14850                 .forEach(
14851                         i -> {
14852                             DatastoreManager.ThrowingCheckedFunction<Integer>
14853                                     numEventReportsPerDestination =
14854                                             measurementDao ->
14855                                                     measurementDao.getNumEventReportsPerDestination(
14856                                                             Uri.parse(
14857                                                                     attributionDestinations.get(i)),
14858                                                             destinationType);
14859                             assertEquals(
14860                                     expectedCounts.get(i),
14861                                     mDatastoreManager
14862                                             .runInTransactionWithResult(
14863                                                     numEventReportsPerDestination)
14864                                             .orElseThrow());
14865                         });
14866     }
14867 
createAppDestinationVariants(int destinationNum)14868     private List<String> createAppDestinationVariants(int destinationNum) {
14869         return Arrays.asList(
14870                 "android-app://subdomain.destination-" + destinationNum + ".app/abcd",
14871                 "android-app://subdomain.destination-" + destinationNum + ".app",
14872                 "android-app://destination-" + destinationNum + ".app/abcd",
14873                 "android-app://destination-" + destinationNum + ".app",
14874                 "android-app://destination-" + destinationNum + ".ap");
14875     }
14876 
createWebDestinationVariants(int destinationNum)14877     private List<String> createWebDestinationVariants(int destinationNum) {
14878         return Arrays.asList(
14879                 "https://subdomain.destination-" + destinationNum + ".com/abcd",
14880                 "https://subdomain.destination-" + destinationNum + ".com",
14881                 "https://destination-" + destinationNum + ".com/abcd",
14882                 "https://destination-" + destinationNum + ".com",
14883                 "https://destination-" + destinationNum + ".co");
14884     }
14885 
getInstallAttributionStatus(String sourceDbId, SQLiteDatabase db)14886     private boolean getInstallAttributionStatus(String sourceDbId, SQLiteDatabase db) {
14887         Cursor cursor =
14888                 db.query(
14889                         SourceContract.TABLE,
14890                         new String[] {SourceContract.IS_INSTALL_ATTRIBUTED},
14891                         SourceContract.ID + " = ? ",
14892                         new String[] {sourceDbId},
14893                         null,
14894                         null,
14895                         null,
14896                         null);
14897         assertTrue(cursor.moveToFirst());
14898         return cursor.getInt(0) == 1;
14899     }
14900 
getInstallAttributionInstallTime(String sourceDbId, SQLiteDatabase db)14901     private Long getInstallAttributionInstallTime(String sourceDbId, SQLiteDatabase db) {
14902         Cursor cursor =
14903                 db.query(
14904                         SourceContract.TABLE,
14905                         new String[] {SourceContract.INSTALL_TIME},
14906                         SourceContract.ID + " = ? ",
14907                         new String[] {sourceDbId},
14908                         null,
14909                         null,
14910                         null,
14911                         null);
14912         assertTrue(cursor.moveToFirst());
14913         if (!cursor.isNull(0)) {
14914             return cursor.getLong(0);
14915         }
14916         return null;
14917     }
14918 
removeSources(List<String> dbIds, SQLiteDatabase db)14919     private void removeSources(List<String> dbIds, SQLiteDatabase db) {
14920         db.delete(
14921                 SourceContract.TABLE,
14922                 SourceContract.ID + " IN ( ? )",
14923                 new String[] {String.join(",", dbIds)});
14924     }
14925 
maybeInsertSourceDestinations( SQLiteDatabase db, Source source, String sourceId)14926     private static void maybeInsertSourceDestinations(
14927             SQLiteDatabase db, Source source, String sourceId) {
14928         if (source.getAppDestinations() != null) {
14929             for (Uri appDestination : source.getAppDestinations()) {
14930                 ContentValues values = new ContentValues();
14931                 values.put(MeasurementTables.SourceDestination.SOURCE_ID, sourceId);
14932                 values.put(
14933                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.APP);
14934                 values.put(
14935                         MeasurementTables.SourceDestination.DESTINATION, appDestination.toString());
14936                 long row = db.insert(MeasurementTables.SourceDestination.TABLE, null, values);
14937                 assertNotEquals("Source app destination insertion failed", -1, row);
14938             }
14939         }
14940         if (source.getWebDestinations() != null) {
14941             for (Uri webDestination : source.getWebDestinations()) {
14942                 ContentValues values = new ContentValues();
14943                 values.put(MeasurementTables.SourceDestination.SOURCE_ID, sourceId);
14944                 values.put(
14945                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.WEB);
14946                 values.put(
14947                         MeasurementTables.SourceDestination.DESTINATION, webDestination.toString());
14948                 long row = db.insert(MeasurementTables.SourceDestination.TABLE, null, values);
14949                 assertNotEquals("Source web destination insertion failed", -1, row);
14950             }
14951         }
14952     }
14953 
getAttributionBuilder(Source source, Trigger trigger)14954     private static Attribution.Builder getAttributionBuilder(Source source, Trigger trigger) {
14955         return new Attribution.Builder()
14956                 .setEnrollmentId(source.getEnrollmentId())
14957                 .setDestinationOrigin(source.getWebDestinations().get(0).toString())
14958                 .setDestinationSite(source.getAppDestinations().get(0).toString())
14959                 .setSourceOrigin(source.getPublisher().toString())
14960                 .setSourceSite(source.getPublisher().toString())
14961                 .setRegistrant(source.getRegistrant().toString())
14962                 .setTriggerTime(
14963                         trigger.getTriggerTime() - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS + 1)
14964                 .setRegistrationOrigin(trigger.getRegistrationOrigin());
14965     }
14966 
14967     /** Create {@link Attribution} object from SQLite datastore. */
constructAttributionFromCursor(Cursor cursor)14968     private static Attribution constructAttributionFromCursor(Cursor cursor) {
14969         Attribution.Builder builder = new Attribution.Builder();
14970         int index = cursor.getColumnIndex(AttributionContract.ID);
14971         if (index > -1 && !cursor.isNull(index)) {
14972             builder.setId(cursor.getString(index));
14973         }
14974         index = cursor.getColumnIndex(AttributionContract.SCOPE);
14975         if (index > -1 && !cursor.isNull(index)) {
14976             builder.setScope(cursor.getInt(index));
14977         }
14978         index = cursor.getColumnIndex(AttributionContract.SOURCE_SITE);
14979         if (index > -1 && !cursor.isNull(index)) {
14980             builder.setSourceSite(cursor.getString(index));
14981         }
14982         index = cursor.getColumnIndex(AttributionContract.SOURCE_ORIGIN);
14983         if (index > -1 && !cursor.isNull(index)) {
14984             builder.setSourceOrigin(cursor.getString(index));
14985         }
14986         index = cursor.getColumnIndex(AttributionContract.DESTINATION_SITE);
14987         if (index > -1 && !cursor.isNull(index)) {
14988             builder.setDestinationSite(cursor.getString(index));
14989         }
14990         index = cursor.getColumnIndex(AttributionContract.DESTINATION_ORIGIN);
14991         if (index > -1 && !cursor.isNull(index)) {
14992             builder.setDestinationOrigin(cursor.getString(index));
14993         }
14994         index = cursor.getColumnIndex(AttributionContract.ENROLLMENT_ID);
14995         if (index > -1 && !cursor.isNull(index)) {
14996             builder.setEnrollmentId(cursor.getString(index));
14997         }
14998         index = cursor.getColumnIndex(AttributionContract.TRIGGER_TIME);
14999         if (index > -1 && !cursor.isNull(index)) {
15000             builder.setTriggerTime(cursor.getLong(index));
15001         }
15002         index = cursor.getColumnIndex(AttributionContract.REGISTRANT);
15003         if (index > -1 && !cursor.isNull(index)) {
15004             builder.setRegistrant(cursor.getString(index));
15005         }
15006         index = cursor.getColumnIndex(AttributionContract.REGISTRATION_ORIGIN);
15007         if (index > -1 && !cursor.isNull(index)) {
15008             builder.setRegistrationOrigin(Uri.parse(cursor.getString(index)));
15009         }
15010         return builder.build();
15011     }
15012 
getAttribution(String attributionId, SQLiteDatabase db)15013     private Attribution getAttribution(String attributionId, SQLiteDatabase db) {
15014         try (Cursor cursor =
15015                 db.query(
15016                         AttributionContract.TABLE,
15017                         /* columns= */ null,
15018                         AttributionContract.ID + " = ? ",
15019                         new String[] {attributionId},
15020                         /* groupBy= */ null,
15021                         /* having= */ null,
15022                         /* orderBy= */ null,
15023                         /* limit= */ null)) {
15024             if (cursor.getCount() == 0) {
15025                 return null;
15026             }
15027             cursor.moveToNext();
15028             return constructAttributionFromCursor(cursor);
15029         }
15030     }
15031 
insertAttributedTrigger(TriggerSpecs triggerSpecs, EventReport eventReport)15032     private static void insertAttributedTrigger(TriggerSpecs triggerSpecs,
15033             EventReport eventReport) {
15034         triggerSpecs.getAttributedTriggers().add(
15035                 new AttributedTrigger(
15036                         eventReport.getTriggerId(),
15037                         eventReport.getTriggerPriority(),
15038                         eventReport.getTriggerData(),
15039                         eventReport.getTriggerValue(),
15040                         eventReport.getTriggerTime(),
15041                         eventReport.getTriggerDedupKey(),
15042                         eventReport.getTriggerDebugKey(),
15043                         false));
15044     }
15045 
insertAttributedTrigger(List<AttributedTrigger> attributedTriggers, EventReport eventReport)15046     private static void insertAttributedTrigger(List<AttributedTrigger> attributedTriggers,
15047             EventReport eventReport) {
15048         attributedTriggers.add(
15049                 new AttributedTrigger(
15050                         eventReport.getTriggerId(),
15051                         eventReport.getTriggerData(),
15052                         eventReport.getTriggerDedupKey()));
15053     }
15054 
getRandomIdsWith(int numIds, String idToInclude)15055     private static List<String> getRandomIdsWith(int numIds, String idToInclude) {
15056         List<String> result = new ArrayList<>();
15057         result.add(idToInclude);
15058         for (int i = 0; i < numIds; i++) {
15059             result.add(UUID.randomUUID().toString());
15060         }
15061         return result;
15062     }
15063 
getFirstSourceIdFromDatastore()15064     private static String getFirstSourceIdFromDatastore() {
15065         return getFirstIdFromDatastore(SourceContract.TABLE, SourceContract.ID);
15066     }
15067 
getFirstIdFromDatastore(String tableName, String idColumn)15068     private static String getFirstIdFromDatastore(String tableName, String idColumn) {
15069         try (Cursor cursor =
15070                 MeasurementDbHelper.getInstance()
15071                         .getReadableDatabase()
15072                         .query(tableName, new String[] {idColumn}, null, null, null, null, null)) {
15073             assertTrue(cursor.moveToNext());
15074             return cursor.getString(cursor.getColumnIndex(idColumn));
15075         }
15076     }
15077 
getNullableUriList(List<Uri> uris)15078     private static List<Uri> getNullableUriList(List<Uri> uris) {
15079         return uris == null ? null : uris;
15080     }
15081 
getMatchingSourceIds(Trigger trigger)15082     private List<String> getMatchingSourceIds(Trigger trigger) {
15083         List<Source> result =
15084                 mDatastoreManager
15085                         .runInTransactionWithResult(
15086                                 measurementDao -> measurementDao.getMatchingActiveSources(trigger))
15087                         .orElseThrow();
15088         return result.stream().map(Source::getId).collect(Collectors.toList());
15089     }
15090 
getMatchingSources(Trigger trigger)15091     private List<Source> getMatchingSources(Trigger trigger) {
15092         return mDatastoreManager
15093                 .runInTransactionWithResult(
15094                         measurementDao -> measurementDao.getMatchingActiveSources(trigger))
15095                 .orElseThrow();
15096     }
15097 
createCountUniqueReport( String reportId, String payload, Uri reportingOrigin, int status, int debugStatus, Long scheduledReportTime, String version, String debugKey, String contextId, String enrollmentId, int contributionValue, long contributionTime)15098     private CountUniqueReport createCountUniqueReport(
15099             String reportId,
15100             String payload,
15101             Uri reportingOrigin,
15102             int status,
15103             int debugStatus,
15104             Long scheduledReportTime,
15105             String version,
15106             String debugKey,
15107             String contextId,
15108             String enrollmentId,
15109             int contributionValue,
15110             long contributionTime) {
15111         CountUniqueReport.Builder builder = new CountUniqueReport.Builder();
15112         builder.setReportId(reportId);
15113         builder.setPayload(payload);
15114         builder.setReportingOrigin(reportingOrigin);
15115         builder.setStatus(status);
15116         builder.setDebugReportStatus(debugStatus);
15117         builder.setScheduledReportTime(scheduledReportTime);
15118         builder.setApiVersion(version);
15119         builder.setDebugKey(debugKey);
15120         builder.setContextId(contextId);
15121         builder.setEnrollmentId(enrollmentId);
15122         builder.setContributionValue(contributionValue);
15123         builder.setContributionTime(contributionTime);
15124         return builder.build();
15125     }
15126 }
15127