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.mock;
52 import static org.mockito.Mockito.spy;
53 import static org.mockito.Mockito.when;
54 
55 import static java.util.concurrent.TimeUnit.DAYS;
56 
57 import android.adservices.measurement.DeletionRequest;
58 import android.content.ContentValues;
59 import android.content.Context;
60 import android.database.Cursor;
61 import android.database.DatabaseUtils;
62 import android.database.sqlite.SQLiteDatabase;
63 import android.net.Uri;
64 import android.util.Pair;
65 
66 import androidx.annotation.NonNull;
67 import androidx.test.core.app.ApplicationProvider;
68 
69 import com.android.adservices.common.WebUtil;
70 import com.android.adservices.data.measurement.MeasurementTables.DebugReportContract;
71 import com.android.adservices.mockito.AdServicesExtendedMockitoRule;
72 import com.android.adservices.service.FakeFlagsFactory;
73 import com.android.adservices.service.Flags;
74 import com.android.adservices.service.FlagsFactory;
75 import com.android.adservices.service.measurement.AggregatableNamedBudgets;
76 import com.android.adservices.service.measurement.AsyncRegistrationFixture;
77 import com.android.adservices.service.measurement.AsyncRegistrationFixture.ValidAsyncRegistrationParams;
78 import com.android.adservices.service.measurement.AttributedTrigger;
79 import com.android.adservices.service.measurement.Attribution;
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 
104 import com.google.common.collect.ImmutableList;
105 import com.google.common.collect.ImmutableMultiset;
106 import com.google.common.truth.Truth;
107 
108 import org.json.JSONException;
109 import org.junit.After;
110 import org.junit.Before;
111 import org.junit.Rule;
112 import org.junit.Test;
113 import org.junit.runner.RunWith;
114 import org.mockito.Mockito;
115 import org.mockito.internal.util.collections.Sets;
116 import org.mockito.junit.MockitoJUnitRunner;
117 import org.mockito.quality.Strictness;
118 
119 import java.time.Instant;
120 import java.util.ArrayList;
121 import java.util.Arrays;
122 import java.util.Collection;
123 import java.util.Collections;
124 import java.util.Comparator;
125 import java.util.HashSet;
126 import java.util.List;
127 import java.util.Map;
128 import java.util.Objects;
129 import java.util.Optional;
130 import java.util.Set;
131 import java.util.UUID;
132 import java.util.concurrent.TimeUnit;
133 import java.util.concurrent.atomic.AtomicLong;
134 import java.util.function.Consumer;
135 import java.util.function.Function;
136 import java.util.stream.Collectors;
137 import java.util.stream.IntStream;
138 import java.util.stream.Stream;
139 
140 @RunWith(MockitoJUnitRunner.class)
141 public class MeasurementDaoTest {
142     protected static final Context sContext = ApplicationProvider.getApplicationContext();
143     private static final Uri APP_TWO_SOURCES = Uri.parse("android-app://com.example1.two-sources");
144     private static final Uri APP_ONE_SOURCE = Uri.parse("android-app://com.example2.one-source");
145     private static final String DEFAULT_ENROLLMENT_ID = "enrollment-id";
146     private static final String ENROLLMENT_ID1 = "enrollment-id1";
147     private static final Uri APP_TWO_PUBLISHER =
148             Uri.parse("android-app://com.publisher2.two-sources");
149     private static final Uri APP_ONE_PUBLISHER =
150             Uri.parse("android-app://com.publisher1.one-source");
151     private static final Uri APP_NO_PUBLISHER =
152             Uri.parse("android-app://com.publisher3.no-sources");
153     private static final Uri APP_BROWSER = Uri.parse("android-app://com.example1.browser");
154     private static final Uri WEB_ONE_DESTINATION = WebUtil.validUri("https://example1.test");
155     private static final Uri WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN =
156             WebUtil.validUri("https://store.example1.test");
157     private static final Uri WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2 =
158             WebUtil.validUri("https://foo.example1.test");
159     private static final Uri WEB_TWO_DESTINATION = WebUtil.validUri("https://example2.test");
160     private static final Uri WEB_THREE_DESTINATION = WebUtil.validUri("https://example3.test");
161     private static final Uri WEB_TWO_DESTINATION_WITH_PATH =
162             WebUtil.validUri("https://www.example2.test/ad/foo");
163     private static final Uri APP_ONE_DESTINATION =
164             Uri.parse("android-app://com.example1.one-trigger");
165     private static final Uri APP_TWO_DESTINATION =
166             Uri.parse("android-app://com.example1.two-triggers");
167     private static final Uri APP_THREE_DESTINATION =
168             Uri.parse("android-app://com.example1.three-triggers");
169     private static final Uri APP_THREE_DESTINATION_PATH1 =
170             Uri.parse("android-app://com.example1.three-triggers/path1");
171     private static final Uri APP_THREE_DESTINATION_PATH2 =
172             Uri.parse("android-app://com.example1.three-triggers/path2");
173     private static final Uri APP_NO_TRIGGERS = Uri.parse("android-app://com.example1.no-triggers");
174     private static final Uri INSTALLED_PACKAGE = Uri.parse("android-app://com.example.installed");
175     private static final Uri WEB_PUBLISHER_ONE = WebUtil.validUri("https://not.example.test");
176     private static final Uri WEB_PUBLISHER_TWO = WebUtil.validUri("https://notexample.test");
177     // Differs from WEB_PUBLISHER_ONE by scheme.
178     private static final Uri WEB_PUBLISHER_THREE = WebUtil.validUri("http://not.example.test");
179     private static final Uri APP_DESTINATION = Uri.parse("android-app://com.destination.example");
180     private static final Uri REGISTRATION_ORIGIN =
181             WebUtil.validUri("https://subdomain.example.test");
182 
183     private static final Uri REGISTRANT = Uri.parse("android-app://com.example.abc");
184     private static final Uri INSTALLED_REGISTRANT = Uri.parse("android-app://installed-registrant");
185     private static final Uri NOT_INSTALLED_REGISTRANT =
186             Uri.parse("android-app://not-installed-registrant");
187 
188     private static final long INSERTION_TIME = 1617297798;
189     private static final long COOLDOWN_WINDOW = TimeUnit.HOURS.toMillis(2);
190     private static final long ATTRIBUTION_SCOPE_LIMIT = 3L;
191     private static final long MAX_EVENT_STATES = 1000L;
192     private static final String REGISTRATION_ID2 = "R2";
193 
194     // Fake ID count for initializing triggers.
195     private int mValueId = 1;
196     private Flags mFlags;
197     private DatastoreManager mDatastoreManager;
198     public static final Uri REGISTRATION_ORIGIN_2 =
199             WebUtil.validUri("https://subdomain_2.example.test");
200     public static final Uri REGISTRATION_ORIGIN_3 =
201             WebUtil.validUri("https://subdomain_3.example.test");
202     public static final Uri REGISTRATION_ORIGIN_4 =
203             WebUtil.validUri("https://subdomain_4.example.test");
204     public static final Uri REGISTRATION_ORIGIN_5 =
205             WebUtil.validUri("https://subdomain_5.example.test");
206 
207     public static final Uri TOP_LEVEL_REGISTRANT_1 = Uri.parse("android-app://com.example1.sample");
208     public static final Uri TOP_LEVEL_REGISTRANT_2 = Uri.parse("android-app://com.example2.sample");
209 
210     @Rule
211     public final AdServicesExtendedMockitoRule adServicesExtendedMockitoRule =
212             new AdServicesExtendedMockitoRule.Builder(this)
213                     .spyStatic(FlagsFactory.class)
214                     .spyStatic(MeasurementDbHelper.class)
215                     .setStrictness(Strictness.WARN)
216                     .build();
217 
218     @Before
before()219     public void before() {
220         ExtendedMockito.doReturn(FakeFlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
221         mFlags = FakeFlagsFactory.getFlagsForTest();
222         mDatastoreManager =
223                 new SQLDatastoreManager(
224                         MeasurementDbHelper.getInstance(),
225                         Mockito.mock(AdServicesErrorLogger.class));
226     }
227 
228     @After
cleanup()229     public void cleanup() {
230         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
231         for (String table : ALL_MSMT_TABLES) {
232             db.delete(table, null, null);
233         }
234     }
235 
236     @Test
testInsertSource()237     public void testInsertSource() {
238         Source validSource =
239                 SourceFixture.getValidSourceBuilder()
240                         .setEventReportWindows("{'start_time': 1, 'end_times': ['3600', '7200']}")
241                         .setStatus(Source.Status.MARKED_TO_DELETE)
242                         .setTriggerDataMatching(Source.TriggerDataMatching.EXACT)
243                         .setTriggerData(Set.of(new UnsignedLong(23L), new UnsignedLong(1L)))
244                         .build();
245         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
246 
247         String sourceId = getFirstSourceIdFromDatastore();
248         Source source =
249                 mDatastoreManager
250                         .runInTransactionWithResult(
251                                 measurementDao -> measurementDao.getSource(sourceId))
252                         .get();
253 
254         assertNotNull(source);
255         assertNotNull(source.getId());
256         assertNull(source.getAppDestinations());
257         assertNull(source.getWebDestinations());
258         assertEquals(validSource.getEnrollmentId(), source.getEnrollmentId());
259         assertEquals(validSource.getRegistrant(), source.getRegistrant());
260         assertEquals(validSource.getEventTime(), source.getEventTime());
261         assertEquals(validSource.getExpiryTime(), source.getExpiryTime());
262         assertEquals(validSource.getStatus(), source.getStatus());
263         assertEquals(validSource.getEventReportWindow(), source.getEventReportWindow());
264         assertEquals(
265                 validSource.getAggregatableReportWindow(), source.getAggregatableReportWindow());
266         assertEquals(validSource.getPriority(), source.getPriority());
267         assertEquals(validSource.getSourceType(), source.getSourceType());
268         assertEquals(
269                 validSource.getInstallAttributionWindow(), source.getInstallAttributionWindow());
270         assertEquals(validSource.getInstallCooldownWindow(), source.getInstallCooldownWindow());
271         assertEquals(validSource.getAttributionMode(), source.getAttributionMode());
272         assertEquals(
273                 validSource.getReinstallReattributionWindow(),
274                 source.getReinstallReattributionWindow());
275         assertEquals(validSource.getAggregateSource(), source.getAggregateSource());
276         assertEquals(validSource.getFilterDataString(), source.getFilterDataString());
277         assertEquals(validSource.getSharedFilterDataKeys(), source.getSharedFilterDataKeys());
278         assertEquals(validSource.getAggregateContributions(), source.getAggregateContributions());
279         assertEquals(validSource.isDebugReporting(), source.isDebugReporting());
280         assertEquals(validSource.getSharedAggregationKeys(), source.getSharedAggregationKeys());
281         assertEquals(validSource.getRegistrationId(), source.getRegistrationId());
282         assertEquals(validSource.getInstallTime(), source.getInstallTime());
283         assertEquals(validSource.getPlatformAdId(), source.getPlatformAdId());
284         assertEquals(validSource.getDebugAdId(), source.getDebugAdId());
285         assertEquals(validSource.getRegistrationOrigin(), source.getRegistrationOrigin());
286         assertEquals(
287                 validSource.hasCoarseEventReportDestinations(),
288                 source.hasCoarseEventReportDestinations());
289         assertEquals(validSource.getTriggerDataMatching(), source.getTriggerDataMatching());
290         assertEquals(validSource.getTriggerData(), source.getTriggerData());
291         assertEquals(validSource.getEventReportWindows(), source.getEventReportWindows());
292         assertEquals(SourceFixture.ValidSourceParams.SHARED_DEBUG_KEY, source.getSharedDebugKey());
293         assertEquals(0L, source.getDestinationLimitPriority());
294 
295         // Assert destinations were inserted into the source destination table.
296 
297         Pair<List<Uri>, List<Uri>> destinations =
298                 mDatastoreManager
299                         .runInTransactionWithResult(
300                                 measurementDao ->
301                                         measurementDao.getSourceDestinations(source.getId()))
302                         .get();
303         assertTrue(
304                 ImmutableMultiset.copyOf(validSource.getAppDestinations())
305                         .equals(ImmutableMultiset.copyOf(destinations.first)));
306         assertTrue(
307                 ImmutableMultiset.copyOf(validSource.getWebDestinations())
308                         .equals(ImmutableMultiset.copyOf(destinations.second)));
309     }
310 
311     @Test
testInsertSource_flexibleEventReport_equal()312     public void testInsertSource_flexibleEventReport_equal() throws JSONException {
313         Source validSource = SourceFixture.getValidSourceWithFlexEventReport();
314         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
315 
316         String sourceId = getFirstSourceIdFromDatastore();
317         Source source =
318                 mDatastoreManager
319                         .runInTransactionWithResult(
320                                 measurementDao -> measurementDao.getSource(sourceId))
321                         .get();
322         source.buildTriggerSpecs();
323 
324         assertNotNull(source);
325         assertNotNull(source.getId());
326         assertNull(source.getAppDestinations());
327         assertNull(source.getWebDestinations());
328         assertEquals(validSource.getEnrollmentId(), source.getEnrollmentId());
329         assertEquals(validSource.getRegistrant(), source.getRegistrant());
330         assertEquals(validSource.getEventTime(), source.getEventTime());
331         assertEquals(validSource.getExpiryTime(), source.getExpiryTime());
332         assertEquals(validSource.getEventReportWindow(), source.getEventReportWindow());
333         assertEquals(
334                 validSource.getAggregatableReportWindow(), source.getAggregatableReportWindow());
335         assertEquals(validSource.getPriority(), source.getPriority());
336         assertEquals(validSource.getSourceType(), source.getSourceType());
337         assertEquals(
338                 validSource.getInstallAttributionWindow(), source.getInstallAttributionWindow());
339         assertEquals(validSource.getInstallCooldownWindow(), source.getInstallCooldownWindow());
340         assertEquals(validSource.getAttributionMode(), source.getAttributionMode());
341         assertEquals(validSource.getAggregateSource(), source.getAggregateSource());
342         assertEquals(validSource.getFilterDataString(), source.getFilterDataString());
343         assertEquals(validSource.getAggregateContributions(), source.getAggregateContributions());
344         assertEquals(validSource.isDebugReporting(), source.isDebugReporting());
345         assertEquals(validSource.getSharedAggregationKeys(), source.getSharedAggregationKeys());
346         assertEquals(validSource.getRegistrationId(), source.getRegistrationId());
347         assertEquals(validSource.getInstallTime(), source.getInstallTime());
348         assertEquals(validSource.getPlatformAdId(), source.getPlatformAdId());
349         assertEquals(validSource.getDebugAdId(), source.getDebugAdId());
350         assertEquals(validSource.getRegistrationOrigin(), source.getRegistrationOrigin());
351         assertEquals(
352                 validSource.getTriggerSpecs().getMaxReports(),
353                 source.getMaxEventLevelReports().intValue());
354         assertEquals(
355                 validSource.getTriggerSpecs().encodeToJson(),
356                 source.getTriggerSpecsString());
357         assertNull(source.getEventAttributionStatus());
358         assertEquals(
359                 validSource.getTriggerSpecs().encodePrivacyParametersToJsonString(),
360                 source.getPrivacyParameters());
361         assertEquals(validSource.getTriggerSpecs(), source.getTriggerSpecs());
362 
363         // Assert destinations were inserted into the source destination table.
364         Pair<List<Uri>, List<Uri>> destinations =
365                 mDatastoreManager
366                         .runInTransactionWithResult(
367                                 measurementDao ->
368                                         measurementDao.getSourceDestinations(source.getId()))
369                         .get();
370         assertTrue(
371                 ImmutableMultiset.copyOf(validSource.getAppDestinations())
372                         .equals(ImmutableMultiset.copyOf(destinations.first)));
373         assertTrue(
374                 ImmutableMultiset.copyOf(validSource.getWebDestinations())
375                         .equals(ImmutableMultiset.copyOf(destinations.second)));
376     }
377 
378     @Test
testInsertSource_reachedDbSizeLimitOnEdgeCase_doNotInsert()379     public void testInsertSource_reachedDbSizeLimitOnEdgeCase_doNotInsert() {
380         insertSourceReachingDbSizeLimit(/* dbSize= */ 100L, /* dbSizeMaxLimit= */ 100L);
381     }
382 
383     @Test
testInsertSource_reachedDbSizeLimitUpperEdgeCase_doNotInsert()384     public void testInsertSource_reachedDbSizeLimitUpperEdgeCase_doNotInsert() {
385         insertSourceReachingDbSizeLimit(/* dbSize= */ 101L, /* dbSizeMaxLimit= */ 100L);
386     }
387 
388     @Test
testInsertSource_attributionScopeEnabled_success()389     public void testInsertSource_attributionScopeEnabled_success() {
390         mFlags = mock(Flags.class);
391         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
392         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
393         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
394 
395         Source validSource =
396                 insertSourceForAttributionScope(
397                         List.of("1", "2", "3"),
398                         ATTRIBUTION_SCOPE_LIMIT,
399                         MAX_EVENT_STATES,
400                         SOURCE_EVENT_TIME,
401                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
402                         List.of(APP_ONE_DESTINATION));
403         Source source =
404                 mDatastoreManager
405                         .runInTransactionWithResult(
406                                 measurementDao -> measurementDao.getSource(validSource.getId()))
407                         .get();
408         assertThat(source.getAttributionScopeLimit())
409                 .isEqualTo(validSource.getAttributionScopeLimit());
410         assertThat(source.getMaxEventStates()).isEqualTo(validSource.getMaxEventStates());
411         List<String> attributionScopes =
412                 mDatastoreManager
413                         .runInTransactionWithResult(
414                                 measurementDao ->
415                                         measurementDao.getSourceAttributionScopes(source.getId()))
416                         .get();
417         assertThat(attributionScopes).containsExactlyElementsIn(validSource.getAttributionScopes());
418     }
419 
420     @Test
421     public void
testInsertSource_attributionScopeDisabled_doesNotInsertAttributionScopeRelatedData()422             testInsertSource_attributionScopeDisabled_doesNotInsertAttributionScopeRelatedData() {
423         mFlags = mock(Flags.class);
424         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
425         doReturn(false).when(mFlags).getMeasurementEnableAttributionScope();
426         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
427 
428         Source validSource =
429                 insertSourceForAttributionScope(
430                         List.of("1", "2", "3"),
431                         ATTRIBUTION_SCOPE_LIMIT,
432                         MAX_EVENT_STATES,
433                         SOURCE_EVENT_TIME,
434                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
435                         List.of(APP_ONE_DESTINATION));
436         Source source =
437                 mDatastoreManager
438                         .runInTransactionWithResult(
439                                 measurementDao -> measurementDao.getSource(validSource.getId()))
440                         .get();
441 
442         assertThat(source.getAttributionScopeLimit()).isEqualTo(null);
443         assertThat(source.getMaxEventStates()).isEqualTo(null);
444         List<String> attributionScopes =
445                 mDatastoreManager
446                         .runInTransactionWithResult(
447                                 measurementDao ->
448                                         measurementDao.getSourceAttributionScopes(source.getId()))
449                         .get();
450         assertThat(attributionScopes).isEmpty();
451     }
452 
453     @Test
testInsertSource_aggregateDebugReportingEnabled_success()454     public void testInsertSource_aggregateDebugReportingEnabled_success() {
455         mFlags = mock(Flags.class);
456         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
457         doReturn(true).when(mFlags).getMeasurementEnableAggregateDebugReporting();
458         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
459 
460         Source validSource =
461                 SourceFixture.getValidSourceBuilder()
462                         .setEventReportWindows("{'start_time': 1, 'end_times': ['3600', '7200']}")
463                         .setStatus(Source.Status.MARKED_TO_DELETE)
464                         .setTriggerDataMatching(Source.TriggerDataMatching.EXACT)
465                         .setTriggerData(Set.of(new UnsignedLong(23L), new UnsignedLong(1L)))
466                         .build();
467         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
468 
469         String sourceId = getFirstSourceIdFromDatastore();
470         Source source =
471                 mDatastoreManager
472                         .runInTransactionWithResult(
473                                 measurementDao -> measurementDao.getSource(sourceId))
474                         .get();
475 
476         assertThat(source).isNotNull();
477         assertThat(source.getId()).isNotNull();
478         assertThat(source.getAppDestinations()).isNull();
479         assertThat(source.getWebDestinations()).isNull();
480         assertThat(validSource.getEnrollmentId()).isEqualTo(source.getEnrollmentId());
481         assertThat(validSource.getRegistrant()).isEqualTo(source.getRegistrant());
482         assertThat(validSource.getEventTime()).isEqualTo(source.getEventTime());
483         assertThat(validSource.getExpiryTime()).isEqualTo(source.getExpiryTime());
484         assertThat(validSource.getStatus()).isEqualTo(source.getStatus());
485         assertThat(validSource.getEventReportWindow()).isEqualTo(source.getEventReportWindow());
486         assertThat(validSource.getAggregatableReportWindow())
487                 .isEqualTo(source.getAggregatableReportWindow());
488         assertThat(validSource.getPriority()).isEqualTo(source.getPriority());
489         assertThat(validSource.getSourceType()).isEqualTo(source.getSourceType());
490         assertThat(validSource.getInstallAttributionWindow())
491                 .isEqualTo(source.getInstallAttributionWindow());
492         assertThat(validSource.getInstallCooldownWindow())
493                 .isEqualTo(source.getInstallCooldownWindow());
494         assertThat(validSource.getAttributionMode()).isEqualTo(source.getAttributionMode());
495         assertThat(validSource.getReinstallReattributionWindow())
496                 .isEqualTo(source.getReinstallReattributionWindow());
497         assertThat(validSource.getAggregateSource()).isEqualTo(source.getAggregateSource());
498         assertThat(validSource.getFilterDataString()).isEqualTo(source.getFilterDataString());
499         assertThat(validSource.getSharedFilterDataKeys())
500                 .isEqualTo(source.getSharedFilterDataKeys());
501         assertThat(validSource.getAggregateContributions())
502                 .isEqualTo(source.getAggregateContributions());
503         assertThat(validSource.isDebugReporting()).isEqualTo(source.isDebugReporting());
504         assertThat(validSource.getSharedAggregationKeys())
505                 .isEqualTo(source.getSharedAggregationKeys());
506         assertThat(validSource.getRegistrationId()).isEqualTo(source.getRegistrationId());
507         assertThat(validSource.getInstallTime()).isEqualTo(source.getInstallTime());
508         assertThat(validSource.getPlatformAdId()).isEqualTo(source.getPlatformAdId());
509         assertThat(validSource.getDebugAdId()).isEqualTo(source.getDebugAdId());
510         assertThat(validSource.getRegistrationOrigin()).isEqualTo(source.getRegistrationOrigin());
511         assertThat(validSource.hasCoarseEventReportDestinations())
512                 .isEqualTo(source.hasCoarseEventReportDestinations());
513         assertThat(validSource.getTriggerDataMatching()).isEqualTo(source.getTriggerDataMatching());
514         assertThat(validSource.getTriggerData()).isEqualTo(source.getTriggerData());
515         assertThat(validSource.getEventReportWindows()).isEqualTo(source.getEventReportWindows());
516         assertThat(SourceFixture.ValidSourceParams.SHARED_DEBUG_KEY)
517                 .isEqualTo(source.getSharedDebugKey());
518         assertThat(source.getDestinationLimitPriority()).isEqualTo(0L);
519         assertThat(validSource.getAggregateDebugReportingString())
520                 .isEqualTo(source.getAggregateDebugReportingString());
521         assertThat(validSource.getAggregateDebugReportContributions())
522                 .isEqualTo(source.getAggregateDebugReportContributions());
523 
524         // Assert destinations were inserted into the source destination table.
525 
526         Pair<List<Uri>, List<Uri>> destinations =
527                 mDatastoreManager
528                         .runInTransactionWithResult(
529                                 measurementDao ->
530                                         measurementDao.getSourceDestinations(source.getId()))
531                         .get();
532         assertThat(
533                         ImmutableMultiset.copyOf(validSource.getAppDestinations())
534                                 .equals(ImmutableMultiset.copyOf(destinations.first)))
535                 .isTrue();
536         assertThat(
537                         ImmutableMultiset.copyOf(validSource.getWebDestinations())
538                                 .equals(ImmutableMultiset.copyOf(destinations.second)))
539                 .isTrue();
540     }
541 
542     @Test
testInsertSource_aggregateDebugReportingDisabled_relatedDataNotInserted()543     public void testInsertSource_aggregateDebugReportingDisabled_relatedDataNotInserted() {
544         mFlags = mock(Flags.class);
545         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
546         doReturn(false).when(mFlags).getMeasurementEnableAggregateDebugReporting();
547         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
548 
549         Source validSource =
550                 SourceFixture.getValidSourceBuilder()
551                         .setEventReportWindows("{'start_time': 1, 'end_times': ['3600', '7200']}")
552                         .setStatus(Source.Status.MARKED_TO_DELETE)
553                         .setTriggerDataMatching(Source.TriggerDataMatching.EXACT)
554                         .setTriggerData(Set.of(new UnsignedLong(23L), new UnsignedLong(1L)))
555                         .build();
556         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
557 
558         String sourceId = getFirstSourceIdFromDatastore();
559         Source source =
560                 mDatastoreManager
561                         .runInTransactionWithResult(
562                                 measurementDao -> measurementDao.getSource(sourceId))
563                         .get();
564 
565         assertThat(source).isNotNull();
566         assertThat(source.getId()).isNotNull();
567         assertThat(source.getAppDestinations()).isNull();
568         assertThat(source.getWebDestinations()).isNull();
569         assertThat(validSource.getEnrollmentId()).isEqualTo(source.getEnrollmentId());
570         assertThat(validSource.getRegistrant()).isEqualTo(source.getRegistrant());
571         assertThat(validSource.getEventTime()).isEqualTo(source.getEventTime());
572         assertThat(validSource.getExpiryTime()).isEqualTo(source.getExpiryTime());
573         assertThat(validSource.getStatus()).isEqualTo(source.getStatus());
574         assertThat(validSource.getEventReportWindow()).isEqualTo(source.getEventReportWindow());
575         assertThat(validSource.getAggregatableReportWindow())
576                 .isEqualTo(source.getAggregatableReportWindow());
577         assertThat(validSource.getPriority()).isEqualTo(source.getPriority());
578         assertThat(validSource.getSourceType()).isEqualTo(source.getSourceType());
579         assertThat(validSource.getInstallAttributionWindow())
580                 .isEqualTo(source.getInstallAttributionWindow());
581         assertThat(validSource.getInstallCooldownWindow())
582                 .isEqualTo(source.getInstallCooldownWindow());
583         assertThat(validSource.getAttributionMode()).isEqualTo(source.getAttributionMode());
584         assertThat(validSource.getReinstallReattributionWindow())
585                 .isEqualTo(source.getReinstallReattributionWindow());
586         assertThat(validSource.getAggregateSource()).isEqualTo(source.getAggregateSource());
587         assertThat(validSource.getFilterDataString()).isEqualTo(source.getFilterDataString());
588         assertThat(validSource.getSharedFilterDataKeys())
589                 .isEqualTo(source.getSharedFilterDataKeys());
590         assertThat(validSource.getAggregateContributions())
591                 .isEqualTo(source.getAggregateContributions());
592         assertThat(validSource.isDebugReporting()).isEqualTo(source.isDebugReporting());
593         assertThat(validSource.getSharedAggregationKeys())
594                 .isEqualTo(source.getSharedAggregationKeys());
595         assertThat(validSource.getRegistrationId()).isEqualTo(source.getRegistrationId());
596         assertThat(validSource.getInstallTime()).isEqualTo(source.getInstallTime());
597         assertThat(validSource.getPlatformAdId()).isEqualTo(source.getPlatformAdId());
598         assertThat(validSource.getDebugAdId()).isEqualTo(source.getDebugAdId());
599         assertThat(validSource.getRegistrationOrigin()).isEqualTo(source.getRegistrationOrigin());
600         assertThat(validSource.hasCoarseEventReportDestinations())
601                 .isEqualTo(source.hasCoarseEventReportDestinations());
602         assertThat(validSource.getTriggerDataMatching()).isEqualTo(source.getTriggerDataMatching());
603         assertThat(validSource.getTriggerData()).isEqualTo(source.getTriggerData());
604         assertThat(validSource.getEventReportWindows()).isEqualTo(source.getEventReportWindows());
605         assertThat(SourceFixture.ValidSourceParams.SHARED_DEBUG_KEY)
606                 .isEqualTo(source.getSharedDebugKey());
607         assertThat(source.getDestinationLimitPriority()).isEqualTo(0L);
608         assertThat(source.getAggregateDebugReportingString()).isEqualTo(null);
609         assertThat(source.getAggregateDebugReportContributions()).isEqualTo(0L);
610 
611         // Assert destinations were inserted into the source destination table.
612 
613         Pair<List<Uri>, List<Uri>> destinations =
614                 mDatastoreManager
615                         .runInTransactionWithResult(
616                                 measurementDao ->
617                                         measurementDao.getSourceDestinations(source.getId()))
618                         .get();
619         assertThat(
620                         ImmutableMultiset.copyOf(validSource.getAppDestinations())
621                                 .equals(ImmutableMultiset.copyOf(destinations.first)))
622                 .isTrue();
623         assertThat(
624                         ImmutableMultiset.copyOf(validSource.getWebDestinations())
625                                 .equals(ImmutableMultiset.copyOf(destinations.second)))
626                 .isTrue();
627     }
628 
629     @Test
testInsertSource_aggregatableNamedBudgetsEnabled_success()630     public void testInsertSource_aggregatableNamedBudgetsEnabled_success() {
631         mFlags = mock(Flags.class);
632         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
633         when(mFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(true);
634         when(mFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
635 
636         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
637         aggregatableNamedBudgets.createContributionBudget("budget1", 400);
638         aggregatableNamedBudgets.setContribution("budget1", 150);
639         aggregatableNamedBudgets.createContributionBudget("budget2", 750);
640         aggregatableNamedBudgets.setContribution("budget2", 320);
641         Source validSource =
642                 insertSourceForAggregatableNamedBudgets(
643                         aggregatableNamedBudgets,
644                         SOURCE_EVENT_TIME,
645                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
646                         List.of(APP_ONE_DESTINATION));
647 
648         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution1 =
649                 mDatastoreManager
650                         .runInTransactionWithResult(
651                                 measurementDao ->
652                                         measurementDao
653                                                 .getSourceAggregatableNamedBudgetAndContribution(
654                                                         validSource.getId(), "budget1"))
655                         .get();
656         assertThat(daoBudgetAndContribution1.getAggregateContribution())
657                 .isEqualTo(
658                         validSource
659                                 .getAggregatableNamedBudgets()
660                                 .maybeGetContribution("budget1")
661                                 .get());
662         assertThat(daoBudgetAndContribution1.getBudget())
663                 .isEqualTo(
664                         validSource.getAggregatableNamedBudgets().maybeGetBudget("budget1").get());
665         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution2 =
666                 mDatastoreManager
667                         .runInTransactionWithResult(
668                                 measurementDao ->
669                                         measurementDao
670                                                 .getSourceAggregatableNamedBudgetAndContribution(
671                                                         validSource.getId(), "budget2"))
672                         .get();
673         assertThat(daoBudgetAndContribution2.getAggregateContribution())
674                 .isEqualTo(
675                         validSource
676                                 .getAggregatableNamedBudgets()
677                                 .maybeGetContribution("budget2")
678                                 .get());
679         assertThat(daoBudgetAndContribution2.getBudget())
680                 .isEqualTo(
681                         validSource.getAggregatableNamedBudgets().maybeGetBudget("budget2").get());
682     }
683 
684     @Test
testInsertSource_aggregatableNamedBudgetsDisabled_relatedDataNotInserted()685     public void testInsertSource_aggregatableNamedBudgetsDisabled_relatedDataNotInserted() {
686         mFlags = mock(Flags.class);
687         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
688         when(mFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(false);
689         when(mFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
690 
691         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
692         aggregatableNamedBudgets.createContributionBudget("budget3", 470);
693         aggregatableNamedBudgets.setContribution("budget3", 153);
694         Source validSource =
695                 insertSourceForAggregatableNamedBudgets(
696                         aggregatableNamedBudgets,
697                         SOURCE_EVENT_TIME,
698                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
699                         List.of(APP_ONE_DESTINATION));
700 
701         Optional<AggregatableNamedBudgets.BudgetAndContribution> daoBudgetAndContribution =
702                 mDatastoreManager.runInTransactionWithResult(
703                         measurementDao ->
704                                 measurementDao.getSourceAggregatableNamedBudgetAndContribution(
705                                         validSource.getId(), "budget3"));
706         assertThat(daoBudgetAndContribution).isEmpty();
707     }
708 
insertSourceReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit)709     private void insertSourceReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit) {
710         final Source validSource = SourceFixture.getValidSource();
711 
712         // Mocking that the DB file has a size of 100 bytes
713         final MeasurementDbHelper spyMeasurementDbHelper = spy(MeasurementDbHelper.getInstance());
714         ExtendedMockito.doReturn(spyMeasurementDbHelper).when(MeasurementDbHelper::getInstance);
715         ExtendedMockito.doReturn(dbSize).when(spyMeasurementDbHelper).getDbFileSize();
716 
717         // Mocking that the flags return a max limit size of 100 bytes
718         Flags mockFlags = Mockito.mock(Flags.class);
719         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
720         ExtendedMockito.doReturn(dbSizeMaxLimit).when(mockFlags).getMeasurementDbSizeLimit();
721 
722         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
723 
724         try (Cursor sourceCursor =
725                 MeasurementDbHelper.getInstance()
726                         .getReadableDatabase()
727                         .query(SourceContract.TABLE, null, null, null, null, null, null)) {
728             assertFalse(sourceCursor.moveToNext());
729         }
730     }
731 
732     @Test
testInsertTrigger()733     public void testInsertTrigger() {
734         Trigger validTrigger = TriggerFixture.getValidTrigger();
735         mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger));
736 
737         try (Cursor triggerCursor =
738                 MeasurementDbHelper.getInstance()
739                         .getReadableDatabase()
740                         .query(TriggerContract.TABLE, null, null, null, null, null, null)) {
741             assertTrue(triggerCursor.moveToNext());
742             Trigger trigger = SqliteObjectMapper.constructTriggerFromCursor(triggerCursor);
743             assertNotNull(trigger);
744             assertNotNull(trigger.getId());
745             assertEquals(
746                     validTrigger.getAttributionDestination(), trigger.getAttributionDestination());
747             assertEquals(validTrigger.getDestinationType(), trigger.getDestinationType());
748             assertEquals(validTrigger.getEnrollmentId(), trigger.getEnrollmentId());
749             assertEquals(validTrigger.getRegistrant(), trigger.getRegistrant());
750             assertEquals(validTrigger.getTriggerTime(), trigger.getTriggerTime());
751             assertEquals(validTrigger.getEventTriggers(), trigger.getEventTriggers());
752             assertEquals(validTrigger.getAttributionConfig(), trigger.getAttributionConfig());
753             assertEquals(validTrigger.getAdtechKeyMapping(), trigger.getAdtechKeyMapping());
754             assertEquals(validTrigger.getPlatformAdId(), trigger.getPlatformAdId());
755             assertEquals(validTrigger.getDebugAdId(), trigger.getDebugAdId());
756             assertEquals(validTrigger.getRegistrationOrigin(), trigger.getRegistrationOrigin());
757             assertEquals(
758                     validTrigger.getAggregationCoordinatorOrigin(),
759                     trigger.getAggregationCoordinatorOrigin());
760             assertEquals(
761                     validTrigger.getAggregatableSourceRegistrationTimeConfig(),
762                     trigger.getAggregatableSourceRegistrationTimeConfig());
763             assertEquals(validTrigger.getTriggerContextId(), trigger.getTriggerContextId());
764             assertEquals(
765                     validTrigger.getAttributionScopesString(),
766                     trigger.getAttributionScopesString());
767             assertEquals(
768                     validTrigger.getAggregatableFilteringIdMaxBytes(),
769                     trigger.getAggregatableFilteringIdMaxBytes());
770             assertThat(validTrigger.getAggregateDebugReportingString())
771                     .isEqualTo(trigger.getAggregateDebugReportingString());
772         }
773     }
774 
775     @Test
testInsertTrigger_reachedDbSizeLimitOnEdgeCase_doNotInsert()776     public void testInsertTrigger_reachedDbSizeLimitOnEdgeCase_doNotInsert() {
777         insertTriggerReachingDbSizeLimit(/* dbSize= */ 100L, /* dbSizeMaxLimit= */ 100L);
778     }
779 
780     @Test
testInsertTrigger_reachedDbSizeLimitUpperEdgeCase_doNotInsert()781     public void testInsertTrigger_reachedDbSizeLimitUpperEdgeCase_doNotInsert() {
782         insertTriggerReachingDbSizeLimit(/* dbSize= */ 101L, /* dbSizeMaxLimit= */ 100L);
783     }
784 
insertTriggerReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit)785     private void insertTriggerReachingDbSizeLimit(long dbSize, long dbSizeMaxLimit) {
786         final Trigger validTrigger = TriggerFixture.getValidTrigger();
787 
788         // Mocking that the DB file has a size of 100 bytes
789         final MeasurementDbHelper spyMeasurementDbHelper = spy(MeasurementDbHelper.getInstance());
790         ExtendedMockito.doReturn(spyMeasurementDbHelper).when(MeasurementDbHelper::getInstance);
791         ExtendedMockito.doReturn(dbSize).when(spyMeasurementDbHelper).getDbFileSize();
792 
793         // Mocking that the flags return a max limit size of 100 bytes
794         Flags mockFlags = Mockito.mock(Flags.class);
795         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
796         ExtendedMockito.doReturn(dbSizeMaxLimit).when(mockFlags).getMeasurementDbSizeLimit();
797 
798         mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger));
799 
800         try (Cursor sourceCursor =
801                 MeasurementDbHelper.getInstance()
802                         .getReadableDatabase()
803                         .query(TriggerContract.TABLE, null, null, null, null, null, null)) {
804             assertFalse(sourceCursor.moveToNext());
805         }
806     }
807 
808     @Test
testGetNumSourcesPerPublisher_publisherTypeApp()809     public void testGetNumSourcesPerPublisher_publisherTypeApp() {
810         setupSourceAndTriggerData();
811         mDatastoreManager.runInTransaction(
812                 measurementDao -> {
813                     assertEquals(
814                             2,
815                             measurementDao.getNumSourcesPerPublisher(
816                                     APP_TWO_PUBLISHER, EventSurfaceType.APP));
817                 });
818         mDatastoreManager.runInTransaction(
819                 measurementDao -> {
820                     assertEquals(
821                             1,
822                             measurementDao.getNumSourcesPerPublisher(
823                                     APP_ONE_PUBLISHER, EventSurfaceType.APP));
824                 });
825         mDatastoreManager.runInTransaction(
826                 measurementDao -> {
827                     assertEquals(
828                             0,
829                             measurementDao.getNumSourcesPerPublisher(
830                                     APP_NO_PUBLISHER, EventSurfaceType.APP));
831                 });
832     }
833 
834     @Test
testGetNumSourcesPerPublisher_publisherTypeWeb()835     public void testGetNumSourcesPerPublisher_publisherTypeWeb() {
836         setupSourceDataForPublisherTypeWeb();
837         mDatastoreManager.runInTransaction(
838                 measurementDao -> {
839                     assertEquals(
840                             1,
841                             measurementDao.getNumSourcesPerPublisher(
842                                     WEB_PUBLISHER_ONE, EventSurfaceType.WEB));
843                 });
844         mDatastoreManager.runInTransaction(
845                 measurementDao -> {
846                     assertEquals(
847                             2,
848                             measurementDao.getNumSourcesPerPublisher(
849                                     WEB_PUBLISHER_TWO, EventSurfaceType.WEB));
850                 });
851         mDatastoreManager.runInTransaction(
852                 measurementDao -> {
853                     assertEquals(
854                             1,
855                             measurementDao.getNumSourcesPerPublisher(
856                                     WEB_PUBLISHER_THREE, EventSurfaceType.WEB));
857                 });
858     }
859 
860     @Test
testGetUninstalledAppNamesContainingData_withDataInSource()861     public void testGetUninstalledAppNamesContainingData_withDataInSource() {
862         // Setup records in source storage
863         final List<Uri> appsInstalled =
864                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
865         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
866         insertSourceForPackageName(
867                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
868                         .toArray(Uri[]::new));
869 
870         // Execution
871         final Optional<List<Uri>> uninstalledAppNames =
872                 mDatastoreManager.runInTransactionWithResult(
873                         measurementDao ->
874                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
875                                         appsInstalled));
876 
877         // Validation, apps not installed should be returned
878         assertNotNull(uninstalledAppNames);
879         assertTrue(uninstalledAppNames.isPresent());
880         assertEquals(2, uninstalledAppNames.get().size());
881         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
882         assertTrue(result.contains(buildRegistrant("qux")));
883         assertTrue(result.contains(buildRegistrant("quux")));
884     }
885 
886     @Test
testGetUninstalledAppNamesContainingData_withDataInTrigger()887     public void testGetUninstalledAppNamesContainingData_withDataInTrigger() {
888         // Setup records in trigger storage
889         final List<Uri> appsInstalled =
890                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
891         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
892         insertTriggerForPackageName(
893                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
894                         .toArray(Uri[]::new));
895 
896         // Execution
897         final Optional<List<Uri>> uninstalledAppNames =
898                 mDatastoreManager.runInTransactionWithResult(
899                         measurementDao ->
900                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
901                                         appsInstalled));
902 
903         // Validation, apps not installed should be returned
904         assertNotNull(uninstalledAppNames);
905         assertTrue(uninstalledAppNames.isPresent());
906         assertEquals(2, uninstalledAppNames.get().size());
907         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
908         assertTrue(result.contains(buildRegistrant("qux")));
909         assertTrue(result.contains(buildRegistrant("quux")));
910     }
911 
912     @Test
testGetUninstalledAppNamesContainingData_withDataInTriggerAndSource()913     public void testGetUninstalledAppNamesContainingData_withDataInTriggerAndSource() {
914         // Setup records in source and trigger storage
915         final List<Uri> appsInstalled =
916                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
917         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
918         final Uri[] appNames =
919                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
920                         .toArray(Uri[]::new);
921         insertSourceForPackageName(appNames);
922         insertTriggerForPackageName(appNames);
923 
924         // Execution
925         final Optional<List<Uri>> uninstalledAppNames =
926                 mDatastoreManager.runInTransactionWithResult(
927                         measurementDao ->
928                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
929                                         appsInstalled));
930 
931         // Validation, apps not installed should be returned
932         assertNotNull(uninstalledAppNames);
933         assertTrue(uninstalledAppNames.isPresent());
934         assertEquals(2, uninstalledAppNames.get().size());
935         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
936         assertTrue(result.contains(buildRegistrant("qux")));
937         assertTrue(result.contains(buildRegistrant("quux")));
938     }
939 
940     @Test
testGetUninstalledAppNamesContainingData_withDataInAsync()941     public void testGetUninstalledAppNamesContainingData_withDataInAsync() {
942         // Setup records in source storage
943         final List<Uri> appsInstalled =
944                 List.of(buildRegistrant("foo"), buildRegistrant("bar"), buildRegistrant("baz"));
945         final List<Uri> appsNotInstalled = List.of(buildRegistrant("qux"), buildRegistrant("quux"));
946         insertAsyncRecordForPackageName(
947                 Stream.concat(appsInstalled.stream(), appsNotInstalled.stream())
948                         .toArray(Uri[]::new));
949 
950         // Execution
951         final Optional<List<Uri>> uninstalledAppNames =
952                 mDatastoreManager.runInTransactionWithResult(
953                         measurementDao ->
954                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
955                                         appsInstalled));
956 
957         // Validation, apps not installed should be returned
958         assertNotNull(uninstalledAppNames);
959         assertTrue(uninstalledAppNames.isPresent());
960         assertEquals(2, uninstalledAppNames.get().size());
961         Set<Uri> result = new HashSet<>(uninstalledAppNames.get());
962         assertTrue(result.contains(buildRegistrant("qux")));
963         assertTrue(result.contains(buildRegistrant("quux")));
964     }
965 
966     @Test
testGetUninstalledAppNamesContainingData_withAnyDataAndNoAppsUninstalled()967     public void testGetUninstalledAppNamesContainingData_withAnyDataAndNoAppsUninstalled() {
968         // Setup records in source storage (any storage), all these apps are still installed
969         final List<Uri> appsInstalled =
970                 List.of(
971                         buildRegistrant("foo"),
972                         buildRegistrant("bar"),
973                         buildRegistrant("baz"),
974                         buildRegistrant("qux"),
975                         buildRegistrant("quux"));
976         insertSourceForPackageName(appsInstalled.stream().toArray(Uri[]::new));
977 
978         // Execution
979         final Optional<List<Uri>> uninstalledAppNames =
980                 mDatastoreManager.runInTransactionWithResult(
981                         measurementDao ->
982                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
983                                         appsInstalled));
984 
985         // Validation, apps not installed should be returned
986         assertNotNull(uninstalledAppNames);
987         assertTrue(uninstalledAppNames.isPresent());
988         assertTrue(uninstalledAppNames.get().isEmpty());
989         assertEquals(0, uninstalledAppNames.get().size());
990     }
991 
992     @Test
testGetUninstalledAppNamesContainingData_withNoData()993     public void testGetUninstalledAppNamesContainingData_withNoData() {
994         // Setup records, these apps don't have source nor trigger data
995         final List<Uri> appsInstalled =
996                 List.of(
997                         buildRegistrant("foo"),
998                         buildRegistrant("bar"),
999                         buildRegistrant("baz"),
1000                         buildRegistrant("qux"),
1001                         buildRegistrant("quux"));
1002 
1003         // Execution
1004         final Optional<List<Uri>> uninstalledAppNames =
1005                 mDatastoreManager.runInTransactionWithResult(
1006                         measurementDao ->
1007                                 measurementDao.getUninstalledAppNamesHavingMeasurementData(
1008                                         appsInstalled));
1009 
1010         // Validation, apps not installed should be returned
1011         assertNotNull(uninstalledAppNames);
1012         assertTrue(uninstalledAppNames.isPresent());
1013         assertTrue(uninstalledAppNames.get().isEmpty());
1014         assertEquals(0, uninstalledAppNames.get().size());
1015     }
1016 
1017     @Test
testCountDistinctReportingOriginPerPublisherXDestinationInAttribution_atWindow()1018     public void testCountDistinctReportingOriginPerPublisherXDestinationInAttribution_atWindow() {
1019         Uri sourceSite = Uri.parse("android-app://publisher.app");
1020         Uri appDestination = Uri.parse("android-app://destination.app");
1021         String registrant = "android-app://registrant.app";
1022         List<Attribution> attributionsWithAppDestinations =
1023                 getAttributionsWithDifferentReportingOrigins(
1024                         4, appDestination, 5000000001L, sourceSite, registrant);
1025         for (Attribution attribution : attributionsWithAppDestinations) {
1026             insertAttribution(attribution);
1027         }
1028         Uri excludedRegistrationOrigin = WebUtil.validUri("https://subdomain0.example.test");
1029         mDatastoreManager.runInTransaction(
1030                 measurementDao -> {
1031                     assertEquals(
1032                             Integer.valueOf(3),
1033                             measurementDao
1034                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1035                                             sourceSite,
1036                                             appDestination,
1037                                             excludedRegistrationOrigin,
1038                                             5000000000L,
1039                                             6000000000L));
1040                 });
1041     }
1042 
1043     @Test
testCountDistinctReportingOriginsPerPublisherXDestInAttribution_beyondWindow()1044     public void testCountDistinctReportingOriginsPerPublisherXDestInAttribution_beyondWindow() {
1045         Uri sourceSite = Uri.parse("android-app://publisher.app");
1046         Uri appDestination = Uri.parse("android-app://destination.app");
1047         String registrant = "android-app://registrant.app";
1048         List<Attribution> attributionsWithAppDestinations =
1049                 getAttributionsWithDifferentReportingOrigins(
1050                         4, appDestination, 5000000000L, sourceSite, registrant);
1051         for (Attribution attribution : attributionsWithAppDestinations) {
1052             insertAttribution(attribution);
1053         }
1054         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain.example0.test");
1055         mDatastoreManager.runInTransaction(
1056                 measurementDao -> {
1057                     assertEquals(
1058                             Integer.valueOf(0),
1059                             measurementDao
1060                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1061                                             sourceSite,
1062                                             appDestination,
1063                                             excludedReportingOrigin,
1064                                             5000000000L,
1065                                             6000000000L));
1066                 });
1067     }
1068 
1069     @Test
testInsertDebugReport()1070     public void testInsertDebugReport() {
1071         DebugReport debugReport = createDebugReport();
1072         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport));
1073 
1074         try (Cursor cursor =
1075                 MeasurementDbHelper.getInstance()
1076                         .getReadableDatabase()
1077                         .query(DebugReportContract.TABLE, null, null, null, null, null, null)) {
1078             assertTrue(cursor.moveToNext());
1079             DebugReport report = SqliteObjectMapper.constructDebugReportFromCursor(cursor);
1080             assertNotNull(report);
1081             assertNotNull(report.getId());
1082             assertEquals(debugReport.getType(), report.getType());
1083             assertEquals(debugReport.getBody().toString(), report.getBody().toString());
1084             assertEquals(debugReport.getEnrollmentId(), report.getEnrollmentId());
1085             assertEquals(debugReport.getRegistrationOrigin(), report.getRegistrationOrigin());
1086             assertEquals(debugReport.getReferenceId(), report.getReferenceId());
1087             assertEquals(debugReport.getInsertionTime(), report.getInsertionTime());
1088             assertEquals(debugReport.getRegistrant(), report.getRegistrant());
1089         }
1090     }
1091 
1092     @Test
singleAppTrigger_triggersPerDestination_returnsOne()1093     public void singleAppTrigger_triggersPerDestination_returnsOne() {
1094         List<Trigger> triggerList = new ArrayList<>();
1095         triggerList.add(createAppTrigger(APP_ONE_DESTINATION, APP_ONE_DESTINATION));
1096         addTriggersToDatabase(triggerList);
1097 
1098         mDatastoreManager.runInTransaction(
1099                 measurementDao ->
1100                         assertThat(
1101                                         measurementDao.getNumTriggersPerDestination(
1102                                                 APP_ONE_DESTINATION, EventSurfaceType.APP))
1103                                 .isEqualTo(1));
1104     }
1105 
1106     @Test
multipleAppTriggers_similarUris_triggersPerDestination()1107     public void multipleAppTriggers_similarUris_triggersPerDestination() {
1108         List<Trigger> triggerList = new ArrayList<>();
1109         triggerList.add(createAppTrigger(APP_TWO_DESTINATION, APP_TWO_DESTINATION));
1110         triggerList.add(createAppTrigger(APP_TWO_DESTINATION, APP_TWO_DESTINATION));
1111         addTriggersToDatabase(triggerList);
1112 
1113         mDatastoreManager.runInTransaction(
1114                 measurementDao ->
1115                         assertThat(
1116                                         measurementDao.getNumTriggersPerDestination(
1117                                                 APP_TWO_DESTINATION, EventSurfaceType.APP))
1118                                 .isEqualTo(2));
1119     }
1120 
1121     @Test
noAppTriggers_triggersPerDestination_returnsNone()1122     public void noAppTriggers_triggersPerDestination_returnsNone() {
1123         mDatastoreManager.runInTransaction(
1124                 measurementDao ->
1125                         assertThat(
1126                                         measurementDao.getNumTriggersPerDestination(
1127                                                 APP_NO_TRIGGERS, EventSurfaceType.APP))
1128                                 .isEqualTo(0));
1129     }
1130 
1131     @Test
multipleAppTriggers_differentPaths_returnsAllMatching()1132     public void multipleAppTriggers_differentPaths_returnsAllMatching() {
1133         List<Trigger> triggerList = new ArrayList<>();
1134         triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION));
1135         triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION_PATH1));
1136         triggerList.add(createAppTrigger(APP_THREE_DESTINATION, APP_THREE_DESTINATION_PATH2));
1137         addTriggersToDatabase(triggerList);
1138 
1139         mDatastoreManager.runInTransaction(
1140                 measurementDao -> {
1141                     assertThat(
1142                                     measurementDao.getNumTriggersPerDestination(
1143                                             APP_THREE_DESTINATION, EventSurfaceType.APP))
1144                             .isEqualTo(3);
1145                     // Try the same thing, but use the app uri with path to find number of triggers.
1146                     assertThat(
1147                                     measurementDao.getNumTriggersPerDestination(
1148                                             APP_THREE_DESTINATION_PATH1, EventSurfaceType.APP))
1149                             .isEqualTo(3);
1150                     assertThat(
1151                                     measurementDao.getNumTriggersPerDestination(
1152                                             APP_THREE_DESTINATION_PATH2, EventSurfaceType.APP))
1153                             .isEqualTo(3);
1154                     Uri unseenAppThreePath =
1155                             Uri.parse("android-app://com.example1.three-triggers/path3");
1156                     assertThat(
1157                                     measurementDao.getNumTriggersPerDestination(
1158                                             unseenAppThreePath, EventSurfaceType.APP))
1159                             .isEqualTo(3);
1160                 });
1161     }
1162 
1163     @Test
singleWebTrigger_triggersPerDestination_returnsOne()1164     public void singleWebTrigger_triggersPerDestination_returnsOne() {
1165         List<Trigger> triggerList = new ArrayList<>();
1166         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION));
1167         addTriggersToDatabase(triggerList);
1168 
1169         mDatastoreManager.runInTransaction(
1170                 measurementDao -> {
1171                     assertThat(
1172                                     measurementDao.getNumTriggersPerDestination(
1173                                             WEB_ONE_DESTINATION, EventSurfaceType.WEB))
1174                             .isEqualTo(1);
1175                 });
1176     }
1177 
1178     @Test
webTriggerMultipleSubDomains_triggersPerDestination_returnsAllMatching()1179     public void webTriggerMultipleSubDomains_triggersPerDestination_returnsAllMatching() {
1180         List<Trigger> triggerList = new ArrayList<>();
1181         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION));
1182         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN));
1183         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2));
1184         addTriggersToDatabase(triggerList);
1185 
1186         mDatastoreManager.runInTransaction(
1187                 measurementDao -> {
1188                     assertThat(
1189                                     measurementDao.getNumTriggersPerDestination(
1190                                             WEB_ONE_DESTINATION, EventSurfaceType.WEB))
1191                             .isEqualTo(3);
1192                     assertThat(
1193                                     measurementDao.getNumTriggersPerDestination(
1194                                             WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN,
1195                                             EventSurfaceType.WEB))
1196                             .isEqualTo(3);
1197                     assertThat(
1198                                     measurementDao.getNumTriggersPerDestination(
1199                                             WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2,
1200                                             EventSurfaceType.WEB))
1201                             .isEqualTo(3);
1202                     assertThat(
1203                                     measurementDao.getNumTriggersPerDestination(
1204                                             WebUtil.validUri("https://new-subdomain.example1.test"),
1205                                             EventSurfaceType.WEB))
1206                             .isEqualTo(3);
1207                     assertThat(
1208                                     measurementDao.getNumTriggersPerDestination(
1209                                             WebUtil.validUri("https://example1.test"),
1210                                             EventSurfaceType.WEB))
1211                             .isEqualTo(3);
1212                 });
1213     }
1214 
1215     @Test
webTriggerWithoutSubdomains_triggersPerDestination_returnsAllMatching()1216     public void webTriggerWithoutSubdomains_triggersPerDestination_returnsAllMatching() {
1217         List<Trigger> triggerList = new ArrayList<>();
1218         Uri webDestinationWithoutSubdomain = WebUtil.validUri("https://example1.test");
1219         Uri webDestinationWithoutSubdomainPath1 = WebUtil.validUri("https://example1.test/path1");
1220         Uri webDestinationWithoutSubdomainPath2 = WebUtil.validUri("https://example1.test/path2");
1221         Uri webDestinationWithoutSubdomainPath3 = WebUtil.validUri("https://example1.test/path3");
1222         triggerList.add(createWebTrigger(webDestinationWithoutSubdomain));
1223         triggerList.add(createWebTrigger(webDestinationWithoutSubdomainPath1));
1224         triggerList.add(createWebTrigger(webDestinationWithoutSubdomainPath2));
1225         addTriggersToDatabase(triggerList);
1226 
1227         mDatastoreManager.runInTransaction(
1228                 measurementDao -> {
1229                     assertThat(
1230                                     measurementDao.getNumTriggersPerDestination(
1231                                             webDestinationWithoutSubdomain, EventSurfaceType.WEB))
1232                             .isEqualTo(3);
1233                     assertThat(
1234                                     measurementDao.getNumTriggersPerDestination(
1235                                             webDestinationWithoutSubdomainPath1,
1236                                             EventSurfaceType.WEB))
1237                             .isEqualTo(3);
1238                     assertThat(
1239                                     measurementDao.getNumTriggersPerDestination(
1240                                             webDestinationWithoutSubdomainPath2,
1241                                             EventSurfaceType.WEB))
1242                             .isEqualTo(3);
1243                     assertThat(
1244                                     measurementDao.getNumTriggersPerDestination(
1245                                             webDestinationWithoutSubdomainPath3,
1246                                             EventSurfaceType.WEB))
1247                             .isEqualTo(3);
1248                 });
1249     }
1250 
1251     @Test
webTriggerDifferentPaths_triggersPerDestination_returnsAllMatching()1252     public void webTriggerDifferentPaths_triggersPerDestination_returnsAllMatching() {
1253         List<Trigger> triggerList = new ArrayList<>();
1254         triggerList.add(createWebTrigger(WEB_TWO_DESTINATION));
1255         triggerList.add(createWebTrigger(WEB_TWO_DESTINATION_WITH_PATH));
1256         addTriggersToDatabase(triggerList);
1257 
1258         mDatastoreManager.runInTransaction(
1259                 measurementDao -> {
1260                     assertThat(
1261                                     measurementDao.getNumTriggersPerDestination(
1262                                             WEB_TWO_DESTINATION, EventSurfaceType.WEB))
1263                             .isEqualTo(2);
1264                     assertThat(
1265                                     measurementDao.getNumTriggersPerDestination(
1266                                             WEB_TWO_DESTINATION_WITH_PATH, EventSurfaceType.WEB))
1267                             .isEqualTo(2);
1268                 });
1269     }
1270 
1271     @Test
noMathingWebTriggers_triggersPerDestination_returnsZero()1272     public void noMathingWebTriggers_triggersPerDestination_returnsZero() {
1273         List<Trigger> triggerList = new ArrayList<>();
1274         triggerList.add(createWebTrigger(WEB_ONE_DESTINATION));
1275         addTriggersToDatabase(triggerList);
1276 
1277         mDatastoreManager.runInTransaction(
1278                 measurementDao -> {
1279                     Uri differentScheme = WebUtil.validUri("http://www.example1.test");
1280                     assertThat(
1281                                     measurementDao.getNumTriggersPerDestination(
1282                                             differentScheme, EventSurfaceType.WEB))
1283                             .isEqualTo(0);
1284 
1285                     Uri notMatchingUrl2 = WebUtil.validUri("https://www.not-example1.test");
1286                     assertThat(
1287                                     measurementDao.getNumTriggersPerDestination(
1288                                             notMatchingUrl2, EventSurfaceType.WEB))
1289                             .isEqualTo(0);
1290 
1291                     Uri notMatchingUrl = WebUtil.validUri("https://www.not-example-1.test");
1292                     assertThat(
1293                                     measurementDao.getNumTriggersPerDestination(
1294                                             notMatchingUrl, EventSurfaceType.WEB))
1295                             .isEqualTo(0);
1296                 });
1297     }
1298 
1299     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_appDest()1300     public void testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_appDest() {
1301         Uri sourceSite = Uri.parse("android-app://publisher.app");
1302         Uri webDestination = WebUtil.validUri("https://web-destination.test");
1303         Uri appDestination = Uri.parse("android-app://destination.app");
1304         String registrant = "android-app://registrant.app";
1305         List<Attribution> attributionsWithAppDestinations1 =
1306                 getAttributionsWithDifferentReportingOrigins(
1307                         4, appDestination, 5000000000L, sourceSite, registrant);
1308         List<Attribution> attributionsWithAppDestinations2 =
1309                 getAttributionsWithDifferentReportingOrigins(
1310                         2, appDestination, 5000000000L, sourceSite, registrant);
1311         List<Attribution> attributionsWithWebDestinations =
1312                 getAttributionsWithDifferentReportingOrigins(
1313                         2, webDestination, 5500000000L, sourceSite, registrant);
1314         List<Attribution> attributionsOutOfWindow =
1315                 getAttributionsWithDifferentReportingOrigins(
1316                         10, appDestination, 50000000000L, sourceSite, registrant);
1317         for (Attribution attribution : attributionsWithAppDestinations1) {
1318             insertAttribution(attribution);
1319         }
1320         for (Attribution attribution : attributionsWithAppDestinations2) {
1321             insertAttribution(attribution);
1322         }
1323         for (Attribution attribution : attributionsWithWebDestinations) {
1324             insertAttribution(attribution);
1325         }
1326         for (Attribution attribution : attributionsOutOfWindow) {
1327             insertAttribution(attribution);
1328         }
1329         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain0.example.test");
1330         mDatastoreManager.runInTransaction(
1331                 measurementDao -> {
1332                     assertEquals(
1333                             Integer.valueOf(3),
1334                             measurementDao
1335                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1336                                             sourceSite,
1337                                             appDestination,
1338                                             excludedReportingOrigin,
1339                                             4000000000L,
1340                                             6000000000L));
1341                 });
1342     }
1343 
1344     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_webDest()1345     public void testCountDistinctReportingOriginsPerPublisherXDestinationInAttribution_webDest() {
1346         Uri sourceSite = Uri.parse("android-app://publisher.app");
1347         Uri webDestination = WebUtil.validUri("https://web-destination.test");
1348         Uri appDestination = Uri.parse("android-app://destination.app");
1349         String registrant = "android-app://registrant.app";
1350         List<Attribution> attributionsWithAppDestinations =
1351                 getAttributionsWithDifferentReportingOrigins(
1352                         2, appDestination, 5000000000L, sourceSite, registrant);
1353         List<Attribution> attributionsWithWebDestinations1 =
1354                 getAttributionsWithDifferentReportingOrigins(
1355                         4, webDestination, 5000000000L, sourceSite, registrant);
1356         List<Attribution> attributionsWithWebDestinations2 =
1357                 getAttributionsWithDifferentReportingOrigins(
1358                         2, webDestination, 5500000000L, sourceSite, registrant);
1359         List<Attribution> attributionsOutOfWindow =
1360                 getAttributionsWithDifferentReportingOrigins(
1361                         10, webDestination, 50000000000L, sourceSite, registrant);
1362         for (Attribution attribution : attributionsWithAppDestinations) {
1363             insertAttribution(attribution);
1364         }
1365         for (Attribution attribution : attributionsWithWebDestinations1) {
1366             insertAttribution(attribution);
1367         }
1368         for (Attribution attribution : attributionsWithWebDestinations2) {
1369             insertAttribution(attribution);
1370         }
1371         for (Attribution attribution : attributionsOutOfWindow) {
1372             insertAttribution(attribution);
1373         }
1374         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain3.example.test");
1375         mDatastoreManager.runInTransaction(
1376                 measurementDao -> {
1377                     assertEquals(
1378                             Integer.valueOf(3),
1379                             measurementDao
1380                                     .countDistinctReportingOriginsPerPublisherXDestInAttribution(
1381                                             sourceSite,
1382                                             webDestination,
1383                                             excludedReportingOrigin,
1384                                             4000000000L,
1385                                             6000000000L));
1386                 });
1387     }
1388 
1389     @Test
testCountDistinctDestinations_atWindow()1390     public void testCountDistinctDestinations_atWindow() {
1391         Uri publisher = Uri.parse("android-app://publisher.app");
1392         List<Source> activeSourcesWithAppAndWebDestinations =
1393                 getSourcesWithDifferentDestinations(
1394                         4,
1395                         true,
1396                         true,
1397                         4500000001L,
1398                         publisher,
1399                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1400                         Source.Status.ACTIVE);
1401         for (Source source : activeSourcesWithAppAndWebDestinations) {
1402             insertSource(source);
1403         }
1404         List<Uri> excludedDestinations =
1405                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1406         mDatastoreManager.runInTransaction(
1407                 measurementDao -> {
1408                     assertEquals(
1409                             Integer.valueOf(3),
1410                             measurementDao
1411                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1412                                             publisher,
1413                                             EventSurfaceType.APP,
1414                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1415                                             excludedDestinations,
1416                                             EventSurfaceType.WEB,
1417                                             4500000000L,
1418                                             6000000000L));
1419                 });
1420         mDatastoreManager.runInTransaction(
1421                 measurementDao -> {
1422                     assertEquals(
1423                             Integer.valueOf(3),
1424                             measurementDao
1425                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1426                                             publisher,
1427                                             EventSurfaceType.APP,
1428                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1429                                             excludedDestinations,
1430                                             EventSurfaceType.WEB,
1431                                             6000000000L));
1432                 });
1433         mDatastoreManager.runInTransaction(
1434                 measurementDao -> {
1435                     assertEquals(
1436                             Integer.valueOf(3),
1437                             measurementDao
1438                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1439                                             publisher,
1440                                             EventSurfaceType.APP,
1441                                             excludedDestinations,
1442                                             EventSurfaceType.WEB,
1443                                             4500000000L,
1444                                             6000000000L));
1445                 });
1446     }
1447 
1448     @Test
testCountDistinctDestinations_expiredSource()1449     public void testCountDistinctDestinations_expiredSource() {
1450         Uri publisher = Uri.parse("android-app://publisher.app");
1451         List<Source> activeSourcesWithAppAndWebDestinations =
1452                 getSourcesWithDifferentDestinations(
1453                         4,
1454                         true,
1455                         true,
1456                         4500000001L,
1457                         publisher,
1458                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1459                         Source.Status.ACTIVE);
1460         List<Source> expiredSourcesWithAppAndWebDestinations =
1461                 getSourcesWithDifferentDestinations(
1462                         6,
1463                         true,
1464                         true,
1465                         4500000001L,
1466                         6000000000L,
1467                         publisher,
1468                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1469                         Source.Status.ACTIVE,
1470                         REGISTRATION_ORIGIN);
1471         for (Source source : activeSourcesWithAppAndWebDestinations) {
1472             insertSource(source);
1473         }
1474         for (Source source : expiredSourcesWithAppAndWebDestinations) {
1475             insertSource(source);
1476         }
1477         List<Uri> excludedDestinations =
1478                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1479         mDatastoreManager.runInTransaction(
1480                 measurementDao -> {
1481                     assertEquals(
1482                             Integer.valueOf(3),
1483                             measurementDao
1484                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1485                                             publisher,
1486                                             EventSurfaceType.APP,
1487                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1488                                             excludedDestinations,
1489                                             EventSurfaceType.WEB,
1490                                             4500000000L,
1491                                             6000000000L));
1492                 });
1493         mDatastoreManager.runInTransaction(
1494                 measurementDao -> {
1495                     assertEquals(
1496                             Integer.valueOf(3),
1497                             measurementDao
1498                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1499                                             publisher,
1500                                             EventSurfaceType.APP,
1501                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1502                                             excludedDestinations,
1503                                             EventSurfaceType.WEB,
1504                                             6000000000L));
1505                 });
1506         mDatastoreManager.runInTransaction(
1507                 measurementDao -> {
1508                     assertEquals(
1509                             Integer.valueOf(3),
1510                             measurementDao
1511                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1512                                             publisher,
1513                                             EventSurfaceType.APP,
1514                                             excludedDestinations,
1515                                             EventSurfaceType.WEB,
1516                                             4500000000L,
1517                                             6000000000L));
1518                 });
1519     }
1520 
1521     @Test
testCountDistinctDestinations_beyondWindow()1522     public void testCountDistinctDestinations_beyondWindow() {
1523         Uri publisher = Uri.parse("android-app://publisher.app");
1524         List<Source> activeSourcesWithAppAndWebDestinations =
1525                 getSourcesWithDifferentDestinations(
1526                         4,
1527                         true,
1528                         true,
1529                         4500000000L,
1530                         publisher,
1531                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1532                         Source.Status.ACTIVE);
1533         for (Source source : activeSourcesWithAppAndWebDestinations) {
1534             insertSource(source);
1535         }
1536         List<Uri> excludedDestinations =
1537                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1538         mDatastoreManager.runInTransaction(
1539                 measurementDao -> {
1540                     assertEquals(
1541                             Integer.valueOf(0),
1542                             measurementDao
1543                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1544                                             publisher,
1545                                             EventSurfaceType.APP,
1546                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1547                                             excludedDestinations,
1548                                             EventSurfaceType.WEB,
1549                                             4500000000L,
1550                                             6000000000L));
1551                 });
1552         mDatastoreManager.runInTransaction(
1553                 measurementDao -> {
1554                     assertEquals(
1555                             Integer.valueOf(3),
1556                             measurementDao
1557                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1558                                             publisher,
1559                                             EventSurfaceType.APP,
1560                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1561                                             excludedDestinations,
1562                                             EventSurfaceType.WEB,
1563                                             6000000000L));
1564                 });
1565         mDatastoreManager.runInTransaction(
1566                 measurementDao -> {
1567                     assertEquals(
1568                             Integer.valueOf(0),
1569                             measurementDao
1570                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1571                                             publisher,
1572                                             EventSurfaceType.APP,
1573                                             excludedDestinations,
1574                                             EventSurfaceType.WEB,
1575                                             4500000000L,
1576                                             6000000000L));
1577                 });
1578     }
1579 
1580     @Test
testCountDistinctDestinations_appPublisher()1581     public void testCountDistinctDestinations_appPublisher() {
1582         Uri publisher = Uri.parse("android-app://publisher.app");
1583         List<Source> activeSourcesWithAppAndWebDestinations =
1584                 getSourcesWithDifferentDestinations(
1585                         4,
1586                         true,
1587                         true,
1588                         4500000000L,
1589                         publisher,
1590                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1591                         Source.Status.ACTIVE);
1592         List<Source> activeSourcesWithAppDestinations =
1593                 getSourcesWithDifferentDestinations(
1594                         2,
1595                         true,
1596                         false,
1597                         5000000000L,
1598                         publisher,
1599                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1600                         Source.Status.ACTIVE);
1601         List<Source> activeSourcesWithWebDestinations =
1602                 getSourcesWithDifferentDestinations(
1603                         2,
1604                         false,
1605                         true,
1606                         5500000000L,
1607                         publisher,
1608                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1609                         Source.Status.ACTIVE);
1610         List<Source> activeSourcesOutOfWindow =
1611                 getSourcesWithDifferentDestinations(
1612                         10,
1613                         true,
1614                         true,
1615                         50000000000L,
1616                         publisher,
1617                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1618                         Source.Status.ACTIVE);
1619         List<Source> ignoredSources =
1620                 getSourcesWithDifferentDestinations(
1621                         6,
1622                         true,
1623                         true,
1624                         5000000000L,
1625                         publisher,
1626                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1627                         Source.Status.IGNORED);
1628         for (Source source : activeSourcesWithAppAndWebDestinations) {
1629             insertSource(source);
1630         }
1631         for (Source source : activeSourcesWithAppDestinations) {
1632             insertSource(source);
1633         }
1634         for (Source source : activeSourcesWithWebDestinations) {
1635             insertSource(source);
1636         }
1637         for (Source source : activeSourcesOutOfWindow) {
1638             insertSource(source);
1639         }
1640         for (Source source : ignoredSources) {
1641             insertSource(source);
1642         }
1643         List<Uri> excludedDestinations =
1644                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1645         mDatastoreManager.runInTransaction(
1646                 measurementDao -> {
1647                     assertEquals(
1648                             Integer.valueOf(5),
1649                             measurementDao
1650                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1651                                             publisher,
1652                                             EventSurfaceType.APP,
1653                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1654                                             excludedDestinations,
1655                                             EventSurfaceType.WEB,
1656                                             4000000000L,
1657                                             6000000000L));
1658                 });
1659         mDatastoreManager.runInTransaction(
1660                 measurementDao -> {
1661                     assertEquals(
1662                             Integer.valueOf(9),
1663                             measurementDao
1664                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1665                                             publisher,
1666                                             EventSurfaceType.APP,
1667                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1668                                             excludedDestinations,
1669                                             EventSurfaceType.WEB,
1670                                             6000000000L));
1671                 });
1672         mDatastoreManager.runInTransaction(
1673                 measurementDao -> {
1674                     assertEquals(
1675                             Integer.valueOf(5),
1676                             measurementDao
1677                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1678                                             publisher,
1679                                             EventSurfaceType.APP,
1680                                             excludedDestinations,
1681                                             EventSurfaceType.WEB,
1682                                             4000000000L,
1683                                             6000000000L));
1684                 });
1685     }
1686 
1687     // (Testing countDistinctDestinationsPerPublisherInActiveSource)
1688     @Test
testCountDistinctDestinations_appPublisher_differentEnrollment()1689     public void testCountDistinctDestinations_appPublisher_differentEnrollment() {
1690         Uri publisher = Uri.parse("android-app://publisher.app");
1691         List<Source> activeSourcesWithAppAndWebDestinations =
1692                 getSourcesWithDifferentDestinations(
1693                         4,
1694                         true,
1695                         true,
1696                         4500000000L,
1697                         publisher,
1698                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1699                         Source.Status.ACTIVE);
1700         List<Source> activeSourcesWithAppDestinations =
1701                 getSourcesWithDifferentDestinations(
1702                         2,
1703                         true,
1704                         false,
1705                         5000000000L,
1706                         publisher,
1707                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1708                         Source.Status.ACTIVE);
1709         List<Source> activeSourcesWithWebDestinations =
1710                 getSourcesWithDifferentDestinations(
1711                         2,
1712                         false,
1713                         true,
1714                         5500000000L,
1715                         publisher,
1716                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1717                         Source.Status.ACTIVE);
1718         List<Source> activeSourcesOutOfWindow =
1719                 getSourcesWithDifferentDestinations(
1720                         10,
1721                         true,
1722                         true,
1723                         50000000000L,
1724                         publisher,
1725                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1726                         Source.Status.ACTIVE);
1727         List<Source> ignoredSources =
1728                 getSourcesWithDifferentDestinations(
1729                         6,
1730                         true,
1731                         true,
1732                         5000000000L,
1733                         publisher,
1734                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1735                         Source.Status.IGNORED);
1736         for (Source source : activeSourcesWithAppAndWebDestinations) {
1737             insertSource(source);
1738         }
1739         for (Source source : activeSourcesWithAppDestinations) {
1740             insertSource(source);
1741         }
1742         for (Source source : activeSourcesWithWebDestinations) {
1743             insertSource(source);
1744         }
1745         for (Source source : activeSourcesOutOfWindow) {
1746             insertSource(source);
1747         }
1748         for (Source source : ignoredSources) {
1749             insertSource(source);
1750         }
1751         List<Uri> excludedDestinations =
1752                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1753         mDatastoreManager.runInTransaction(
1754                 measurementDao -> {
1755                     assertEquals(
1756                             Integer.valueOf(0),
1757                             measurementDao
1758                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1759                                             publisher,
1760                                             EventSurfaceType.APP,
1761                                             "unmatched-enrollment-id",
1762                                             excludedDestinations,
1763                                             EventSurfaceType.WEB,
1764                                             4000000000L,
1765                                             6000000000L));
1766                 });
1767         mDatastoreManager.runInTransaction(
1768                 measurementDao -> {
1769                     assertEquals(
1770                             Integer.valueOf(0),
1771                             measurementDao
1772                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1773                                             publisher,
1774                                             EventSurfaceType.APP,
1775                                             "unmatched-enrollment-id",
1776                                             excludedDestinations,
1777                                             EventSurfaceType.WEB,
1778                                             6000000000L));
1779                 });
1780         mDatastoreManager.runInTransaction(
1781                 measurementDao -> {
1782                     assertEquals(
1783                             Integer.valueOf(5),
1784                             measurementDao
1785                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1786                                             publisher,
1787                                             EventSurfaceType.APP,
1788                                             excludedDestinations,
1789                                             EventSurfaceType.WEB,
1790                                             4000000000L,
1791                                             6000000000L));
1792                 });
1793     }
1794 
1795     @Test
testCountDistinctDestinations_webPublisher_exactMatch()1796     public void testCountDistinctDestinations_webPublisher_exactMatch() {
1797         Uri publisher = WebUtil.validUri("https://publisher.test");
1798         List<Source> activeSourcesWithAppAndWebDestinations =
1799                 getSourcesWithDifferentDestinations(
1800                         4,
1801                         true,
1802                         true,
1803                         4500000000L,
1804                         publisher,
1805                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1806                         Source.Status.ACTIVE);
1807         List<Source> activeSourcesWithAppDestinations =
1808                 getSourcesWithDifferentDestinations(
1809                         2,
1810                         true,
1811                         false,
1812                         5000000000L,
1813                         publisher,
1814                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1815                         Source.Status.ACTIVE);
1816         List<Source> activeSourcesWithWebDestinations =
1817                 getSourcesWithDifferentDestinations(
1818                         2,
1819                         false,
1820                         true,
1821                         5500000000L,
1822                         publisher,
1823                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1824                         Source.Status.ACTIVE);
1825         List<Source> activeSourcesOutOfWindow =
1826                 getSourcesWithDifferentDestinations(
1827                         10,
1828                         true,
1829                         true,
1830                         50000000000L,
1831                         publisher,
1832                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1833                         Source.Status.ACTIVE);
1834         List<Source> ignoredSources =
1835                 getSourcesWithDifferentDestinations(
1836                         6,
1837                         true,
1838                         true,
1839                         5000000000L,
1840                         publisher,
1841                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1842                         Source.Status.IGNORED);
1843         for (Source source : activeSourcesWithAppAndWebDestinations) {
1844             insertSource(source);
1845         }
1846         for (Source source : activeSourcesWithAppDestinations) {
1847             insertSource(source);
1848         }
1849         for (Source source : activeSourcesWithWebDestinations) {
1850             insertSource(source);
1851         }
1852         for (Source source : activeSourcesOutOfWindow) {
1853             insertSource(source);
1854         }
1855         for (Source source : ignoredSources) {
1856             insertSource(source);
1857         }
1858         List<Uri> excludedDestinations =
1859                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1860         mDatastoreManager.runInTransaction(
1861                 measurementDao -> {
1862                     assertEquals(
1863                             Integer.valueOf(5),
1864                             measurementDao
1865                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1866                                             publisher,
1867                                             EventSurfaceType.WEB,
1868                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1869                                             excludedDestinations,
1870                                             EventSurfaceType.WEB,
1871                                             4000000000L,
1872                                             6000000000L));
1873                 });
1874         mDatastoreManager.runInTransaction(
1875                 measurementDao -> {
1876                     assertEquals(
1877                             Integer.valueOf(9),
1878                             measurementDao
1879                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
1880                                             publisher,
1881                                             EventSurfaceType.WEB,
1882                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1883                                             excludedDestinations,
1884                                             EventSurfaceType.WEB,
1885                                             6000000000L));
1886                 });
1887         mDatastoreManager.runInTransaction(
1888                 measurementDao -> {
1889                     assertEquals(
1890                             Integer.valueOf(5),
1891                             measurementDao
1892                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
1893                                             publisher,
1894                                             EventSurfaceType.WEB,
1895                                             excludedDestinations,
1896                                             EventSurfaceType.WEB,
1897                                             4000000000L,
1898                                             6000000000L));
1899                 });
1900     }
1901 
1902     @Test
testCountDistinctDestinations_webPublisher_doesNotMatchDomainAsSuffix()1903     public void testCountDistinctDestinations_webPublisher_doesNotMatchDomainAsSuffix() {
1904         Uri publisher = WebUtil.validUri("https://publisher.test");
1905         Uri publisherAsSuffix = WebUtil.validUri("https://prefix-publisher.test");
1906         List<Source> activeSourcesWithAppAndWebDestinations =
1907                 getSourcesWithDifferentDestinations(
1908                         8,
1909                         true,
1910                         true,
1911                         4500000000L,
1912                         publisherAsSuffix,
1913                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1914                         Source.Status.ACTIVE);
1915         List<Source> ignoredSourcesWithAppAndWebDestinations =
1916                 getSourcesWithDifferentDestinations(
1917                         4,
1918                         true,
1919                         true,
1920                         4500000000L,
1921                         publisher,
1922                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1923                         Source.Status.IGNORED);
1924         List<Source> activeSourcesWithAppDestinations =
1925                 getSourcesWithDifferentDestinations(
1926                         2,
1927                         true,
1928                         false,
1929                         5000000000L,
1930                         publisher,
1931                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1932                         Source.Status.ACTIVE);
1933         List<Source> activeSourcesWithWebDestinations =
1934                 getSourcesWithDifferentDestinations(
1935                         2,
1936                         false,
1937                         true,
1938                         5500000000L,
1939                         publisher,
1940                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1941                         Source.Status.ACTIVE);
1942         List<Source> activeSourcesOutOfWindow =
1943                 getSourcesWithDifferentDestinations(
1944                         10,
1945                         true,
1946                         true,
1947                         50000000000L,
1948                         publisherAsSuffix,
1949                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1950                         Source.Status.ACTIVE);
1951         List<Source> markedAsDeletedSources =
1952                 getSourcesWithDifferentDestinations(
1953                         5,
1954                         true,
1955                         true,
1956                         5000000000L,
1957                         publisher,
1958                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1959                         Source.Status.MARKED_TO_DELETE);
1960         for (Source source : activeSourcesWithAppAndWebDestinations) {
1961             insertSource(source);
1962         }
1963         for (Source source : ignoredSourcesWithAppAndWebDestinations) {
1964             insertSource(source);
1965         }
1966         for (Source source : activeSourcesWithAppDestinations) {
1967             insertSource(source);
1968         }
1969         for (Source source : activeSourcesWithWebDestinations) {
1970             insertSource(source);
1971         }
1972         for (Source source : activeSourcesOutOfWindow) {
1973             insertSource(source);
1974         }
1975         for (Source source : markedAsDeletedSources) {
1976             insertSource(source);
1977         }
1978         List<Uri> excludedDestinations =
1979                 List.of(WebUtil.validUri("https://web-destination-2.test"));
1980         mDatastoreManager.runInTransaction(
1981                 measurementDao -> {
1982                     assertEquals(
1983                             Integer.valueOf(4),
1984                             measurementDao
1985                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
1986                                             publisher,
1987                                             EventSurfaceType.WEB,
1988                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
1989                                             excludedDestinations,
1990                                             EventSurfaceType.WEB,
1991                                             4000000000L,
1992                                             6000000000L));
1993                 });
1994         mDatastoreManager.runInTransaction(
1995                 measurementDao -> {
1996                     assertEquals(
1997                             Integer.valueOf(3),
1998                             measurementDao
1999                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
2000                                             publisher,
2001                                             EventSurfaceType.WEB,
2002                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2003                                             excludedDestinations,
2004                                             EventSurfaceType.WEB,
2005                                             6000000000L));
2006                 });
2007         mDatastoreManager.runInTransaction(
2008                 measurementDao ->
2009                         assertEquals(
2010                                 Integer.valueOf(4),
2011                                 measurementDao
2012                                         .countDistinctDestinationsPerPublisherPerRateLimitWindow(
2013                                                 publisher,
2014                                                 EventSurfaceType.WEB,
2015                                                 excludedDestinations,
2016                                                 EventSurfaceType.WEB,
2017                                                 4000000000L,
2018                                                 6000000000L)));
2019     }
2020 
2021     @Test
testCountDistinctDestinations_webPublisher_doesNotMatchDifferentScheme()2022     public void testCountDistinctDestinations_webPublisher_doesNotMatchDifferentScheme() {
2023         Uri publisher = WebUtil.validUri("https://publisher.test");
2024         Uri publisherWithDifferentScheme = WebUtil.validUri("http://publisher.test");
2025         List<Source> activeSourcesWithAppAndWebDestinations =
2026                 getSourcesWithDifferentDestinations(
2027                         4,
2028                         true,
2029                         true,
2030                         4500000000L,
2031                         publisherWithDifferentScheme,
2032                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2033                         Source.Status.ACTIVE);
2034         List<Source> activeSourcesWithAppDestinations =
2035                 getSourcesWithDifferentDestinations(
2036                         2,
2037                         true,
2038                         false,
2039                         5000000000L,
2040                         publisher,
2041                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2042                         Source.Status.ACTIVE);
2043         List<Source> activeSourcesWithWebDestinations =
2044                 getSourcesWithDifferentDestinations(
2045                         2,
2046                         false,
2047                         true,
2048                         5500000000L,
2049                         publisher,
2050                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2051                         Source.Status.ACTIVE);
2052         List<Source> activeSourcesOutOfWindow =
2053                 getSourcesWithDifferentDestinations(
2054                         10,
2055                         true,
2056                         true,
2057                         50000000000L,
2058                         publisher,
2059                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2060                         Source.Status.ACTIVE);
2061         List<Source> ignoredSources =
2062                 getSourcesWithDifferentDestinations(
2063                         6,
2064                         true,
2065                         true,
2066                         5000000000L,
2067                         publisher,
2068                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2069                         Source.Status.IGNORED);
2070         for (Source source : activeSourcesWithAppAndWebDestinations) {
2071             insertSource(source);
2072         }
2073         for (Source source : activeSourcesWithAppDestinations) {
2074             insertSource(source);
2075         }
2076         for (Source source : activeSourcesWithWebDestinations) {
2077             insertSource(source);
2078         }
2079         for (Source source : activeSourcesOutOfWindow) {
2080             insertSource(source);
2081         }
2082         for (Source source : ignoredSources) {
2083             insertSource(source);
2084         }
2085         List<Uri> excludedDestinations =
2086                 List.of(WebUtil.validUri("https://web-destination-2.test"));
2087         mDatastoreManager.runInTransaction(
2088                 measurementDao -> {
2089                     assertEquals(
2090                             Integer.valueOf(5),
2091                             measurementDao
2092                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
2093                                             publisher,
2094                                             EventSurfaceType.WEB,
2095                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2096                                             excludedDestinations,
2097                                             EventSurfaceType.WEB,
2098                                             4000000000L,
2099                                             6000000000L));
2100                 });
2101         mDatastoreManager.runInTransaction(
2102                 measurementDao -> {
2103                     assertEquals(
2104                             Integer.valueOf(9),
2105                             measurementDao
2106                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
2107                                             publisher,
2108                                             EventSurfaceType.WEB,
2109                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2110                                             excludedDestinations,
2111                                             EventSurfaceType.WEB,
2112                                             6000000000L));
2113                 });
2114         mDatastoreManager.runInTransaction(
2115                 measurementDao -> {
2116                     assertEquals(
2117                             Integer.valueOf(5),
2118                             measurementDao
2119                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
2120                                             publisher,
2121                                             EventSurfaceType.WEB,
2122                                             excludedDestinations,
2123                                             EventSurfaceType.WEB,
2124                                             4000000000L,
2125                                             6000000000L));
2126                 });
2127     }
2128 
2129     @Test
testCountDistinctDestinations_webPublisher_multipleDestinations()2130     public void testCountDistinctDestinations_webPublisher_multipleDestinations() {
2131         Uri publisher = WebUtil.validUri("https://publisher.test");
2132         // One source with multiple destinations
2133         Source activeSourceWithAppAndWebDestinations =
2134                 getSourceWithDifferentDestinations(
2135                         3,
2136                         true,
2137                         true,
2138                         4500000000L,
2139                         publisher,
2140                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2141                         Source.Status.ACTIVE);
2142         List<Source> activeSourcesWithAppDestinations =
2143                 getSourcesWithDifferentDestinations(
2144                         2,
2145                         true,
2146                         false,
2147                         5000000000L,
2148                         publisher,
2149                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2150                         Source.Status.ACTIVE);
2151         List<Source> activeSourcesWithWebDestinations =
2152                 getSourcesWithDifferentDestinations(
2153                         1,
2154                         false,
2155                         true,
2156                         5500000000L,
2157                         publisher,
2158                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2159                         Source.Status.ACTIVE);
2160         List<Source> activeSourcesOutOfWindow =
2161                 getSourcesWithDifferentDestinations(
2162                         10,
2163                         true,
2164                         true,
2165                         50000000000L,
2166                         publisher,
2167                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2168                         Source.Status.ACTIVE);
2169         List<Source> ignoredSources =
2170                 getSourcesWithDifferentDestinations(
2171                         4,
2172                         true,
2173                         true,
2174                         5000000000L,
2175                         publisher,
2176                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2177                         Source.Status.IGNORED);
2178         insertSource(activeSourceWithAppAndWebDestinations);
2179         for (Source source : activeSourcesWithAppDestinations) {
2180             insertSource(source);
2181         }
2182         for (Source source : activeSourcesWithWebDestinations) {
2183             insertSource(source);
2184         }
2185         for (Source source : activeSourcesOutOfWindow) {
2186             insertSource(source);
2187         }
2188         for (Source source : ignoredSources) {
2189             insertSource(source);
2190         }
2191         List<Uri> excludedDestinations =
2192                 List.of(
2193                         WebUtil.validUri("https://web-destination-1.test"),
2194                         WebUtil.validUri("https://web-destination-2.test"));
2195         mDatastoreManager.runInTransaction(
2196                 measurementDao -> {
2197                     assertEquals(
2198                             Integer.valueOf(2),
2199                             measurementDao
2200                                     .countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow(
2201                                             publisher,
2202                                             EventSurfaceType.WEB,
2203                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2204                                             excludedDestinations,
2205                                             EventSurfaceType.WEB,
2206                                             4000000000L,
2207                                             6000000000L));
2208                 });
2209         mDatastoreManager.runInTransaction(
2210                 measurementDao -> {
2211                     assertEquals(
2212                             Integer.valueOf(8),
2213                             measurementDao
2214                                     .countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource(
2215                                             publisher,
2216                                             EventSurfaceType.WEB,
2217                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2218                                             excludedDestinations,
2219                                             EventSurfaceType.WEB,
2220                                             6000000000L));
2221                 });
2222         mDatastoreManager.runInTransaction(
2223                 measurementDao -> {
2224                     assertEquals(
2225                             Integer.valueOf(2),
2226                             measurementDao
2227                                     .countDistinctDestinationsPerPublisherPerRateLimitWindow(
2228                                             publisher,
2229                                             EventSurfaceType.WEB,
2230                                             excludedDestinations,
2231                                             EventSurfaceType.WEB,
2232                                             4000000000L,
2233                                             6000000000L));
2234                 });
2235     }
2236 
2237     // Tests countSourcesPerPublisherXEnrollmentExcludingRegistrationOriginSinceTime
2238     @Test
testCountSourcesExclRegOrigin_forSameOrigin_returnsZero()2239     public void testCountSourcesExclRegOrigin_forSameOrigin_returnsZero() {
2240         // Positive case. For same registration origin we always pass the 1 origin
2241         // per site limit and return 0
2242         Uri appPublisher = Uri.parse("android-app://publisher.app");
2243         List<Source> sourcesMoreThanOneDayOld =
2244                 getSourcesWithDifferentDestinations(
2245                         5,
2246                         true,
2247                         true,
2248                         System.currentTimeMillis() - DAYS.toMillis(2),
2249                         appPublisher,
2250                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2251                         Source.Status.ACTIVE,
2252                         REGISTRATION_ORIGIN);
2253 
2254         List<Source> sourcesRecent =
2255                 getSourcesWithDifferentDestinations(
2256                         5,
2257                         true,
2258                         true,
2259                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2260                         appPublisher,
2261                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2262                         Source.Status.ACTIVE,
2263                         REGISTRATION_ORIGIN);
2264 
2265         for (Source source : sourcesMoreThanOneDayOld) {
2266             insertSource(source);
2267         }
2268         for (Source source : sourcesRecent) {
2269             insertSource(source);
2270         }
2271         mDatastoreManager.runInTransaction(
2272                 measurementDao -> {
2273                     assertEquals(
2274                             Integer.valueOf(0),
2275                             measurementDao
2276                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2277                                             REGISTRATION_ORIGIN,
2278                                             appPublisher,
2279                                             EventSurfaceType.APP,
2280                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2281                                             System.currentTimeMillis(),
2282                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2283                 });
2284     }
2285 
2286     @Test
testCountSourcesExclRegOrigin_forDifferentAppPublisher_returnsZero()2287     public void testCountSourcesExclRegOrigin_forDifferentAppPublisher_returnsZero() {
2288         // Positive case. For different publisher we always pass the 1 origin
2289         // per site limit and return 0
2290         Uri appPublisher = Uri.parse("android-app://publisher.app");
2291         Uri appPublisher2 = Uri.parse("android-app://publisher2.app");
2292         List<Source> sources =
2293                 getSourcesWithDifferentDestinations(
2294                         5,
2295                         true,
2296                         true,
2297                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2298                         appPublisher,
2299                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2300                         Source.Status.ACTIVE,
2301                         REGISTRATION_ORIGIN);
2302         for (Source source : sources) {
2303             insertSource(source);
2304         }
2305         mDatastoreManager.runInTransaction(
2306                 measurementDao -> {
2307                     assertEquals(
2308                             Integer.valueOf(0),
2309                             measurementDao
2310                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2311                                             REGISTRATION_ORIGIN_2,
2312                                             appPublisher2,
2313                                             EventSurfaceType.APP,
2314                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2315                                             System.currentTimeMillis(),
2316                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2317                 });
2318     }
2319 
2320     @Test
testCountSourcesExclRegOrigin_forDifferentWebPublisher_returnsZero()2321     public void testCountSourcesExclRegOrigin_forDifferentWebPublisher_returnsZero() {
2322         // Positive case. For different publisher we always pass the 1 origin
2323         // per site limit and return 0
2324         Uri publisher = WebUtil.validUri("https://publisher.test");
2325         Uri publisher2 = WebUtil.validUri("https://publisher2.test");
2326         List<Source> sources =
2327                 getSourcesWithDifferentDestinations(
2328                         5,
2329                         true,
2330                         true,
2331                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2332                         publisher,
2333                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2334                         Source.Status.ACTIVE,
2335                         REGISTRATION_ORIGIN);
2336         for (Source source : sources) {
2337             insertSource(source);
2338         }
2339         mDatastoreManager.runInTransaction(
2340                 measurementDao -> {
2341                     assertEquals(
2342                             Integer.valueOf(0),
2343                             measurementDao
2344                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2345                                             REGISTRATION_ORIGIN_2,
2346                                             publisher2,
2347                                             EventSurfaceType.WEB,
2348                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2349                                             System.currentTimeMillis(),
2350                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2351                 });
2352     }
2353 
2354     @Test
testCountSourcesExclRegOrigin_forDifferentEnrollment_returnsZero()2355     public void testCountSourcesExclRegOrigin_forDifferentEnrollment_returnsZero() {
2356         // Positive case. For different enrollment (aka reporting site)
2357         // we always pass the 1 origin per site limit and return 0
2358         String differentEnrollment = "new-enrollment";
2359         Uri differentSite = WebUtil.validUri("https://subdomain.different-site.test");
2360         Uri appPublisher = Uri.parse("android-app://publisher.app");
2361         List<Source> sources =
2362                 getSourcesWithDifferentDestinations(
2363                         5,
2364                         true,
2365                         true,
2366                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2367                         appPublisher,
2368                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2369                         Source.Status.ACTIVE,
2370                         REGISTRATION_ORIGIN);
2371         for (Source source : sources) {
2372             insertSource(source);
2373         }
2374         mDatastoreManager.runInTransaction(
2375                 measurementDao -> {
2376                     assertEquals(
2377                             Integer.valueOf(0),
2378                             measurementDao
2379                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2380                                             differentSite,
2381                                             appPublisher,
2382                                             EventSurfaceType.APP,
2383                                             differentEnrollment,
2384                                             System.currentTimeMillis(),
2385                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2386                 });
2387     }
2388 
2389     @Test
testCountSourcesExclRegOrigin_forDifferentOriginMoreThanTimeWindow_returnsZero()2390     public void testCountSourcesExclRegOrigin_forDifferentOriginMoreThanTimeWindow_returnsZero() {
2391         // Positive case. For different origin with same enrollment
2392         // more than time window of 1 day we always pass the 1 origin per site
2393         // limit and return 0
2394         Uri appPublisher = Uri.parse("android-app://publisher.app");
2395         List<Source> sources =
2396                 getSourcesWithDifferentDestinations(
2397                         5,
2398                         true,
2399                         true,
2400                         System.currentTimeMillis() - DAYS.toMillis(2),
2401                         appPublisher,
2402                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2403                         Source.Status.ACTIVE,
2404                         REGISTRATION_ORIGIN);
2405         for (Source source : sources) {
2406             insertSource(source);
2407         }
2408         mDatastoreManager.runInTransaction(
2409                 measurementDao -> {
2410                     assertEquals(
2411                             Integer.valueOf(0),
2412                             measurementDao
2413                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2414                                             REGISTRATION_ORIGIN_2,
2415                                             appPublisher,
2416                                             EventSurfaceType.APP,
2417                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2418                                             System.currentTimeMillis(),
2419                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2420                 });
2421     }
2422 
2423     // Tests countSourcesPerPublisherXEnrollmentExcludingRegistrationOriginSinceTime
2424     @Test
testCountSources_forDifferentOriginWithinTimeWindow_returnsNumOfSources()2425     public void testCountSources_forDifferentOriginWithinTimeWindow_returnsNumOfSources() {
2426         // Negative case. For different origin with same enrollment
2427         // we always fail the 1 origin per site limit and return 1
2428         Uri appPublisher = Uri.parse("android-app://publisher.app");
2429         List<Source> sources =
2430                 getSourcesWithDifferentDestinations(
2431                         5,
2432                         true,
2433                         true,
2434                         System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2),
2435                         appPublisher,
2436                         SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2437                         Source.Status.ACTIVE,
2438                         REGISTRATION_ORIGIN);
2439         for (Source source : sources) {
2440             insertSource(source);
2441         }
2442         mDatastoreManager.runInTransaction(
2443                 measurementDao -> {
2444                     assertEquals(
2445                             Integer.valueOf(1),
2446                             measurementDao
2447                                     .countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin(
2448                                             REGISTRATION_ORIGIN_2,
2449                                             appPublisher,
2450                                             EventSurfaceType.APP,
2451                                             SourceFixture.ValidSourceParams.ENROLLMENT_ID,
2452                                             System.currentTimeMillis(),
2453                                             MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW));
2454                 });
2455     }
2456 
2457     @Test
testCountDistinctRegistrationOriginPerPublisherXDestinationInSource_atWindow()2458     public void testCountDistinctRegistrationOriginPerPublisherXDestinationInSource_atWindow() {
2459         Uri publisher = Uri.parse("android-app://publisher.app");
2460         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2461         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2462         List<Source> activeSourcesWithAppAndWebDestinations =
2463                 getSourcesWithDifferentRegistrationOrigins(
2464                         2,
2465                         appDestinations,
2466                         webDestinations,
2467                         4500000001L,
2468                         publisher,
2469                         Source.Status.ACTIVE);
2470         for (Source source : activeSourcesWithAppAndWebDestinations) {
2471             insertSource(source);
2472         }
2473         Uri excludedRegistrationOrigin = WebUtil.validUri("https://subdomain1.example.test");
2474         mDatastoreManager.runInTransaction(
2475                 measurementDao -> {
2476                     assertEquals(
2477                             Integer.valueOf(1),
2478                             measurementDao
2479                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2480                                             publisher,
2481                                             EventSurfaceType.APP,
2482                                             appDestinations,
2483                                             excludedRegistrationOrigin,
2484                                             4500000000L,
2485                                             6000000000L));
2486                 });
2487     }
2488 
2489     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_beyondWindow()2490     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_beyondWindow() {
2491         Uri publisher = Uri.parse("android-app://publisher.app");
2492         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2493         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2494         List<Source> activeSourcesWithAppAndWebDestinations =
2495                 getSourcesWithDifferentRegistrationOrigins(
2496                         2,
2497                         appDestinations,
2498                         webDestinations,
2499                         4500000000L,
2500                         publisher,
2501                         Source.Status.ACTIVE);
2502         for (Source source : activeSourcesWithAppAndWebDestinations) {
2503             insertSource(source);
2504         }
2505         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2506         mDatastoreManager.runInTransaction(
2507                 measurementDao -> {
2508                     assertEquals(
2509                             Integer.valueOf(0),
2510                             measurementDao
2511                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2512                                             publisher,
2513                                             EventSurfaceType.APP,
2514                                             appDestinations,
2515                                             excludedReportingOrigin,
2516                                             4500000000L,
2517                                             6000000000L));
2518                 });
2519     }
2520 
2521     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_expiredSource()2522     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_expiredSource() {
2523         Uri publisher = Uri.parse("android-app://publisher.app");
2524         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2525         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2526         List<Source> activeSourcesWithAppAndWebDestinations =
2527                 getSourcesWithDifferentRegistrationOrigins(
2528                         2,
2529                         appDestinations,
2530                         webDestinations,
2531                         4500000001L,
2532                         publisher,
2533                         Source.Status.ACTIVE);
2534         List<Source> expiredSourcesWithAppAndWebDestinations =
2535                 getSourcesWithDifferentRegistrationOrigins(
2536                         4,
2537                         appDestinations,
2538                         webDestinations,
2539                         4500000001L,
2540                         6000000000L,
2541                         publisher,
2542                         Source.Status.ACTIVE);
2543         for (Source source : activeSourcesWithAppAndWebDestinations) {
2544             insertSource(source);
2545         }
2546         for (Source source : expiredSourcesWithAppAndWebDestinations) {
2547             insertSource(source);
2548         }
2549         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2550         mDatastoreManager.runInTransaction(
2551                 measurementDao -> {
2552                     assertEquals(
2553                             Integer.valueOf(3),
2554                             measurementDao
2555                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2556                                             publisher,
2557                                             EventSurfaceType.APP,
2558                                             appDestinations,
2559                                             excludedReportingOrigin,
2560                                             4500000000L,
2561                                             6000000000L));
2562                 });
2563     }
2564 
2565     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_appDestination()2566     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_appDestination() {
2567         Uri publisher = Uri.parse("android-app://publisher.app");
2568         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2569         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2570         List<Source> activeSourcesWithAppAndWebDestinations =
2571                 getSourcesWithDifferentRegistrationOrigins(
2572                         2,
2573                         appDestinations,
2574                         webDestinations,
2575                         4500000000L,
2576                         publisher,
2577                         Source.Status.ACTIVE);
2578         List<Source> activeSourcesWithAppDestinations =
2579                 getSourcesWithDifferentRegistrationOrigins(
2580                         2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE);
2581         List<Source> activeSourcesWithWebDestinations =
2582                 getSourcesWithDifferentRegistrationOrigins(
2583                         2, null, webDestinations, 5500000000L, publisher, Source.Status.ACTIVE);
2584         List<Source> activeSourcesOutOfWindow =
2585                 getSourcesWithDifferentRegistrationOrigins(
2586                         10,
2587                         appDestinations,
2588                         webDestinations,
2589                         50000000000L,
2590                         publisher,
2591                         Source.Status.ACTIVE);
2592         List<Source> ignoredSources =
2593                 getSourcesWithDifferentRegistrationOrigins(
2594                         3,
2595                         appDestinations,
2596                         webDestinations,
2597                         5000000000L,
2598                         publisher,
2599                         Source.Status.IGNORED);
2600         for (Source source : activeSourcesWithAppAndWebDestinations) {
2601             insertSource(source);
2602         }
2603         for (Source source : activeSourcesWithAppDestinations) {
2604             insertSource(source);
2605         }
2606         for (Source source : activeSourcesWithWebDestinations) {
2607             insertSource(source);
2608         }
2609         for (Source source : activeSourcesOutOfWindow) {
2610             insertSource(source);
2611         }
2612         for (Source source : ignoredSources) {
2613             insertSource(source);
2614         }
2615         String excludedEnrollmentId = "enrollment-id-1";
2616         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2617         mDatastoreManager.runInTransaction(
2618                 measurementDao -> {
2619                     assertEquals(
2620                             Integer.valueOf(2),
2621                             measurementDao
2622                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2623                                             publisher,
2624                                             EventSurfaceType.APP,
2625                                             appDestinations,
2626                                             excludedReportingOrigin,
2627                                             4000000000L,
2628                                             6000000000L));
2629                 });
2630     }
2631 
2632     @Test
testCountDistinctReportingOriginsPerPublisherXDestinationInSource_webDestination()2633     public void testCountDistinctReportingOriginsPerPublisherXDestinationInSource_webDestination() {
2634         Uri publisher = Uri.parse("android-app://publisher.app");
2635         List<Uri> webDestinations = List.of(WebUtil.validUri("https://web-destination.test"));
2636         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2637         List<Source> activeSourcesWithAppAndWebDestinations =
2638                 getSourcesWithDifferentRegistrationOrigins(
2639                         2,
2640                         appDestinations,
2641                         webDestinations,
2642                         4500000000L,
2643                         publisher,
2644                         Source.Status.ACTIVE);
2645         List<Source> activeSourcesWithAppDestinations =
2646                 getSourcesWithDifferentRegistrationOrigins(
2647                         2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE);
2648         List<Source> activeSourcesWithWebDestinations =
2649                 getSourcesWithDifferentRegistrationOrigins(
2650                         2, null, webDestinations, 5500000000L, publisher, Source.Status.ACTIVE);
2651         List<Source> activeSourcesOutOfWindow =
2652                 getSourcesWithDifferentRegistrationOrigins(
2653                         10,
2654                         appDestinations,
2655                         webDestinations,
2656                         50000000000L,
2657                         publisher,
2658                         Source.Status.ACTIVE);
2659         List<Source> ignoredSources =
2660                 getSourcesWithDifferentRegistrationOrigins(
2661                         3,
2662                         appDestinations,
2663                         webDestinations,
2664                         5000000000L,
2665                         publisher,
2666                         Source.Status.IGNORED);
2667         for (Source source : activeSourcesWithAppAndWebDestinations) {
2668             insertSource(source);
2669         }
2670         for (Source source : activeSourcesWithAppDestinations) {
2671             insertSource(source);
2672         }
2673         for (Source source : activeSourcesWithWebDestinations) {
2674             insertSource(source);
2675         }
2676         for (Source source : activeSourcesOutOfWindow) {
2677             insertSource(source);
2678         }
2679         for (Source source : ignoredSources) {
2680             insertSource(source);
2681         }
2682         String excludedEnrollmentId = "enrollment-id-22";
2683         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain22.example.test");
2684         mDatastoreManager.runInTransaction(
2685                 measurementDao -> {
2686                     assertEquals(
2687                             Integer.valueOf(3),
2688                             measurementDao
2689                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2690                                             publisher,
2691                                             EventSurfaceType.WEB,
2692                                             webDestinations,
2693                                             excludedReportingOrigin,
2694                                             4000000000L,
2695                                             6000000000L));
2696                 });
2697     }
2698 
2699     // countDistinctEnrollmentsPerPublisherXDestinationInSource
2700     @Test
countDistinctReportingOriginsPerPublisher_webDestination_multipleDestinations()2701     public void countDistinctReportingOriginsPerPublisher_webDestination_multipleDestinations() {
2702         Uri publisher = Uri.parse("android-app://publisher.app");
2703         List<Uri> webDestinations1 = List.of(WebUtil.validUri("https://web-destination-1.test"));
2704         List<Uri> webDestinations2 =
2705                 List.of(
2706                         WebUtil.validUri("https://web-destination-1.test"),
2707                         WebUtil.validUri("https://web-destination-2.test"));
2708         List<Uri> appDestinations = List.of(Uri.parse("android-app://destination.app"));
2709         List<Source> activeSourcesWithAppAndWebDestinations =
2710                 getSourcesWithDifferentRegistrationOrigins(
2711                         3,
2712                         appDestinations,
2713                         webDestinations1,
2714                         4500000000L,
2715                         publisher,
2716                         Source.Status.ACTIVE);
2717         List<Source> activeSourcesWithAppDestinations =
2718                 getSourcesWithDifferentRegistrationOrigins(
2719                         2, appDestinations, null, 5000000000L, publisher, Source.Status.ACTIVE);
2720         List<Source> activeSourcesWithWebDestinations =
2721                 getSourcesWithDifferentRegistrationOrigins(
2722                         2, null, webDestinations2, 5500000000L, publisher, Source.Status.ACTIVE);
2723         List<Source> activeSourcesOutOfWindow =
2724                 getSourcesWithDifferentRegistrationOrigins(
2725                         10,
2726                         appDestinations,
2727                         webDestinations2,
2728                         50000000000L,
2729                         publisher,
2730                         Source.Status.ACTIVE);
2731         List<Source> ignoredSources =
2732                 getSourcesWithDifferentRegistrationOrigins(
2733                         2,
2734                         appDestinations,
2735                         webDestinations1,
2736                         5000000000L,
2737                         publisher,
2738                         Source.Status.IGNORED);
2739         for (Source source : activeSourcesWithAppAndWebDestinations) {
2740             insertSource(source);
2741         }
2742         for (Source source : activeSourcesWithAppDestinations) {
2743             insertSource(source);
2744         }
2745         for (Source source : activeSourcesWithWebDestinations) {
2746             insertSource(source);
2747         }
2748         for (Source source : activeSourcesOutOfWindow) {
2749             insertSource(source);
2750         }
2751         for (Source source : ignoredSources) {
2752             insertSource(source);
2753         }
2754         String excludedEnrollmentId = "enrollment-id-1";
2755         Uri excludedReportingOrigin = WebUtil.validUri("https://subdomain1.example.test");
2756         mDatastoreManager.runInTransaction(
2757                 measurementDao -> {
2758                     assertEquals(
2759                             Integer.valueOf(2),
2760                             measurementDao
2761                                     .countDistinctReportingOriginsPerPublisherXDestinationInSource(
2762                                             publisher,
2763                                             EventSurfaceType.WEB,
2764                                             webDestinations2,
2765                                             excludedReportingOrigin,
2766                                             4000000000L,
2767                                             6000000000L));
2768                 });
2769     }
2770 
2771     @Test
testInstallAttribution_selectHighestPriority()2772     public void testInstallAttribution_selectHighestPriority() {
2773         long currentTimestamp = System.currentTimeMillis();
2774 
2775         insertSource(
2776                 createSourceForIATest(
2777                                 "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID)
2778                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2779                         .build(),
2780                 "IA1");
2781         insertSource(
2782                 createSourceForIATest("IA2", currentTimestamp, 50, -1, false, DEFAULT_ENROLLMENT_ID)
2783                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2784                         .build(),
2785                 "IA2");
2786         // Should select id IA1 because it has higher priority
2787         assertTrue(
2788                 mDatastoreManager.runInTransaction(
2789                         measurementDao -> {
2790                             measurementDao.doInstallAttribution(
2791                                     INSTALLED_PACKAGE, currentTimestamp);
2792                         }));
2793         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2794         assertTrue(getInstallAttributionStatus("IA1", db));
2795         assertFalse(getInstallAttributionStatus("IA2", db));
2796         removeSources(Arrays.asList("IA1", "IA2"), db);
2797     }
2798 
2799     @Test
testInstallAttribution_selectLatest()2800     public void testInstallAttribution_selectLatest() {
2801         long currentTimestamp = System.currentTimeMillis();
2802         insertSource(
2803                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2804                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2805                         .build(),
2806                 "IA1");
2807         insertSource(
2808                 createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID)
2809                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2810                         .build(),
2811                 "IA2");
2812         // Should select id=IA2 as it is latest
2813         assertTrue(
2814                 mDatastoreManager.runInTransaction(
2815                         measurementDao -> {
2816                             measurementDao.doInstallAttribution(
2817                                     INSTALLED_PACKAGE, currentTimestamp);
2818                         }));
2819         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2820         assertFalse(getInstallAttributionStatus("IA1", db));
2821         assertTrue(getInstallAttributionStatus("IA2", db));
2822 
2823         removeSources(Arrays.asList("IA1", "IA2"), db);
2824     }
2825 
2826     @Test
installAttribution_noCooldownWindow_ignoredToBeMarked()2827     public void installAttribution_noCooldownWindow_ignoredToBeMarked() {
2828         long currentTimestamp = System.currentTimeMillis();
2829         insertSource(
2830                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2831                         .setInstallCooldownWindow(0)
2832                         .build(),
2833                 "IA1");
2834         // Should select id=IA2 as it is latest
2835         assertTrue(
2836                 mDatastoreManager.runInTransaction(
2837                         measurementDao -> {
2838                             measurementDao.doInstallAttribution(
2839                                     INSTALLED_PACKAGE, currentTimestamp);
2840                         }));
2841         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2842         assertFalse(getInstallAttributionStatus("IA1", db));
2843 
2844         removeSources(Arrays.asList("IA1", "IA2"), db);
2845     }
2846 
2847     @Test
testInstallAttribution_ignoreNewerSources()2848     public void testInstallAttribution_ignoreNewerSources() {
2849         long currentTimestamp = System.currentTimeMillis();
2850         insertSource(
2851                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2852                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2853                         .build(),
2854                 "IA1");
2855         insertSource(
2856                 createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID)
2857                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2858                         .build(),
2859                 "IA2");
2860         // Should select id=IA1 as it is the only valid choice.
2861         // id=IA2 is newer than the evenTimestamp of install event.
2862         assertTrue(
2863                 mDatastoreManager.runInTransaction(
2864                         measurementDao -> {
2865                             measurementDao.doInstallAttribution(
2866                                     INSTALLED_PACKAGE, currentTimestamp - DAYS.toMillis(7));
2867                         }));
2868         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2869         assertTrue(getInstallAttributionStatus("IA1", db));
2870         assertFalse(getInstallAttributionStatus("IA2", db));
2871         removeSources(Arrays.asList("IA1", "IA2"), db);
2872     }
2873 
2874     @Test
testInstallAttribution_noValidSource()2875     public void testInstallAttribution_noValidSource() {
2876         long currentTimestamp = System.currentTimeMillis();
2877         insertSource(
2878                 createSourceForIATest("IA1", currentTimestamp, 10, 10, true, DEFAULT_ENROLLMENT_ID)
2879                         .build(),
2880                 "IA1");
2881         insertSource(
2882                 createSourceForIATest("IA2", currentTimestamp, 10, 11, true, DEFAULT_ENROLLMENT_ID)
2883                         .build(),
2884                 "IA2");
2885         // Should not update any sources.
2886         assertTrue(
2887                 mDatastoreManager.runInTransaction(
2888                         measurementDao ->
2889                                 measurementDao.doInstallAttribution(
2890                                         INSTALLED_PACKAGE, currentTimestamp)));
2891         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2892         assertFalse(getInstallAttributionStatus("IA1", db));
2893         assertFalse(getInstallAttributionStatus("IA2", db));
2894         removeSources(Arrays.asList("IA1", "IA2"), db);
2895     }
2896 
2897     @Test
installAttribution_install_installTimeEqualsEventTime()2898     public void installAttribution_install_installTimeEqualsEventTime() {
2899         long currentTimestamp = System.currentTimeMillis();
2900         insertSource(
2901                 createSourceForIATest("IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID)
2902                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2903                         .build(),
2904                 "IA1");
2905         assertTrue(
2906                 mDatastoreManager.runInTransaction(
2907                         measurementDao -> {
2908                             measurementDao.doInstallAttribution(
2909                                     INSTALLED_PACKAGE, currentTimestamp - DAYS.toMillis(7));
2910                         }));
2911         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2912         assertEquals(
2913                 currentTimestamp - DAYS.toMillis(7),
2914                 getInstallAttributionInstallTime("IA1", db).longValue());
2915         removeSources(Arrays.asList("IA1"), db);
2916     }
2917 
2918     @Test
2919     public void
testInstallAttribution_reinstallReattributionEnabled_skipsAttributionForReinstall()2920             testInstallAttribution_reinstallReattributionEnabled_skipsAttributionForReinstall() {
2921         mFlags = mock(Flags.class);
2922         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
2923         doReturn(true).when(mFlags).getMeasurementEnableReinstallReattribution();
2924         long currentTimestamp = System.currentTimeMillis();
2925         long reinstallWindow = DAYS.toMillis(50);
2926         insertSource(
2927                 createSourceForIATest(
2928                                 /* id= */ "IA1",
2929                                 currentTimestamp,
2930                                 /* priority= */ -1,
2931                                 /* eventTimePastDays= */ 10,
2932                                 /* expiredIAWindow= */ false,
2933                                 DEFAULT_ENROLLMENT_ID)
2934                         .setReinstallReattributionWindow(reinstallWindow)
2935                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2936                         .setInstallAttributed(true)
2937                         .build(),
2938                 /* sourceId= */ "IA1");
2939 
2940         insertSource(
2941                 createSourceForIATest(
2942                                 /* id= */ "IA2",
2943                                 currentTimestamp,
2944                                 /* priority= */ -1,
2945                                 /* eventTimePastDays= */ 5,
2946                                 /* expiredIAWindow= */ false,
2947                                 DEFAULT_ENROLLMENT_ID)
2948                         .setReinstallReattributionWindow(reinstallWindow)
2949                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2950                         .build(),
2951                 /* sourceId= */ "IA2");
2952         insertSource(
2953                 createSourceForIATest(
2954                                 /* id= */ "IA3",
2955                                 currentTimestamp,
2956                                 /* priority= */ -1,
2957                                 /* eventTimePastDays= */ 10,
2958                                 /* expiredIAWindow= */ false,
2959                                 ENROLLMENT_ID1,
2960                                 REGISTRATION_ORIGIN_2)
2961                         .setReinstallReattributionWindow(reinstallWindow)
2962                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2963                         .build(),
2964                 /* sourceId= */ "IA3");
2965         insertSource(
2966                 createSourceForIATest(
2967                                 /* id= */ "IA4",
2968                                 currentTimestamp,
2969                                 /* priority= */ -1,
2970                                 /* eventTimePastDays= */ 5,
2971                                 /* expiredIAWindow= */ false,
2972                                 ENROLLMENT_ID1,
2973                                 REGISTRATION_ORIGIN_2)
2974                         .setReinstallReattributionWindow(reinstallWindow)
2975                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
2976                         .build(),
2977                 /* sourceId= */ "IA4");
2978         assertTrue(
2979                 mDatastoreManager.runInTransaction(
2980                         measurementDao -> {
2981                             measurementDao.insertOrUpdateAppReportHistory(
2982                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
2983                         }));
2984 
2985         assertTrue(
2986                 mDatastoreManager.runInTransaction(
2987                         measurementDao -> {
2988                             measurementDao.doInstallAttribution(
2989                                     INSTALLED_PACKAGE, currentTimestamp);
2990                         }));
2991         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
2992         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
2993         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
2994         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA3", db));
2995         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA4", db));
2996 
2997         removeSources(Arrays.asList("IA1", "IA2", "IA3", "IA4"), db);
2998     }
2999 
3000     @Test
testInstallAttribution_reinstallReattributionDisabled_doesNotSkipReinstall()3001     public void testInstallAttribution_reinstallReattributionDisabled_doesNotSkipReinstall() {
3002         mFlags = mock(Flags.class);
3003         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
3004         doReturn(false).when(mFlags).getMeasurementEnableReinstallReattribution();
3005         long currentTimestamp = System.currentTimeMillis();
3006         insertSource(
3007                 createSourceForIATest(
3008                                 /* id= */ "IA1",
3009                                 currentTimestamp,
3010                                 /* priority= */ -1,
3011                                 /* eventTimePastDays= */ 10,
3012                                 /* expiredIAWindow= */ false,
3013                                 DEFAULT_ENROLLMENT_ID)
3014                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3015                         .setInstallAttributed(true)
3016                         .build(),
3017                 "IA1");
3018         insertSource(
3019                 createSourceForIATest(
3020                                 /* id= */ "IA2",
3021                                 currentTimestamp,
3022                                 /* priority= */ -1,
3023                                 /* eventTimePastDays= */ 5,
3024                                 /* expiredIAWindow= */ false,
3025                                 DEFAULT_ENROLLMENT_ID)
3026                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3027                         .build(),
3028                 "IA2");
3029         insertSource(
3030                 createSourceForIATest(
3031                                 /* id= */ "IA3",
3032                                 currentTimestamp,
3033                                 /* priority= */ -1,
3034                                 /* eventTimePastDays= */ 10,
3035                                 /* expiredIAWindow= */ false,
3036                                 ENROLLMENT_ID1,
3037                                 REGISTRATION_ORIGIN_2)
3038                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3039                         .build(),
3040                 "IA3");
3041         insertSource(
3042                 createSourceForIATest(
3043                                 /* id= */ "IA4",
3044                                 currentTimestamp,
3045                                 /* priority= */ -1,
3046                                 /* eventTimePastDays= */ 5,
3047                                 /* expiredIAWindow= */ false,
3048                                 ENROLLMENT_ID1,
3049                                 REGISTRATION_ORIGIN_2)
3050                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3051                         .build(),
3052                 "IA4");
3053         assertTrue(
3054                 mDatastoreManager.runInTransaction(
3055                         measurementDao -> {
3056                             measurementDao.insertOrUpdateAppReportHistory(
3057                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
3058                         }));
3059         // Should select id=IA2 as it is latest
3060         assertTrue(
3061                 mDatastoreManager.runInTransaction(
3062                         measurementDao -> {
3063                             measurementDao.doInstallAttribution(
3064                                     INSTALLED_PACKAGE, currentTimestamp);
3065                         }));
3066         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3067         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
3068         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
3069         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA3", db));
3070         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA4", db));
3071 
3072         removeSources(Arrays.asList("IA1", "IA2", "IA3", "IA4"), db);
3073     }
3074 
3075     @Test
3076     public void
testInstallAttribution_reinstallReattributionEnabledNoWindow_doesNotSkipReinstall()3077             testInstallAttribution_reinstallReattributionEnabledNoWindow_doesNotSkipReinstall() {
3078         mFlags = mock(Flags.class);
3079         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
3080         doReturn(true).when(mFlags).getMeasurementEnableReinstallReattribution();
3081         long currentTimestamp = System.currentTimeMillis();
3082         insertSource(
3083                 createSourceForIATest(
3084                                 /* id= */ "IA1",
3085                                 currentTimestamp,
3086                                 /* priority= */ -1,
3087                                 /* eventTimePastDays= */ 10,
3088                                 /* expiredIAWindow= */ false,
3089                                 DEFAULT_ENROLLMENT_ID)
3090                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3091                         .setReinstallReattributionWindow(0L)
3092                         .setInstallAttributed(true)
3093                         .build(),
3094                 "IA1");
3095         insertSource(
3096                 createSourceForIATest(
3097                                 /* id= */ "IA2",
3098                                 currentTimestamp,
3099                                 /* priority= */ -1,
3100                                 /* eventTimePastDays= */ 5,
3101                                 /* expiredIAWindow= */ false,
3102                                 DEFAULT_ENROLLMENT_ID)
3103                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3104                         .setReinstallReattributionWindow(0L)
3105                         .build(),
3106                 "IA2");
3107         insertSource(
3108                 createSourceForIATest(
3109                                 /* id= */ "IA3",
3110                                 currentTimestamp,
3111                                 /* priority= */ -1,
3112                                 /* eventTimePastDays= */ 10,
3113                                 /* expiredIAWindow= */ false,
3114                                 ENROLLMENT_ID1,
3115                                 REGISTRATION_ORIGIN_2)
3116                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3117                         .setReinstallReattributionWindow(0L)
3118                         .build(),
3119                 "IA3");
3120         insertSource(
3121                 createSourceForIATest(
3122                                 /* id= */ "IA4",
3123                                 currentTimestamp,
3124                                 /* priority= */ -1,
3125                                 /* eventTimePastDays= */ 5,
3126                                 /* expiredIAWindow= */ false,
3127                                 ENROLLMENT_ID1,
3128                                 REGISTRATION_ORIGIN_2)
3129                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3130                         .setReinstallReattributionWindow(0L)
3131                         .build(),
3132                 "IA4");
3133         assertTrue(
3134                 mDatastoreManager.runInTransaction(
3135                         measurementDao -> {
3136                             measurementDao.insertOrUpdateAppReportHistory(
3137                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
3138                         }));
3139         // Should select id=IA2 as it is latest
3140         assertTrue(
3141                 mDatastoreManager.runInTransaction(
3142                         measurementDao -> {
3143                             measurementDao.doInstallAttribution(
3144                                     INSTALLED_PACKAGE, currentTimestamp);
3145                         }));
3146         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3147         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
3148         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
3149         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA3", db));
3150         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA4", db));
3151 
3152         removeSources(Arrays.asList("IA1", "IA2", "IA3", "IA4"), db);
3153     }
3154 
3155     @Test
testInstallAttribution_reinstallReattributionEnabledNoReinstall_doesNotSkip()3156     public void testInstallAttribution_reinstallReattributionEnabledNoReinstall_doesNotSkip() {
3157         mFlags = mock(Flags.class);
3158         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
3159         doReturn(true).when(mFlags).getMeasurementEnableReinstallReattribution();
3160         long currentTimestamp = System.currentTimeMillis();
3161         insertSource(
3162                 createSourceForIATest(
3163                                 /* id= */ "IA1",
3164                                 currentTimestamp,
3165                                 /* priority= */ -1,
3166                                 /* eventTimePastDays= */ 10,
3167                                 /* expiredIAWindow= */ false,
3168                                 DEFAULT_ENROLLMENT_ID)
3169                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3170                         .setReinstallReattributionWindow(0L)
3171                         .build(),
3172                 "IA1");
3173         insertSource(
3174                 createSourceForIATest(
3175                                 /* id= */ "IA2",
3176                                 currentTimestamp,
3177                                 /* priority= */ -1,
3178                                 /* eventTimePastDays= */ 5,
3179                                 /* expiredIAWindow= */ false,
3180                                 DEFAULT_ENROLLMENT_ID)
3181                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3182                         .setReinstallReattributionWindow(0L)
3183                         .build(),
3184                 "IA2");
3185 
3186         assertTrue(
3187                 mDatastoreManager.runInTransaction(
3188                         measurementDao -> {
3189                             measurementDao.insertOrUpdateAppReportHistory(
3190                                     INSTALLED_PACKAGE, REGISTRATION_ORIGIN, currentTimestamp);
3191                         }));
3192         // Should select id=IA2 as it is latest
3193         assertTrue(
3194                 mDatastoreManager.runInTransaction(
3195                         measurementDao -> {
3196                             measurementDao.doInstallAttribution(
3197                                     INSTALLED_PACKAGE, currentTimestamp);
3198                         }));
3199         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3200         assertFalse(getInstallAttributionStatus(/* sourceDbId= */ "IA1", db));
3201         assertTrue(getInstallAttributionStatus(/* sourceDbId= */ "IA2", db));
3202 
3203         removeSources(Arrays.asList("IA1", "IA2"), db);
3204     }
3205 
3206     @Test
doInstallAttribution_noValidSourceStatus_IgnoresSources()3207     public void doInstallAttribution_noValidSourceStatus_IgnoresSources() {
3208         long currentTimestamp = System.currentTimeMillis();
3209         Source source =
3210                 createSourceForIATest(
3211                                 "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID)
3212                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3213                         .build();
3214 
3215         // Execution
3216         // Active source should get install attributed
3217         source.setStatus(Source.Status.ACTIVE);
3218         insertSource(source, source.getId());
3219         assertTrue(
3220                 mDatastoreManager.runInTransaction(
3221                         measurementDao ->
3222                                 measurementDao.doInstallAttribution(
3223                                         INSTALLED_PACKAGE, currentTimestamp)));
3224         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3225         assertTrue(getInstallAttributionStatus("IA1", db));
3226         removeSources(Collections.singletonList("IA1"), db);
3227 
3228         // Active source should not get install attributed
3229         source.setStatus(Source.Status.IGNORED);
3230         insertSource(source, source.getId());
3231         assertTrue(
3232                 mDatastoreManager.runInTransaction(
3233                         measurementDao ->
3234                                 measurementDao.doInstallAttribution(
3235                                         INSTALLED_PACKAGE, currentTimestamp)));
3236         assertFalse(getInstallAttributionStatus("IA1", db));
3237         removeSources(Collections.singletonList("IA1"), db);
3238 
3239         // MARKED_TO_DELETE source should not get install attributed
3240         source.setStatus(Source.Status.MARKED_TO_DELETE);
3241         insertSource(source, source.getId());
3242         assertTrue(
3243                 mDatastoreManager.runInTransaction(
3244                         measurementDao ->
3245                                 measurementDao.doInstallAttribution(
3246                                         INSTALLED_PACKAGE, currentTimestamp)));
3247         assertFalse(getInstallAttributionStatus("IA1", db));
3248         removeSources(Collections.singletonList("IA1"), db);
3249     }
3250 
3251     @Test
3252     public void
doInstallAttribution_withSourcesAcrossEnrollments_marksOneInstallFromEachRegOrigin()3253             doInstallAttribution_withSourcesAcrossEnrollments_marksOneInstallFromEachRegOrigin() {
3254         long currentTimestamp = System.currentTimeMillis();
3255 
3256         // Enrollment1: Choose IA2 because that's newer and still occurred before install
3257         insertSource(
3258                 createSourceForIATest(
3259                                 "IA1",
3260                                 currentTimestamp,
3261                                 -1,
3262                                 10,
3263                                 false,
3264                                 DEFAULT_ENROLLMENT_ID + "_1")
3265                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example1.test"))
3266                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3267                         .build(),
3268                 "IA1");
3269         insertSource(
3270                 createSourceForIATest(
3271                                 "IA2", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_1")
3272                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example1.test"))
3273                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3274                         .build(),
3275                 "IA2");
3276 
3277         // Enrollment2: Choose IA4 because IA3's install attribution window has expired
3278         insertSource(
3279                 createSourceForIATest(
3280                                 "IA3", currentTimestamp, -1, 10, true, DEFAULT_ENROLLMENT_ID + "_2")
3281                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example2.test"))
3282                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3283                         .build(),
3284                 "IA3");
3285         insertSource(
3286                 createSourceForIATest(
3287                                 "IA4", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_2")
3288                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example2.test"))
3289                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3290                         .build(),
3291                 "IA4");
3292 
3293         // Enrollment3: Choose IA5 because IA6 was registered after install event
3294         insertSource(
3295                 createSourceForIATest(
3296                                 "IA5",
3297                                 currentTimestamp,
3298                                 -1,
3299                                 10,
3300                                 false,
3301                                 DEFAULT_ENROLLMENT_ID + "_3")
3302                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example3.test"))
3303                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3304                         .build(),
3305                 "IA5");
3306         insertSource(
3307                 createSourceForIATest(
3308                                 "IA6", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID + "_3")
3309                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example3.test"))
3310                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3311                         .build(),
3312                 "IA6");
3313 
3314         // Enrollment4: Choose IA8 due to higher priority
3315         insertSource(
3316                 createSourceForIATest(
3317                                 "IA7", currentTimestamp, 5, 10, false, DEFAULT_ENROLLMENT_ID + "_4")
3318                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example4.test"))
3319                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3320                         .build(),
3321                 "IA7");
3322         insertSource(
3323                 createSourceForIATest(
3324                                 "IA8",
3325                                 currentTimestamp,
3326                                 10,
3327                                 10,
3328                                 false,
3329                                 DEFAULT_ENROLLMENT_ID + "_4")
3330                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example4.test"))
3331                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3332                         .build(),
3333                 "IA8");
3334 
3335         // Enrollment5: Choose none because both sources are ineligible
3336         // Expired install attribution window
3337         insertSource(
3338                 createSourceForIATest(
3339                                 "IA9", currentTimestamp, 5, 31, true, DEFAULT_ENROLLMENT_ID + "_5")
3340                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example5.test"))
3341                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3342                         .build(),
3343                 "IA9");
3344         // Registered after install attribution
3345         insertSource(
3346                 createSourceForIATest(
3347                                 "IA10",
3348                                 currentTimestamp,
3349                                 10,
3350                                 3,
3351                                 false,
3352                                 DEFAULT_ENROLLMENT_ID + "_5")
3353                         .setRegistrationOrigin(WebUtil.validUri("https://subdomain.example5.test"))
3354                         .setInstallCooldownWindow(COOLDOWN_WINDOW)
3355                         .build(),
3356                 "IA10");
3357 
3358         assertTrue(
3359                 mDatastoreManager.runInTransaction(
3360                         measurementDao -> {
3361                             measurementDao.doInstallAttribution(
3362                                     INSTALLED_PACKAGE, currentTimestamp - DAYS.toMillis(7));
3363                         }));
3364         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3365         assertTrue(getInstallAttributionStatus("IA2", db));
3366         assertTrue(getInstallAttributionStatus("IA4", db));
3367         assertTrue(getInstallAttributionStatus("IA5", db));
3368         assertTrue(getInstallAttributionStatus("IA8", db));
3369 
3370         assertFalse(getInstallAttributionStatus("IA1", db));
3371         assertFalse(getInstallAttributionStatus("IA3", db));
3372         assertFalse(getInstallAttributionStatus("IA6", db));
3373         assertFalse(getInstallAttributionStatus("IA7", db));
3374         assertFalse(getInstallAttributionStatus("IA9", db));
3375         assertFalse(getInstallAttributionStatus("IA10", db));
3376 
3377         removeSources(
3378                 Arrays.asList(
3379                         "IA1", "IA2", "IA3", "IA4", "IA5", "IA6", "IA7", "IA8", "IA8", "IA10"),
3380                 db);
3381     }
3382 
3383     @Test
deleteFlexEventReportsAndAttributions_success()3384     public void deleteFlexEventReportsAndAttributions_success() throws JSONException {
3385         // Setup - Creates the following -
3386         // source - S1, S2
3387         // trigger - T1, T2, T3
3388         // event reports - (E13, E23) (E11_1, E11_2, E11_3) (E21) (E22_1, E22_2)
3389         // attributions
3390         //    (ATT11_00 (aggregate scope), ATT11_01 (aggregate scope))
3391         //    (ATT11_1, ATT11_2, ATT11_3) (ATT21) (ATT22_1, ATT22_2)
3392         prepareDataForFlexEventReportAndAttributionDeletion();
3393 
3394         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3395 
3396         // Assert attributions present
3397         assertNotNull(getAttribution("ATT11_1", db));
3398         assertNotNull(getAttribution("ATT11_2", db));
3399         assertNotNull(getAttribution("ATT11_3", db));
3400         assertNotNull(getAttribution("ATT21", db));
3401         assertNotNull(getAttribution("ATT22_1", db));
3402         assertNotNull(getAttribution("ATT22_2", db));
3403 
3404         mDatastoreManager.runInTransaction(
3405                 measurementDao -> {
3406                     // Assert sources and triggers present
3407                     assertNotNull(measurementDao.getSource("S1"));
3408                     assertNotNull(measurementDao.getSource("S2"));
3409                     assertNotNull(measurementDao.getTrigger("T1"));
3410                     assertNotNull(measurementDao.getTrigger("T2"));
3411 
3412                     // Validate presence of unmatched event reports
3413                     measurementDao.getEventReport("E13");
3414                     measurementDao.getEventReport("E23");
3415 
3416                     // Event report group 1
3417                     // Validate event reports present
3418                     EventReport e111 = measurementDao.getEventReport("E11_1");
3419                     EventReport e112 = measurementDao.getEventReport("E11_2");
3420 
3421                     // Deletion
3422                     measurementDao.deleteFlexEventReportsAndAttributions(
3423                             List.of(e111, e112));
3424 
3425                     // Validate event report deletion
3426                     assertThrows(
3427                             DatastoreException.class,
3428                             () -> {
3429                                 measurementDao.getEventReport("E11_1");
3430                             });
3431                     assertThrows(
3432                             DatastoreException.class,
3433                             () -> {
3434                                 measurementDao.getEventReport("E11_2");
3435                             });
3436                     assertNotNull(measurementDao.getEventReport("E11_3"));
3437 
3438                     // Event report group 2
3439                     // Validate event reports present
3440                     EventReport e21 = measurementDao.getEventReport("E21");
3441 
3442                     // Deletion
3443                     measurementDao.deleteFlexEventReportsAndAttributions(
3444                             List.of(e21));
3445 
3446                     // Validate event report deletion
3447                     assertThrows(
3448                             DatastoreException.class,
3449                             () -> {
3450                                 measurementDao.getEventReport("E21");
3451                             });
3452 
3453                     // Event report group 3
3454                     // Validate event reports present (retrieval doesn't throw)
3455                     measurementDao.getEventReport("E22_1");
3456                     EventReport e222 = measurementDao.getEventReport("E22_2");
3457 
3458                     // Deletion
3459                     measurementDao.deleteFlexEventReportsAndAttributions(
3460                             List.of(e222));
3461 
3462                     // Validate event report deletion
3463                     assertThrows(
3464                             DatastoreException.class,
3465                             () -> {
3466                                 measurementDao.getEventReport("E22_2");
3467                             });
3468                     assertNotNull(measurementDao.getEventReport("E22_1"));
3469 
3470                     // Validate sources and triggers present
3471                     assertNotNull(measurementDao.getSource("S1"));
3472                     assertNotNull(measurementDao.getSource("S2"));
3473                     assertNotNull(measurementDao.getTrigger("T1"));
3474                     assertNotNull(measurementDao.getTrigger("T2"));
3475 
3476                     // Validate presence of unmatched event reports
3477                     measurementDao.getEventReport("E13");
3478                     measurementDao.getEventReport("E23");
3479                 });
3480 
3481         // Validate attribution deletion
3482         assertNotNull(getAttribution("ATT11_00", db));
3483         assertNotNull(getAttribution("ATT11_01", db));
3484         assertNull(getAttribution("ATT11_1", db));
3485         assertNull(getAttribution("ATT11_2", db));
3486         assertNotNull(getAttribution("ATT11_3", db));
3487         assertNull(getAttribution("ATT21", db));
3488         // Attribution deletion order within the group associated with an event report is generally
3489         // by insertion order, although it's not guaranteed. We deleted event report E22_2 but the
3490         // first limited associated attribution returned is ATT22_1.
3491         assertNull(getAttribution("ATT22_1", db));
3492         assertNotNull(getAttribution("ATT22_2", db));
3493     }
3494 
3495     @Test
deleteSources_providedIds_deletesMatchingSourcesAndRelatedData()3496     public void deleteSources_providedIds_deletesMatchingSourcesAndRelatedData()
3497             throws JSONException {
3498         // Setup - Creates the following -
3499         // source - S1, S2, S3, S4
3500         // trigger - T1, T2, T3, T4
3501         // event reports - E11, E12, E21, E22, E23, E33, E44
3502         // aggregate reports - AR11, AR12, AR21, AR34
3503         // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
3504         prepareDataForSourceAndTriggerDeletion();
3505 
3506         // Execution
3507         mDatastoreManager.runInTransaction(
3508                 measurementDao -> {
3509                     measurementDao.deleteSources(List.of("S1", "S2"));
3510 
3511                     assertThrows(
3512                             DatastoreException.class,
3513                             () -> {
3514                                 measurementDao.getSource("S1");
3515                             });
3516                     assertThrows(
3517                             DatastoreException.class,
3518                             () -> {
3519                                 measurementDao.getSource("S2");
3520                             });
3521 
3522                     assertNotNull(measurementDao.getSource("S3"));
3523                     assertNotNull(measurementDao.getSource("S4"));
3524                     assertNotNull(measurementDao.getTrigger("T1"));
3525                     assertNotNull(measurementDao.getTrigger("T2"));
3526                     assertNotNull(measurementDao.getTrigger("T3"));
3527                     assertNotNull(measurementDao.getTrigger("T4"));
3528 
3529                     assertThrows(
3530                             DatastoreException.class,
3531                             () -> {
3532                                 measurementDao.getEventReport("E11");
3533                             });
3534                     assertThrows(
3535                             DatastoreException.class,
3536                             () -> {
3537                                 measurementDao.getEventReport("E12");
3538                             });
3539                     assertThrows(
3540                             DatastoreException.class,
3541                             () -> {
3542                                 measurementDao.getEventReport("E21");
3543                             });
3544                     assertThrows(
3545                             DatastoreException.class,
3546                             () -> {
3547                                 measurementDao.getEventReport("E22");
3548                             });
3549                     assertThrows(
3550                             DatastoreException.class,
3551                             () -> {
3552                                 measurementDao.getEventReport("E23");
3553                             });
3554                     assertNotNull(measurementDao.getEventReport("E33"));
3555                     assertNotNull(measurementDao.getEventReport("E44"));
3556 
3557                     assertThrows(
3558                             DatastoreException.class,
3559                             () -> {
3560                                 measurementDao.getAggregateReport("AR11");
3561                             });
3562                     assertThrows(
3563                             DatastoreException.class,
3564                             () -> {
3565                                 measurementDao.getAggregateReport("AR12");
3566                             });
3567                     assertThrows(
3568                             DatastoreException.class,
3569                             () -> {
3570                                 measurementDao.getAggregateReport("AR21");
3571                             });
3572                     assertNotNull(measurementDao.getAggregateReport("AR34"));
3573                 });
3574 
3575         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3576         assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
3577     }
3578 
3579     @Test
deleteSource_providedId_deletesMatchingXnaIgnoredSource()3580     public void deleteSource_providedId_deletesMatchingXnaIgnoredSource() {
3581         // Setup
3582         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3583 
3584         Source s1 =
3585                 SourceFixture.getMinimalValidSourceBuilder()
3586                         .setEventId(new UnsignedLong(1L))
3587                         .setId("S1")
3588                         .setEnrollmentId("1")
3589                         .build();
3590 
3591         ContentValues sourceValues = new ContentValues();
3592         sourceValues.put(SourceContract.ID, s1.getId());
3593         sourceValues.put(SourceContract.EVENT_ID, s1.getEventId().getValue());
3594 
3595         ContentValues xnaIgnoredSourceValues = new ContentValues();
3596         xnaIgnoredSourceValues.put(XnaIgnoredSourcesContract.SOURCE_ID, s1.getId());
3597         xnaIgnoredSourceValues.put(XnaIgnoredSourcesContract.ENROLLMENT_ID, s1.getEnrollmentId());
3598 
3599         // Execution
3600         db.insert(SourceContract.TABLE, null, sourceValues);
3601         db.insert(XnaIgnoredSourcesContract.TABLE, null, xnaIgnoredSourceValues);
3602 
3603         // Assertion
3604         assertEquals(1, DatabaseUtils.queryNumEntries(db, SourceContract.TABLE));
3605         assertEquals(1, DatabaseUtils.queryNumEntries(db, XnaIgnoredSourcesContract.TABLE));
3606 
3607         // Execution
3608         removeSources(Collections.singletonList(s1.getId()), db);
3609 
3610         // Assertion
3611         assertEquals(0, DatabaseUtils.queryNumEntries(db, SourceContract.TABLE));
3612         assertEquals(0, DatabaseUtils.queryNumEntries(db, XnaIgnoredSourcesContract.TABLE));
3613     }
3614 
3615     @Test
deleteTriggers_providedIds_deletesMatchingTriggersAndRelatedData()3616     public void deleteTriggers_providedIds_deletesMatchingTriggersAndRelatedData()
3617             throws JSONException {
3618         // Setup - Creates the following -
3619         // source - S1, S2, S3, S4
3620         // trigger - T1, T2, T3, T4
3621         // event reports - E11, E12, E21, E22, E23, E33, E44
3622         // aggregate reports - AR11, AR12, AR21, AR34
3623         // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
3624         prepareDataForSourceAndTriggerDeletion();
3625 
3626         // Execution
3627         mDatastoreManager.runInTransaction(
3628                 measurementDao -> {
3629                     measurementDao.deleteTriggers(List.of("T1", "T2"));
3630 
3631                     assertNotNull(measurementDao.getSource("S1"));
3632                     assertNotNull(measurementDao.getSource("S2"));
3633                     assertNotNull(measurementDao.getSource("S3"));
3634                     assertNotNull(measurementDao.getSource("S4"));
3635                     assertThrows(DatastoreException.class, () -> measurementDao.getTrigger("T1"));
3636                     assertThrows(DatastoreException.class, () -> measurementDao.getTrigger("T2"));
3637                     assertNotNull(measurementDao.getTrigger("T3"));
3638                     assertNotNull(measurementDao.getTrigger("T4"));
3639 
3640                     assertThrows(
3641                             DatastoreException.class, () -> measurementDao.getEventReport("E11"));
3642                     assertThrows(
3643                             DatastoreException.class, () -> measurementDao.getEventReport("E12"));
3644                     assertThrows(
3645                             DatastoreException.class, () -> measurementDao.getEventReport("E21"));
3646                     assertThrows(
3647                             DatastoreException.class, () -> measurementDao.getEventReport("E22"));
3648                     assertNotNull(measurementDao.getEventReport("E23"));
3649                     assertNotNull(measurementDao.getEventReport("E33"));
3650                     assertNotNull(measurementDao.getEventReport("E44"));
3651 
3652                     assertThrows(
3653                             DatastoreException.class,
3654                             () -> measurementDao.getAggregateReport("AR11"));
3655                     assertThrows(
3656                             DatastoreException.class,
3657                             () -> measurementDao.getAggregateReport("AR12"));
3658                     assertThrows(
3659                             DatastoreException.class,
3660                             () -> measurementDao.getAggregateReport("AR21"));
3661 
3662                     assertNotNull(measurementDao.getAggregateReport("AR34"));
3663                 });
3664 
3665         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3666         assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
3667     }
3668 
3669     // Setup - Creates the following -
3670     // source - S1, S2
3671     // trigger - T1, T2
3672     // event reports - E11_1, E11_2, E11_3, E21, E22_1, E22_2
3673     // attributions - ATT11_1, ATT11_2, ATT11_3, ATT21, ATT22_1, ATT22_2
prepareDataForFlexEventReportAndAttributionDeletion()3674     private void prepareDataForFlexEventReportAndAttributionDeletion() throws JSONException {
3675         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3676         Source s1 =
3677                 SourceFixture.getMinimalValidSourceBuilder()
3678                         .setEventId(new UnsignedLong(1L))
3679                         .setId("S1")
3680                         .build();
3681         Source s2 =
3682                 SourceFixture.getMinimalValidSourceBuilder()
3683                         .setEventId(new UnsignedLong(2L))
3684                         .setId("S2")
3685                         .build();
3686         Trigger t1 =
3687                 TriggerFixture.getValidTriggerBuilder()
3688                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
3689                         .setId("T1")
3690                         .build();
3691         Trigger t2 =
3692                 TriggerFixture.getValidTriggerBuilder()
3693                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
3694                         .setId("T2")
3695                         .build();
3696         Trigger t3 =
3697                 TriggerFixture.getValidTriggerBuilder()
3698                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
3699                         .setId("T2")
3700                         .build();
3701         EventReport e111 = createEventReportForSourceAndTrigger("E11_1", s1, t1);
3702         EventReport e112 = createEventReportForSourceAndTrigger("E11_2", s1, t1);
3703         EventReport e113 = createEventReportForSourceAndTrigger("E11_3", s1, t1);
3704         EventReport e21 = createEventReportForSourceAndTrigger("E21", s2, t1);
3705         EventReport e221 = createEventReportForSourceAndTrigger("E22_1", s2, t2);
3706         EventReport e222 = createEventReportForSourceAndTrigger("E22_2", s2, t2);
3707         EventReport e13 = createEventReportForSourceAndTrigger("E13", s1, t3);
3708         EventReport e23 = createEventReportForSourceAndTrigger("E23", s2, t3);
3709         Attribution att1100 =
3710                 createAttribution(
3711                         "ATT11_00",
3712                         Attribution.Scope.AGGREGATE,
3713                         s1.getId(),
3714                         t1.getId());
3715         Attribution att1101 =
3716                 createAttribution(
3717                         "ATT11_01",
3718                         Attribution.Scope.AGGREGATE,
3719                         s1.getId(),
3720                         t1.getId());
3721         Attribution att111 =
3722                 createAttribution(
3723                         "ATT11_1", s1.getId(), t1.getId());
3724         Attribution att112 =
3725                 createAttribution(
3726                         "ATT11_2", s1.getId(), t1.getId());
3727         Attribution att113 =
3728                 createAttribution(
3729                         "ATT11_3", s1.getId(), t1.getId());
3730         Attribution att21 =
3731                 createAttribution(
3732                         "ATT21", s2.getId(), t1.getId());
3733         Attribution att221 =
3734                 createAttribution(
3735                         "ATT22_1", s2.getId(), t2.getId());
3736         Attribution att222 =
3737                 createAttribution(
3738                         "ATT22_2", s2.getId(), t2.getId());
3739 
3740         insertSource(s1, s1.getId());
3741         insertSource(s2, s2.getId());
3742         AbstractDbIntegrationTest.insertToDb(t1, db);
3743         AbstractDbIntegrationTest.insertToDb(t2, db);
3744         AbstractDbIntegrationTest.insertToDb(e111, db);
3745         AbstractDbIntegrationTest.insertToDb(e112, db);
3746         AbstractDbIntegrationTest.insertToDb(e113, db);
3747         AbstractDbIntegrationTest.insertToDb(e21, db);
3748         AbstractDbIntegrationTest.insertToDb(e221, db);
3749         AbstractDbIntegrationTest.insertToDb(e222, db);
3750         AbstractDbIntegrationTest.insertToDb(e13, db);
3751         AbstractDbIntegrationTest.insertToDb(e23, db);
3752         AbstractDbIntegrationTest.insertToDb(att1100, db);
3753         AbstractDbIntegrationTest.insertToDb(att1101, db);
3754         AbstractDbIntegrationTest.insertToDb(att111, db);
3755         AbstractDbIntegrationTest.insertToDb(att112, db);
3756         AbstractDbIntegrationTest.insertToDb(att113, db);
3757         AbstractDbIntegrationTest.insertToDb(att21, db);
3758         AbstractDbIntegrationTest.insertToDb(att221, db);
3759         AbstractDbIntegrationTest.insertToDb(att222, db);
3760     }
3761 
3762     // Setup - Creates the following -
3763     // source - S1, S2, S3, S4
3764     // trigger - T1, T2, T3, T4
3765     // event reports - E11, E12, E21, E22, E23, E33, E44
3766     // aggregate reports - AR11, AR12, AR21, AR34
3767     // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
prepareDataForSourceAndTriggerDeletion()3768     private void prepareDataForSourceAndTriggerDeletion() throws JSONException {
3769         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3770         Source s1 =
3771                 SourceFixture.getMinimalValidSourceBuilder()
3772                         .setEventId(new UnsignedLong(1L))
3773                         .setId("S1")
3774                         .build(); // deleted
3775         Source s2 =
3776                 SourceFixture.getMinimalValidSourceBuilder()
3777                         .setEventId(new UnsignedLong(2L))
3778                         .setId("S2")
3779                         .build(); // deleted
3780         Source s3 =
3781                 SourceFixture.getMinimalValidSourceBuilder()
3782                         .setEventId(new UnsignedLong(3L))
3783                         .setId("S3")
3784                         .build();
3785         Source s4 =
3786                 SourceFixture.getMinimalValidSourceBuilder()
3787                         .setEventId(new UnsignedLong(4L))
3788                         .setId("S4")
3789                         .build();
3790         Trigger t1 =
3791                 TriggerFixture.getValidTriggerBuilder()
3792                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
3793                         .setId("T1")
3794                         .build();
3795         Trigger t2 =
3796                 TriggerFixture.getValidTriggerBuilder()
3797                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
3798                         .setId("T2")
3799                         .build();
3800         Trigger t3 =
3801                 TriggerFixture.getValidTriggerBuilder()
3802                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
3803                         .setId("T3")
3804                         .build();
3805         Trigger t4 =
3806                 TriggerFixture.getValidTriggerBuilder()
3807                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
3808                         .setId("T4")
3809                         .build();
3810         EventReport e11 = createEventReportForSourceAndTrigger("E11", s1, t1);
3811         EventReport e12 = createEventReportForSourceAndTrigger("E12", s1, t2);
3812         EventReport e21 = createEventReportForSourceAndTrigger("E21", s2, t1);
3813         EventReport e22 = createEventReportForSourceAndTrigger("E22", s2, t2);
3814         EventReport e23 = createEventReportForSourceAndTrigger("E23", s2, t3);
3815         EventReport e33 = createEventReportForSourceAndTrigger("E33", s3, t3);
3816         EventReport e44 = createEventReportForSourceAndTrigger("E44", s4, t4);
3817         AggregateReport ar11 = createAggregateReportForSourceAndTrigger("AR11", s1, t1);
3818         AggregateReport ar12 = createAggregateReportForSourceAndTrigger("AR12", s1, t2);
3819         AggregateReport ar21 = createAggregateReportForSourceAndTrigger("AR21", s2, t1);
3820         AggregateReport ar34 = createAggregateReportForSourceAndTrigger("AR34", s3, t4);
3821         Attribution att11 =
3822                 createAttribution(
3823                         "ATT11", s1.getId(), t1.getId()); // deleted
3824         Attribution att12 =
3825                 createAttribution(
3826                         "ATT12", s1.getId(), t2.getId()); // deleted
3827         Attribution att21 =
3828                 createAttribution(
3829                         "ATT21", s2.getId(), t1.getId()); // deleted
3830         Attribution att22 =
3831                 createAttribution(
3832                         "ATT22", s2.getId(), t2.getId()); // deleted
3833         Attribution att33 =
3834                 createAttribution("ATT33", s3.getId(), t3.getId());
3835         Attribution att44 =
3836                 createAttribution("ATT44", s4.getId(), t4.getId());
3837 
3838         insertSource(s1, s1.getId());
3839         insertSource(s2, s2.getId());
3840         insertSource(s3, s3.getId());
3841         insertSource(s4, s4.getId());
3842 
3843         AbstractDbIntegrationTest.insertToDb(t1, db);
3844         AbstractDbIntegrationTest.insertToDb(t2, db);
3845         AbstractDbIntegrationTest.insertToDb(t3, db);
3846         AbstractDbIntegrationTest.insertToDb(t4, db);
3847 
3848         AbstractDbIntegrationTest.insertToDb(e11, db);
3849         AbstractDbIntegrationTest.insertToDb(e12, db);
3850         AbstractDbIntegrationTest.insertToDb(e21, db);
3851         AbstractDbIntegrationTest.insertToDb(e22, db);
3852         AbstractDbIntegrationTest.insertToDb(e23, db);
3853         AbstractDbIntegrationTest.insertToDb(e33, db);
3854         AbstractDbIntegrationTest.insertToDb(e44, db);
3855 
3856         AbstractDbIntegrationTest.insertToDb(ar11, db);
3857         AbstractDbIntegrationTest.insertToDb(ar12, db);
3858         AbstractDbIntegrationTest.insertToDb(ar21, db);
3859         AbstractDbIntegrationTest.insertToDb(ar34, db);
3860 
3861         AbstractDbIntegrationTest.insertToDb(att11, db);
3862         AbstractDbIntegrationTest.insertToDb(att12, db);
3863         AbstractDbIntegrationTest.insertToDb(att21, db);
3864         AbstractDbIntegrationTest.insertToDb(att22, db);
3865         AbstractDbIntegrationTest.insertToDb(att33, db);
3866         AbstractDbIntegrationTest.insertToDb(att44, db);
3867     }
3868 
3869     @Test
testUndoInstallAttribution_noMarkedSource()3870     public void testUndoInstallAttribution_noMarkedSource() {
3871         long currentTimestamp = System.currentTimeMillis();
3872         Source source =
3873                 createSourceForIATest("IA1", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID)
3874                         .build();
3875         source.setInstallAttributed(true);
3876         insertSource(source, source.getId());
3877         assertTrue(
3878                 mDatastoreManager.runInTransaction(
3879                         measurementDao ->
3880                                 measurementDao.undoInstallAttribution(INSTALLED_PACKAGE)));
3881         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3882         // Should set installAttributed = false for id=IA1
3883         assertFalse(getInstallAttributionStatus("IA1", db));
3884     }
3885 
3886     @Test
undoInstallAttribution_uninstall_nullInstallTime()3887     public void undoInstallAttribution_uninstall_nullInstallTime() {
3888         long currentTimestamp = System.currentTimeMillis();
3889         Source source =
3890                 createSourceForIATest("IA1", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID)
3891                         .build();
3892         source.setInstallAttributed(true);
3893         insertSource(source, source.getId());
3894         assertTrue(
3895                 mDatastoreManager.runInTransaction(
3896                         measurementDao ->
3897                                 measurementDao.undoInstallAttribution(INSTALLED_PACKAGE)));
3898         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
3899         // Should set installTime = null for id=IA1
3900         assertNull(getInstallAttributionInstallTime("IA1", db));
3901     }
3902 
3903     @Test
getSourceDestinations_returnsExpected()3904     public void getSourceDestinations_returnsExpected() {
3905         // Insert two sources with some intersection of destinations
3906         // and assert all destination queries work.
3907 
3908         // First source
3909         List<Uri> webDestinations1 =
3910                 List.of(
3911                         Uri.parse("https://first-place.test"),
3912                         Uri.parse("https://second-place.test"),
3913                         Uri.parse("https://third-place.test"));
3914         List<Uri> appDestinations1 = List.of(Uri.parse("android-app://test.first-place"));
3915         Source source1 =
3916                 SourceFixture.getMinimalValidSourceBuilder()
3917                         .setId("1")
3918                         .setAppDestinations(appDestinations1)
3919                         .setWebDestinations(webDestinations1)
3920                         .build();
3921         insertSource(source1, source1.getId());
3922 
3923         // Second source
3924         List<Uri> webDestinations2 =
3925                 List.of(
3926                         Uri.parse("https://not-first-place.test"),
3927                         Uri.parse("https://not-second-place.test"),
3928                         Uri.parse("https://third-place.test"));
3929         List<Uri> appDestinations2 = List.of(Uri.parse("android-app://test.not-first-place"));
3930         Source source2 =
3931                 SourceFixture.getMinimalValidSourceBuilder()
3932                         .setId("2")
3933                         .setAppDestinations(appDestinations2)
3934                         .setWebDestinations(webDestinations2)
3935                         .build();
3936         insertSource(source2, source2.getId());
3937 
3938         Pair<List<Uri>, List<Uri>> result1 =
3939                 mDatastoreManager
3940                         .runInTransactionWithResult(
3941                                 measurementDao ->
3942                                         measurementDao.getSourceDestinations(source1.getId()))
3943                         .get();
3944         // Assert first app destinations
3945         assertTrue(
3946                 ImmutableMultiset.copyOf(source1.getAppDestinations())
3947                         .equals(ImmutableMultiset.copyOf(result1.first)));
3948         // Assert first web destinations
3949         assertTrue(
3950                 ImmutableMultiset.copyOf(source1.getWebDestinations())
3951                         .equals(ImmutableMultiset.copyOf(result1.second)));
3952 
3953         Pair<List<Uri>, List<Uri>> result2 =
3954                 mDatastoreManager
3955                         .runInTransactionWithResult(
3956                                 measurementDao ->
3957                                         measurementDao.getSourceDestinations(source2.getId()))
3958                         .get();
3959         // Assert second app destinations
3960         assertTrue(
3961                 ImmutableMultiset.copyOf(source2.getAppDestinations())
3962                         .equals(ImmutableMultiset.copyOf(result2.first)));
3963         // Assert second web destinations
3964         assertTrue(
3965                 ImmutableMultiset.copyOf(source2.getWebDestinations())
3966                         .equals(ImmutableMultiset.copyOf(result2.second)));
3967     }
3968 
3969     @Test
getNumAggregateReportsPerSource_returnsExpected()3970     public void getNumAggregateReportsPerSource_returnsExpected() {
3971         List<Source> sources =
3972                 Arrays.asList(
3973                         SourceFixture.getMinimalValidSourceBuilder()
3974                                 .setEventId(new UnsignedLong(1L))
3975                                 .setId("source1")
3976                                 .build(),
3977                         SourceFixture.getMinimalValidSourceBuilder()
3978                                 .setEventId(new UnsignedLong(2L))
3979                                 .setId("source2")
3980                                 .build(),
3981                         SourceFixture.getMinimalValidSourceBuilder()
3982                                 .setEventId(new UnsignedLong(3L))
3983                                 .setId("source3")
3984                                 .build());
3985         List<AggregateReport> reports =
3986                 Arrays.asList(
3987                         generateMockAggregateReport(
3988                                 WebUtil.validUrl("https://destination-1.test"),
3989                                 1,
3990                                 "source1",
3991                                 AggregateReportFixture.ValidAggregateReportParams.API),
3992                         generateMockAggregateReport(
3993                                 WebUtil.validUrl("https://destination-1.test"),
3994                                 2,
3995                                 "source1",
3996                                 AggregateReportFixture.ValidAggregateReportParams.API),
3997                         generateMockAggregateReport(
3998                                 WebUtil.validUrl("https://destination-2.test"),
3999                                 3,
4000                                 "source2",
4001                                 AggregateReportFixture.ValidAggregateReportParams.API),
4002                         generateMockAggregateReport(
4003                                 WebUtil.validUrl("https://destination-1.test"),
4004                                 4,
4005                                 "source3",
4006                                 AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API),
4007                         generateMockAggregateReport(
4008                                 WebUtil.validUrl("https://destination-1.test"),
4009                                 5,
4010                                 "source3",
4011                                 AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API),
4012                         generateMockAggregateReport(
4013                                 WebUtil.validUrl("https://destination-2.test"),
4014                                 6,
4015                                 "source3",
4016                                 AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API));
4017 
4018         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4019         Objects.requireNonNull(db);
4020         sources.forEach(source -> insertSource(source, source.getId()));
4021         Consumer<AggregateReport> aggregateReportConsumer =
4022                 aggregateReport -> {
4023                     ContentValues values = new ContentValues();
4024                     values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId());
4025                     values.put(
4026                             MeasurementTables.AggregateReport.SOURCE_ID,
4027                             aggregateReport.getSourceId());
4028                     values.put(
4029                             MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
4030                             aggregateReport.getAttributionDestination().toString());
4031                     values.put(MeasurementTables.AggregateReport.API, aggregateReport.getApi());
4032                     db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
4033                 };
4034         reports.forEach(aggregateReportConsumer);
4035 
4036         mDatastoreManager.runInTransaction(
4037                 measurementDao -> {
4038                     assertThat(
4039                                     measurementDao.countNumAggregateReportsPerSource(
4040                                             "source1",
4041                                             AggregateReportFixture.ValidAggregateReportParams.API))
4042                             .isEqualTo(2);
4043                     assertThat(
4044                                     measurementDao.countNumAggregateReportsPerSource(
4045                                             "source2",
4046                                             AggregateReportFixture.ValidAggregateReportParams.API))
4047                             .isEqualTo(1);
4048                     assertThat(
4049                                     measurementDao.countNumAggregateReportsPerSource(
4050                                             "source3",
4051                                             AggregateReportFixture.ValidAggregateReportParams.API))
4052                             .isEqualTo(0);
4053                     assertThat(
4054                                     measurementDao.countNumAggregateReportsPerSource(
4055                                             "source1",
4056                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4057                             .isEqualTo(0);
4058                     assertThat(
4059                                     measurementDao.countNumAggregateReportsPerSource(
4060                                             "source2",
4061                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4062                             .isEqualTo(0);
4063                     assertThat(
4064                                     measurementDao.countNumAggregateReportsPerSource(
4065                                             "source3",
4066                                             AggregateDebugReportApi.AGGREGATE_DEBUG_REPORT_API))
4067                             .isEqualTo(3);
4068                 });
4069     }
4070 
4071     @Test
getNumAggregateReportsPerDestination_returnsExpected()4072     public void getNumAggregateReportsPerDestination_returnsExpected() {
4073         List<AggregateReport> reportsWithPlainDestination =
4074                 Arrays.asList(
4075                         generateMockAggregateReport(
4076                                 WebUtil.validUrl("https://destination-1.test"), 1));
4077         List<AggregateReport> reportsWithPlainAndSubDomainDestination =
4078                 Arrays.asList(
4079                         generateMockAggregateReport(
4080                                 WebUtil.validUrl("https://destination-2.test"), 2),
4081                         generateMockAggregateReport(
4082                                 WebUtil.validUrl("https://subdomain.destination-2.test"), 3));
4083         List<AggregateReport> reportsWithPlainAndPathDestination =
4084                 Arrays.asList(
4085                         generateMockAggregateReport(
4086                                 WebUtil.validUrl("https://subdomain.destination-3.test"), 4),
4087                         generateMockAggregateReport(
4088                                 WebUtil.validUrl("https://subdomain.destination-3.test/abcd"), 5));
4089         List<AggregateReport> reportsWithAll3Types =
4090                 Arrays.asList(
4091                         generateMockAggregateReport(
4092                                 WebUtil.validUrl("https://destination-4.test"), 6),
4093                         generateMockAggregateReport(
4094                                 WebUtil.validUrl("https://subdomain.destination-4.test"), 7),
4095                         generateMockAggregateReport(
4096                                 WebUtil.validUrl("https://subdomain.destination-4.test/abcd"), 8));
4097         List<AggregateReport> reportsWithAndroidAppDestination =
4098                 Arrays.asList(generateMockAggregateReport("android-app://destination-5.app", 9));
4099 
4100         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4101         Objects.requireNonNull(db);
4102         Stream.of(
4103                         reportsWithPlainDestination,
4104                         reportsWithPlainAndSubDomainDestination,
4105                         reportsWithPlainAndPathDestination,
4106                         reportsWithAll3Types,
4107                         reportsWithAndroidAppDestination)
4108                 .flatMap(Collection::stream)
4109                 .forEach(
4110                         aggregateReport -> {
4111                             ContentValues values = new ContentValues();
4112                             values.put(
4113                                     MeasurementTables.AggregateReport.ID, aggregateReport.getId());
4114                             values.put(
4115                                     MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
4116                                     aggregateReport.getAttributionDestination().toString());
4117                             values.put(
4118                                     MeasurementTables.AggregateReport.IS_FAKE_REPORT,
4119                                     aggregateReport.isFakeReport());
4120                             db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
4121                         });
4122 
4123         List<String> attributionDestinations1 = createWebDestinationVariants(1);
4124         List<String> attributionDestinations2 = createWebDestinationVariants(2);
4125         List<String> attributionDestinations3 = createWebDestinationVariants(3);
4126         List<String> attributionDestinations4 = createWebDestinationVariants(4);
4127         List<String> attributionDestinations5 = createAppDestinationVariants(5);
4128 
4129         // expected query return values for attribution destination variants
4130         List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0);
4131         List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4132         List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4133         List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0);
4134         List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0);
4135         assertAggregateReportCount(
4136                 attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts);
4137         assertAggregateReportCount(
4138                 attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts);
4139         assertAggregateReportCount(
4140                 attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts);
4141         assertAggregateReportCount(
4142                 attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts);
4143         assertAggregateReportCount(
4144                 attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts);
4145     }
4146 
4147     @Test
getAggregateReportById_fakeReport()4148     public void getAggregateReportById_fakeReport() {
4149         AggregateReport ar11 =
4150                 AggregateReportFixture.getValidAggregateReportBuilder()
4151                         .setId("11")
4152                         .setIsFakeReport(true)
4153                         .build();
4154         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4155         AbstractDbIntegrationTest.insertToDb(ar11, db);
4156 
4157         Optional<AggregateReport> resOpt =
4158                 mDatastoreManager.runInTransactionWithResult((dao) -> dao.getAggregateReport("11"));
4159         assertTrue(resOpt.isPresent());
4160         AggregateReport res = resOpt.get();
4161         assertTrue(res.isFakeReport());
4162     }
4163 
4164     @Test
getNumEventReportsPerDestination_returnsExpected()4165     public void getNumEventReportsPerDestination_returnsExpected() {
4166         List<EventReport> reportsWithPlainDestination =
4167                 Arrays.asList(
4168                         generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1));
4169         List<EventReport> reportsWithPlainAndSubDomainDestination =
4170                 Arrays.asList(
4171                         generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2),
4172                         generateMockEventReport(
4173                                 WebUtil.validUrl("https://subdomain.destination-2.test"), 3));
4174         List<EventReport> reportsWithPlainAndPathDestination =
4175                 Arrays.asList(
4176                         generateMockEventReport(
4177                                 WebUtil.validUrl("https://subdomain.destination-3.test"), 4),
4178                         generateMockEventReport(
4179                                 WebUtil.validUrl("https://subdomain.destination-3.test/abcd"), 5));
4180         List<EventReport> reportsWithAll3Types =
4181                 Arrays.asList(
4182                         generateMockEventReport(WebUtil.validUrl("https://destination-4.test"), 6),
4183                         generateMockEventReport(
4184                                 WebUtil.validUrl("https://subdomain.destination-4.test"), 7),
4185                         generateMockEventReport(
4186                                 WebUtil.validUrl("https://subdomain.destination-4.test/abcd"), 8));
4187         List<EventReport> reportsWithAndroidAppDestination =
4188                 Arrays.asList(generateMockEventReport("android-app://destination-5.app", 9));
4189 
4190         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4191         Objects.requireNonNull(db);
4192         Stream.of(
4193                         reportsWithPlainDestination,
4194                         reportsWithPlainAndSubDomainDestination,
4195                         reportsWithPlainAndPathDestination,
4196                         reportsWithAll3Types,
4197                         reportsWithAndroidAppDestination)
4198                 .flatMap(Collection::stream)
4199                 .forEach(
4200                         eventReport -> {
4201                             ContentValues values = new ContentValues();
4202                             values.put(EventReportContract.ID, eventReport.getId());
4203                             values.put(
4204                                     EventReportContract.ATTRIBUTION_DESTINATION,
4205                                     eventReport.getAttributionDestinations().get(0).toString());
4206                             db.insert(EventReportContract.TABLE, null, values);
4207                         });
4208 
4209         List<String> attributionDestinations1 = createWebDestinationVariants(1);
4210         List<String> attributionDestinations2 = createWebDestinationVariants(2);
4211         List<String> attributionDestinations3 = createWebDestinationVariants(3);
4212         List<String> attributionDestinations4 = createWebDestinationVariants(4);
4213         List<String> attributionDestinations5 = createAppDestinationVariants(5);
4214 
4215         // expected query return values for attribution destination variants
4216         List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0);
4217         List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4218         List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
4219         List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0);
4220         List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0);
4221         assertEventReportCount(
4222                 attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts);
4223         assertEventReportCount(
4224                 attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts);
4225         assertEventReportCount(
4226                 attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts);
4227         assertEventReportCount(
4228                 attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts);
4229         assertEventReportCount(
4230                 attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts);
4231     }
4232 
4233     @Test
testGetSourceEventReports()4234     public void testGetSourceEventReports() {
4235         List<Source> sourceList =
4236                 Arrays.asList(
4237                         SourceFixture.getMinimalValidSourceBuilder()
4238                                 .setId("1")
4239                                 .setEventId(new UnsignedLong(3L))
4240                                 .setEnrollmentId("1")
4241                                 .build(),
4242                         SourceFixture.getMinimalValidSourceBuilder()
4243                                 .setId("2")
4244                                 .setEventId(new UnsignedLong(4L))
4245                                 .setEnrollmentId("1")
4246                                 .build(),
4247                         // Should always be ignored
4248                         SourceFixture.getMinimalValidSourceBuilder()
4249                                 .setId("3")
4250                                 .setEventId(new UnsignedLong(4L))
4251                                 .setEnrollmentId("2")
4252                                 .build(),
4253                         SourceFixture.getMinimalValidSourceBuilder()
4254                                 .setId("15")
4255                                 .setEventId(new UnsignedLong(15L))
4256                                 .setEnrollmentId("2")
4257                                 .build(),
4258                         SourceFixture.getMinimalValidSourceBuilder()
4259                                 .setId("16")
4260                                 .setEventId(new UnsignedLong(16L))
4261                                 .setEnrollmentId("2")
4262                                 .build(),
4263                         SourceFixture.getMinimalValidSourceBuilder()
4264                                 .setId("20")
4265                                 .setEventId(new UnsignedLong(20L))
4266                                 .setEnrollmentId("2")
4267                                 .build());
4268 
4269         List<Trigger> triggers =
4270                 Arrays.asList(
4271                         TriggerFixture.getValidTriggerBuilder()
4272                                 .setId("101")
4273                                 .setEnrollmentId("2")
4274                                 .build(),
4275                         TriggerFixture.getValidTriggerBuilder()
4276                                 .setId("102")
4277                                 .setEnrollmentId("2")
4278                                 .build(),
4279                         TriggerFixture.getValidTriggerBuilder()
4280                                 .setId("201")
4281                                 .setEnrollmentId("2")
4282                                 .build(),
4283                         TriggerFixture.getValidTriggerBuilder()
4284                                 .setId("202")
4285                                 .setEnrollmentId("2")
4286                                 .build(),
4287                         TriggerFixture.getValidTriggerBuilder()
4288                                 .setId("1001")
4289                                 .setEnrollmentId("2")
4290                                 .build());
4291 
4292         // Should match with source 1
4293         List<EventReport> reportList1 = new ArrayList<>();
4294         reportList1.add(
4295                 new EventReport.Builder()
4296                         .setId("1")
4297                         .setSourceEventId(new UnsignedLong(3L))
4298                         .setEnrollmentId("1")
4299                         .setAttributionDestinations(sourceList.get(0).getAppDestinations())
4300                         .setSourceType(sourceList.get(0).getSourceType())
4301                         .setSourceId("1")
4302                         .setTriggerId("101")
4303                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4304                         .build());
4305         reportList1.add(
4306                 new EventReport.Builder()
4307                         .setId("7")
4308                         .setSourceEventId(new UnsignedLong(3L))
4309                         .setEnrollmentId("1")
4310                         .setAttributionDestinations(List.of(APP_DESTINATION))
4311                         .setSourceType(sourceList.get(0).getSourceType())
4312                         .setSourceId("1")
4313                         .setTriggerId("102")
4314                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4315                         .build());
4316 
4317         // Should match with source 2
4318         List<EventReport> reportList2 = new ArrayList<>();
4319         reportList2.add(
4320                 new EventReport.Builder()
4321                         .setId("3")
4322                         .setSourceEventId(new UnsignedLong(4L))
4323                         .setEnrollmentId("1")
4324                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4325                         .setSourceType(sourceList.get(1).getSourceType())
4326                         .setSourceId("2")
4327                         .setTriggerId("201")
4328                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4329                         .build());
4330         reportList2.add(
4331                 new EventReport.Builder()
4332                         .setId("8")
4333                         .setSourceEventId(new UnsignedLong(4L))
4334                         .setEnrollmentId("1")
4335                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4336                         .setSourceType(sourceList.get(1).getSourceType())
4337                         .setSourceId("2")
4338                         .setTriggerId("202")
4339                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4340                         .build());
4341 
4342         List<EventReport> reportList3 = new ArrayList<>();
4343         // Should not match with any source
4344         reportList3.add(
4345                 new EventReport.Builder()
4346                         .setId("2")
4347                         .setSourceEventId(new UnsignedLong(5L))
4348                         .setEnrollmentId("1")
4349                         .setSourceType(Source.SourceType.EVENT)
4350                         .setAttributionDestinations(List.of(APP_DESTINATION))
4351                         .setSourceId("15")
4352                         .setTriggerId("1001")
4353                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4354                         .build());
4355         reportList3.add(
4356                 new EventReport.Builder()
4357                         .setId("4")
4358                         .setSourceEventId(new UnsignedLong(6L))
4359                         .setEnrollmentId("1")
4360                         .setSourceType(Source.SourceType.EVENT)
4361                         .setAttributionDestinations(List.of(APP_DESTINATION))
4362                         .setSourceId("16")
4363                         .setTriggerId("1001")
4364                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4365                         .setTriggerValue(100L)
4366                         .build());
4367         reportList3.add(
4368                 new EventReport.Builder()
4369                         .setId("5")
4370                         .setSourceEventId(new UnsignedLong(1L))
4371                         .setEnrollmentId("1")
4372                         .setSourceType(Source.SourceType.EVENT)
4373                         .setAttributionDestinations(List.of(APP_DESTINATION))
4374                         .setSourceId("15")
4375                         .setTriggerId("1001")
4376                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4377                         .setTriggerValue(120L)
4378                         .build());
4379         reportList3.add(
4380                 new EventReport.Builder()
4381                         .setId("6")
4382                         .setSourceEventId(new UnsignedLong(2L))
4383                         .setEnrollmentId("1")
4384                         .setSourceType(Source.SourceType.EVENT)
4385                         .setAttributionDestinations(List.of(APP_DESTINATION))
4386                         .setSourceId("20")
4387                         .setTriggerId("1001")
4388                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4389                         .setTriggerValue(200L)
4390                         .build());
4391 
4392         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4393         Objects.requireNonNull(db);
4394         sourceList.forEach(source -> insertSource(source, source.getId()));
4395         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
4396 
4397         Stream.of(reportList1, reportList2, reportList3)
4398                 .flatMap(Collection::stream)
4399                 .forEach(
4400                         (eventReport -> {
4401                             mDatastoreManager.runInTransaction(
4402                                     (dao) -> dao.insertEventReport(eventReport));
4403                         }));
4404 
4405         assertEquals(
4406                 reportList1,
4407                 mDatastoreManager
4408                         .runInTransactionWithResult(
4409                                 measurementDao ->
4410                                         measurementDao.getSourceEventReports(sourceList.get(0)))
4411                         .orElseThrow());
4412 
4413         assertEquals(
4414                 reportList2,
4415                 mDatastoreManager
4416                         .runInTransactionWithResult(
4417                                 measurementDao ->
4418                                         measurementDao.getSourceEventReports(sourceList.get(1)))
4419                         .orElseThrow());
4420     }
4421 
4422     @Test
getSourceEventReports_sourcesWithSameEventId_haveSeparateEventReportsMatch()4423     public void getSourceEventReports_sourcesWithSameEventId_haveSeparateEventReportsMatch() {
4424         List<Source> sourceList =
4425                 Arrays.asList(
4426                         SourceFixture.getMinimalValidSourceBuilder()
4427                                 .setId("1")
4428                                 .setEventId(new UnsignedLong(1L))
4429                                 .setEnrollmentId("1")
4430                                 .build(),
4431                         SourceFixture.getMinimalValidSourceBuilder()
4432                                 .setId("2")
4433                                 .setEventId(new UnsignedLong(1L))
4434                                 .setEnrollmentId("1")
4435                                 .build(),
4436                         SourceFixture.getMinimalValidSourceBuilder()
4437                                 .setId("3")
4438                                 .setEventId(new UnsignedLong(2L))
4439                                 .setEnrollmentId("2")
4440                                 .build(),
4441                         SourceFixture.getMinimalValidSourceBuilder()
4442                                 .setId("4")
4443                                 .setEventId(new UnsignedLong(2L))
4444                                 .setEnrollmentId("2")
4445                                 .build());
4446 
4447         List<Trigger> triggers =
4448                 Arrays.asList(
4449                         TriggerFixture.getValidTriggerBuilder()
4450                                 .setId("101")
4451                                 .setEnrollmentId("2")
4452                                 .build(),
4453                         TriggerFixture.getValidTriggerBuilder()
4454                                 .setId("102")
4455                                 .setEnrollmentId("2")
4456                                 .build());
4457 
4458         // Should match with source 1
4459         List<EventReport> reportList1 = new ArrayList<>();
4460         reportList1.add(
4461                 new EventReport.Builder()
4462                         .setId("1")
4463                         .setSourceEventId(new UnsignedLong(1L))
4464                         .setEnrollmentId("1")
4465                         .setAttributionDestinations(sourceList.get(0).getAppDestinations())
4466                         .setSourceType(sourceList.get(0).getSourceType())
4467                         .setSourceId("1")
4468                         .setTriggerId("101")
4469                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4470                         .build());
4471         reportList1.add(
4472                 new EventReport.Builder()
4473                         .setId("2")
4474                         .setSourceEventId(new UnsignedLong(1L))
4475                         .setEnrollmentId("1")
4476                         .setAttributionDestinations(List.of(APP_DESTINATION))
4477                         .setSourceType(sourceList.get(0).getSourceType())
4478                         .setSourceId("1")
4479                         .setTriggerId("102")
4480                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4481                         .build());
4482 
4483         // Should match with source 2
4484         List<EventReport> reportList2 = new ArrayList<>();
4485         reportList2.add(
4486                 new EventReport.Builder()
4487                         .setId("3")
4488                         .setSourceEventId(new UnsignedLong(2L))
4489                         .setEnrollmentId("1")
4490                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4491                         .setSourceType(sourceList.get(1).getSourceType())
4492                         .setSourceId("2")
4493                         .setTriggerId("101")
4494                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4495                         .build());
4496         reportList2.add(
4497                 new EventReport.Builder()
4498                         .setId("4")
4499                         .setSourceEventId(new UnsignedLong(2L))
4500                         .setEnrollmentId("1")
4501                         .setAttributionDestinations(sourceList.get(1).getAppDestinations())
4502                         .setSourceType(sourceList.get(1).getSourceType())
4503                         .setSourceId("2")
4504                         .setTriggerId("102")
4505                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4506                         .build());
4507 
4508         // Match with source3
4509         List<EventReport> reportList3 = new ArrayList<>();
4510         reportList3.add(
4511                 new EventReport.Builder()
4512                         .setId("5")
4513                         .setSourceEventId(new UnsignedLong(2L))
4514                         .setEnrollmentId("2")
4515                         .setSourceType(Source.SourceType.EVENT)
4516                         .setAttributionDestinations(List.of(APP_DESTINATION))
4517                         .setSourceId("3")
4518                         .setTriggerId("101")
4519                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4520                         .build());
4521         reportList3.add(
4522                 new EventReport.Builder()
4523                         .setId("6")
4524                         .setSourceEventId(new UnsignedLong(2L))
4525                         .setEnrollmentId("2")
4526                         .setSourceType(Source.SourceType.EVENT)
4527                         .setAttributionDestinations(List.of(APP_DESTINATION))
4528                         .setSourceId("3")
4529                         .setTriggerId("102")
4530                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
4531                         .build());
4532 
4533         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4534         Objects.requireNonNull(db);
4535         sourceList.forEach(source -> insertSource(source, source.getId()));
4536         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
4537 
4538         Stream.of(reportList1, reportList2, reportList3)
4539                 .flatMap(Collection::stream)
4540                 .forEach(
4541                         (eventReport -> {
4542                             mDatastoreManager.runInTransaction(
4543                                     (dao) -> dao.insertEventReport(eventReport));
4544                         }));
4545 
4546         assertEquals(
4547                 reportList1,
4548                 mDatastoreManager
4549                         .runInTransactionWithResult(
4550                                 measurementDao ->
4551                                         measurementDao.getSourceEventReports(sourceList.get(0)))
4552                         .orElseThrow());
4553 
4554         assertEquals(
4555                 reportList2,
4556                 mDatastoreManager
4557                         .runInTransactionWithResult(
4558                                 measurementDao ->
4559                                         measurementDao.getSourceEventReports(sourceList.get(1)))
4560                         .orElseThrow());
4561 
4562         assertEquals(
4563                 reportList3,
4564                 mDatastoreManager
4565                         .runInTransactionWithResult(
4566                                 measurementDao ->
4567                                         measurementDao.getSourceEventReports(sourceList.get(2)))
4568                         .orElseThrow());
4569     }
4570 
4571     @Test
testUpdateSourceStatus()4572     public void testUpdateSourceStatus() {
4573         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4574         Objects.requireNonNull(db);
4575 
4576         List<Source> sourceList = new ArrayList<>();
4577         sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("1").build());
4578         sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("2").build());
4579         sourceList.add(SourceFixture.getMinimalValidSourceBuilder().setId("3").build());
4580         sourceList.forEach(
4581                 source -> {
4582                     ContentValues values = new ContentValues();
4583                     values.put(SourceContract.ID, source.getId());
4584                     values.put(SourceContract.STATUS, 1);
4585                     db.insert(SourceContract.TABLE, null, values);
4586                 });
4587 
4588         // Multiple Elements
4589         assertTrue(
4590                 mDatastoreManager.runInTransaction(
4591                         measurementDao ->
4592                                 measurementDao.updateSourceStatus(
4593                                         List.of("1", "2", "3"), Source.Status.IGNORED)));
4594 
4595         // Single Element
4596         assertTrue(
4597                 mDatastoreManager.runInTransaction(
4598                         measurementDao ->
4599                                 measurementDao.updateSourceStatus(
4600                                         List.of("1", "2"), Source.Status.IGNORED)));
4601     }
4602 
4603     @Test
updateSourceAttributedTriggers_baseline_equal()4604     public void updateSourceAttributedTriggers_baseline_equal() throws JSONException {
4605         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4606         Objects.requireNonNull(db);
4607 
4608         List<Source> sourceList = new ArrayList<>();
4609         sourceList.add(
4610                 SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum()
4611                         .setId("1")
4612                         .build());
4613         sourceList.add(
4614                 SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum()
4615                         .setId("2")
4616                         .build());
4617         sourceList.add(
4618                 SourceFixture.getValidFullSourceBuilderWithFlexEventReportValueSum()
4619                         .setId("3")
4620                         .build());
4621         mDatastoreManager.runInTransaction(
4622                 (dao) -> {
4623                     for (Source source : sourceList) {
4624                         dao.insertSource(source);
4625                     }
4626                 });
4627         List<EventReport> eventReportList = new ArrayList<>();
4628         eventReportList.add(
4629                 EventReportFixture.getBaseEventReportBuild()
4630                         .setTriggerData(new UnsignedLong(1L))
4631                         .setTriggerPriority(3L)
4632                         .setTriggerValue(5L)
4633                         .setReportTime(10000L)
4634                         .build());
4635         Source originalSource = sourceList.get(0);
4636         insertAttributedTrigger(originalSource.getTriggerSpecs(), eventReportList.get(0));
4637         Optional<Source> newSource =
4638                 mDatastoreManager.runInTransactionWithResult(
4639                         measurementDao -> measurementDao.getSource(originalSource.getId()));
4640         assertTrue(newSource.isPresent());
4641 
4642         assertNotEquals(newSource.get(), originalSource);
4643         newSource.get().buildTriggerSpecs();
4644         assertEquals(0, newSource.get().getTriggerSpecs().getAttributedTriggers().size());
4645 
4646         mDatastoreManager.runInTransaction(
4647                 measurementDao ->
4648                         measurementDao.updateSourceAttributedTriggers(
4649                                 originalSource.getId(),
4650                                 originalSource.attributedTriggersToJsonFlexApi()));
4651 
4652         mDatastoreManager.runInTransaction(
4653                 measurementDao -> {
4654                     assertEquals(
4655                             originalSource.attributedTriggersToJsonFlexApi(),
4656                             measurementDao
4657                                     .getSource(originalSource.getId())
4658                                     .getEventAttributionStatus());
4659                 });
4660     }
4661 
4662     @Test
testGetMatchingActiveSources()4663     public void testGetMatchingActiveSources() {
4664         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4665         Objects.requireNonNull(db);
4666         String enrollmentId = "enrollment-id";
4667         Uri appDestination = Uri.parse("android-app://com.example.abc");
4668         Uri webDestination = WebUtil.validUri("https://example.test");
4669         Uri webDestinationWithSubdomain = WebUtil.validUri("https://xyz.example.test");
4670         Source sApp1 =
4671                 SourceFixture.getMinimalValidSourceBuilder()
4672                         .setId("1")
4673                         .setEventTime(10)
4674                         .setExpiryTime(20)
4675                         .setAppDestinations(List.of(appDestination))
4676                         .setEnrollmentId(enrollmentId)
4677                         .build();
4678         Source sApp2 =
4679                 SourceFixture.getMinimalValidSourceBuilder()
4680                         .setId("2")
4681                         .setEventTime(10)
4682                         .setExpiryTime(50)
4683                         .setAppDestinations(List.of(appDestination))
4684                         .setEnrollmentId(enrollmentId)
4685                         .build();
4686         Source sApp3 =
4687                 SourceFixture.getMinimalValidSourceBuilder()
4688                         .setId("3")
4689                         .setEventTime(20)
4690                         .setExpiryTime(50)
4691                         .setAppDestinations(List.of(appDestination))
4692                         .setEnrollmentId(enrollmentId)
4693                         .build();
4694         Source sApp4 =
4695                 SourceFixture.getMinimalValidSourceBuilder()
4696                         .setId("4")
4697                         .setEventTime(30)
4698                         .setExpiryTime(50)
4699                         .setAppDestinations(List.of(appDestination))
4700                         .setEnrollmentId(enrollmentId)
4701                         .build();
4702         Source sWeb5 =
4703                 SourceFixture.getMinimalValidSourceBuilder()
4704                         .setId("5")
4705                         .setEventTime(10)
4706                         .setExpiryTime(20)
4707                         .setWebDestinations(List.of(webDestination))
4708                         .setEnrollmentId(enrollmentId)
4709                         .build();
4710         Source sWeb6 =
4711                 SourceFixture.getMinimalValidSourceBuilder()
4712                         .setId("6")
4713                         .setEventTime(10)
4714                         .setExpiryTime(50)
4715                         .setWebDestinations(List.of(webDestination))
4716                         .setEnrollmentId(enrollmentId)
4717                         .build();
4718         Source sAppWeb7 =
4719                 SourceFixture.getMinimalValidSourceBuilder()
4720                         .setId("7")
4721                         .setEventTime(10)
4722                         .setExpiryTime(20)
4723                         .setAppDestinations(List.of(appDestination))
4724                         .setWebDestinations(List.of(webDestination))
4725                         .setEnrollmentId(enrollmentId)
4726                         .build();
4727 
4728         List<Source> sources = Arrays.asList(sApp1, sApp2, sApp3, sApp4, sWeb5, sWeb6, sAppWeb7);
4729         sources.forEach(source -> insertInDb(db, source));
4730 
4731         Function<Trigger, List<Source>> runFunc =
4732                 trigger -> {
4733                     List<Source> result =
4734                             mDatastoreManager
4735                                     .runInTransactionWithResult(
4736                                             measurementDao ->
4737                                                     measurementDao.getMatchingActiveSources(
4738                                                             trigger))
4739                                     .orElseThrow();
4740                     result.sort(Comparator.comparing(Source::getId));
4741                     return result;
4742                 };
4743 
4744         // Trigger Time > sApp1's eventTime and < sApp1's expiryTime
4745         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
4746         // Trigger Time < sApp3's eventTime
4747         // Trigger Time < sApp4's eventTime
4748         // sApp5 and sApp6 don't have app destination
4749         // Trigger Time > sAppWeb7's eventTime and < sAppWeb7's expiryTime
4750         // Expected: Match with sApp1, sApp2, sAppWeb7
4751         Trigger trigger1MatchSource1And2 =
4752                 TriggerFixture.getValidTriggerBuilder()
4753                         .setTriggerTime(12)
4754                         .setEnrollmentId(enrollmentId)
4755                         .setAttributionDestination(appDestination)
4756                         .setDestinationType(EventSurfaceType.APP)
4757                         .build();
4758         List<Source> result1 = runFunc.apply(trigger1MatchSource1And2);
4759         assertEquals(3, result1.size());
4760         assertEquals(sApp1.getId(), result1.get(0).getId());
4761         assertEquals(sApp2.getId(), result1.get(1).getId());
4762         assertEquals(sAppWeb7.getId(), result1.get(2).getId());
4763 
4764         // Trigger Time > sApp1's eventTime and = sApp1's expiryTime
4765         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
4766         // Trigger Time = sApp3's eventTime
4767         // Trigger Time < sApp4's eventTime
4768         // sApp5 and sApp6 don't have app destination
4769         // Trigger Time > sAppWeb7's eventTime and = sAppWeb7's expiryTime
4770         // Expected: Match with sApp2, sApp3
4771         Trigger trigger2MatchSource127 =
4772                 TriggerFixture.getValidTriggerBuilder()
4773                         .setTriggerTime(20)
4774                         .setEnrollmentId(enrollmentId)
4775                         .setAttributionDestination(appDestination)
4776                         .setDestinationType(EventSurfaceType.APP)
4777                         .build();
4778 
4779         List<Source> result2 = runFunc.apply(trigger2MatchSource127);
4780         assertEquals(2, result2.size());
4781         assertEquals(sApp2.getId(), result2.get(0).getId());
4782         assertEquals(sApp3.getId(), result2.get(1).getId());
4783 
4784         // Trigger Time > sApp1's expiryTime
4785         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
4786         // Trigger Time > sApp3's eventTime and < sApp3's expiryTime
4787         // Trigger Time < sApp4's eventTime
4788         // sApp5 and sApp6 don't have app destination
4789         // Trigger Time > sAppWeb7's expiryTime
4790         // Expected: Match with sApp2, sApp3
4791         Trigger trigger3MatchSource237 =
4792                 TriggerFixture.getValidTriggerBuilder()
4793                         .setTriggerTime(21)
4794                         .setEnrollmentId(enrollmentId)
4795                         .setAttributionDestination(appDestination)
4796                         .setDestinationType(EventSurfaceType.APP)
4797                         .build();
4798 
4799         List<Source> result3 = runFunc.apply(trigger3MatchSource237);
4800         assertEquals(2, result3.size());
4801         assertEquals(sApp2.getId(), result3.get(0).getId());
4802         assertEquals(sApp3.getId(), result3.get(1).getId());
4803 
4804         // Trigger Time > sApp1's expiryTime
4805         // Trigger Time > sApp2's eventTime and < sApp2's expiryTime
4806         // Trigger Time > sApp3's eventTime and < sApp3's expiryTime
4807         // Trigger Time > sApp4's eventTime and < sApp4's expiryTime
4808         // sApp5 and sApp6 don't have app destination
4809         // Trigger Time > sAppWeb7's expiryTime
4810         // Expected: Match with sApp2, sApp3 and sApp4
4811         Trigger trigger4MatchSource1And2And3 =
4812                 TriggerFixture.getValidTriggerBuilder()
4813                         .setTriggerTime(31)
4814                         .setEnrollmentId(enrollmentId)
4815                         .setAttributionDestination(appDestination)
4816                         .setDestinationType(EventSurfaceType.APP)
4817                         .build();
4818 
4819         List<Source> result4 = runFunc.apply(trigger4MatchSource1And2And3);
4820         assertEquals(3, result4.size());
4821         assertEquals(sApp2.getId(), result4.get(0).getId());
4822         assertEquals(sApp3.getId(), result4.get(1).getId());
4823         assertEquals(sApp4.getId(), result4.get(2).getId());
4824 
4825         // sApp1, sApp2, sApp3, sApp4 don't have web destination
4826         // Trigger Time > sWeb5's eventTime and < sApp5's expiryTime
4827         // Trigger Time > sWeb6's eventTime and < sApp6's expiryTime
4828         // Trigger Time > sAppWeb7's eventTime and < sAppWeb7's expiryTime
4829         // Expected: Match with sApp5, sApp6, sAppWeb7
4830         Trigger trigger5MatchSource567 =
4831                 TriggerFixture.getValidTriggerBuilder()
4832                         .setTriggerTime(12)
4833                         .setEnrollmentId(enrollmentId)
4834                         .setAttributionDestination(webDestination)
4835                         .setDestinationType(EventSurfaceType.WEB)
4836                         .build();
4837         List<Source> result5 = runFunc.apply(trigger5MatchSource567);
4838         assertEquals(3, result1.size());
4839         assertEquals(sWeb5.getId(), result5.get(0).getId());
4840         assertEquals(sWeb6.getId(), result5.get(1).getId());
4841         assertEquals(sAppWeb7.getId(), result5.get(2).getId());
4842 
4843         // sApp1, sApp2, sApp3, sApp4 don't have web destination
4844         // Trigger Time > sWeb5's expiryTime
4845         // Trigger Time > sWeb6's eventTime and < sApp6's expiryTime
4846         // Trigger Time > sWeb7's expiryTime
4847         // Expected: Match with sApp6 only
4848         Trigger trigger6MatchSource67 =
4849                 TriggerFixture.getValidTriggerBuilder()
4850                         .setTriggerTime(21)
4851                         .setEnrollmentId(enrollmentId)
4852                         .setAttributionDestination(webDestinationWithSubdomain)
4853                         .setDestinationType(EventSurfaceType.WEB)
4854                         .build();
4855 
4856         List<Source> result6 = runFunc.apply(trigger6MatchSource67);
4857         assertEquals(1, result6.size());
4858         assertEquals(sWeb6.getId(), result6.get(0).getId());
4859 
4860         // Trigger with different subdomain than source
4861         // Expected: No Match found
4862         Trigger triggerDifferentRegistrationOrigin =
4863                 TriggerFixture.getValidTriggerBuilder()
4864                         .setTriggerTime(12)
4865                         .setEnrollmentId(enrollmentId)
4866                         .setAttributionDestination(appDestination)
4867                         .setDestinationType(EventSurfaceType.APP)
4868                         .setRegistrationOrigin(
4869                                 WebUtil.validUri("https://subdomain-different.example.test"))
4870                         .build();
4871 
4872         List<Source> result7 = runFunc.apply(triggerDifferentRegistrationOrigin);
4873         assertTrue(result7.isEmpty());
4874 
4875         // Trigger with different domain than source
4876         // Expected: No Match found
4877         Trigger triggerDifferentDomainOrigin =
4878                 TriggerFixture.getValidTriggerBuilder()
4879                         .setTriggerTime(12)
4880                         .setEnrollmentId(enrollmentId)
4881                         .setAttributionDestination(appDestination)
4882                         .setDestinationType(EventSurfaceType.APP)
4883                         .setRegistrationOrigin(
4884                                 WebUtil.validUri("https://subdomain.example-different.test"))
4885                         .build();
4886 
4887         List<Source> result8 = runFunc.apply(triggerDifferentDomainOrigin);
4888         assertTrue(result8.isEmpty());
4889 
4890         // Trigger with different port than source
4891         // Expected: No Match found
4892         Trigger triggerDifferentPort =
4893                 TriggerFixture.getValidTriggerBuilder()
4894                         .setTriggerTime(12)
4895                         .setEnrollmentId(enrollmentId)
4896                         .setAttributionDestination(appDestination)
4897                         .setDestinationType(EventSurfaceType.APP)
4898                         .setRegistrationOrigin(
4899                                 WebUtil.validUri("https://subdomain.example.test:8083"))
4900                         .build();
4901 
4902         List<Source> result9 = runFunc.apply(triggerDifferentPort);
4903         assertTrue(result9.isEmpty());
4904 
4905         // Enrollment id for trigger and source not same
4906         // Registration Origin for trigger and source same
4907         // Expected: Match with sApp1, sApp2, sAppWeb7
4908         Trigger triggerDifferentEnrollmentSameRegistration =
4909                 TriggerFixture.getValidTriggerBuilder()
4910                         .setTriggerTime(12)
4911                         .setEnrollmentId("different-enrollment-id")
4912                         .setAttributionDestination(appDestination)
4913                         .setDestinationType(EventSurfaceType.APP)
4914                         .build();
4915         List<Source> result10 = runFunc.apply(triggerDifferentEnrollmentSameRegistration);
4916         assertEquals(3, result10.size());
4917         assertEquals(sApp1.getId(), result10.get(0).getId());
4918         assertEquals(sApp2.getId(), result10.get(1).getId());
4919         assertEquals(sAppWeb7.getId(), result10.get(2).getId());
4920     }
4921 
4922     @Test
testGetMatchingActiveSources_multipleDestinations()4923     public void testGetMatchingActiveSources_multipleDestinations() {
4924         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
4925         String enrollmentId = "enrollment-id";
4926         Uri webDestination1 = WebUtil.validUri("https://example.test");
4927         Uri webDestination1WithSubdomain = WebUtil.validUri("https://xyz.example.test");
4928         Uri webDestination2 = WebUtil.validUri("https://example2.test");
4929         Source sWeb1 =
4930                 SourceFixture.getMinimalValidSourceBuilder()
4931                         .setId("1")
4932                         .setEventTime(10)
4933                         .setExpiryTime(20)
4934                         .setWebDestinations(List.of(webDestination1))
4935                         .setEnrollmentId(enrollmentId)
4936                         .build();
4937         Source sWeb2 =
4938                 SourceFixture.getMinimalValidSourceBuilder()
4939                         .setId("2")
4940                         .setEventTime(10)
4941                         .setExpiryTime(50)
4942                         .setWebDestinations(List.of(webDestination1, webDestination2))
4943                         .setEnrollmentId(enrollmentId)
4944                         .build();
4945         Source sAppWeb3 =
4946                 SourceFixture.getMinimalValidSourceBuilder()
4947                         .setId("3")
4948                         .setEventTime(10)
4949                         .setExpiryTime(20)
4950                         .setWebDestinations(List.of(webDestination1))
4951                         .setEnrollmentId(enrollmentId)
4952                         .build();
4953 
4954         List<Source> sources = Arrays.asList(sWeb1, sWeb2, sAppWeb3);
4955         sources.forEach(source -> insertInDb(db, source));
4956 
4957         Function<Trigger, List<Source>> getMatchingSources =
4958                 trigger -> {
4959                     List<Source> result =
4960                             mDatastoreManager
4961                                     .runInTransactionWithResult(
4962                                             measurementDao ->
4963                                                     measurementDao.getMatchingActiveSources(
4964                                                             trigger))
4965                                     .orElseThrow();
4966                     result.sort(Comparator.comparing(Source::getId));
4967                     return result;
4968                 };
4969 
4970         Trigger triggerMatchSourceWeb2 =
4971                 TriggerFixture.getValidTriggerBuilder()
4972                         .setTriggerTime(21)
4973                         .setEnrollmentId(enrollmentId)
4974                         .setAttributionDestination(webDestination1WithSubdomain)
4975                         .setDestinationType(EventSurfaceType.WEB)
4976                         .build();
4977 
4978         List<Source> result = getMatchingSources.apply(triggerMatchSourceWeb2);
4979         assertEquals(1, result.size());
4980         assertEquals(sWeb2.getId(), result.get(0).getId());
4981     }
4982 
4983     @Test
testGetMatchingActiveSources_attributionScopeEnabled_populateScopes()4984     public void testGetMatchingActiveSources_attributionScopeEnabled_populateScopes() {
4985         mFlags = mock(Flags.class);
4986         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
4987         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
4988         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
4989 
4990         // S0: attribution scopes -> [], destinations -> [D1, D2]
4991         Source source0 =
4992                 insertSourceForAttributionScope(
4993                         /* attributionScopes= */ null,
4994                         /* attributionScopeLimit= */ null,
4995                         /* maxEventStates= */ null,
4996                         SOURCE_EVENT_TIME,
4997                         List.of(WEB_ONE_DESTINATION),
4998                         List.of(APP_ONE_DESTINATION));
4999         // S1: attribution scopes -> ["1", "2"], destinations -> [D1]
5000         Source source1 =
5001                 insertSourceForAttributionScope(
5002                         List.of("1", "2"),
5003                         ATTRIBUTION_SCOPE_LIMIT,
5004                         MAX_EVENT_STATES,
5005                         SOURCE_EVENT_TIME + 1,
5006                         null,
5007                         List.of(APP_ONE_DESTINATION));
5008         // S2: attribution scopes -> ["2", "3"], destinations -> [D2]
5009         Source source2 =
5010                 insertSourceForAttributionScope(
5011                         List.of("2", "3"),
5012                         ATTRIBUTION_SCOPE_LIMIT,
5013                         MAX_EVENT_STATES,
5014                         SOURCE_EVENT_TIME + 2,
5015                         List.of(WEB_ONE_DESTINATION),
5016                         null);
5017 
5018         Trigger trigger0 =
5019                 TriggerFixture.getValidTriggerBuilder()
5020                         .setTriggerTime(SOURCE_EVENT_TIME + 3)
5021                         .setAttributionDestination(APP_ONE_DESTINATION)
5022                         .setDestinationType(EventSurfaceType.APP)
5023                         .build();
5024         List<Source> matchingSources0 = getMatchingSources(trigger0);
5025         assertThat(matchingSources0.size()).isEqualTo(2);
5026         List<String> matchingSourceIds0 =
5027                 matchingSources0.stream().map(Source::getId).collect(Collectors.toList());
5028         List<List<String>> matchingSourceAttributionScopes0 =
5029                 matchingSources0.stream()
5030                         .map(Source::getAttributionScopes)
5031                         .collect(Collectors.toList());
5032         assertThat(matchingSourceIds0).containsExactly(source0.getId(), source1.getId());
5033         // Source attribution scopes won't be populated if trigger doesn't have attribution scope.
5034         assertThat(matchingSourceAttributionScopes0).containsExactly(null, null);
5035 
5036         Trigger trigger1 =
5037                 TriggerFixture.getValidTriggerBuilder()
5038                         .setTriggerTime(SOURCE_EVENT_TIME + 4)
5039                         .setAttributionScopesString("1")
5040                         .setAttributionDestination(APP_ONE_DESTINATION)
5041                         .setDestinationType(EventSurfaceType.APP)
5042                         .build();
5043         List<Source> matchingSources1 = getMatchingSources(trigger1);
5044         List<String> matchingSourceIds1 =
5045                 matchingSources1.stream().map(Source::getId).collect(Collectors.toList());
5046         List<List<String>> matchingSourceAttributionScopes1 =
5047                 matchingSources1.stream()
5048                         .map(Source::getAttributionScopes)
5049                         .collect(Collectors.toList());
5050         assertThat(matchingSourceIds1).containsExactly(source0.getId(), source1.getId());
5051         assertThat(matchingSourceAttributionScopes1)
5052                 .containsExactly(source0.getAttributionScopes(), source1.getAttributionScopes());
5053 
5054         Trigger trigger2 =
5055                 TriggerFixture.getValidTriggerBuilder()
5056                         .setTriggerTime(SOURCE_EVENT_TIME + 5)
5057                         .setAttributionScopesString("2")
5058                         .setAttributionDestination(WEB_ONE_DESTINATION)
5059                         .setDestinationType(EventSurfaceType.WEB)
5060                         .build();
5061         List<Source> matchingSources2 = getMatchingSources(trigger2);
5062         List<String> matchingSourceIds2 =
5063                 matchingSources2.stream().map(Source::getId).collect(Collectors.toList());
5064         List<List<String>> matchingSourceAttributionScopes2 =
5065                 matchingSources2.stream()
5066                         .map(Source::getAttributionScopes)
5067                         .collect(Collectors.toList());
5068         assertThat(matchingSourceIds2).containsExactly(source0.getId(), source2.getId());
5069         assertThat(matchingSourceAttributionScopes2)
5070                 .containsExactly(source0.getAttributionScopes(), source2.getAttributionScopes());
5071     }
5072 
5073     @Test
testGetAttributionScopesForRegistration()5074     public void testGetAttributionScopesForRegistration() {
5075         mFlags = mock(Flags.class);
5076         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
5077         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
5078         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
5079 
5080         insertSourceForAttributionScope(
5081                 List.of("1"),
5082                 ATTRIBUTION_SCOPE_LIMIT,
5083                 MAX_EVENT_STATES,
5084                 SOURCE_EVENT_TIME,
5085                 List.of(WEB_ONE_DESTINATION),
5086                 List.of(APP_ONE_DESTINATION),
5087                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN,
5088                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
5089                 Source.SourceType.NAVIGATION,
5090                 Source.Status.ACTIVE);
5091         insertSourceForAttributionScope(
5092                 List.of("2"),
5093                 ATTRIBUTION_SCOPE_LIMIT,
5094                 MAX_EVENT_STATES,
5095                 SOURCE_EVENT_TIME,
5096                 List.of(WEB_ONE_DESTINATION),
5097                 List.of(APP_ONE_DESTINATION),
5098                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN,
5099                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
5100                 Source.SourceType.NAVIGATION,
5101                 Source.Status.ACTIVE);
5102         insertSourceForAttributionScope(
5103                 List.of("3"),
5104                 ATTRIBUTION_SCOPE_LIMIT,
5105                 MAX_EVENT_STATES,
5106                 SOURCE_EVENT_TIME,
5107                 List.of(WEB_ONE_DESTINATION),
5108                 List.of(APP_ONE_DESTINATION),
5109                 REGISTRATION_ORIGIN_2,
5110                 REGISTRATION_ID2,
5111                 Source.SourceType.NAVIGATION,
5112                 Source.Status.ACTIVE);
5113         // Ignored source, attribution scopes ignored.
5114         insertSourceForAttributionScope(
5115                 List.of("4"),
5116                 ATTRIBUTION_SCOPE_LIMIT,
5117                 MAX_EVENT_STATES,
5118                 SOURCE_EVENT_TIME,
5119                 List.of(WEB_ONE_DESTINATION),
5120                 List.of(APP_ONE_DESTINATION),
5121                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN,
5122                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
5123                 Source.SourceType.NAVIGATION,
5124                 Source.Status.IGNORED);
5125 
5126         // Execution
5127         mDatastoreManager.runInTransaction(
5128                 (dao) -> {
5129                     assertThat(
5130                                     dao.getAttributionScopesForRegistration(
5131                                                     SourceFixture.ValidSourceParams.REGISTRATION_ID,
5132                                                     SourceFixture.ValidSourceParams
5133                                                             .REGISTRATION_ORIGIN
5134                                                             .toString())
5135                                             .get())
5136                             .containsExactly("1", "2");
5137                     assertThat(
5138                                     dao.getAttributionScopesForRegistration(
5139                                                     REGISTRATION_ID2,
5140                                                     REGISTRATION_ORIGIN_2.toString())
5141                                             .get())
5142                             .containsExactly("3");
5143                     assertThat(
5144                                     dao.getAttributionScopesForRegistration(
5145                                                     SourceFixture.ValidSourceParams.REGISTRATION_ID,
5146                                                     SourceFixture.ValidSourceParams
5147                                                             .REGISTRATION_ORIGIN
5148                                                             .toString())
5149                                             .get())
5150                             .containsExactly("1", "2");
5151                     assertThat(
5152                                     dao.getAttributionScopesForRegistration(
5153                                                     REGISTRATION_ID2,
5154                                                     REGISTRATION_ORIGIN_2.toString())
5155                                             .get())
5156                             .containsExactly("3");
5157                     assertThat(
5158                                     dao.getAttributionScopesForRegistration(
5159                                                     SourceFixture.ValidSourceParams.REGISTRATION_ID,
5160                                                     REGISTRATION_ORIGIN_2.toString())
5161                                             .isEmpty())
5162                             .isTrue();
5163                 });
5164     }
5165 
5166     @Test
testGetMatchingActiveDelayedSources()5167     public void testGetMatchingActiveDelayedSources() {
5168         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5169         Objects.requireNonNull(db);
5170         String enrollmentId = "enrollment-id";
5171         Uri appDestination = Uri.parse("android-app://com.example.abc");
5172         Uri webDestination = WebUtil.validUri("https://example.test");
5173         Source sApp1 =
5174                 SourceFixture.getMinimalValidSourceBuilder()
5175                         .setId("1")
5176                         .setEventTime(10)
5177                         .setExpiryTime(20)
5178                         .setAppDestinations(List.of(appDestination))
5179                         .setEnrollmentId(enrollmentId)
5180                         .build();
5181         Source sApp2 =
5182                 SourceFixture.getMinimalValidSourceBuilder()
5183                         .setId("2")
5184                         .setEventTime(140)
5185                         .setExpiryTime(200)
5186                         .setAppDestinations(List.of(appDestination))
5187                         .setEnrollmentId(enrollmentId)
5188                         .build();
5189         Source sApp3 =
5190                 SourceFixture.getMinimalValidSourceBuilder()
5191                         .setId("3")
5192                         .setEventTime(20)
5193                         .setExpiryTime(50)
5194                         .setAppDestinations(List.of(appDestination))
5195                         .setEnrollmentId(enrollmentId)
5196                         .build();
5197         Source sApp4 =
5198                 SourceFixture.getMinimalValidSourceBuilder()
5199                         .setId("4")
5200                         .setEventTime(16)
5201                         .setExpiryTime(50)
5202                         .setAppDestinations(List.of(appDestination))
5203                         .setEnrollmentId(enrollmentId)
5204                         .build();
5205         Source sWeb5 =
5206                 SourceFixture.getMinimalValidSourceBuilder()
5207                         .setId("5")
5208                         .setEventTime(13)
5209                         .setExpiryTime(20)
5210                         .setWebDestinations(List.of(webDestination))
5211                         .setEnrollmentId(enrollmentId)
5212                         .build();
5213         Source sWeb6 =
5214                 SourceFixture.getMinimalValidSourceBuilder()
5215                         .setId("6")
5216                         .setEventTime(14)
5217                         .setExpiryTime(50)
5218                         .setWebDestinations(List.of(webDestination))
5219                         .setEnrollmentId(enrollmentId)
5220                         .build();
5221         Source sAppWeb7 =
5222                 SourceFixture.getMinimalValidSourceBuilder()
5223                         .setId("7")
5224                         .setEventTime(10)
5225                         .setExpiryTime(20)
5226                         .setAppDestinations(List.of(appDestination))
5227                         .setWebDestinations(List.of(webDestination))
5228                         .setEnrollmentId(enrollmentId)
5229                         .build();
5230         Source sAppWeb8 =
5231                 SourceFixture.getMinimalValidSourceBuilder()
5232                         .setId("8")
5233                         .setEventTime(15)
5234                         .setExpiryTime(25)
5235                         .setAppDestinations(List.of(appDestination))
5236                         .setWebDestinations(List.of(webDestination))
5237                         .setEnrollmentId(enrollmentId)
5238                         .build();
5239 
5240         List<Source> sources =
5241                 Arrays.asList(sApp1, sApp2, sApp3, sApp4, sWeb5, sWeb6, sAppWeb7, sAppWeb8);
5242         sources.forEach(source -> insertInDb(db, source));
5243 
5244         Function<Trigger, Optional<Source>> runFunc =
5245                 trigger -> {
5246                     Optional<Source> result =
5247                             mDatastoreManager
5248                                     .runInTransactionWithResult(
5249                                             measurementDao ->
5250                                                     measurementDao
5251                                                             .getNearestDelayedMatchingActiveSource(
5252                                                                     trigger))
5253                                     .orElseThrow();
5254                     return result;
5255                 };
5256 
5257         // sApp1's eventTime <= Trigger Time
5258         // Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW > sApp2's eventTime
5259         // Trigger Time < sApp3's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5260         // Trigger Time < sApp4's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5261         // sWeb5 and sWeb6 don't have app destination
5262         // sAppWeb7's eventTime <= Trigger Time
5263         // Trigger Time < sAppWeb8's eventTime <= Trigger Time +
5264         // MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5265         // Expected: Match with sAppWeb8
5266         Trigger trigger1MatchSource8 =
5267                 TriggerFixture.getValidTriggerBuilder()
5268                         .setTriggerTime(12)
5269                         .setEnrollmentId(enrollmentId)
5270                         .setAttributionDestination(appDestination)
5271                         .setDestinationType(EventSurfaceType.APP)
5272                         .build();
5273         Optional<Source> result1 = runFunc.apply(trigger1MatchSource8);
5274         assertEquals(sAppWeb8.getId(), result1.get().getId());
5275 
5276         // sApp1's eventTime <= Trigger Time
5277         // Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW > sApp2's eventTime
5278         // Trigger Time < sApp3's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5279         // Trigger Time < sApp4's eventTime <= Trigger Time + MAX_DELAYED_SOURCE_REGISTRATION_WINDOW
5280         // sWeb5 and sWeb6 don't have app destination
5281         // sAppWeb7's eventTime <= Trigger Time
5282         // sAppWeb8's eventTime <= Trigger Time
5283         // Expected: Match with sApp4
5284         Trigger trigger2MatchSource4 =
5285                 TriggerFixture.getValidTriggerBuilder()
5286                         .setTriggerTime(15)
5287                         .setEnrollmentId(enrollmentId)
5288                         .setAttributionDestination(appDestination)
5289                         .setDestinationType(EventSurfaceType.APP)
5290                         .build();
5291         Optional<Source> result2 = runFunc.apply(trigger2MatchSource4);
5292         assertEquals(sApp4.getId(), result2.get().getId());
5293 
5294         // sApp1's eventTime <= Trigger Time
5295         // sApp2's eventTime <= Trigger Time
5296         // sApp3's eventTime <= Trigger Time
5297         // sApp4's eventTime <= Trigger Time
5298         // sWeb5 and sWeb6 don't have app destination
5299         // sAppWeb7's eventTime <= Trigger Time
5300         // sAppWeb8's eventTime <= Trigger Time
5301         // Expected: no match
5302         Trigger trigger3NoMatchingSource =
5303                 TriggerFixture.getValidTriggerBuilder()
5304                         .setTriggerTime(150)
5305                         .setEnrollmentId(enrollmentId)
5306                         .setAttributionDestination(appDestination)
5307                         .setDestinationType(EventSurfaceType.APP)
5308                         .build();
5309         Optional<Source> result3 = runFunc.apply(trigger3NoMatchingSource);
5310         assertFalse(result3.isPresent());
5311     }
5312 
5313     @Test
testInsertAggregateEncryptionKey()5314     public void testInsertAggregateEncryptionKey() {
5315         String keyId = "38b1d571-f924-4dc0-abe1-e2bac9b6a6be";
5316         String publicKey = "/amqBgfDOvHAIuatDyoHxhfHaMoYA4BDxZxwtWBRQhc=";
5317         long expiry = 1653620135831L;
5318         Uri aggregationOrigin = WebUtil.validUri("https://a.test");
5319 
5320         mDatastoreManager.runInTransaction(
5321                 (dao) ->
5322                         dao.insertAggregateEncryptionKey(
5323                                 new AggregateEncryptionKey.Builder()
5324                                         .setKeyId(keyId)
5325                                         .setPublicKey(publicKey)
5326                                         .setExpiry(expiry)
5327                                         .setAggregationCoordinatorOrigin(aggregationOrigin)
5328                                         .build()));
5329 
5330         try (Cursor cursor =
5331                 MeasurementDbHelper.getInstance()
5332                         .getReadableDatabase()
5333                         .query(
5334                                 MeasurementTables.AggregateEncryptionKey.TABLE,
5335                                 null,
5336                                 null,
5337                                 null,
5338                                 null,
5339                                 null,
5340                                 null)) {
5341             assertTrue(cursor.moveToNext());
5342             AggregateEncryptionKey aggregateEncryptionKey =
5343                     SqliteObjectMapper.constructAggregateEncryptionKeyFromCursor(cursor);
5344             assertNotNull(aggregateEncryptionKey);
5345             assertNotNull(aggregateEncryptionKey.getId());
5346             assertEquals(keyId, aggregateEncryptionKey.getKeyId());
5347             assertEquals(publicKey, aggregateEncryptionKey.getPublicKey());
5348             assertEquals(expiry, aggregateEncryptionKey.getExpiry());
5349             assertEquals(
5350                     aggregationOrigin, aggregateEncryptionKey.getAggregationCoordinatorOrigin());
5351         }
5352     }
5353 
5354     @Test
testInsertAggregateReport()5355     public void testInsertAggregateReport() {
5356         AggregateReport validAggregateReport = AggregateReportFixture.getValidAggregateReport();
5357         mDatastoreManager.runInTransaction(
5358                 (dao) -> dao.insertAggregateReport(validAggregateReport));
5359 
5360         try (Cursor cursor =
5361                 MeasurementDbHelper.getInstance()
5362                         .getReadableDatabase()
5363                         .query(
5364                                 MeasurementTables.AggregateReport.TABLE,
5365                                 null,
5366                                 null,
5367                                 null,
5368                                 null,
5369                                 null,
5370                                 null)) {
5371             assertTrue(cursor.moveToNext());
5372             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5373             assertNotNull(aggregateReport);
5374             assertNotNull(aggregateReport.getId());
5375             assertEquals(validAggregateReport, aggregateReport);
5376         }
5377     }
5378 
5379     @Test
testInsertAggregateReport_withNullSourceRegistrationTime()5380     public void testInsertAggregateReport_withNullSourceRegistrationTime() {
5381         AggregateReport.Builder builder = AggregateReportFixture.getValidAggregateReportBuilder();
5382         builder.setSourceRegistrationTime(null);
5383         AggregateReport aggregateReportWithNullSourceRegistrationTime = builder.build();
5384         mDatastoreManager.runInTransaction(
5385                 (dao) -> dao.insertAggregateReport(aggregateReportWithNullSourceRegistrationTime));
5386 
5387         try (Cursor cursor =
5388                 MeasurementDbHelper.getInstance()
5389                         .getReadableDatabase()
5390                         .query(
5391                                 MeasurementTables.AggregateReport.TABLE,
5392                                 null,
5393                                 null,
5394                                 null,
5395                                 null,
5396                                 null,
5397                                 null)) {
5398             assertTrue(cursor.moveToNext());
5399             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5400             assertNotNull(aggregateReport);
5401             assertNotNull(aggregateReport.getId());
5402             assertEquals(aggregateReportWithNullSourceRegistrationTime, aggregateReport);
5403         }
5404     }
5405 
5406     @Test
testExistsSourcesWithSameDestination()5407     public void testExistsSourcesWithSameDestination() {
5408         long currentTime = System.currentTimeMillis();
5409         Uri destination1 = Uri.parse("android-app://com.destination1");
5410         Uri destination2 = Uri.parse("android-app://com.destination2");
5411         Uri destination3 = Uri.parse("android-app://com.destination3");
5412         Uri destination4 = Uri.parse("android-app://com.destination4");
5413         Uri destination5 = Uri.parse("android-app://com.destination5");
5414 
5415         // Return true.
5416         Source s1 =
5417                 SourceFixture.getMinimalValidSourceBuilder()
5418                         .setId("S1")
5419                         .setStatus(0)
5420                         .setExpiryTime(currentTime + 1000L)
5421                         .setAppDestinations(List.of(destination1))
5422                         .build();
5423 
5424         // Inactive source. Return false.
5425         Source s2 =
5426                 SourceFixture.getMinimalValidSourceBuilder()
5427                         .setId("S1")
5428                         .setStatus(1)
5429                         .setExpiryTime(currentTime + 1000L)
5430                         .setAppDestinations(List.of(destination2))
5431                         .build();
5432 
5433         // Expired source. Return false.
5434         Source s3 =
5435                 SourceFixture.getMinimalValidSourceBuilder()
5436                         .setId("S3")
5437                         .setStatus(0)
5438                         .setExpiryTime(currentTime - 1000L)
5439                         .setAppDestinations(List.of(destination3))
5440                         .build();
5441 
5442         // Web source. Return false.
5443         Source s4 =
5444                 SourceFixture.getMinimalValidSourceBuilder()
5445                         .setId("S4")
5446                         .setStatus(0)
5447                         .setExpiryTime(currentTime + 1000L)
5448                         .setWebDestinations(List.of(destination4))
5449                         .build();
5450 
5451         insertSource(s1, "S1");
5452         insertSource(s2, "S2");
5453         insertSource(s3, "S3");
5454         insertSource(s4, "S4");
5455 
5456         Optional<Boolean> result1 =
5457                 mDatastoreManager.runInTransactionWithResult(
5458                         (dao) -> dao.existsActiveSourcesWithDestination(destination1, currentTime));
5459 
5460         Optional<Boolean> result2 =
5461                 mDatastoreManager.runInTransactionWithResult(
5462                         (dao) -> dao.existsActiveSourcesWithDestination(destination2, currentTime));
5463 
5464         Optional<Boolean> result3 =
5465                 mDatastoreManager.runInTransactionWithResult(
5466                         (dao) -> dao.existsActiveSourcesWithDestination(destination3, currentTime));
5467 
5468         Optional<Boolean> result4 =
5469                 mDatastoreManager.runInTransactionWithResult(
5470                         (dao) -> dao.existsActiveSourcesWithDestination(destination4, currentTime));
5471 
5472         // No source with app destination 5. Return false.
5473         Optional<Boolean> result5 =
5474                 mDatastoreManager.runInTransactionWithResult(
5475                         (dao) -> dao.existsActiveSourcesWithDestination(destination5, currentTime));
5476 
5477         assertThat(result1.get()).isTrue();
5478         assertThat(result2.get()).isFalse();
5479         assertThat(result3.get()).isFalse();
5480         assertThat(result4.get()).isFalse();
5481         assertThat(result5.get()).isFalse();
5482     }
5483 
5484     @Test
testInsertAggregateReport_withTriggerTime()5485     public void testInsertAggregateReport_withTriggerTime() {
5486         AggregateReport.Builder builder = AggregateReportFixture.getValidAggregateReportBuilder();
5487         builder.setTriggerTime(1L);
5488         AggregateReport aggregateReportWithTriggerTime = builder.build();
5489         mDatastoreManager.runInTransaction(
5490                 (dao) -> dao.insertAggregateReport(aggregateReportWithTriggerTime));
5491 
5492         try (Cursor cursor =
5493                 MeasurementDbHelper.getInstance()
5494                         .getReadableDatabase()
5495                         .query(
5496                                 MeasurementTables.AggregateReport.TABLE,
5497                                 null,
5498                                 null,
5499                                 null,
5500                                 null,
5501                                 null,
5502                                 null)) {
5503             assertTrue(cursor.moveToNext());
5504             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5505             assertNotNull(aggregateReport);
5506             assertNotNull(aggregateReport.getId());
5507             assertEquals(aggregateReportWithTriggerTime, aggregateReport);
5508         }
5509     }
5510 
5511     @Test
testInsertAggregateReport_withAggregatableFilteringIdMaxBytes()5512     public void testInsertAggregateReport_withAggregatableFilteringIdMaxBytes() {
5513         AggregateReport.Builder builder = AggregateReportFixture.getValidAggregateReportBuilder();
5514         builder.setAggregatableFilteringIdMaxBytes(2);
5515         AggregateReport aggregateReportWithAggregatableFilteringIdMaxBytes = builder.build();
5516         mDatastoreManager.runInTransaction(
5517                 (dao) ->
5518                         dao.insertAggregateReport(
5519                                 aggregateReportWithAggregatableFilteringIdMaxBytes));
5520 
5521         try (Cursor cursor =
5522                 MeasurementDbHelper.getInstance()
5523                         .getReadableDatabase()
5524                         .query(
5525                                 MeasurementTables.AggregateReport.TABLE,
5526                                 null,
5527                                 null,
5528                                 null,
5529                                 null,
5530                                 null,
5531                                 null)) {
5532             assertTrue(cursor.moveToNext());
5533             AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
5534             assertNotNull(aggregateReport);
5535             assertNotNull(aggregateReport.getId());
5536             assertEquals(aggregateReportWithAggregatableFilteringIdMaxBytes, aggregateReport);
5537         }
5538     }
5539 
5540     @Test
testDeleteAllMeasurementDataWithEmptyList()5541     public void testDeleteAllMeasurementDataWithEmptyList() {
5542         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5543 
5544         Source source = SourceFixture.getMinimalValidSourceBuilder().setId("S1").build();
5545         ContentValues sourceValue = new ContentValues();
5546         sourceValue.put("_id", source.getId());
5547         db.insert(SourceContract.TABLE, null, sourceValue);
5548 
5549         Trigger trigger = TriggerFixture.getValidTriggerBuilder().setId("T1").build();
5550         ContentValues triggerValue = new ContentValues();
5551         triggerValue.put("_id", trigger.getId());
5552         db.insert(TriggerContract.TABLE, null, triggerValue);
5553 
5554         EventReport eventReport = new EventReport.Builder().setId("E1").build();
5555         ContentValues eventReportValue = new ContentValues();
5556         eventReportValue.put("_id", eventReport.getId());
5557         db.insert(EventReportContract.TABLE, null, eventReportValue);
5558 
5559         AggregateReport aggregateReport = new AggregateReport.Builder().setId("A1").build();
5560         ContentValues aggregateReportValue = new ContentValues();
5561         aggregateReportValue.put("_id", aggregateReport.getId());
5562         db.insert(MeasurementTables.AggregateReport.TABLE, null, aggregateReportValue);
5563 
5564         ContentValues rateLimitValue = new ContentValues();
5565         rateLimitValue.put(AttributionContract.ID, "ARL1");
5566         rateLimitValue.put(AttributionContract.SOURCE_SITE, "sourceSite");
5567         rateLimitValue.put(AttributionContract.SOURCE_ORIGIN, "sourceOrigin");
5568         rateLimitValue.put(AttributionContract.DESTINATION_SITE, "destinationSite");
5569         rateLimitValue.put(AttributionContract.TRIGGER_TIME, 5L);
5570         rateLimitValue.put(AttributionContract.REGISTRANT, "registrant");
5571         rateLimitValue.put(AttributionContract.ENROLLMENT_ID, "enrollmentId");
5572 
5573         db.insert(AttributionContract.TABLE, null, rateLimitValue);
5574 
5575         AggregateEncryptionKey key =
5576                 new AggregateEncryptionKey.Builder()
5577                         .setId("K1")
5578                         .setKeyId("keyId")
5579                         .setPublicKey("publicKey")
5580                         .setExpiry(1)
5581                         .setAggregationCoordinatorOrigin(Uri.parse("https://1.test"))
5582                         .build();
5583         ContentValues keyValues = new ContentValues();
5584         keyValues.put("_id", key.getId());
5585 
5586         mDatastoreManager.runInTransaction(
5587                 (dao) -> dao.deleteAllMeasurementData(Collections.emptyList()));
5588 
5589         for (String table : ALL_MSMT_TABLES) {
5590             assertThat(
5591                             db.query(
5592                                             /* table */ table,
5593                                             /* columns */ null,
5594                                             /* selection */ null,
5595                                             /* selectionArgs */ null,
5596                                             /* groupBy */ null,
5597                                             /* having */ null,
5598                                             /* orderedBy */ null)
5599                                     .getCount())
5600                     .isEqualTo(0);
5601         }
5602     }
5603 
5604     @Test
testDeleteAllMeasurementDataWithNonEmptyList()5605     public void testDeleteAllMeasurementDataWithNonEmptyList() {
5606         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5607 
5608         Source source = SourceFixture.getMinimalValidSourceBuilder().setId("S1").build();
5609         ContentValues sourceValue = new ContentValues();
5610         sourceValue.put("_id", source.getId());
5611         db.insert(SourceContract.TABLE, null, sourceValue);
5612 
5613         Trigger trigger = TriggerFixture.getValidTriggerBuilder().setId("T1").build();
5614         ContentValues triggerValue = new ContentValues();
5615         triggerValue.put("_id", trigger.getId());
5616         db.insert(TriggerContract.TABLE, null, triggerValue);
5617 
5618         EventReport eventReport = new EventReport.Builder().setId("E1").build();
5619         ContentValues eventReportValue = new ContentValues();
5620         eventReportValue.put("_id", eventReport.getId());
5621         db.insert(EventReportContract.TABLE, null, eventReportValue);
5622 
5623         AggregateReport aggregateReport = new AggregateReport.Builder().setId("A1").build();
5624         ContentValues aggregateReportValue = new ContentValues();
5625         aggregateReportValue.put("_id", aggregateReport.getId());
5626         db.insert(MeasurementTables.AggregateReport.TABLE, null, aggregateReportValue);
5627 
5628         ContentValues rateLimitValue = new ContentValues();
5629         rateLimitValue.put(AttributionContract.ID, "ARL1");
5630         rateLimitValue.put(AttributionContract.SOURCE_SITE, "sourceSite");
5631         rateLimitValue.put(AttributionContract.SOURCE_ORIGIN, "sourceOrigin");
5632         rateLimitValue.put(AttributionContract.DESTINATION_SITE, "destinationSite");
5633         rateLimitValue.put(AttributionContract.TRIGGER_TIME, 5L);
5634         rateLimitValue.put(AttributionContract.REGISTRANT, "registrant");
5635         rateLimitValue.put(AttributionContract.ENROLLMENT_ID, "enrollmentId");
5636         db.insert(AttributionContract.TABLE, null, rateLimitValue);
5637 
5638         AggregateEncryptionKey key =
5639                 new AggregateEncryptionKey.Builder()
5640                         .setId("K1")
5641                         .setKeyId("keyId")
5642                         .setPublicKey("publicKey")
5643                         .setExpiry(1)
5644                         .setAggregationCoordinatorOrigin(Uri.parse("https://1.test"))
5645                         .build();
5646         ContentValues keyValues = new ContentValues();
5647         keyValues.put("_id", key.getId());
5648 
5649         List<String> excludedTables = List.of(SourceContract.TABLE);
5650 
5651         mDatastoreManager.runInTransaction((dao) -> dao.deleteAllMeasurementData(excludedTables));
5652 
5653         for (String table : ALL_MSMT_TABLES) {
5654             if (!excludedTables.contains(table)) {
5655                 assertThat(
5656                                 db.query(
5657                                                 /* table */ table,
5658                                                 /* columns */ null,
5659                                                 /* selection */ null,
5660                                                 /* selectionArgs */ null,
5661                                                 /* groupBy */ null,
5662                                                 /* having */ null,
5663                                                 /* orderedBy */ null)
5664                                         .getCount())
5665                         .isEqualTo(0);
5666             } else {
5667                 assertThat(
5668                                 db.query(
5669                                                 /* table */ table,
5670                                                 /* columns */ null,
5671                                                 /* selection */ null,
5672                                                 /* selectionArgs */ null,
5673                                                 /* groupBy */ null,
5674                                                 /* having */ null,
5675                                                 /* orderedBy */ null)
5676                                         .getCount())
5677                         .isNotEqualTo(0);
5678             }
5679         }
5680     }
5681 
5682     /** Test that the variable ALL_MSMT_TABLES actually has all the measurement related tables. */
5683     @Test
testAllMsmtTables()5684     public void testAllMsmtTables() {
5685         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5686         Cursor cursor =
5687                 db.query(
5688                         "sqlite_master",
5689                         /* columns */ null,
5690                         /* selection */ "type = ? AND name like ?",
5691                         /* selectionArgs*/ new String[] {"table", MSMT_TABLE_PREFIX + "%"},
5692                         /* groupBy */ null,
5693                         /* having */ null,
5694                         /* orderBy */ null);
5695 
5696         List<String> tableNames = new ArrayList<>();
5697         while (cursor.moveToNext()) {
5698             String tableName = cursor.getString(cursor.getColumnIndex("name"));
5699             tableNames.add(tableName);
5700         }
5701         assertThat(tableNames.size()).isEqualTo(ALL_MSMT_TABLES.length);
5702         for (String tableName : tableNames) {
5703             assertThat(ALL_MSMT_TABLES).asList().contains(tableName);
5704         }
5705     }
5706 
5707     @Test
insertAttributionRateLimit()5708     public void insertAttributionRateLimit() {
5709         // Setup
5710         Source source = SourceFixture.getValidSource();
5711         Trigger trigger =
5712                 TriggerFixture.getValidTriggerBuilder()
5713                         .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
5714                         .build();
5715         Attribution attribution =
5716                 new Attribution.Builder()
5717                         .setScope(Attribution.Scope.AGGREGATE)
5718                         .setEnrollmentId(source.getEnrollmentId())
5719                         .setDestinationOrigin(source.getWebDestinations().get(0).toString())
5720                         .setDestinationSite(source.getAppDestinations().get(0).toString())
5721                         .setSourceOrigin(source.getPublisher().toString())
5722                         .setSourceSite(source.getPublisher().toString())
5723                         .setRegistrant(source.getRegistrant().toString())
5724                         .setTriggerTime(trigger.getTriggerTime())
5725                         .setRegistrationOrigin(trigger.getRegistrationOrigin())
5726                         .setReportId(UUID.randomUUID().toString())
5727                         .build();
5728 
5729         // Execution
5730         mDatastoreManager.runInTransaction(
5731                 (dao) -> {
5732                     dao.insertAttribution(attribution);
5733                 });
5734 
5735         // Assertion
5736         try (Cursor cursor =
5737                 MeasurementDbHelper.getInstance()
5738                         .getReadableDatabase()
5739                         .query(AttributionContract.TABLE, null, null, null, null, null, null)) {
5740             assertTrue(cursor.moveToNext());
5741             assertEquals(
5742                     attribution.getScope(),
5743                     cursor.getInt(cursor.getColumnIndex(AttributionContract.SCOPE)));
5744             assertEquals(
5745                     attribution.getEnrollmentId(),
5746                     cursor.getString(cursor.getColumnIndex(AttributionContract.ENROLLMENT_ID)));
5747             assertEquals(
5748                     attribution.getDestinationOrigin(),
5749                     cursor.getString(
5750                             cursor.getColumnIndex(AttributionContract.DESTINATION_ORIGIN)));
5751             assertEquals(
5752                     attribution.getDestinationSite(),
5753                     cursor.getString(cursor.getColumnIndex(AttributionContract.DESTINATION_SITE)));
5754             assertEquals(
5755                     attribution.getSourceOrigin(),
5756                     cursor.getString(cursor.getColumnIndex(AttributionContract.SOURCE_ORIGIN)));
5757             assertEquals(
5758                     attribution.getSourceSite(),
5759                     cursor.getString(cursor.getColumnIndex(AttributionContract.SOURCE_SITE)));
5760             assertEquals(
5761                     attribution.getRegistrant(),
5762                     cursor.getString(cursor.getColumnIndex(AttributionContract.REGISTRANT)));
5763             assertEquals(
5764                     attribution.getTriggerTime(),
5765                     cursor.getLong(cursor.getColumnIndex(AttributionContract.TRIGGER_TIME)));
5766             assertEquals(
5767                     attribution.getRegistrationOrigin(),
5768                     Uri.parse(
5769                             cursor.getString(
5770                                     cursor.getColumnIndex(
5771                                             AttributionContract.REGISTRATION_ORIGIN))));
5772             assertEquals(
5773                     attribution.getReportId(),
5774                     cursor.getString(cursor.getColumnIndex(AttributionContract.REPORT_ID)));
5775         }
5776     }
5777 
5778     @Test
getAttributionsPerRateLimitWindow_atTimeWindowScoped_countsAttribution()5779     public void getAttributionsPerRateLimitWindow_atTimeWindowScoped_countsAttribution() {
5780         // Setup
5781         Source source = SourceFixture.getValidSource();
5782         Trigger trigger =
5783                 TriggerFixture.getValidTriggerBuilder()
5784                         .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
5785                         .build();
5786 
5787         Attribution eventAttribution =
5788                 getAttributionBuilder(source, trigger).setScope(Attribution.Scope.EVENT).build();
5789 
5790         Attribution aggregateAttribution =
5791                 getAttributionBuilder(source, trigger)
5792                         .setScope(Attribution.Scope.AGGREGATE)
5793                         .build();
5794 
5795         // Execution
5796         mDatastoreManager.runInTransaction(
5797                 (dao) -> {
5798                     dao.insertAttribution(eventAttribution);
5799                     dao.insertAttribution(aggregateAttribution);
5800                 });
5801 
5802         // Assertion
5803         AtomicLong eventAttributionsCount = new AtomicLong();
5804         AtomicLong aggregateAttributionsCount = new AtomicLong();
5805         mDatastoreManager.runInTransaction(
5806                 (dao) -> {
5807                     eventAttributionsCount.set(
5808                             dao.getAttributionsPerRateLimitWindow(
5809                                     Attribution.Scope.EVENT, source, trigger));
5810                     aggregateAttributionsCount.set(
5811                             dao.getAttributionsPerRateLimitWindow(
5812                                     Attribution.Scope.AGGREGATE, source, trigger));
5813                 });
5814 
5815         assertEquals(1L, eventAttributionsCount.get());
5816         assertEquals(1L, aggregateAttributionsCount.get());
5817     }
5818 
5819     @Test
getAttributionsPerRateLimitWindow_beyondTimeWindowScoped_countsAttribution()5820     public void getAttributionsPerRateLimitWindow_beyondTimeWindowScoped_countsAttribution() {
5821         // Setup
5822         Source source = SourceFixture.getValidSource();
5823         Trigger trigger =
5824                 TriggerFixture.getValidTriggerBuilder()
5825                         .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
5826                         .build();
5827 
5828         Attribution eventAttribution =
5829                 getAttributionBuilder(source, trigger)
5830                         .setTriggerTime(
5831                                 trigger.getTriggerTime()
5832                                         - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS)
5833                         .setScope(Attribution.Scope.EVENT)
5834                         .build();
5835 
5836         Attribution aggregateAttribution =
5837                 getAttributionBuilder(source, trigger)
5838                         .setTriggerTime(
5839                                 trigger.getTriggerTime()
5840                                         - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS)
5841                         .setScope(Attribution.Scope.AGGREGATE)
5842                         .build();
5843 
5844         // Execution
5845         mDatastoreManager.runInTransaction(
5846                 (dao) -> {
5847                     dao.insertAttribution(eventAttribution);
5848                     dao.insertAttribution(aggregateAttribution);
5849                 });
5850 
5851         // Assertion
5852         AtomicLong eventAttributionsCount = new AtomicLong();
5853         AtomicLong aggregateAttributionsCount = new AtomicLong();
5854         mDatastoreManager.runInTransaction(
5855                 (dao) -> {
5856                     eventAttributionsCount.set(
5857                             dao.getAttributionsPerRateLimitWindow(
5858                                     Attribution.Scope.EVENT, source, trigger));
5859                     aggregateAttributionsCount.set(
5860                             dao.getAttributionsPerRateLimitWindow(
5861                                     Attribution.Scope.AGGREGATE, source, trigger));
5862                 });
5863 
5864         assertEquals(0L, eventAttributionsCount.get());
5865         assertEquals(0L, aggregateAttributionsCount.get());
5866     }
5867 
5868     @Test
testTransactionRollbackForRuntimeException()5869     public void testTransactionRollbackForRuntimeException() {
5870         assertThrows(
5871                 IllegalArgumentException.class,
5872                 () ->
5873                         mDatastoreManager.runInTransaction(
5874                                 (dao) -> {
5875                                     dao.insertSource(SourceFixture.getValidSource());
5876                                     // build() call throws IllegalArgumentException
5877                                     Trigger trigger = new Trigger.Builder().build();
5878                                     dao.insertTrigger(trigger);
5879                                 }));
5880         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5881         Objects.requireNonNull(db);
5882         // There should be no insertions
5883         assertEquals(
5884                 0, db.query(SourceContract.TABLE, null, null, null, null, null, null).getCount());
5885         assertEquals(
5886                 0, db.query(TriggerContract.TABLE, null, null, null, null, null, null).getCount());
5887     }
5888 
5889     @Test
testDeleteEventReportAndAttribution()5890     public void testDeleteEventReportAndAttribution() throws JSONException {
5891         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5892         Source s1 =
5893                 SourceFixture.getMinimalValidSourceBuilder()
5894                         .setEventId(new UnsignedLong(1L))
5895                         .setId("S1")
5896                         .build();
5897         Trigger t1 =
5898                 TriggerFixture.getValidTriggerBuilder()
5899                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
5900                         .setId("T1")
5901                         .build();
5902         Trigger t2 =
5903                 TriggerFixture.getValidTriggerBuilder()
5904                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
5905                         .setId("T2")
5906                         .build();
5907         EventReport e11 = createEventReportForSourceAndTrigger("E11", s1, t1);
5908         EventReport e12 = createEventReportForSourceAndTrigger("E12", s1, t2);
5909 
5910         Attribution aggregateAttribution11 =
5911                 createAttribution(
5912                         "ATT11_aggregate",
5913                         Attribution.Scope.AGGREGATE,
5914                         s1.getId(),
5915                         t1.getId(),
5916                         "E11");
5917         Attribution aggregateAttribution12 =
5918                 createAttribution(
5919                         "ATT12_aggregate",
5920                         Attribution.Scope.AGGREGATE,
5921                         s1.getId(),
5922                         t2.getId(),
5923                         "E12");
5924         Attribution eventAttribution11 =
5925                 createAttribution(
5926                         "ATT11_event",
5927                         Attribution.Scope.EVENT,
5928                         s1.getId(),
5929                         t1.getId(),
5930                         "E11");
5931         Attribution eventAttribution12 =
5932                 createAttribution(
5933                         "ATT12_event",
5934                         Attribution.Scope.EVENT,
5935                         s1.getId(),
5936                         t2.getId(),
5937                         "E12");
5938 
5939         insertSource(s1, s1.getId());
5940         AbstractDbIntegrationTest.insertToDb(t1, db);
5941         AbstractDbIntegrationTest.insertToDb(t2, db);
5942         AbstractDbIntegrationTest.insertToDb(e11, db);
5943         AbstractDbIntegrationTest.insertToDb(e12, db);
5944         AbstractDbIntegrationTest.insertToDb(aggregateAttribution11, db);
5945         AbstractDbIntegrationTest.insertToDb(aggregateAttribution12, db);
5946         AbstractDbIntegrationTest.insertToDb(eventAttribution11, db);
5947         AbstractDbIntegrationTest.insertToDb(eventAttribution12, db);
5948 
5949         // Assert attributions present
5950         assertNotNull(getAttribution("ATT11_aggregate", db));
5951         assertNotNull(getAttribution("ATT12_aggregate", db));
5952         assertNotNull(getAttribution("ATT11_event", db));
5953         assertNotNull(getAttribution("ATT12_event", db));
5954 
5955         mDatastoreManager.runInTransaction(
5956                 measurementDao -> {
5957                     // Assert sources and triggers present
5958                     assertNotNull(measurementDao.getSource("S1"));
5959                     assertNotNull(measurementDao.getTrigger("T1"));
5960                     assertNotNull(measurementDao.getTrigger("T2"));
5961 
5962                     // Validate presence of event reports
5963                     measurementDao.getEventReport("E11");
5964                     measurementDao.getEventReport("E12");
5965 
5966                     // Deletion
5967                     measurementDao.deleteEventReportAndAttribution(e11);
5968 
5969                     // Validate event report deletion
5970                     assertThrows(
5971                             DatastoreException.class,
5972                             () -> {
5973                                 measurementDao.getEventReport("E11");
5974                             });
5975                     assertNotNull(measurementDao.getEventReport("E12"));
5976 
5977                     // Validate sources and triggers present
5978                     assertNotNull(measurementDao.getSource("S1"));
5979                     assertNotNull(measurementDao.getTrigger("T1"));
5980                     assertNotNull(measurementDao.getTrigger("T2"));
5981                 });
5982 
5983         // Validate attribution deletion
5984         assertNotNull(getAttribution("ATT11_aggregate", db));
5985         assertNotNull(getAttribution("ATT12_aggregate", db));
5986         assertNull(getAttribution("ATT11_event", db));
5987         assertNotNull(getAttribution("ATT12_event", db));
5988     }
5989 
5990     @Test
testDeleteDebugReport()5991     public void testDeleteDebugReport() {
5992         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
5993         DebugReport debugReport = createDebugReport();
5994 
5995         ContentValues values = new ContentValues();
5996         values.put(DebugReportContract.ID, debugReport.getId());
5997         values.put(DebugReportContract.TYPE, debugReport.getType());
5998         values.put(DebugReportContract.BODY, debugReport.getBody().toString());
5999         values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId());
6000         values.put(
6001                 DebugReportContract.REGISTRATION_ORIGIN,
6002                 debugReport.getRegistrationOrigin().toString());
6003         db.insert(DebugReportContract.TABLE, null, values);
6004 
6005         long count =
6006                 DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6007         assertEquals(1, count);
6008 
6009         assertTrue(
6010                 mDatastoreManager.runInTransaction(
6011                         measurementDao -> measurementDao.deleteDebugReport(debugReport.getId())));
6012 
6013         count = DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6014         assertEquals(0, count);
6015     }
6016 
6017     @Test
testDeleteDebugReports_byRegistrantAndRange()6018     public void testDeleteDebugReports_byRegistrantAndRange() {
6019         final String registrantMatching = "foo";
6020         final long insertionTimeWithinRange = 1701206853050L;
6021         final long insertionTimeNotWithinRange = 1701206853000L;
6022         DebugReport debugReportMatchingRegistrantAndWithinRange =
6023                 createDebugReport(
6024                         /* id= */ "1",
6025                         buildRegistrant(registrantMatching),
6026                         insertionTimeWithinRange);
6027 
6028         DebugReport debugReportNotMatchingRegistrantAndWithinRange =
6029                 createDebugReport(/* id= */ "2", buildRegistrant("bar"), insertionTimeWithinRange);
6030 
6031         DebugReport debugReportMatchingRegistrantAndNotWithinRange =
6032                 createDebugReport(
6033                         /* id= */ "3",
6034                         buildRegistrant(registrantMatching),
6035                         insertionTimeNotWithinRange);
6036 
6037         mDatastoreManager.runInTransaction(
6038                 (dao) -> {
6039                     dao.insertDebugReport(debugReportMatchingRegistrantAndWithinRange);
6040                     dao.insertDebugReport(debugReportNotMatchingRegistrantAndWithinRange);
6041                     dao.insertDebugReport(debugReportMatchingRegistrantAndNotWithinRange);
6042                 });
6043 
6044         mDatastoreManager.runInTransaction(
6045                 (dao) ->
6046                         dao.deleteDebugReports(
6047                                 buildRegistrant(registrantMatching),
6048                                 /* start= */ Instant.ofEpochMilli(insertionTimeWithinRange - 1),
6049                                 /* end= */ Instant.ofEpochMilli(insertionTimeWithinRange + 1)));
6050 
6051         Set<String> ids = new HashSet<>();
6052         try (Cursor cursor =
6053                 MeasurementDbHelper.getInstance()
6054                         .getReadableDatabase()
6055                         .query(DebugReportContract.TABLE, null, null, null, null, null, null)) {
6056             while (cursor.moveToNext()) {
6057                 ids.add(cursor.getString(cursor.getColumnIndexOrThrow(DebugReportContract.ID)));
6058             }
6059         }
6060         assertEquals(2, ids.size());
6061         assertTrue(ids.contains("2"));
6062         assertTrue(ids.contains("3"));
6063     }
6064 
6065     @Test
testGetDebugReportIds()6066     public void testGetDebugReportIds() {
6067         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6068         DebugReport debugReport = createDebugReport();
6069 
6070         ContentValues values = new ContentValues();
6071         values.put(DebugReportContract.ID, debugReport.getId());
6072         values.put(DebugReportContract.TYPE, debugReport.getType());
6073         values.put(DebugReportContract.BODY, debugReport.getBody().toString());
6074         values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId());
6075         values.put(
6076                 DebugReportContract.REGISTRATION_ORIGIN,
6077                 debugReport.getRegistrationOrigin().toString());
6078         db.insert(DebugReportContract.TABLE, null, values);
6079 
6080         long count =
6081                 DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6082         assertEquals(1, count);
6083 
6084         assertTrue(
6085                 mDatastoreManager.runInTransaction(
6086                         measurementDao ->
6087                                 assertEquals(
6088                                         List.of(debugReport.getId()),
6089                                         measurementDao.getDebugReportIds())));
6090     }
6091 
6092     @Test
testGetDebugReportIdsWithRetryLimit()6093     public void testGetDebugReportIdsWithRetryLimit() {
6094         // Mocking that the flags return a Max Retry of 1
6095         Flags mockFlags = Mockito.mock(Flags.class);
6096         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6097         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
6098         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
6099 
6100         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6101         DebugReport debugReport = createDebugReport();
6102 
6103         ContentValues values = new ContentValues();
6104         values.put(DebugReportContract.ID, debugReport.getId());
6105         values.put(DebugReportContract.TYPE, debugReport.getType());
6106         values.put(DebugReportContract.BODY, debugReport.getBody().toString());
6107         values.put(DebugReportContract.ENROLLMENT_ID, debugReport.getEnrollmentId());
6108         values.put(
6109                 DebugReportContract.REGISTRATION_ORIGIN,
6110                 debugReport.getRegistrationOrigin().toString());
6111         db.insert(DebugReportContract.TABLE, null, values);
6112 
6113         long count =
6114                 DatabaseUtils.queryNumEntries(db, DebugReportContract.TABLE, /* selection */ null);
6115         assertEquals(1, count);
6116 
6117         assertTrue(
6118                 mDatastoreManager.runInTransaction(
6119                         measurementDao ->
6120                                 assertEquals(
6121                                         List.of(debugReport.getId()),
6122                                         measurementDao.getDebugReportIds())));
6123         assertTrue(
6124                 mDatastoreManager.runInTransaction(
6125                         measurementDao -> {
6126                             // Adds records to KeyValueData table for Retry Count.
6127                             measurementDao.incrementAndGetReportingRetryCount(
6128                                     debugReport.getId(), DataType.DEBUG_REPORT_RETRY_COUNT);
6129                             assertTrue(measurementDao.getDebugReportIds().isEmpty());
6130                         }));
6131     }
6132 
6133     @Test
testDeleteExpiredRecordsForAsyncRegistrations()6134     public void testDeleteExpiredRecordsForAsyncRegistrations() {
6135         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6136 
6137         List<AsyncRegistration> asyncRegistrationList = new ArrayList<>();
6138         int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST;
6139 
6140         // Will be deleted by request time
6141         asyncRegistrationList.add(
6142                 new AsyncRegistration.Builder()
6143                         .setId("1")
6144                         .setOsDestination(Uri.parse("android-app://installed-app-destination"))
6145                         .setRegistrant(INSTALLED_REGISTRANT)
6146                         .setTopOrigin(INSTALLED_REGISTRANT)
6147                         .setAdIdPermission(false)
6148                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6149                         .setRequestTime(1)
6150                         .setRetryCount(retryLimit - 1L)
6151                         .setRegistrationId(UUID.randomUUID().toString())
6152                         .build());
6153 
6154         // Will be deleted by either request time or retry limit
6155         asyncRegistrationList.add(
6156                 new AsyncRegistration.Builder()
6157                         .setId("2")
6158                         .setOsDestination(Uri.parse("android-app://installed-app-destination"))
6159                         .setRegistrant(INSTALLED_REGISTRANT)
6160                         .setTopOrigin(INSTALLED_REGISTRANT)
6161                         .setAdIdPermission(false)
6162                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6163                         .setRequestTime(1)
6164                         .setRetryCount(retryLimit)
6165                         .setRegistrationId(UUID.randomUUID().toString())
6166                         .build());
6167 
6168         // Will not be deleted
6169         asyncRegistrationList.add(
6170                 new AsyncRegistration.Builder()
6171                         .setId("3")
6172                         .setOsDestination(Uri.parse("android-app://not-installed-app-destination"))
6173                         .setRegistrant(INSTALLED_REGISTRANT)
6174                         .setTopOrigin(INSTALLED_REGISTRANT)
6175                         .setAdIdPermission(false)
6176                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6177                         .setRequestTime(Long.MAX_VALUE)
6178                         .setRetryCount(retryLimit - 1L)
6179                         .setRegistrationId(UUID.randomUUID().toString())
6180                         .build());
6181 
6182         // Will be deleted due to retry limit
6183         asyncRegistrationList.add(
6184                 new AsyncRegistration.Builder()
6185                         .setId("4")
6186                         .setOsDestination(Uri.parse("android-app://not-installed-app-destination"))
6187                         .setRegistrant(INSTALLED_REGISTRANT)
6188                         .setTopOrigin(INSTALLED_REGISTRANT)
6189                         .setAdIdPermission(false)
6190                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
6191                         .setRequestTime(Long.MAX_VALUE)
6192                         .setRetryCount(retryLimit)
6193                         .setRegistrationId(UUID.randomUUID().toString())
6194                         .build());
6195 
6196         asyncRegistrationList.forEach(
6197                 asyncRegistration -> {
6198                     ContentValues values = new ContentValues();
6199                     values.put(AsyncRegistrationContract.ID, asyncRegistration.getId());
6200                     values.put(
6201                             AsyncRegistrationContract.REGISTRANT,
6202                             asyncRegistration.getRegistrant().toString());
6203                     values.put(
6204                             AsyncRegistrationContract.TOP_ORIGIN,
6205                             asyncRegistration.getTopOrigin().toString());
6206                     values.put(
6207                             AsyncRegistrationContract.OS_DESTINATION,
6208                             asyncRegistration.getOsDestination().toString());
6209                     values.put(
6210                             AsyncRegistrationContract.AD_ID_PERMISSION,
6211                             asyncRegistration.getDebugKeyAllowed());
6212                     values.put(
6213                             AsyncRegistrationContract.TYPE, asyncRegistration.getType().toString());
6214                     values.put(
6215                             AsyncRegistrationContract.REQUEST_TIME,
6216                             asyncRegistration.getRequestTime());
6217                     values.put(
6218                             AsyncRegistrationContract.RETRY_COUNT,
6219                             asyncRegistration.getRetryCount());
6220                     values.put(
6221                             AsyncRegistrationContract.REGISTRATION_ID,
6222                             asyncRegistration.getRegistrationId());
6223                     db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values);
6224                 });
6225 
6226         long count =
6227                 DatabaseUtils.queryNumEntries(
6228                         db, AsyncRegistrationContract.TABLE, /* selection */ null);
6229         assertEquals(4, count);
6230 
6231         long earliestValidInsertion = System.currentTimeMillis() - 2;
6232 
6233         assertTrue(
6234                 mDatastoreManager.runInTransaction(
6235                         measurementDao ->
6236                                 measurementDao.deleteExpiredRecords(
6237                                         earliestValidInsertion,
6238                                         retryLimit,
6239                                         /* earliestValidAppReportInsertion */ null,
6240                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
6241 
6242         count =
6243                 DatabaseUtils.queryNumEntries(
6244                         db, AsyncRegistrationContract.TABLE, /* selection */ null);
6245         assertEquals(1, count);
6246 
6247         Cursor cursor =
6248                 db.query(
6249                         AsyncRegistrationContract.TABLE,
6250                         /* columns */ null,
6251                         /* selection */ null,
6252                         /* selectionArgs */ null,
6253                         /* groupBy */ null,
6254                         /* having */ null,
6255                         /* orderBy */ null);
6256 
6257         Set<String> ids = new HashSet<>(Arrays.asList("3"));
6258         List<AsyncRegistration> asyncRegistrations = new ArrayList<>();
6259         while (cursor.moveToNext()) {
6260             AsyncRegistration asyncRegistration =
6261                     SqliteObjectMapper.constructAsyncRegistration(cursor);
6262             asyncRegistrations.add(asyncRegistration);
6263         }
6264         for (AsyncRegistration asyncRegistration : asyncRegistrations) {
6265             assertTrue(ids.contains(asyncRegistration.getId()));
6266         }
6267     }
6268 
6269     @Test
deleteExpiredRecords_registrationRedirectCount()6270     public void deleteExpiredRecords_registrationRedirectCount() {
6271         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6272         List<Pair<String, String>> regIdCounts =
6273                 List.of(
6274                         new Pair<>("reg1", "1"),
6275                         new Pair<>("reg2", "2"),
6276                         new Pair<>("reg3", "3"),
6277                         new Pair<>("reg4", "4"));
6278         for (Pair<String, String> regIdCount : regIdCounts) {
6279             ContentValues contentValues = new ContentValues();
6280             contentValues.put(
6281                     KeyValueDataContract.DATA_TYPE,
6282                     KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
6283             contentValues.put(KeyValueDataContract.KEY, regIdCount.first);
6284             contentValues.put(KeyValueDataContract.VALUE, regIdCount.second);
6285             db.insert(KeyValueDataContract.TABLE, null, contentValues);
6286         }
6287         AsyncRegistration asyncRegistration1 =
6288                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
6289                         .setRegistrationId("reg1")
6290                         .setRequestTime(System.currentTimeMillis() + 60000) // Avoid deletion
6291                         .build();
6292         AsyncRegistration asyncRegistration2 =
6293                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
6294                         .setRegistrationId("reg2")
6295                         .setRequestTime(System.currentTimeMillis() + 60000) // Avoid deletion
6296                         .build();
6297         List<AsyncRegistration> asyncRegistrations =
6298                 List.of(asyncRegistration1, asyncRegistration2);
6299         asyncRegistrations.forEach(
6300                 asyncRegistration ->
6301                         mDatastoreManager.runInTransaction(
6302                                 dao -> dao.insertAsyncRegistration(asyncRegistration)));
6303 
6304         long earliestValidInsertion = System.currentTimeMillis() - 60000;
6305         int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST;
6306         assertTrue(
6307                 mDatastoreManager.runInTransaction(
6308                         (dao) ->
6309                                 dao.deleteExpiredRecords(
6310                                         earliestValidInsertion,
6311                                         retryLimit,
6312                                         /* earliestValidAggregateDebugReportInsertion */ null,
6313                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
6314 
6315         Cursor cursor =
6316                 db.query(
6317                         KeyValueDataContract.TABLE,
6318                         null,
6319                         null,
6320                         null,
6321                         null,
6322                         null,
6323                         KeyValueDataContract.KEY);
6324         assertEquals(2, cursor.getCount());
6325         cursor.moveToNext();
6326         assertEquals(
6327                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
6328                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
6329         assertEquals("reg1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
6330         assertEquals("1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
6331         cursor.moveToNext();
6332         assertEquals(
6333                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
6334                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
6335         assertEquals("reg2", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
6336         assertEquals("2", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
6337         cursor.close();
6338     }
6339 
6340     @Test
deleteExpiredRecords_skipDeliveredEventReportsOutsideWindow()6341     public void deleteExpiredRecords_skipDeliveredEventReportsOutsideWindow() {
6342         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6343         ContentValues sourceValid = new ContentValues();
6344         sourceValid.put(SourceContract.ID, "s1");
6345         sourceValid.put(SourceContract.EVENT_TIME, System.currentTimeMillis());
6346 
6347         ContentValues sourceExpired = new ContentValues();
6348         sourceExpired.put(SourceContract.ID, "s2");
6349         sourceExpired.put(
6350                 SourceContract.EVENT_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6351 
6352         ContentValues triggerValid = new ContentValues();
6353         triggerValid.put(TriggerContract.ID, "t1");
6354         triggerValid.put(TriggerContract.TRIGGER_TIME, System.currentTimeMillis());
6355 
6356         ContentValues triggerExpired = new ContentValues();
6357         triggerExpired.put(TriggerContract.ID, "t2");
6358         triggerExpired.put(
6359                 TriggerContract.TRIGGER_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6360 
6361         db.insert(SourceContract.TABLE, null, sourceValid);
6362         db.insert(SourceContract.TABLE, null, sourceExpired);
6363         db.insert(TriggerContract.TABLE, null, triggerValid);
6364         db.insert(TriggerContract.TABLE, null, triggerExpired);
6365 
6366         ContentValues eventReport_NotDelivered_WithinWindow = new ContentValues();
6367         eventReport_NotDelivered_WithinWindow.put(EventReportContract.ID, "e1");
6368         eventReport_NotDelivered_WithinWindow.put(
6369                 EventReportContract.REPORT_TIME, System.currentTimeMillis());
6370         eventReport_NotDelivered_WithinWindow.put(
6371                 EventReportContract.STATUS, EventReport.Status.PENDING);
6372         eventReport_NotDelivered_WithinWindow.put(
6373                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6374         eventReport_NotDelivered_WithinWindow.put(
6375                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6376         db.insert(EventReportContract.TABLE, null, eventReport_NotDelivered_WithinWindow);
6377 
6378         ContentValues eventReport_Delivered_WithinWindow = new ContentValues();
6379         eventReport_Delivered_WithinWindow.put(EventReportContract.ID, "e2");
6380         eventReport_Delivered_WithinWindow.put(
6381                 EventReportContract.REPORT_TIME, System.currentTimeMillis());
6382         eventReport_Delivered_WithinWindow.put(
6383                 EventReportContract.STATUS, EventReport.Status.DELIVERED);
6384         eventReport_Delivered_WithinWindow.put(
6385                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6386         eventReport_Delivered_WithinWindow.put(
6387                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6388         db.insert(EventReportContract.TABLE, null, eventReport_Delivered_WithinWindow);
6389 
6390         ContentValues eventReport_Delivered_OutsideWindow = new ContentValues();
6391         eventReport_Delivered_OutsideWindow.put(EventReportContract.ID, "e3");
6392         eventReport_Delivered_OutsideWindow.put(
6393                 EventReportContract.REPORT_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6394         eventReport_Delivered_OutsideWindow.put(
6395                 EventReportContract.STATUS, EventReport.Status.DELIVERED);
6396         eventReport_Delivered_OutsideWindow.put(
6397                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6398         eventReport_Delivered_OutsideWindow.put(
6399                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6400         db.insert(EventReportContract.TABLE, null, eventReport_Delivered_OutsideWindow);
6401 
6402         ContentValues eventReport_NotDelivered_OutsideWindow = new ContentValues();
6403         eventReport_NotDelivered_OutsideWindow.put(EventReportContract.ID, "e4");
6404         eventReport_NotDelivered_OutsideWindow.put(
6405                 EventReportContract.REPORT_TIME, System.currentTimeMillis() - DAYS.toMillis(20));
6406         eventReport_NotDelivered_OutsideWindow.put(
6407                 EventReportContract.STATUS, EventReport.Status.PENDING);
6408         eventReport_NotDelivered_OutsideWindow.put(
6409                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6410         eventReport_NotDelivered_OutsideWindow.put(
6411                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6412         db.insert(EventReportContract.TABLE, null, eventReport_NotDelivered_OutsideWindow);
6413 
6414         ContentValues eventReport_expiredSource = new ContentValues();
6415         eventReport_expiredSource.put(EventReportContract.ID, "e5");
6416         eventReport_expiredSource.put(EventReportContract.REPORT_TIME, System.currentTimeMillis());
6417         eventReport_expiredSource.put(EventReportContract.STATUS, EventReport.Status.PENDING);
6418         eventReport_expiredSource.put(
6419                 EventReportContract.SOURCE_ID, sourceExpired.getAsString(SourceContract.ID));
6420         eventReport_expiredSource.put(
6421                 EventReportContract.TRIGGER_ID, triggerValid.getAsString(TriggerContract.ID));
6422         db.insert(EventReportContract.TABLE, null, eventReport_expiredSource);
6423 
6424         ContentValues eventReport_expiredTrigger = new ContentValues();
6425         eventReport_expiredTrigger.put(EventReportContract.ID, "e6");
6426         eventReport_expiredTrigger.put(EventReportContract.REPORT_TIME, System.currentTimeMillis());
6427         eventReport_expiredTrigger.put(EventReportContract.STATUS, EventReport.Status.PENDING);
6428         eventReport_expiredTrigger.put(
6429                 EventReportContract.SOURCE_ID, sourceValid.getAsString(SourceContract.ID));
6430         eventReport_expiredTrigger.put(
6431                 EventReportContract.TRIGGER_ID, triggerExpired.getAsString(TriggerContract.ID));
6432         db.insert(EventReportContract.TABLE, null, eventReport_expiredTrigger);
6433 
6434         long earliestValidInsertion = System.currentTimeMillis() - DAYS.toMillis(10);
6435         int retryLimit = Flags.MEASUREMENT_MAX_RETRIES_PER_REGISTRATION_REQUEST;
6436         mDatastoreManager.runInTransaction(
6437                 measurementDao ->
6438                         measurementDao.deleteExpiredRecords(
6439                                 earliestValidInsertion,
6440                                 retryLimit,
6441                                 /* earliestValidAppReportInsertion */ null,
6442                                 /* earliestValidAggregateDebugReportInsertion */ 0));
6443 
6444         List<ContentValues> deletedReports =
6445                 List.of(eventReport_expiredSource, eventReport_expiredTrigger);
6446 
6447         List<ContentValues> notDeletedReports =
6448                 List.of(
6449                         eventReport_Delivered_OutsideWindow,
6450                         eventReport_Delivered_WithinWindow,
6451                         eventReport_NotDelivered_OutsideWindow,
6452                         eventReport_NotDelivered_WithinWindow);
6453 
6454         assertEquals(
6455                 notDeletedReports.size(),
6456                 DatabaseUtils.longForQuery(
6457                         db,
6458                         "SELECT COUNT("
6459                                 + EventReportContract.ID
6460                                 + ") FROM "
6461                                 + EventReportContract.TABLE
6462                                 + " WHERE "
6463                                 + EventReportContract.ID
6464                                 + " IN ("
6465                                 + notDeletedReports.stream()
6466                                         .map(
6467                                                 (eR) -> {
6468                                                     return DatabaseUtils.sqlEscapeString(
6469                                                             eR.getAsString(EventReportContract.ID));
6470                                                 })
6471                                         .collect(Collectors.joining(","))
6472                                 + ")",
6473                         null));
6474 
6475         assertEquals(
6476                 0,
6477                 DatabaseUtils.longForQuery(
6478                         db,
6479                         "SELECT COUNT("
6480                                 + EventReportContract.ID
6481                                 + ") FROM "
6482                                 + EventReportContract.TABLE
6483                                 + " WHERE "
6484                                 + EventReportContract.ID
6485                                 + " IN ("
6486                                 + deletedReports.stream()
6487                                         .map(
6488                                                 (eR) -> {
6489                                                     return DatabaseUtils.sqlEscapeString(
6490                                                             eR.getAsString(EventReportContract.ID));
6491                                                 })
6492                                         .collect(Collectors.joining(","))
6493                                 + ")",
6494                         null));
6495     }
6496 
6497     @Test
deleteExpiredRecords_VerboseDebugReportsWhileLimitingRetries()6498     public void deleteExpiredRecords_VerboseDebugReportsWhileLimitingRetries() {
6499         // Mocking that the flags return a Max Retry of 1
6500         Flags mockFlags = Mockito.mock(Flags.class);
6501         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6502         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
6503         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
6504 
6505         SQLiteDatabase db = MeasurementDbHelper.getInstance().getReadableDatabase();
6506 
6507         DebugReport debugReport1 =
6508                 new DebugReport.Builder()
6509                         .setId("reportId1")
6510                         .setType("trigger-event-deduplicated")
6511                         .setBody(
6512                                 " {\n"
6513                                         + "      \"attribution_destination\":"
6514                                         + " \"https://destination.example\",\n"
6515                                         + "      \"source_event_id\": \"45623\"\n"
6516                                         + "    }")
6517                         .setEnrollmentId("1")
6518                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
6519                         .build();
6520 
6521         DebugReport debugReport2 =
6522                 new DebugReport.Builder()
6523                         .setId("reportId2")
6524                         .setType("trigger-event-deduplicated")
6525                         .setBody(
6526                                 " {\n"
6527                                         + "      \"attribution_destination\":"
6528                                         + " \"https://destination.example\",\n"
6529                                         + "      \"source_event_id\": \"45623\"\n"
6530                                         + "    }")
6531                         .setEnrollmentId("1")
6532                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
6533                         .build();
6534 
6535         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport1));
6536         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport2));
6537 
6538         mDatastoreManager.runInTransaction(
6539                 dao ->
6540                         dao.deleteExpiredRecords(
6541                                 /* earliestValidInsertion */ 0,
6542                                 /* registrationRetryLimit */ 0,
6543                                 /* earliestValidAppReportInsertion */ null,
6544                                 /* earliestValidAggregateDebugReportInsertion */ 0));
6545         assertEquals(
6546                 2,
6547                 DatabaseUtils.longForQuery(
6548                         db,
6549                         "SELECT COUNT("
6550                                 + DebugReportContract.ID
6551                                 + ") FROM "
6552                                 + DebugReportContract.TABLE,
6553                         null));
6554         // Increment Attempt Record 1
6555         mDatastoreManager.runInTransaction(
6556                 (dao) ->
6557                         dao.incrementAndGetReportingRetryCount(
6558                                 debugReport1.getId(), DataType.DEBUG_REPORT_RETRY_COUNT));
6559         // Delete Expired (Record 1)
6560         mDatastoreManager.runInTransaction(
6561                 dao ->
6562                         dao.deleteExpiredRecords(
6563                                 /* earliestValidInsertion */ 0,
6564                                 /* registrationRetryLimit */ 0,
6565                                 /* earliestValidAppReportInsertion */ null,
6566                                 /* earliestValidAggregateDebugReportInsertion */ 0));
6567 
6568         // Assert Record 2 remains.
6569         assertEquals(
6570                 1,
6571                 DatabaseUtils.longForQuery(
6572                         db,
6573                         "SELECT COUNT("
6574                                 + DebugReportContract.ID
6575                                 + ") FROM "
6576                                 + DebugReportContract.TABLE
6577                                 + " WHERE "
6578                                 + DebugReportContract.ID
6579                                 + " = ?",
6580                         new String[] {debugReport2.getId()}));
6581 
6582         // Assert Record 1 Removed
6583         assertEquals(
6584                 0,
6585                 DatabaseUtils.longForQuery(
6586                         db,
6587                         "SELECT COUNT("
6588                                 + DebugReportContract.ID
6589                                 + ") FROM "
6590                                 + DebugReportContract.TABLE
6591                                 + " WHERE "
6592                                 + DebugReportContract.ID
6593                                 + " = ?",
6594                         new String[] {debugReport1.getId()}));
6595     }
6596 
6597     @Test
deleteExpiredRecords_VerboseDebugReportsWhileNotLimitingRetries()6598     public void deleteExpiredRecords_VerboseDebugReportsWhileNotLimitingRetries() {
6599         // Mocking that the retry Limiting Disable, but has limit number,
6600         Flags mockFlags = Mockito.mock(Flags.class);
6601         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6602         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
6603         ExtendedMockito.doReturn(false).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
6604 
6605         SQLiteDatabase db = MeasurementDbHelper.getInstance().getReadableDatabase();
6606 
6607         DebugReport debugReport1 =
6608                 new DebugReport.Builder()
6609                         .setId("reportId1")
6610                         .setType("trigger-event-deduplicated")
6611                         .setBody(
6612                                 " {\n"
6613                                         + "      \"attribution_destination\":"
6614                                         + " \"https://destination.example\",\n"
6615                                         + "      \"source_event_id\": \"45623\"\n"
6616                                         + "    }")
6617                         .setEnrollmentId("1")
6618                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
6619                         .setInsertionTime(System.currentTimeMillis() + 60000L)
6620                         .build();
6621 
6622         DebugReport debugReport2 =
6623                 new DebugReport.Builder()
6624                         .setId("reportId2")
6625                         .setType("trigger-event-deduplicated")
6626                         .setBody(
6627                                 " {\n"
6628                                         + "      \"attribution_destination\":"
6629                                         + " \"https://destination.example\",\n"
6630                                         + "      \"source_event_id\": \"45623\"\n"
6631                                         + "    }")
6632                         .setEnrollmentId("1")
6633                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
6634                         .setInsertionTime(System.currentTimeMillis() - 60000L)
6635                         .build();
6636         // Insert
6637         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport1));
6638         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport2));
6639 
6640         // Increment Attempt
6641         mDatastoreManager.runInTransaction(
6642                 (dao) ->
6643                         dao.incrementAndGetReportingRetryCount(
6644                                 debugReport1.getId(), DataType.DEBUG_REPORT_RETRY_COUNT));
6645         // Delete Expired
6646         long earliestValidInsertion = System.currentTimeMillis();
6647 
6648         assertTrue(
6649                 mDatastoreManager.runInTransaction(
6650                         measurementDao ->
6651                                 measurementDao.deleteExpiredRecords(
6652                                         earliestValidInsertion,
6653                                         /* earliestValidAppReportInsertion */ 0,
6654                                         /* earliestValidAppReportInsertion */ null,
6655                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
6656 
6657         // Assert Record 1 remains because not expired and Retry Limiting Off.
6658         assertEquals(
6659                 1,
6660                 DatabaseUtils.longForQuery(
6661                         db,
6662                         "SELECT COUNT("
6663                                 + DebugReportContract.ID
6664                                 + ") FROM "
6665                                 + DebugReportContract.TABLE
6666                                 + " WHERE "
6667                                 + DebugReportContract.ID
6668                                 + " = ?",
6669                         new String[] {debugReport1.getId()}));
6670 
6671         // Assert Record 2 Removed because expired.
6672         assertEquals(
6673                 0,
6674                 DatabaseUtils.longForQuery(
6675                         db,
6676                         "SELECT COUNT("
6677                                 + DebugReportContract.ID
6678                                 + ") FROM "
6679                                 + DebugReportContract.TABLE
6680                                 + " WHERE "
6681                                 + DebugReportContract.ID
6682                                 + " = ?",
6683                         new String[] {debugReport2.getId()}));
6684     }
6685 
6686     @Test
deleteExpiredRecords_RetryKeyValueData()6687     public void deleteExpiredRecords_RetryKeyValueData() {
6688         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6689         // Non-stale join record
6690         DebugReport debugReport = createDebugReport();
6691         mDatastoreManager.runInTransaction((dao) -> dao.insertDebugReport(debugReport));
6692 
6693         // Should Remain
6694         ContentValues nonStaleValues = new ContentValues();
6695         nonStaleValues.put(
6696                 KeyValueDataContract.DATA_TYPE, DataType.DEBUG_REPORT_RETRY_COUNT.toString());
6697         nonStaleValues.put(KeyValueDataContract.KEY, debugReport.getId());
6698         nonStaleValues.put(KeyValueDataContract.VALUE, "1");
6699         db.insert(KeyValueDataContract.TABLE, null, nonStaleValues);
6700 
6701         // Should Delete
6702         ContentValues staleValues = new ContentValues();
6703         staleValues.put(
6704                 KeyValueDataContract.DATA_TYPE, DataType.DEBUG_REPORT_RETRY_COUNT.toString());
6705         staleValues.put(KeyValueDataContract.KEY, "stale-key");
6706         staleValues.put(KeyValueDataContract.VALUE, "1");
6707         db.insert(KeyValueDataContract.TABLE, null, staleValues);
6708 
6709         mDatastoreManager.runInTransaction(
6710                 dao ->
6711                         dao.deleteExpiredRecords(
6712                                 /* earliestValidInsertion */ 0,
6713                                 /* registrationRetryLimit */ 0,
6714                                 /* earliestValidAppReportInsertion */ null,
6715                                 /* earliestValidAggregateDebugReportInsertion */ 0));
6716 
6717         // Assert Non-Stale record remains.
6718         assertEquals(
6719                 1,
6720                 DatabaseUtils.longForQuery(
6721                         db,
6722                         "SELECT COUNT("
6723                                 + KeyValueDataContract.KEY
6724                                 + ") FROM "
6725                                 + KeyValueDataContract.TABLE
6726                                 + " WHERE "
6727                                 + KeyValueDataContract.KEY
6728                                 + " = ?",
6729                         new String[] {nonStaleValues.getAsString(KeyValueDataContract.KEY)}));
6730 
6731         // Assert Stale Record Removed
6732         assertEquals(
6733                 0,
6734                 DatabaseUtils.longForQuery(
6735                         db,
6736                         "SELECT COUNT("
6737                                 + KeyValueDataContract.KEY
6738                                 + ") FROM "
6739                                 + KeyValueDataContract.TABLE
6740                                 + " WHERE "
6741                                 + KeyValueDataContract.KEY
6742                                 + " = ?",
6743                         new String[] {staleValues.getAsString(KeyValueDataContract.KEY)}));
6744     }
6745 
6746     @Test
deleteExpiredRecords_reinstallAttributionEnabled_deletesExpiredAppInstallHistory()6747     public void deleteExpiredRecords_reinstallAttributionEnabled_deletesExpiredAppInstallHistory() {
6748         Flags mockFlags = Mockito.mock(Flags.class);
6749         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6750         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementEnableReinstallReattribution();
6751 
6752         SQLiteDatabase db = MeasurementDbHelper.getInstance().getReadableDatabase();
6753         long now = System.currentTimeMillis();
6754         mDatastoreManager.runInTransaction(
6755                 (dao) ->
6756                         dao.insertOrUpdateAppReportHistory(
6757                                 INSTALLED_PACKAGE,
6758                                 REGISTRATION_ORIGIN,
6759                                 /* lastReportDeliveredTimestamp= */ now
6760                                         - TimeUnit.DAYS.toMillis(1)));
6761         mDatastoreManager.runInTransaction(
6762                 (dao) ->
6763                         dao.insertOrUpdateAppReportHistory(
6764                                 INSTALLED_PACKAGE,
6765                                 REGISTRATION_ORIGIN_2,
6766                                 /* lastReportDeliveredTimestamp= */ now
6767                                         - TimeUnit.DAYS.toMillis(3)));
6768 
6769         long earliestValidInsertion = now - TimeUnit.DAYS.toMillis(2);
6770         assertTrue(
6771                 mDatastoreManager.runInTransaction(
6772                         measurementDao ->
6773                                 measurementDao.deleteExpiredRecords(
6774                                         earliestValidInsertion,
6775                                         /* registrationRetryLimit= */ 0,
6776                                         earliestValidInsertion,
6777                                         /* earliestValidAggregateDebugReportInsertion */ 0)));
6778         // Assert Record 1 remains because not expired and Retry Limiting Off.
6779         assertEquals(
6780                 1,
6781                 DatabaseUtils.longForQuery(
6782                         db,
6783                         "SELECT COUNT("
6784                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
6785                                 + ") FROM "
6786                                 + AppReportHistoryContract.TABLE
6787                                 + " WHERE "
6788                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
6789                                 + " = ?",
6790                         new String[] {REGISTRATION_ORIGIN.toString()}));
6791 
6792         // Assert Record 2 Removed because expired.
6793         assertEquals(
6794                 0,
6795                 DatabaseUtils.longForQuery(
6796                         db,
6797                         "SELECT COUNT("
6798                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
6799                                 + ") FROM "
6800                                 + AppReportHistoryContract.TABLE
6801                                 + " WHERE "
6802                                 + AppReportHistoryContract.REGISTRATION_ORIGIN
6803                                 + " = ?",
6804                         new String[] {REGISTRATION_ORIGIN_2.toString()}));
6805     }
6806 
6807     @Test
deleteExpiredRecords_withAdrBudgetTrackerRecords_deletesOlderRecords()6808     public void deleteExpiredRecords_withAdrBudgetTrackerRecords_deletesOlderRecords() {
6809         // Setup
6810         Flags mockFlags = Mockito.mock(Flags.class);
6811         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6812         ExtendedMockito.doReturn(true)
6813                 .when(mockFlags)
6814                 .getMeasurementEnableAggregateDebugReporting();
6815         long currentTime = System.currentTimeMillis();
6816 
6817         // record1 - 6 hour old report record
6818         AggregateDebugReportRecord record1 =
6819                 new AggregateDebugReportRecord.Builder(
6820                                 currentTime - TimeUnit.HOURS.toMillis(6),
6821                                 Uri.parse("android-app://com.example.abc"),
6822                                 APP_ONE_PUBLISHER,
6823                                 REGISTRATION_ORIGIN,
6824                                 1)
6825                         .build();
6826         // record2 - 1 day 1 millisecond old report record
6827         AggregateDebugReportRecord record2 =
6828                 new AggregateDebugReportRecord.Builder(
6829                                 currentTime - DAYS.toMillis(1) - 1,
6830                                 Uri.parse("android-app://com.example.def"),
6831                                 APP_TWO_PUBLISHER,
6832                                 REGISTRATION_ORIGIN_2,
6833                                 10)
6834                         .build();
6835         // record3 - 10 days old report record
6836         AggregateDebugReportRecord record3 =
6837                 new AggregateDebugReportRecord.Builder(
6838                                 currentTime - DAYS.toMillis(10),
6839                                 Uri.parse("android-app://com.example.ghi"),
6840                                 APP_ONE_PUBLISHER,
6841                                 REGISTRATION_ORIGIN_3,
6842                                 100)
6843                         .build();
6844         // record4 - 1 minute old report record
6845         AggregateDebugReportRecord record4 =
6846                 new AggregateDebugReportRecord.Builder(
6847                                 currentTime - TimeUnit.MINUTES.toMillis(1),
6848                                 Uri.parse("android-app://com.example.abc"),
6849                                 APP_TWO_SOURCES,
6850                                 REGISTRATION_ORIGIN_4,
6851                                 1000)
6852                         .build();
6853         List<AggregateDebugReportRecord> records =
6854                 Arrays.asList(record1, record2, record3, record4);
6855         records.forEach(
6856                 record ->
6857                         mDatastoreManager.runInTransaction(
6858                                 dao -> dao.insertAggregateDebugReportRecord(record)));
6859 
6860         // Execution - delete records older than 1 day - should retain record1 & record4
6861         mDatastoreManager.runInTransaction(
6862                 dao ->
6863                         dao.deleteExpiredRecords(
6864                                 /* earliestValidInsertion */ 0L,
6865                                 /* registrationRetryLimit */ 0,
6866                                 /* earliestValidAppReportInsertion */ null,
6867                                 currentTime - DAYS.toMillis(1)));
6868 
6869         // Assertion
6870         assertThat(
6871                         DatabaseUtils.queryNumEntries(
6872                                 MeasurementDbHelper.getInstance().getReadableDatabase(),
6873                                 AggregatableDebugReportBudgetTrackerContract.TABLE))
6874                 .isEqualTo(2);
6875         try (Cursor recordCursor =
6876                 MeasurementDbHelper.getInstance()
6877                         .getReadableDatabase()
6878                         .query(
6879                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
6880                                 new String[] {
6881                                     AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN
6882                                 },
6883                                 null,
6884                                 null,
6885                                 null,
6886                                 null,
6887                                 AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN)) {
6888             assertThat(recordCursor.getCount()).isEqualTo(2);
6889             assertThat(recordCursor.moveToNext()).isTrue();
6890             assertThat(
6891                             recordCursor.getString(
6892                                     recordCursor.getColumnIndex(
6893                                             AggregatableDebugReportBudgetTrackerContract
6894                                                     .REGISTRATION_ORIGIN)))
6895                     .isEqualTo(REGISTRATION_ORIGIN.toString());
6896             assertThat(recordCursor.moveToNext()).isTrue();
6897             assertThat(
6898                             recordCursor.getString(
6899                                     recordCursor.getColumnIndex(
6900                                             AggregatableDebugReportBudgetTrackerContract
6901                                                     .REGISTRATION_ORIGIN)))
6902                     .isEqualTo(REGISTRATION_ORIGIN_4.toString());
6903         }
6904     }
6905 
6906     @Test
deleteExpiredRecords_withAdrRecords_deletesRecordsDueToSourceTriggerDeletion()6907     public void deleteExpiredRecords_withAdrRecords_deletesRecordsDueToSourceTriggerDeletion() {
6908         // Setup
6909         Flags mockFlags = Mockito.mock(Flags.class);
6910         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
6911         ExtendedMockito.doReturn(true)
6912                 .when(mockFlags)
6913                 .getMeasurementEnableAggregateDebugReporting();
6914         long currentTime = System.currentTimeMillis();
6915         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
6916 
6917         // 30 days old
6918         Source s1 =
6919                 SourceFixture.getValidSourceBuilder()
6920                         .setId("S1")
6921                         .setEventTime(currentTime - DAYS.toMillis(30))
6922                         .build();
6923         Trigger t1 =
6924                 TriggerFixture.getValidTriggerBuilder()
6925                         .setId("T1")
6926                         .setTriggerTime(currentTime - DAYS.toMillis(30))
6927                         .build();
6928         AbstractDbIntegrationTest.insertToDb(s1, db);
6929         AbstractDbIntegrationTest.insertToDb(t1, db);
6930 
6931         // 1 day old
6932         Source s2 =
6933                 SourceFixture.getValidSourceBuilder()
6934                         .setId("S2")
6935                         .setEventTime(currentTime - DAYS.toMillis(1))
6936                         .build();
6937         Trigger t2 =
6938                 TriggerFixture.getValidTriggerBuilder()
6939                         .setId("T2")
6940                         .setTriggerTime(currentTime - DAYS.toMillis(1))
6941                         .build();
6942         AbstractDbIntegrationTest.insertToDb(s2, db);
6943         AbstractDbIntegrationTest.insertToDb(t2, db);
6944 
6945         // deleted - record1 - 6 hour old report record but source and trigger are 30 days old
6946         AggregateDebugReportRecord record1 =
6947                 new AggregateDebugReportRecord.Builder(
6948                                 currentTime - TimeUnit.HOURS.toMillis(6),
6949                                 Uri.parse("android-app://com.example.abc"),
6950                                 APP_ONE_PUBLISHER,
6951                                 REGISTRATION_ORIGIN,
6952                                 1)
6953                         .setSourceId(s1.getId())
6954                         .setTriggerId(t1.getId())
6955                         .build();
6956         // deleted - record2 - 6 hour old report record but source is 30 days old
6957         AggregateDebugReportRecord record2 =
6958                 new AggregateDebugReportRecord.Builder(
6959                                 currentTime - TimeUnit.HOURS.toMillis(6),
6960                                 Uri.parse("android-app://com.example.def"),
6961                                 APP_TWO_PUBLISHER,
6962                                 REGISTRATION_ORIGIN_2,
6963                                 10)
6964                         .setSourceId(s1.getId())
6965                         .setTriggerId(null)
6966                         .build();
6967         // deleted - record3 - 6 hour old report record but trigger is 30 days old
6968         AggregateDebugReportRecord record3 =
6969                 new AggregateDebugReportRecord.Builder(
6970                                 currentTime - TimeUnit.HOURS.toMillis(6),
6971                                 Uri.parse("android-app://com.example.ghi"),
6972                                 APP_ONE_PUBLISHER,
6973                                 REGISTRATION_ORIGIN_3,
6974                                 100)
6975                         .setSourceId(null)
6976                         .setTriggerId(t1.getId())
6977                         .build();
6978         // retained - record4 - 6 hour old report record and source is 1 day old
6979         AggregateDebugReportRecord record4 =
6980                 new AggregateDebugReportRecord.Builder(
6981                                 currentTime - TimeUnit.HOURS.toMillis(6),
6982                                 Uri.parse("android-app://com.example.jkl"),
6983                                 APP_TWO_SOURCES,
6984                                 REGISTRATION_ORIGIN_4,
6985                                 1000)
6986                         .setSourceId(s2.getId())
6987                         .setTriggerId(null)
6988                         .build();
6989         // retained - record4 - 6 hour old report record and trigger is 1 day old
6990         AggregateDebugReportRecord record5 =
6991                 new AggregateDebugReportRecord.Builder(
6992                                 currentTime - TimeUnit.HOURS.toMillis(6),
6993                                 Uri.parse("android-app://com.example.mno"),
6994                                 APP_TWO_SOURCES,
6995                                 REGISTRATION_ORIGIN_5,
6996                                 1000)
6997                         .setSourceId(null)
6998                         .setTriggerId(t2.getId())
6999                         .build();
7000         List<AggregateDebugReportRecord> records =
7001                 Arrays.asList(record1, record2, record3, record4, record5);
7002         records.forEach(
7003                 record ->
7004                         mDatastoreManager.runInTransaction(
7005                                 dao -> dao.insertAggregateDebugReportRecord(record)));
7006 
7007         // Execution - delete records older than 10 days - should retain record4 & record5
7008         mDatastoreManager.runInTransaction(
7009                 dao ->
7010                         dao.deleteExpiredRecords(
7011                                 /* earliestValidInsertion */ currentTime - DAYS.toMillis(10),
7012                                 /* registrationRetryLimit */ 0,
7013                                 /* earliestValidAppReportInsertion */ null,
7014                                 /* earliestValidAggregateDebugReportInsertion */ 0));
7015 
7016         // Assertion
7017         assertThat(
7018                         DatabaseUtils.queryNumEntries(
7019                                 MeasurementDbHelper.getInstance().getReadableDatabase(),
7020                                 AggregatableDebugReportBudgetTrackerContract.TABLE))
7021                 .isEqualTo(2);
7022         try (Cursor recordCursor =
7023                 MeasurementDbHelper.getInstance()
7024                         .getReadableDatabase()
7025                         .query(
7026                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
7027                                 new String[] {
7028                                     AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN
7029                                 },
7030                                 null,
7031                                 null,
7032                                 null,
7033                                 null,
7034                                 AggregatableDebugReportBudgetTrackerContract.REGISTRATION_ORIGIN)) {
7035             assertThat(recordCursor.getCount()).isEqualTo(2);
7036             assertThat(recordCursor.moveToNext()).isTrue();
7037             assertThat(
7038                             recordCursor.getString(
7039                                     recordCursor.getColumnIndex(
7040                                             AggregatableDebugReportBudgetTrackerContract
7041                                                     .REGISTRATION_ORIGIN)))
7042                     .isEqualTo(REGISTRATION_ORIGIN_4.toString());
7043             assertThat(recordCursor.moveToNext()).isTrue();
7044             assertThat(
7045                             recordCursor.getString(
7046                                     recordCursor.getColumnIndex(
7047                                             AggregatableDebugReportBudgetTrackerContract
7048                                                     .REGISTRATION_ORIGIN)))
7049                     .isEqualTo(REGISTRATION_ORIGIN_5.toString());
7050         }
7051     }
7052 
7053     @Test
getRegistrationRedirectCount_keyMissing()7054     public void getRegistrationRedirectCount_keyMissing() {
7055         Optional<KeyValueData> optKeyValueData =
7056                 mDatastoreManager.runInTransactionWithResult(
7057                         (dao) ->
7058                                 dao.getKeyValueData(
7059                                         "missing_random_id", DataType.REGISTRATION_REDIRECT_COUNT));
7060         assertTrue(optKeyValueData.isPresent());
7061         KeyValueData keyValueData = optKeyValueData.get();
7062         assertEquals(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT, keyValueData.getDataType());
7063         assertEquals("missing_random_id", keyValueData.getKey());
7064         assertNull(keyValueData.getValue());
7065         assertEquals(1, keyValueData.getRegistrationRedirectCount());
7066     }
7067 
7068     @Test
getRegistrationRedirectCount_keyExists()7069     public void getRegistrationRedirectCount_keyExists() {
7070         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7071         ContentValues contentValues = new ContentValues();
7072         contentValues.put(
7073                 KeyValueDataContract.DATA_TYPE,
7074                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7075         contentValues.put(KeyValueDataContract.KEY, "random_id");
7076         contentValues.put(KeyValueDataContract.VALUE, "2");
7077         db.insert(KeyValueDataContract.TABLE, null, contentValues);
7078         Optional<KeyValueData> optKeyValueData =
7079                 mDatastoreManager.runInTransactionWithResult(
7080                         (dao) ->
7081                                 dao.getKeyValueData(
7082                                         "random_id",
7083                                         KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT));
7084         assertTrue(optKeyValueData.isPresent());
7085         KeyValueData keyValueData = optKeyValueData.get();
7086         assertEquals(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT, keyValueData.getDataType());
7087         assertEquals("random_id", keyValueData.getKey());
7088         assertEquals("2", keyValueData.getValue());
7089         assertEquals(2, keyValueData.getRegistrationRedirectCount());
7090     }
7091 
7092     @Test
updateRegistrationRedirectCount_keyMissing()7093     public void updateRegistrationRedirectCount_keyMissing() {
7094         KeyValueData keyValueData =
7095                 new KeyValueData.Builder()
7096                         .setDataType(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT)
7097                         .setKey("key_1")
7098                         .setValue("4")
7099                         .build();
7100         mDatastoreManager.runInTransaction((dao) -> dao.insertOrUpdateKeyValueData(keyValueData));
7101         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7102         Cursor cursor = db.query(KeyValueDataContract.TABLE, null, null, null, null, null, null);
7103         assertEquals(1, cursor.getCount());
7104         cursor.moveToNext();
7105         assertEquals(
7106                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
7107                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
7108         assertEquals("key_1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
7109         assertEquals("4", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
7110         cursor.close();
7111     }
7112 
7113     @Test
updateRegistrationRedirectCount_keyExists()7114     public void updateRegistrationRedirectCount_keyExists() {
7115         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7116         ContentValues contentValues = new ContentValues();
7117         contentValues.put(
7118                 KeyValueDataContract.DATA_TYPE,
7119                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7120         contentValues.put(KeyValueDataContract.KEY, "key_1");
7121         contentValues.put(KeyValueDataContract.VALUE, "2");
7122         db.insert(KeyValueDataContract.TABLE, null, contentValues);
7123 
7124         KeyValueData keyValueData =
7125                 new KeyValueData.Builder()
7126                         .setDataType(KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT)
7127                         .setKey("key_1")
7128                         .setValue("4")
7129                         .build();
7130         mDatastoreManager.runInTransaction((dao) -> dao.insertOrUpdateKeyValueData(keyValueData));
7131 
7132         Cursor cursor = db.query(KeyValueDataContract.TABLE, null, null, null, null, null, null);
7133         assertEquals(1, cursor.getCount());
7134         cursor.moveToNext();
7135         assertEquals(
7136                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString(),
7137                 cursor.getString(cursor.getColumnIndex(KeyValueDataContract.DATA_TYPE)));
7138         assertEquals("key_1", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.KEY)));
7139         assertEquals("4", cursor.getString(cursor.getColumnIndex(KeyValueDataContract.VALUE)));
7140         cursor.close();
7141     }
7142 
7143     @Test
keyValueDataTable_PrimaryKeyConstraint()7144     public void keyValueDataTable_PrimaryKeyConstraint() {
7145         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
7146         ContentValues contentValues1 = new ContentValues();
7147         contentValues1.put(
7148                 KeyValueDataContract.DATA_TYPE,
7149                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7150         contentValues1.put(KeyValueDataContract.KEY, "key_1");
7151         contentValues1.put(KeyValueDataContract.VALUE, "2");
7152 
7153         assertNotEquals(-1, db.insert(KeyValueDataContract.TABLE, null, contentValues1));
7154 
7155         // Should fail because we are using <DataType, Key> as primary key
7156         assertEquals(-1, db.insert(KeyValueDataContract.TABLE, null, contentValues1));
7157 
7158         ContentValues contentValues2 = new ContentValues();
7159         contentValues2.put(
7160                 KeyValueDataContract.DATA_TYPE,
7161                 KeyValueData.DataType.REGISTRATION_REDIRECT_COUNT.toString());
7162         contentValues2.put(KeyValueDataContract.KEY, "key_2");
7163         contentValues2.put(KeyValueDataContract.VALUE, "2");
7164 
7165         assertNotEquals(-1, db.insert(KeyValueDataContract.TABLE, null, contentValues2));
7166     }
7167 
7168     @Test
7169     public void
fetchSourceIdsForLowestPriorityDest_appDestEmptyExclusions_delLowPriorityDestination()7170             fetchSourceIdsForLowestPriorityDest_appDestEmptyExclusions_delLowPriorityDestination() {
7171         // Setup
7172         mFlags = mock(Flags.class);
7173         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
7174         doReturn(true).when(mFlags).getMeasurementEnableSourceDestinationLimitPriority();
7175         long baseEventTime = System.currentTimeMillis();
7176         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7177         insertSource(
7178                 createSourceBuilder()
7179                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7180                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7181                         .setEventTime(baseEventTime)
7182                         .setExpiryTime(commonExpiryTime)
7183                         .setDestinationLimitPriority(10)
7184                         .build(),
7185                 "s11");
7186         insertSource(
7187                 createSourceBuilder()
7188                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7189                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7190                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7191                         .setExpiryTime(commonExpiryTime)
7192                         .setDestinationLimitPriority(20)
7193                         .build(),
7194                 "s21");
7195         insertSource(
7196                 createSourceBuilder()
7197                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7198                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7199                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7200                         .setExpiryTime(commonExpiryTime)
7201                         .setDestinationLimitPriority(30)
7202                         .build(),
7203                 "s31");
7204         insertSource(
7205                 createSourceBuilder()
7206                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7207                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7208                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7209                         .setExpiryTime(commonExpiryTime)
7210                         .setDestinationLimitPriority(10)
7211                         .build(),
7212                 "s12");
7213         insertSource(
7214                 createSourceBuilder()
7215                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7216                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7217                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7218                         .setExpiryTime(commonExpiryTime)
7219                         .setDestinationLimitPriority(20)
7220                         .build(),
7221                 "s22");
7222 
7223         // Execute
7224         // com.example.app1 has the least priority of all as 10
7225         mDatastoreManager.runInTransaction(
7226                 (dao) -> {
7227                     Pair<Long, List<String>> destinationPriorityAndSourcesTodelete =
7228                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7229                                     SourceFixture.ValidSourceParams.PUBLISHER,
7230                                     EventSurfaceType.APP,
7231                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7232                                     Collections.emptyList(),
7233                                     EventSurfaceType.APP,
7234                                     baseEventTime + DAYS.toMillis(10) // request time
7235                                     );
7236 
7237                     assertEquals(10L, (long) destinationPriorityAndSourcesTodelete.first);
7238                     assertEquals(
7239                             Sets.newSet("s11", "s12"),
7240                             new HashSet<>(destinationPriorityAndSourcesTodelete.second));
7241                 });
7242     }
7243 
7244     @Test
7245     public void
fetchSourceIdsForLowPriorityDest_webDestEmptyExclusions_delLowPriorityDestinations()7246             fetchSourceIdsForLowPriorityDest_webDestEmptyExclusions_delLowPriorityDestinations() {
7247         // Setup
7248         mFlags = mock(Flags.class);
7249         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
7250         doReturn(true).when(mFlags).getMeasurementEnableSourceDestinationLimitPriority();
7251         long baseEventTime = System.currentTimeMillis();
7252         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7253         insertSource(
7254                 createSourceBuilder()
7255                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7256                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7257                         .setEventTime(baseEventTime)
7258                         .setExpiryTime(commonExpiryTime)
7259                         .setDestinationLimitPriority(10)
7260                         .build(),
7261                 "s11");
7262         insertSource(
7263                 createSourceBuilder()
7264                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7265                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7266                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7267                         .setExpiryTime(commonExpiryTime)
7268                         .setDestinationLimitPriority(20)
7269                         .build(),
7270                 "s21");
7271         insertSource(
7272                 createSourceBuilder()
7273                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7274                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7275                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7276                         .setExpiryTime(commonExpiryTime)
7277                         .setDestinationLimitPriority(30)
7278                         .build(),
7279                 "s31");
7280         insertSource(
7281                 createSourceBuilder()
7282                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7283                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7284                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7285                         .setExpiryTime(commonExpiryTime)
7286                         .setDestinationLimitPriority(40)
7287                         .build(),
7288                 "s12");
7289         insertSource(
7290                 createSourceBuilder()
7291                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7292                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7293                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7294                         .setExpiryTime(commonExpiryTime)
7295                         .setDestinationLimitPriority(20)
7296                         .build(),
7297                 "s22");
7298 
7299         // Execute
7300         // web1.example.com has priority 10 with s11 and priority 40 with s12, the higher one will
7301         // be considered, i.e. 40. web2.example.com" has priority as 20 through both s21 and s22,
7302         // its associated sources will be deleted instead.
7303         mDatastoreManager.runInTransaction(
7304                 (dao) -> {
7305                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7306                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7307                                     SourceFixture.ValidSourceParams.PUBLISHER,
7308                                     EventSurfaceType.APP,
7309                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7310                                     Collections.emptyList(),
7311                                     EventSurfaceType.WEB,
7312                                     baseEventTime + DAYS.toMillis(10) // request time
7313                                     );
7314 
7315                     assertEquals(20L, (long) destinationPriorityAndSourcesToDelete.first);
7316                     assertEquals(
7317                             Sets.newSet("s21", "s22"),
7318                             new HashSet<>(destinationPriorityAndSourcesToDelete.second));
7319                 });
7320     }
7321 
7322     @Test
fetchSourceIdsForLowPriorityDest_appDestEmptyExclusions_delLruDestinations()7323     public void fetchSourceIdsForLowPriorityDest_appDestEmptyExclusions_delLruDestinations() {
7324         // Setup
7325         long baseEventTime = System.currentTimeMillis();
7326         insert5SourcesForLruDestDeletion(baseEventTime);
7327 
7328         // Execute
7329         // com.example.app3 would be the least recently used destination, as 1 & 2 are used
7330         // afterwards
7331         mDatastoreManager.runInTransaction(
7332                 (dao) -> {
7333                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7334                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7335                                     SourceFixture.ValidSourceParams.PUBLISHER,
7336                                     EventSurfaceType.APP,
7337                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7338                                     Collections.emptyList(),
7339                                     EventSurfaceType.APP,
7340                                     baseEventTime + DAYS.toMillis(10) // request time
7341                                     );
7342 
7343                     assertEquals(0, (long) destinationPriorityAndSourcesToDelete.first);
7344                     assertEquals(List.of("s31"), destinationPriorityAndSourcesToDelete.second);
7345                 });
7346     }
7347 
7348     @Test
7349     public void
fetchSourceIdsForLowPriorityDest_appDestWebPubEmptyExclusions_delLowPrioDestSource()7350             fetchSourceIdsForLowPriorityDest_appDestWebPubEmptyExclusions_delLowPrioDestSource() {
7351         // Setup
7352         long baseEventTime = System.currentTimeMillis();
7353         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7354         insertSource(
7355                 createSourceBuilder()
7356                         .setPublisher(Uri.parse("https://web.example.com"))
7357                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7358                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7359                         .setEventTime(baseEventTime)
7360                         .setExpiryTime(commonExpiryTime)
7361                         .build(),
7362                 "s11");
7363         insertSource(
7364                 createSourceBuilder()
7365                         .setPublisher(Uri.parse("https://web.example.com"))
7366                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7367                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7368                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7369                         .setExpiryTime(commonExpiryTime)
7370                         .build(),
7371                 "s21");
7372         insertSource(
7373                 createSourceBuilder()
7374                         .setPublisher(Uri.parse("https://web.example.com"))
7375                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7376                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7377                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7378                         .setExpiryTime(commonExpiryTime)
7379                         .build(),
7380                 "s31");
7381         insertSource(
7382                 createSourceBuilder()
7383                         .setPublisher(Uri.parse("https://web.example.com"))
7384                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7385                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7386                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7387                         .setExpiryTime(commonExpiryTime)
7388                         .build(),
7389                 "s12");
7390         insertSource(
7391                 createSourceBuilder()
7392                         .setPublisher(Uri.parse("https://web.example.com"))
7393                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7394                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7395                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7396                         .setExpiryTime(commonExpiryTime)
7397                         .build(),
7398                 "s22");
7399 
7400         // Execute
7401         // com.example.app3 would be the least recently used destination, as 1 & 2 are used
7402         // afterwards
7403         mDatastoreManager.runInTransaction(
7404                 (dao) -> {
7405                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7406                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7407                                     Uri.parse("https://web.example.com"),
7408                                     EventSurfaceType.WEB,
7409                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7410                                     Collections.emptyList(),
7411                                     EventSurfaceType.APP,
7412                                     baseEventTime + DAYS.toMillis(10) // request time
7413                                     );
7414 
7415                     assertEquals(0L, (long) destinationPriorityAndSourcesToDelete.first);
7416                     assertEquals(List.of("s31"), destinationPriorityAndSourcesToDelete.second);
7417                 });
7418     }
7419 
7420     @Test
7421     public void
fetchSourceIdsForLowPriorityDest_diffEnrollments_delOldDestSourceForChosenEnrollment()7422             fetchSourceIdsForLowPriorityDest_diffEnrollments_delOldDestSourceForChosenEnrollment() {
7423         // Setup
7424         long baseEventTime = System.currentTimeMillis();
7425         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7426         insertSource(
7427                 createSourceBuilder()
7428                         .setEnrollmentId("enrollment1")
7429                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7430                         .setEventTime(baseEventTime)
7431                         .setExpiryTime(commonExpiryTime)
7432                         .build(),
7433                 "s11");
7434         insertSource(
7435                 createSourceBuilder()
7436                         .setEnrollmentId("enrollment1")
7437                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7438                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7439                         .setExpiryTime(commonExpiryTime)
7440                         .build(),
7441                 "s21");
7442         insertSource(
7443                 createSourceBuilder()
7444                         .setEnrollmentId("enrollment1")
7445                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7446                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7447                         .setExpiryTime(commonExpiryTime)
7448                         .build(),
7449                 "s31");
7450         insertSource(
7451                 createSourceBuilder()
7452                         .setEnrollmentId("enrollment2")
7453                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7454                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7455                         .setExpiryTime(commonExpiryTime)
7456                         .build(),
7457                 "s12");
7458         insertSource(
7459                 createSourceBuilder()
7460                         .setEnrollmentId("enrollment2")
7461                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7462                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7463                         .setExpiryTime(commonExpiryTime)
7464                         .build(),
7465                 "s22");
7466 
7467         // Execute
7468         // com.example.app1 would be the least recently used destination for enrollment2, that will
7469         // be deleted
7470         mDatastoreManager.runInTransaction(
7471                 (dao) -> {
7472                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7473                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7474                                     SourceFixture.ValidSourceParams.PUBLISHER,
7475                                     EventSurfaceType.APP,
7476                                     "enrollment2",
7477                                     Collections.emptyList(),
7478                                     EventSurfaceType.APP,
7479                                     baseEventTime + DAYS.toMillis(10) // request time
7480                                     );
7481 
7482                     assertEquals(0L, (long) destinationPriorityAndSourcesToDelete.first);
7483                     assertEquals(List.of("s12"), destinationPriorityAndSourcesToDelete.second);
7484                 });
7485     }
7486 
7487     @Test
7488     public void
fetchSourceIdsForLowPriorityDest_appDestExcludeLruSource_deletes2ndLruDestSources()7489             fetchSourceIdsForLowPriorityDest_appDestExcludeLruSource_deletes2ndLruDestSources() {
7490         // Setup
7491         long baseEventTime = System.currentTimeMillis();
7492         insert5SourcesForLruDestDeletion(System.currentTimeMillis());
7493 
7494         // Execute
7495         // com.example.app1 would be the second least recently used destination, as 2 is used
7496         // afterwards and 3 is ignored to be deleted.
7497         mDatastoreManager.runInTransaction(
7498                 (dao) -> {
7499                     Pair<Long, List<String>> destinationPriorityAndSourcesToDelete =
7500                             dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher(
7501                                     SourceFixture.ValidSourceParams.PUBLISHER,
7502                                     EventSurfaceType.APP,
7503                                     SourceFixture.ValidSourceParams.ENROLLMENT_ID,
7504                                     List.of(Uri.parse("android-app://com.example.app3")),
7505                                     EventSurfaceType.APP,
7506                                     baseEventTime + DAYS.toMillis(10) // request time
7507                                     );
7508 
7509                     assertEquals(0L, (long) destinationPriorityAndSourcesToDelete.first);
7510                     assertEquals(
7511                             List.of("s11", "s12"), destinationPriorityAndSourcesToDelete.second);
7512                 });
7513     }
7514 
insert5SourcesForLruDestDeletion(long baseEventTime)7515     private void insert5SourcesForLruDestDeletion(long baseEventTime) {
7516         long commonExpiryTime = baseEventTime + DAYS.toMillis(30);
7517         insertSource(
7518                 createSourceBuilder()
7519                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7520                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7521                         .setEventTime(baseEventTime)
7522                         .setExpiryTime(commonExpiryTime)
7523                         .build(),
7524                 "s11");
7525         insertSource(
7526                 createSourceBuilder()
7527                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7528                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7529                         .setEventTime(baseEventTime + DAYS.toMillis(1))
7530                         .setExpiryTime(commonExpiryTime)
7531                         .build(),
7532                 "s21");
7533         insertSource(
7534                 createSourceBuilder()
7535                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app3")))
7536                         .setWebDestinations(List.of(Uri.parse("https://web3.example.com")))
7537                         .setEventTime(baseEventTime + DAYS.toMillis(2))
7538                         .setExpiryTime(commonExpiryTime)
7539                         .build(),
7540                 "s31");
7541         insertSource(
7542                 createSourceBuilder()
7543                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app1")))
7544                         .setWebDestinations(List.of(Uri.parse("https://web1.example.com")))
7545                         .setEventTime(baseEventTime + DAYS.toMillis(3))
7546                         .setExpiryTime(commonExpiryTime)
7547                         .build(),
7548                 "s12");
7549         insertSource(
7550                 createSourceBuilder()
7551                         .setAppDestinations(List.of(Uri.parse("android-app://com.example.app2")))
7552                         .setWebDestinations(List.of(Uri.parse("https://web2.example.com")))
7553                         .setEventTime(baseEventTime + DAYS.toMillis(4))
7554                         .setExpiryTime(commonExpiryTime)
7555                         .build(),
7556                 "s22");
7557     }
7558 
7559     @Test
deletePendingAggregateReportsAndAttributionsForSources_success()7560     public void deletePendingAggregateReportsAndAttributionsForSources_success() {
7561         // Setup
7562         long baseTime = System.currentTimeMillis();
7563         // Sources
7564         insertSource(SourceFixture.getValidSource(), "S1");
7565         insertSource(SourceFixture.getValidSource(), "S2");
7566         insertSource(SourceFixture.getValidSource(), "S3");
7567         insertSource(SourceFixture.getValidSource(), "S4");
7568 
7569         mDatastoreManager.runInTransaction(
7570                 (dao) -> {
7571                     // Aggregate reports
7572                     // Should get deleted
7573                     AggregateReport agg1 =
7574                             AggregateReportFixture.getValidAggregateReportBuilder()
7575                                     .setId("Agg1")
7576                                     .setSourceId("S1")
7577                                     .setScheduledReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
7578                                     .setStatus(AggregateReport.Status.PENDING)
7579                                     .build();
7580                     dao.insertAggregateReport(agg1);
7581                     dao.insertAttribution(
7582                             createAttribution(
7583                                     "Att1", Attribution.Scope.AGGREGATE, "S1", null, agg1.getId()));
7584 
7585                     // Should not get deleted because S2 is not provided
7586                     AggregateReport agg2 =
7587                             AggregateReportFixture.getValidAggregateReportBuilder()
7588                                     .setId("Agg2")
7589                                     .setSourceId("S2")
7590                                     .setScheduledReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
7591                                     .setStatus(AggregateReport.Status.PENDING)
7592                                     .build();
7593                     dao.insertAggregateReport(agg2);
7594                     dao.insertAttribution(
7595                             createAttribution(
7596                                     "Att2", Attribution.Scope.AGGREGATE, "S2", null, agg2.getId()));
7597 
7598                     // Infeasible case, but it should not get deleted because its status is
7599                     // DELIVERED
7600                     AggregateReport agg3 =
7601                             AggregateReportFixture.getValidAggregateReportBuilder()
7602                                     .setId("Agg3")
7603                                     .setSourceId("S3")
7604                                     .setScheduledReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
7605                                     .setStatus(AggregateReport.Status.DELIVERED)
7606                                     .build();
7607                     dao.insertAggregateReport(agg3);
7608                     dao.insertAttribution(
7609                             createAttribution(
7610                                     "Att3", Attribution.Scope.AGGREGATE, "S3", null, agg3.getId()));
7611 
7612                     // Execution
7613                     dao.deletePendingAggregateReportsAndAttributionsForSources(List.of("S1", "S3"));
7614 
7615                     // Assertion
7616                     assertThrows(DatastoreException.class, () -> dao.getAggregateReport("Agg1"));
7617                     assertEquals(agg2, dao.getAggregateReport("Agg2"));
7618                     assertEquals(agg3, dao.getAggregateReport("Agg3"));
7619                 });
7620 
7621         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
7622         assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
7623         Set<String> reportIds = new HashSet<>();
7624         try (Cursor cursor =
7625                 db.rawQuery(
7626                         "SELECT "
7627                                 + AttributionContract.REPORT_ID
7628                                 + " FROM "
7629                                 + AttributionContract.TABLE,
7630                         null)) {
7631             while (cursor.moveToNext()) {
7632                 reportIds.add(cursor.getString(0));
7633             }
7634         }
7635         assertEquals(Set.of("Agg2", "Agg3"), reportIds);
7636     }
7637 
7638     @Test
fetchMatchingSourcesUninstall_outsideReportLifetime_deleteSources()7639     public void fetchMatchingSourcesUninstall_outsideReportLifetime_deleteSources()
7640             throws JSONException {
7641         // Setup
7642         mFlags = mock(Flags.class);
7643         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
7644         doReturn(true).when(mFlags).getMeasurementEnableMinReportLifespanForUninstall();
7645         doReturn(TimeUnit.DAYS.toSeconds(1))
7646                 .when(mFlags)
7647                 .getMeasurementMinReportLifespanForUninstallSeconds();
7648 
7649         long currentTime = System.currentTimeMillis();
7650         long baseEventTime = currentTime - DAYS.toMillis(3);
7651         long expiryTime = baseEventTime + DAYS.toMillis(30);
7652 
7653         List<Source> sources =
7654                 Arrays.asList(
7655                         SourceFixture.getMinimalValidSourceBuilder()
7656                                 .setEventId(new UnsignedLong(1L))
7657                                 .setId("source1")
7658                                 .setEventTime(baseEventTime)
7659                                 .setExpiryTime(expiryTime)
7660                                 .build());
7661 
7662         // All trigger times more than 24 hours ago.
7663         List<Trigger> triggers =
7664                 Arrays.asList(
7665                         TriggerFixture.getValidTriggerBuilder()
7666                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7667                                 .setId("trigger1")
7668                                 .setTriggerTime(
7669                                         currentTime - DAYS.toMillis(1) - TimeUnit.HOURS.toMillis(1))
7670                                 .build(),
7671                         TriggerFixture.getValidTriggerBuilder()
7672                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7673                                 .setId("trigger2")
7674                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
7675                                 .build(),
7676                         TriggerFixture.getValidTriggerBuilder()
7677                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7678                                 .setId("trigger3")
7679                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
7680                                 .build());
7681 
7682         EventReport report0 =
7683                 createEventReportForSourceAndTriggerForUninstall(
7684                         "report0", sources.get(0), triggers.get(0));
7685         EventReport report1 =
7686                 createEventReportForSourceAndTriggerForUninstall(
7687                         "report1", sources.get(0), triggers.get(1));
7688 
7689         AggregateReport aggregateReport0 =
7690                 createAggregateReportForSourceAndTrigger(
7691                         "areport0", sources.get(0), triggers.get(2));
7692 
7693         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
7694         sources.forEach(source -> insertSource(source, source.getId()));
7695         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
7696 
7697         mDatastoreManager.runInTransaction(
7698                 (dao) -> {
7699                     dao.insertEventReport(report0);
7700                     dao.insertEventReport(report1);
7701                     dao.insertAggregateReport(aggregateReport0);
7702                 });
7703 
7704         // Execution
7705         mDatastoreManager.runInTransaction(
7706                 dao -> {
7707                     Pair<List<String>, List<String>> actualSources =
7708                             dao.fetchMatchingSourcesUninstall(
7709                                     SourceFixture.ValidSourceParams.REGISTRANT, currentTime);
7710                     // Source is deleted
7711                     Truth.assertThat(actualSources.first.size()).isEqualTo(1);
7712                     Truth.assertThat(actualSources.second.size()).isEqualTo(0);
7713                 });
7714     }
7715 
7716     @Test
fetchMatchingSourcesUninstall_withinReportLifetime_ignoreSources()7717     public void fetchMatchingSourcesUninstall_withinReportLifetime_ignoreSources()
7718             throws JSONException {
7719         // Setup
7720         mFlags = mock(Flags.class);
7721         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
7722         doReturn(true).when(mFlags).getMeasurementEnableMinReportLifespanForUninstall();
7723         doReturn(TimeUnit.DAYS.toSeconds(1))
7724                 .when(mFlags)
7725                 .getMeasurementMinReportLifespanForUninstallSeconds();
7726 
7727         long currentTime = System.currentTimeMillis();
7728         long baseEventTime = currentTime - DAYS.toMillis(3);
7729         long expiryTime = baseEventTime + DAYS.toMillis(30);
7730 
7731         List<Source> sources =
7732                 Arrays.asList(
7733                         SourceFixture.getMinimalValidSourceBuilder()
7734                                 .setEventId(new UnsignedLong(1L))
7735                                 .setId("source1")
7736                                 .setEventTime(baseEventTime)
7737                                 .setExpiryTime(expiryTime)
7738                                 .build());
7739 
7740         // All trigger times more than 24 hours ago except trigger1.
7741         List<Trigger> triggers =
7742                 Arrays.asList(
7743                         TriggerFixture.getValidTriggerBuilder()
7744                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7745                                 .setId("trigger1")
7746                                 .setTriggerTime(currentTime)
7747                                 .build(),
7748                         TriggerFixture.getValidTriggerBuilder()
7749                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7750                                 .setId("trigger2")
7751                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
7752                                 .build(),
7753                         TriggerFixture.getValidTriggerBuilder()
7754                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7755                                 .setId("trigger3")
7756                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
7757                                 .build());
7758 
7759         EventReport report0 =
7760                 createEventReportForSourceAndTriggerForUninstall(
7761                         "report0", sources.get(0), triggers.get(0));
7762         EventReport report1 =
7763                 createEventReportForSourceAndTriggerForUninstall(
7764                         "report1", sources.get(0), triggers.get(1));
7765 
7766         AggregateReport aggregateReport0 =
7767                 createAggregateReportForSourceAndTrigger(
7768                         "areport0", sources.get(0), triggers.get(2));
7769 
7770         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
7771         sources.forEach(source -> insertSource(source, source.getId()));
7772         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
7773 
7774         mDatastoreManager.runInTransaction(
7775                 (dao) -> {
7776                     dao.insertEventReport(report0);
7777                     dao.insertEventReport(report1);
7778                     dao.insertAggregateReport(aggregateReport0);
7779                 });
7780 
7781         // Execution
7782         mDatastoreManager.runInTransaction(
7783                 dao -> {
7784                     Pair<List<String>, List<String>> actualSources =
7785                             dao.fetchMatchingSourcesUninstall(
7786                                     SourceFixture.ValidSourceParams.REGISTRANT, currentTime);
7787                     // Source is ignored
7788                     Truth.assertThat(actualSources.first.size()).isEqualTo(0);
7789                     Truth.assertThat(actualSources.second.size()).isEqualTo(1);
7790                 });
7791     }
7792 
7793     @Test
fetchMatchingSourcesUninstall_deleteAndIgnoreSources()7794     public void fetchMatchingSourcesUninstall_deleteAndIgnoreSources() throws JSONException {
7795         // Setup
7796         mFlags = mock(Flags.class);
7797         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
7798         doReturn(true).when(mFlags).getMeasurementEnableMinReportLifespanForUninstall();
7799         doReturn(TimeUnit.DAYS.toSeconds(1))
7800                 .when(mFlags)
7801                 .getMeasurementMinReportLifespanForUninstallSeconds();
7802 
7803         long currentTime = System.currentTimeMillis();
7804         long baseEventTime = currentTime - DAYS.toMillis(3);
7805         long expiryTime = baseEventTime + DAYS.toMillis(30);
7806 
7807         List<Source> sources =
7808                 Arrays.asList(
7809                         SourceFixture.getMinimalValidSourceBuilder()
7810                                 .setEventId(new UnsignedLong(1L))
7811                                 .setId("source1")
7812                                 .setEventTime(baseEventTime)
7813                                 .setExpiryTime(expiryTime)
7814                                 .build(),
7815                         SourceFixture.getMinimalValidSourceBuilder()
7816                                 .setEventId(new UnsignedLong(1L))
7817                                 .setId("source2")
7818                                 .setEventTime(baseEventTime)
7819                                 .setExpiryTime(expiryTime)
7820                                 .build(),
7821                         SourceFixture.getMinimalValidSourceBuilder()
7822                                 .setEventId(new UnsignedLong(1L))
7823                                 .setId("source3")
7824                                 .setEventTime(baseEventTime)
7825                                 .setExpiryTime(expiryTime)
7826                                 .build());
7827 
7828         // All trigger times more than 24 hours ago except trigger1.
7829         List<Trigger> triggers =
7830                 Arrays.asList(
7831                         TriggerFixture.getValidTriggerBuilder()
7832                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7833                                 .setId("trigger1")
7834                                 .setTriggerTime(currentTime)
7835                                 .build(),
7836                         TriggerFixture.getValidTriggerBuilder()
7837                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7838                                 .setId("trigger2")
7839                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
7840                                 .build(),
7841                         TriggerFixture.getValidTriggerBuilder()
7842                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7843                                 .setId("trigger3")
7844                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
7845                                 .build());
7846 
7847         EventReport report0 =
7848                 createEventReportForSourceAndTriggerForUninstall(
7849                         "report0", sources.get(0), triggers.get(0));
7850         EventReport report1 =
7851                 createEventReportForSourceAndTriggerForUninstall(
7852                         "report1", sources.get(1), triggers.get(1));
7853 
7854         AggregateReport aggregateReport0 =
7855                 createAggregateReportForSourceAndTrigger(
7856                         "areport0", sources.get(2), triggers.get(2));
7857 
7858         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
7859         sources.forEach(source -> insertSource(source, source.getId()));
7860         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
7861 
7862         mDatastoreManager.runInTransaction(
7863                 (dao) -> {
7864                     dao.insertEventReport(report0);
7865                     dao.insertEventReport(report1);
7866                     dao.insertAggregateReport(aggregateReport0);
7867                 });
7868 
7869         // Execution
7870         mDatastoreManager.runInTransaction(
7871                 dao -> {
7872                     Pair<List<String>, List<String>> actualSources =
7873                             dao.fetchMatchingSourcesUninstall(
7874                                     SourceFixture.ValidSourceParams.REGISTRANT, currentTime);
7875                     // Source is ignored
7876                     Truth.assertThat(actualSources.first.size()).isEqualTo(2);
7877                     Truth.assertThat(actualSources.second.size()).isEqualTo(1);
7878                 });
7879     }
7880 
7881     @Test
fetchMatchingTriggersUninstall_outsideReportLifetime_deleteTriggers()7882     public void fetchMatchingTriggersUninstall_outsideReportLifetime_deleteTriggers()
7883             throws JSONException {
7884         // Setup
7885         mFlags = mock(Flags.class);
7886         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
7887         doReturn(true).when(mFlags).getMeasurementEnableMinReportLifespanForUninstall();
7888         doReturn(TimeUnit.DAYS.toSeconds(1))
7889                 .when(mFlags)
7890                 .getMeasurementMinReportLifespanForUninstallSeconds();
7891 
7892         long currentTime = System.currentTimeMillis();
7893         long baseEventTime = currentTime - DAYS.toMillis(3);
7894         long expiryTime = baseEventTime + DAYS.toMillis(30);
7895 
7896         List<Source> sources =
7897                 Arrays.asList(
7898                         SourceFixture.getMinimalValidSourceBuilder()
7899                                 .setEventId(new UnsignedLong(1L))
7900                                 .setId("source1")
7901                                 .setEventTime(baseEventTime)
7902                                 .setExpiryTime(expiryTime)
7903                                 .build());
7904 
7905         List<Trigger> triggers =
7906                 Arrays.asList(
7907                         TriggerFixture.getValidTriggerBuilder()
7908                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7909                                 .setId("trigger1")
7910                                 .setTriggerTime(
7911                                         currentTime - DAYS.toMillis(1) - TimeUnit.HOURS.toMillis(1))
7912                                 .build(),
7913                         TriggerFixture.getValidTriggerBuilder()
7914                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7915                                 .setId("trigger2")
7916                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
7917                                 .build(),
7918                         TriggerFixture.getValidTriggerBuilder()
7919                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7920                                 .setId("trigger3")
7921                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
7922                                 .build());
7923 
7924         EventReport report0 =
7925                 createEventReportForSourceAndTriggerForUninstall(
7926                         "report0", sources.get(0), triggers.get(0));
7927         EventReport report1 =
7928                 createEventReportForSourceAndTriggerForUninstall(
7929                         "report1", sources.get(0), triggers.get(1));
7930 
7931         AggregateReport aggregateReport0 =
7932                 createAggregateReportForSourceAndTrigger(
7933                         "areport0", sources.get(0), triggers.get(2));
7934 
7935         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
7936         sources.forEach(source -> insertSource(source, source.getId()));
7937         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
7938 
7939         mDatastoreManager.runInTransaction(
7940                 (dao) -> {
7941                     dao.insertEventReport(report0);
7942                     dao.insertEventReport(report1);
7943                     dao.insertAggregateReport(aggregateReport0);
7944                 });
7945 
7946         // Execution
7947         mDatastoreManager.runInTransaction(
7948                 dao -> {
7949                     Pair<List<String>, List<String>> actualTriggers =
7950                             dao.fetchMatchingTriggersUninstall(
7951                                     TriggerFixture.ValidTriggerParams.REGISTRANT, currentTime);
7952                     // Triggers are deleted
7953                     Truth.assertThat(actualTriggers.first.size()).isEqualTo(3);
7954                     Truth.assertThat(actualTriggers.second.size()).isEqualTo(0);
7955                 });
7956     }
7957 
7958     @Test
fetchMatchingTriggersUninstall_withinReportLifetime_ignoreTriggers()7959     public void fetchMatchingTriggersUninstall_withinReportLifetime_ignoreTriggers()
7960             throws JSONException {
7961         // Setup
7962         mFlags = mock(Flags.class);
7963         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
7964         doReturn(true).when(mFlags).getMeasurementEnableMinReportLifespanForUninstall();
7965         doReturn(TimeUnit.DAYS.toSeconds(1))
7966                 .when(mFlags)
7967                 .getMeasurementMinReportLifespanForUninstallSeconds();
7968 
7969         long currentTime = System.currentTimeMillis();
7970         long baseEventTime = currentTime - DAYS.toMillis(3);
7971         long expiryTime = baseEventTime + DAYS.toMillis(30);
7972 
7973         List<Source> sources =
7974                 Arrays.asList(
7975                         SourceFixture.getMinimalValidSourceBuilder()
7976                                 .setEventId(new UnsignedLong(1L))
7977                                 .setId("source1")
7978                                 .setEventTime(baseEventTime)
7979                                 .setExpiryTime(expiryTime)
7980                                 .build());
7981 
7982         List<Trigger> triggers =
7983                 Arrays.asList(
7984                         TriggerFixture.getValidTriggerBuilder()
7985                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7986                                 .setId("trigger1")
7987                                 .setTriggerTime(currentTime - TimeUnit.HOURS.toMillis(23))
7988                                 .build(),
7989                         TriggerFixture.getValidTriggerBuilder()
7990                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7991                                 .setId("trigger2")
7992                                 .setTriggerTime(currentTime - TimeUnit.HOURS.toMillis(1))
7993                                 .build(),
7994                         TriggerFixture.getValidTriggerBuilder()
7995                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
7996                                 .setId("trigger3")
7997                                 .setTriggerTime(currentTime - TimeUnit.HOURS.toMillis(12))
7998                                 .build());
7999 
8000         EventReport report0 =
8001                 createEventReportForSourceAndTriggerForUninstall(
8002                         "report0", sources.get(0), triggers.get(0));
8003         EventReport report1 =
8004                 createEventReportForSourceAndTriggerForUninstall(
8005                         "report1", sources.get(0), triggers.get(1));
8006 
8007         AggregateReport aggregateReport0 =
8008                 createAggregateReportForSourceAndTrigger(
8009                         "areport0", sources.get(0), triggers.get(2));
8010 
8011         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8012         sources.forEach(source -> insertSource(source, source.getId()));
8013         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8014 
8015         mDatastoreManager.runInTransaction(
8016                 (dao) -> {
8017                     dao.insertEventReport(report0);
8018                     dao.insertEventReport(report1);
8019                     dao.insertAggregateReport(aggregateReport0);
8020                 });
8021 
8022         // Execution
8023         mDatastoreManager.runInTransaction(
8024                 dao -> {
8025                     Pair<List<String>, List<String>> actualTriggers =
8026                             dao.fetchMatchingTriggersUninstall(
8027                                     TriggerFixture.ValidTriggerParams.REGISTRANT, currentTime);
8028                     // Triggers are ignored
8029                     Truth.assertThat(actualTriggers.first.size()).isEqualTo(0);
8030                     Truth.assertThat(actualTriggers.second.size()).isEqualTo(3);
8031                 });
8032     }
8033 
8034     @Test
fetchMatchingTriggersUninstall_deleteAndIgnoreTriggers()8035     public void fetchMatchingTriggersUninstall_deleteAndIgnoreTriggers() throws JSONException {
8036         // Setup
8037         mFlags = mock(Flags.class);
8038         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
8039         doReturn(true).when(mFlags).getMeasurementEnableMinReportLifespanForUninstall();
8040         doReturn(TimeUnit.DAYS.toSeconds(1))
8041                 .when(mFlags)
8042                 .getMeasurementMinReportLifespanForUninstallSeconds();
8043 
8044         long currentTime = System.currentTimeMillis();
8045         long baseEventTime = currentTime - DAYS.toMillis(3);
8046         long expiryTime = baseEventTime + DAYS.toMillis(30);
8047 
8048         List<Source> sources =
8049                 Arrays.asList(
8050                         SourceFixture.getMinimalValidSourceBuilder()
8051                                 .setEventId(new UnsignedLong(1L))
8052                                 .setId("source1")
8053                                 .setEventTime(baseEventTime)
8054                                 .setExpiryTime(expiryTime)
8055                                 .build(),
8056                         SourceFixture.getMinimalValidSourceBuilder()
8057                                 .setEventId(new UnsignedLong(1L))
8058                                 .setId("source2")
8059                                 .setEventTime(baseEventTime)
8060                                 .setExpiryTime(expiryTime)
8061                                 .build(),
8062                         SourceFixture.getMinimalValidSourceBuilder()
8063                                 .setEventId(new UnsignedLong(1L))
8064                                 .setId("source3")
8065                                 .setEventTime(baseEventTime)
8066                                 .setExpiryTime(expiryTime)
8067                                 .build());
8068 
8069         List<Trigger> triggers =
8070                 Arrays.asList(
8071                         TriggerFixture.getValidTriggerBuilder()
8072                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8073                                 .setId("trigger1")
8074                                 .setTriggerTime(currentTime)
8075                                 .build(),
8076                         TriggerFixture.getValidTriggerBuilder()
8077                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8078                                 .setId("trigger2")
8079                                 .setTriggerTime(currentTime - DAYS.toMillis(2))
8080                                 .build(),
8081                         TriggerFixture.getValidTriggerBuilder()
8082                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
8083                                 .setId("trigger3")
8084                                 .setTriggerTime(currentTime - DAYS.toMillis(3))
8085                                 .build());
8086 
8087         EventReport report0 =
8088                 createEventReportForSourceAndTriggerForUninstall(
8089                         "report0", sources.get(0), triggers.get(0));
8090         EventReport report1 =
8091                 createEventReportForSourceAndTriggerForUninstall(
8092                         "report1", sources.get(1), triggers.get(1));
8093 
8094         AggregateReport aggregateReport0 =
8095                 createAggregateReportForSourceAndTrigger(
8096                         "areport0", sources.get(2), triggers.get(2));
8097 
8098         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8099         sources.forEach(source -> insertSource(source, source.getId()));
8100         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
8101 
8102         mDatastoreManager.runInTransaction(
8103                 (dao) -> {
8104                     dao.insertEventReport(report0);
8105                     dao.insertEventReport(report1);
8106                     dao.insertAggregateReport(aggregateReport0);
8107                 });
8108 
8109         // Execution
8110         mDatastoreManager.runInTransaction(
8111                 dao -> {
8112                     Pair<List<String>, List<String>> actualTriggers =
8113                             dao.fetchMatchingTriggersUninstall(
8114                                     TriggerFixture.ValidTriggerParams.REGISTRANT, currentTime);
8115                     // Triggers are deleted
8116                     Truth.assertThat(actualTriggers.first.size()).isEqualTo(2);
8117                     Truth.assertThat(actualTriggers.second.size()).isEqualTo(1);
8118                 });
8119     }
8120 
8121     @Test
deletePendingFakeEventReportsForSources_success()8122     public void deletePendingFakeEventReportsForSources_success() {
8123         // Setup
8124         long baseTime = SOURCE_EVENT_TIME;
8125         // Sources
8126         insertSource(SourceFixture.getValidSource(), "S1");
8127         insertSource(SourceFixture.getValidSource(), "S2");
8128         insertSource(SourceFixture.getValidSource(), "S3");
8129         insertSource(SourceFixture.getValidSource(), "S4");
8130 
8131         mDatastoreManager.runInTransaction(
8132                 (dao) -> {
8133                     // Event reports
8134                     // Should get deleted
8135                     EventReport eventReport1 =
8136                             EventReportFixture.getBaseEventReportBuild()
8137                                     .setId("Event1")
8138                                     .setSourceId("S1")
8139                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8140                                     // trigger time > source event time => fake report
8141                                     .setTriggerTime(baseTime + TimeUnit.HOURS.toMillis(1) + 1L)
8142                                     .setStatus(EventReport.Status.PENDING)
8143                                     .setTriggerId(null)
8144                                     .build();
8145                     dao.insertEventReport(eventReport1);
8146                     dao.insertAttribution(
8147                             createAttribution(
8148                                     "Att1",
8149                                     Attribution.Scope.EVENT,
8150                                     "S1",
8151                                     null,
8152                                     eventReport1.getId()));
8153 
8154                     // Should not get deleted because S2 is not provided
8155                     EventReport eventReport2 =
8156                             EventReportFixture.getBaseEventReportBuild()
8157                                     .setId("Event2")
8158                                     .setSourceId("S2")
8159                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8160                                     // trigger time > source event time => fake report
8161                                     .setTriggerTime(
8162                                             baseTime
8163                                                     + TimeUnit.HOURS.toMillis(1)
8164                                                     + TimeUnit.MINUTES.toMillis(1))
8165                                     .setStatus(EventReport.Status.PENDING)
8166                                     .setTriggerId(null)
8167                                     .build();
8168                     dao.insertEventReport(eventReport2);
8169                     dao.insertAttribution(
8170                             createAttribution(
8171                                     "Att2",
8172                                     Attribution.Scope.EVENT,
8173                                     "S2",
8174                                     null,
8175                                     eventReport2.getId()));
8176 
8177                     // Should not get deleted because it's a real report
8178                     EventReport eventReport3 =
8179                             EventReportFixture.getBaseEventReportBuild()
8180                                     .setId("Event3")
8181                                     .setSourceId("S1")
8182                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8183                                     // trigger time < source event time => real report
8184                                     .setTriggerTime(baseTime - 1L)
8185                                     .setStatus(EventReport.Status.PENDING)
8186                                     .setTriggerId(null)
8187                                     .build();
8188                     dao.insertEventReport(eventReport3);
8189                     dao.insertAttribution(
8190                             createAttribution(
8191                                     "Att3",
8192                                     Attribution.Scope.EVENT,
8193                                     "S1",
8194                                     null,
8195                                     eventReport3.getId()));
8196 
8197                     // Infeasible case, but it should not get deleted because its status is
8198                     // DELIVERED
8199                     EventReport eventReport4 =
8200                             EventReportFixture.getBaseEventReportBuild()
8201                                     .setId("Event4")
8202                                     .setSourceId("S3")
8203                                     .setReportTime(baseTime + TimeUnit.HOURS.toMillis(1))
8204                                     // trigger time > source event time => fake report
8205                                     .setTriggerTime(
8206                                             baseTime
8207                                                     + TimeUnit.HOURS.toMillis(1)
8208                                                     + TimeUnit.SECONDS.toMillis(1))
8209                                     .setStatus(EventReport.Status.DELIVERED)
8210                                     .setTriggerId(null)
8211                                     .build();
8212                     dao.insertEventReport(eventReport4);
8213                     dao.insertAttribution(
8214                             createAttribution(
8215                                     "Att4",
8216                                     Attribution.Scope.EVENT,
8217                                     "S3",
8218                                     null,
8219                                     eventReport4.getId()));
8220 
8221                     // Execution
8222                     dao.deleteFutureFakeEventReportsForSources(List.of("S1", "S3"), baseTime);
8223 
8224                     // Assertion
8225                     assertThrows(DatastoreException.class, () -> dao.getEventReport("Event1"));
8226                     assertEquals(eventReport2, dao.getEventReport("Event2"));
8227                     assertEquals(eventReport3, dao.getEventReport("Event3"));
8228                     assertEquals(eventReport4, dao.getEventReport("Event4"));
8229                 });
8230 
8231         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
8232         assertEquals(4, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
8233         Set<String> reportIds = new HashSet<>();
8234         try (Cursor cursor =
8235                 db.rawQuery(
8236                         "SELECT "
8237                                 + AttributionContract.REPORT_ID
8238                                 + " FROM "
8239                                 + AttributionContract.TABLE,
8240                         null)) {
8241             while (cursor.moveToNext()) {
8242                 reportIds.add(cursor.getString(0));
8243             }
8244         }
8245         assertEquals(Set.of("Event1", "Event2", "Event3", "Event4"), reportIds);
8246     }
8247 
getSourceWithDifferentDestinations( int numDestinations, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus)8248     private static Source getSourceWithDifferentDestinations(
8249             int numDestinations,
8250             boolean hasAppDestinations,
8251             boolean hasWebDestinations,
8252             long eventTime,
8253             Uri publisher,
8254             String enrollmentId,
8255             @Source.Status int sourceStatus) {
8256         List<Uri> appDestinations = null;
8257         List<Uri> webDestinations = null;
8258         if (hasAppDestinations) {
8259             appDestinations = new ArrayList<>();
8260             appDestinations.add(Uri.parse("android-app://com.app-destination"));
8261         }
8262         if (hasWebDestinations) {
8263             webDestinations = new ArrayList<>();
8264             for (int i = 0; i < numDestinations; i++) {
8265                 webDestinations.add(
8266                         Uri.parse("https://web-destination-" + String.valueOf(i) + ".com"));
8267             }
8268         }
8269         long expiryTime =
8270                 eventTime
8271                         + TimeUnit.SECONDS.toMillis(
8272                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8273         return new Source.Builder()
8274                 .setEventId(new UnsignedLong(0L))
8275                 .setEventTime(eventTime)
8276                 .setExpiryTime(expiryTime)
8277                 .setPublisher(publisher)
8278                 .setAppDestinations(appDestinations)
8279                 .setWebDestinations(webDestinations)
8280                 .setEnrollmentId(enrollmentId)
8281                 .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
8282                 .setStatus(sourceStatus)
8283                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
8284                 .build();
8285     }
8286 
getSourcesWithDifferentDestinations( int numSources, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus, Uri registrationOrigin)8287     private static List<Source> getSourcesWithDifferentDestinations(
8288             int numSources,
8289             boolean hasAppDestinations,
8290             boolean hasWebDestinations,
8291             long eventTime,
8292             Uri publisher,
8293             String enrollmentId,
8294             @Source.Status int sourceStatus,
8295             Uri registrationOrigin) {
8296         long expiryTime =
8297                 eventTime
8298                         + TimeUnit.SECONDS.toMillis(
8299                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8300         return getSourcesWithDifferentDestinations(
8301                 numSources,
8302                 hasAppDestinations,
8303                 hasWebDestinations,
8304                 eventTime,
8305                 expiryTime,
8306                 publisher,
8307                 enrollmentId,
8308                 sourceStatus,
8309                 registrationOrigin);
8310     }
8311 
getSourcesWithDifferentDestinations( int numSources, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus)8312     private static List<Source> getSourcesWithDifferentDestinations(
8313             int numSources,
8314             boolean hasAppDestinations,
8315             boolean hasWebDestinations,
8316             long eventTime,
8317             Uri publisher,
8318             String enrollmentId,
8319             @Source.Status int sourceStatus) {
8320         long expiryTime =
8321                 eventTime
8322                         + TimeUnit.SECONDS.toMillis(
8323                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8324         return getSourcesWithDifferentDestinations(
8325                 numSources,
8326                 hasAppDestinations,
8327                 hasWebDestinations,
8328                 eventTime,
8329                 expiryTime,
8330                 publisher,
8331                 enrollmentId,
8332                 sourceStatus,
8333                 REGISTRATION_ORIGIN);
8334     }
8335 
getSourcesWithDifferentDestinations( int numSources, boolean hasAppDestinations, boolean hasWebDestinations, long eventTime, long expiryTime, Uri publisher, String enrollmentId, @Source.Status int sourceStatus, Uri registrationOrigin)8336     private static List<Source> getSourcesWithDifferentDestinations(
8337             int numSources,
8338             boolean hasAppDestinations,
8339             boolean hasWebDestinations,
8340             long eventTime,
8341             long expiryTime,
8342             Uri publisher,
8343             String enrollmentId,
8344             @Source.Status int sourceStatus,
8345             Uri registrationOrigin) {
8346         List<Source> sources = new ArrayList<>();
8347         for (int i = 0; i < numSources; i++) {
8348             Source.Builder sourceBuilder =
8349                     new Source.Builder()
8350                             .setEventId(new UnsignedLong(0L))
8351                             .setEventTime(eventTime)
8352                             .setExpiryTime(expiryTime)
8353                             .setPublisher(publisher)
8354                             .setEnrollmentId(enrollmentId)
8355                             .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
8356                             .setStatus(sourceStatus)
8357                             .setRegistrationOrigin(registrationOrigin);
8358             if (hasAppDestinations) {
8359                 sourceBuilder.setAppDestinations(
8360                         List.of(Uri.parse("android-app://app-destination-" + String.valueOf(i))));
8361             }
8362             if (hasWebDestinations) {
8363                 sourceBuilder.setWebDestinations(
8364                         List.of(
8365                                 Uri.parse(
8366                                         "https://web-destination-" + String.valueOf(i) + ".com")));
8367             }
8368             sources.add(sourceBuilder.build());
8369         }
8370         return sources;
8371     }
8372 
getSourcesWithDifferentRegistrationOrigins( int numSources, List<Uri> appDestinations, List<Uri> webDestinations, long eventTime, Uri publisher, @Source.Status int sourceStatus)8373     private static List<Source> getSourcesWithDifferentRegistrationOrigins(
8374             int numSources,
8375             List<Uri> appDestinations,
8376             List<Uri> webDestinations,
8377             long eventTime,
8378             Uri publisher,
8379             @Source.Status int sourceStatus) {
8380         long expiryTime =
8381                 eventTime
8382                         + TimeUnit.SECONDS.toMillis(
8383                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
8384         return getSourcesWithDifferentRegistrationOrigins(
8385                 numSources,
8386                 appDestinations,
8387                 webDestinations,
8388                 eventTime,
8389                 expiryTime,
8390                 publisher,
8391                 sourceStatus);
8392     }
8393 
getSourcesWithDifferentRegistrationOrigins( int numSources, List<Uri> appDestinations, List<Uri> webDestinations, long eventTime, long expiryTime, Uri publisher, @Source.Status int sourceStatus)8394     private static List<Source> getSourcesWithDifferentRegistrationOrigins(
8395             int numSources,
8396             List<Uri> appDestinations,
8397             List<Uri> webDestinations,
8398             long eventTime,
8399             long expiryTime,
8400             Uri publisher,
8401             @Source.Status int sourceStatus) {
8402         List<Source> sources = new ArrayList<>();
8403         for (int i = 0; i < numSources; i++) {
8404             Source.Builder sourceBuilder =
8405                     new Source.Builder()
8406                             .setEventId(new UnsignedLong(0L))
8407                             .setEventTime(eventTime)
8408                             .setExpiryTime(expiryTime)
8409                             .setPublisher(publisher)
8410                             .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
8411                             .setStatus(sourceStatus)
8412                             .setAppDestinations(getNullableUriList(appDestinations))
8413                             .setWebDestinations(getNullableUriList(webDestinations))
8414                             .setEnrollmentId("enrollment-id")
8415                             .setRegistrationOrigin(
8416                                     WebUtil.validUri("https://subdomain" + i + ".example.test"));
8417             sources.add(sourceBuilder.build());
8418         }
8419         return sources;
8420     }
8421 
getAttributionsWithDifferentReportingOrigins( int numAttributions, Uri destinationSite, long triggerTime, Uri sourceSite, String registrant)8422     private static List<Attribution> getAttributionsWithDifferentReportingOrigins(
8423             int numAttributions,
8424             Uri destinationSite,
8425             long triggerTime,
8426             Uri sourceSite,
8427             String registrant) {
8428         List<Attribution> attributions = new ArrayList<>();
8429         for (int i = 0; i < numAttributions; i++) {
8430             Attribution.Builder attributionBuilder =
8431                     new Attribution.Builder()
8432                             .setTriggerTime(triggerTime)
8433                             .setSourceSite(sourceSite.toString())
8434                             .setSourceOrigin(sourceSite.toString())
8435                             .setDestinationSite(destinationSite.toString())
8436                             .setDestinationOrigin(destinationSite.toString())
8437                             .setEnrollmentId("enrollment-id")
8438                             .setRegistrationOrigin(
8439                                     WebUtil.validUri("https://subdomain" + i + ".example.test"))
8440                             .setRegistrant(registrant);
8441             attributions.add(attributionBuilder.build());
8442         }
8443         return attributions;
8444     }
8445 
insertAttribution(Attribution attribution)8446     private static void insertAttribution(Attribution attribution) {
8447         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8448         ContentValues values = new ContentValues();
8449         values.put(AttributionContract.ID, UUID.randomUUID().toString());
8450         values.put(AttributionContract.SOURCE_SITE, attribution.getSourceSite());
8451         values.put(AttributionContract.DESTINATION_SITE, attribution.getDestinationSite());
8452         values.put(AttributionContract.ENROLLMENT_ID, attribution.getEnrollmentId());
8453         values.put(AttributionContract.TRIGGER_TIME, attribution.getTriggerTime());
8454         values.put(AttributionContract.SOURCE_ID, attribution.getSourceId());
8455         values.put(AttributionContract.TRIGGER_ID, attribution.getTriggerId());
8456         values.put(
8457                 AttributionContract.REGISTRATION_ORIGIN,
8458                 attribution.getRegistrationOrigin().toString());
8459         long row = db.insert("msmt_attribution", null, values);
8460         assertNotEquals("Attribution insertion failed", -1, row);
8461     }
8462 
createAttribution( String attributionId, String sourceId, String triggerId)8463     private static Attribution createAttribution(
8464             String attributionId,
8465             String sourceId,
8466             String triggerId) {
8467         return createAttribution(attributionId, Attribution.Scope.EVENT, sourceId, triggerId);
8468     }
8469 
createAttribution( String attributionId, @Attribution.Scope int scope, String sourceId, String triggerId)8470     private static Attribution createAttribution(
8471             String attributionId,
8472             @Attribution.Scope int scope,
8473             String sourceId,
8474             String triggerId) {
8475         return createAttribution(attributionId, scope, sourceId, triggerId, null);
8476     }
8477 
createAttribution( String attributionId, @Attribution.Scope int scope, String sourceId, String triggerId, String reportId)8478     private static Attribution createAttribution(
8479             String attributionId,
8480             @Attribution.Scope int scope,
8481             String sourceId,
8482             String triggerId,
8483             String reportId) {
8484         return new Attribution.Builder()
8485                 .setId(attributionId)
8486                 .setScope(scope)
8487                 .setTriggerTime(0L)
8488                 .setSourceSite("android-app://source.app")
8489                 .setSourceOrigin("android-app://source.app")
8490                 .setDestinationSite("android-app://destination.app")
8491                 .setDestinationOrigin("android-app://destination.app")
8492                 .setEnrollmentId("enrollment-id-")
8493                 .setRegistrant("android-app://registrant.app")
8494                 .setSourceId(sourceId)
8495                 .setTriggerId(triggerId)
8496                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
8497                 .setReportId(reportId)
8498                 .build();
8499     }
8500 
insertSource(Source source)8501     private static void insertSource(Source source) {
8502         insertSource(source, UUID.randomUUID().toString());
8503     }
8504 
8505     // This is needed because MeasurementDao::insertSource inserts a default value for status.
insertSource(Source source, String sourceId)8506     private static void insertSource(Source source, String sourceId) {
8507         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8508         ContentValues values = new ContentValues();
8509         values.put(SourceContract.ID, sourceId);
8510         if (source.getEventId() != null) {
8511             values.put(SourceContract.EVENT_ID, source.getEventId().getValue());
8512         }
8513         values.put(SourceContract.PUBLISHER, source.getPublisher().toString());
8514         values.put(SourceContract.PUBLISHER_TYPE, source.getPublisherType());
8515         values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId());
8516         values.put(SourceContract.EVENT_TIME, source.getEventTime());
8517         values.put(SourceContract.EXPIRY_TIME, source.getExpiryTime());
8518         values.put(SourceContract.PRIORITY, source.getPriority());
8519         values.put(SourceContract.STATUS, source.getStatus());
8520         values.put(SourceContract.SOURCE_TYPE, source.getSourceType().toString());
8521         values.put(SourceContract.REGISTRANT, source.getRegistrant().toString());
8522         values.put(SourceContract.INSTALL_ATTRIBUTION_WINDOW, source.getInstallAttributionWindow());
8523         values.put(SourceContract.INSTALL_COOLDOWN_WINDOW, source.getInstallCooldownWindow());
8524         values.put(SourceContract.ATTRIBUTION_MODE, source.getAttributionMode());
8525         values.put(SourceContract.AGGREGATE_SOURCE, source.getAggregateSource());
8526         values.put(SourceContract.FILTER_DATA, source.getFilterDataString());
8527         values.put(SourceContract.SHARED_FILTER_DATA_KEYS, source.getSharedFilterDataKeys());
8528         values.put(SourceContract.AGGREGATE_CONTRIBUTIONS, source.getAggregateContributions());
8529         values.put(SourceContract.DEBUG_REPORTING, source.isDebugReporting());
8530         values.put(SourceContract.INSTALL_TIME, source.getInstallTime());
8531         values.put(SourceContract.REGISTRATION_ID, source.getRegistrationId());
8532         values.put(SourceContract.SHARED_AGGREGATION_KEYS, source.getSharedAggregationKeys());
8533         values.put(SourceContract.REGISTRATION_ORIGIN, source.getRegistrationOrigin().toString());
8534         values.put(SourceContract.DESTINATION_LIMIT_PRIORITY, source.getDestinationLimitPriority());
8535         values.put(SourceContract.IS_INSTALL_ATTRIBUTED, source.isInstallAttributed());
8536         values.put(
8537                 SourceContract.AGGREGATE_DEBUG_REPORT_CONTRIBUTIONS,
8538                 source.getAggregateDebugReportContributions());
8539         values.put(
8540                 SourceContract.REINSTALL_REATTRIBUTION_WINDOW,
8541                 source.getReinstallReattributionWindow());
8542         long row = db.insert(SourceContract.TABLE, null, values);
8543         assertNotEquals("Source insertion failed", -1, row);
8544 
8545         maybeInsertSourceDestinations(db, source, sourceId);
8546     }
8547 
getNullableUriString(List<Uri> uriList)8548     private static String getNullableUriString(List<Uri> uriList) {
8549         return Optional.ofNullable(uriList).map(uris -> uris.get(0).toString()).orElse(null);
8550     }
8551 
8552     /** Test that the AsyncRegistration is inserted correctly. */
8553     @Test
testInsertAsyncRegistration()8554     public void testInsertAsyncRegistration() {
8555         AsyncRegistration validAsyncRegistration =
8556                 AsyncRegistrationFixture.getValidAsyncRegistration();
8557         String validAsyncRegistrationId = validAsyncRegistration.getId();
8558 
8559         mDatastoreManager.runInTransaction(
8560                 (dao) -> dao.insertAsyncRegistration(validAsyncRegistration));
8561 
8562         try (Cursor cursor =
8563                 MeasurementDbHelper.getInstance()
8564                         .getReadableDatabase()
8565                         .query(
8566                                 AsyncRegistrationContract.TABLE,
8567                                 null,
8568                                 AsyncRegistrationContract.ID + " = ? ",
8569                                 new String[] {validAsyncRegistrationId},
8570                                 null,
8571                                 null,
8572                                 null)) {
8573 
8574             assertTrue(cursor.moveToNext());
8575             AsyncRegistration asyncRegistration =
8576                     SqliteObjectMapper.constructAsyncRegistration(cursor);
8577             assertNotNull(asyncRegistration);
8578             assertNotNull(asyncRegistration.getId());
8579             assertEquals(asyncRegistration.getId(), validAsyncRegistration.getId());
8580             assertNotNull(asyncRegistration.getRegistrationUri());
8581             assertNotNull(asyncRegistration.getTopOrigin());
8582             assertEquals(asyncRegistration.getTopOrigin(), validAsyncRegistration.getTopOrigin());
8583             assertNotNull(asyncRegistration.getRegistrant());
8584             assertEquals(asyncRegistration.getRegistrant(), validAsyncRegistration.getRegistrant());
8585             assertNotNull(asyncRegistration.getSourceType());
8586             assertEquals(asyncRegistration.getSourceType(), validAsyncRegistration.getSourceType());
8587             assertEquals(
8588                     asyncRegistration.getDebugKeyAllowed(),
8589                     validAsyncRegistration.getDebugKeyAllowed());
8590             assertEquals(asyncRegistration.getRetryCount(), validAsyncRegistration.getRetryCount());
8591             assertEquals(
8592                     asyncRegistration.getRequestTime(), validAsyncRegistration.getRequestTime());
8593             assertNotNull(asyncRegistration.getOsDestination());
8594             assertEquals(
8595                     asyncRegistration.getOsDestination(),
8596                     validAsyncRegistration.getOsDestination());
8597             assertNotNull(asyncRegistration.getRegistrationUri());
8598             assertEquals(
8599                     asyncRegistration.getRegistrationUri(),
8600                     validAsyncRegistration.getRegistrationUri());
8601             assertEquals(
8602                     asyncRegistration.getDebugKeyAllowed(),
8603                     validAsyncRegistration.getDebugKeyAllowed());
8604             assertEquals(
8605                     asyncRegistration.getPlatformAdId(), validAsyncRegistration.getPlatformAdId());
8606             assertEquals(asyncRegistration.getPostBody(), validAsyncRegistration.getPostBody());
8607             assertEquals(
8608                     asyncRegistration.getRedirectBehavior(),
8609                     validAsyncRegistration.getRedirectBehavior());
8610         }
8611     }
8612 
8613     /** Test that records in AsyncRegistration queue are fetched properly. */
8614     @Test
testFetchNextQueuedAsyncRegistration_validRetryLimit()8615     public void testFetchNextQueuedAsyncRegistration_validRetryLimit() {
8616         AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
8617         String asyncRegistrationId = asyncRegistration.getId();
8618 
8619         mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
8620         mDatastoreManager.runInTransaction(
8621                 (dao) -> {
8622                     AsyncRegistration fetchedAsyncRegistration =
8623                             dao.fetchNextQueuedAsyncRegistration((short) 1, new HashSet<>());
8624                     assertNotNull(fetchedAsyncRegistration);
8625                     assertEquals(fetchedAsyncRegistration.getId(), asyncRegistrationId);
8626                     fetchedAsyncRegistration.incrementRetryCount();
8627                     dao.updateRetryCount(fetchedAsyncRegistration);
8628                 });
8629 
8630         mDatastoreManager.runInTransaction(
8631                 (dao) -> {
8632                     AsyncRegistration fetchedAsyncRegistration =
8633                             dao.fetchNextQueuedAsyncRegistration((short) 1, new HashSet<>());
8634                     assertNull(fetchedAsyncRegistration);
8635                 });
8636     }
8637 
8638     /** Test that records in AsyncRegistration queue are fetched properly. */
8639     @Test
testFetchNextQueuedAsyncRegistration_excludeByOrigin()8640     public void testFetchNextQueuedAsyncRegistration_excludeByOrigin() {
8641         Uri origin1 = Uri.parse("https://adtech1.test");
8642         Uri origin2 = Uri.parse("https://adtech2.test");
8643         Uri regUri1 = origin1.buildUpon().appendPath("/hello").build();
8644         Uri regUri2 = origin2;
8645         AsyncRegistration asyncRegistration1 =
8646                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
8647                         .setRegistrationUri(regUri1)
8648                         .build();
8649         AsyncRegistration asyncRegistration2 =
8650                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
8651                         .setRegistrationUri(regUri2)
8652                         .build();
8653 
8654         mDatastoreManager.runInTransaction(
8655                 (dao) -> {
8656                     dao.insertAsyncRegistration(asyncRegistration1);
8657                     dao.insertAsyncRegistration(asyncRegistration2);
8658                 });
8659         // Should fetch none
8660         Set<Uri> excludedOrigins1 = Set.of(origin1, origin2);
8661         Optional<AsyncRegistration> optAsyncRegistration =
8662                 mDatastoreManager.runInTransactionWithResult(
8663                         (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins1));
8664         assertTrue(optAsyncRegistration.isEmpty());
8665 
8666         // Should fetch only origin1
8667         Set<Uri> excludedOrigins2 = Set.of(origin2);
8668         optAsyncRegistration =
8669                 mDatastoreManager.runInTransactionWithResult(
8670                         (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins2));
8671         assertTrue(optAsyncRegistration.isPresent());
8672         assertEquals(regUri1, optAsyncRegistration.get().getRegistrationUri());
8673 
8674         // Should fetch only origin2
8675         Set<Uri> excludedOrigins3 = Set.of(origin1);
8676         optAsyncRegistration =
8677                 mDatastoreManager.runInTransactionWithResult(
8678                         (dao) -> dao.fetchNextQueuedAsyncRegistration((short) 4, excludedOrigins3));
8679         assertTrue(optAsyncRegistration.isPresent());
8680         assertEquals(regUri2, optAsyncRegistration.get().getRegistrationUri());
8681     }
8682 
8683     /** Test that AsyncRegistration is deleted correctly. */
8684     @Test
testDeleteAsyncRegistration()8685     public void testDeleteAsyncRegistration() {
8686         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8687         AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
8688         String asyncRegistrationID = asyncRegistration.getId();
8689 
8690         mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
8691         try (Cursor cursor =
8692                 MeasurementDbHelper.getInstance()
8693                         .getReadableDatabase()
8694                         .query(
8695                                 AsyncRegistrationContract.TABLE,
8696                                 null,
8697                                 AsyncRegistrationContract.ID + " = ? ",
8698                                 new String[] {asyncRegistration.getId().toString()},
8699                                 null,
8700                                 null,
8701                                 null)) {
8702             assertTrue(cursor.moveToNext());
8703             AsyncRegistration updateAsyncRegistration =
8704                     SqliteObjectMapper.constructAsyncRegistration(cursor);
8705             assertNotNull(updateAsyncRegistration);
8706         }
8707         mDatastoreManager.runInTransaction(
8708                 (dao) -> dao.deleteAsyncRegistration(asyncRegistration.getId()));
8709 
8710         db.query(
8711                 /* table */ AsyncRegistrationContract.TABLE,
8712                 /* columns */ null,
8713                 /* selection */ AsyncRegistrationContract.ID + " = ? ",
8714                 /* selectionArgs */ new String[] {asyncRegistrationID.toString()},
8715                 /* groupBy */ null,
8716                 /* having */ null,
8717                 /* orderedBy */ null);
8718 
8719         assertThat(
8720                         db.query(
8721                                         /* table */ AsyncRegistrationContract.TABLE,
8722                                         /* columns */ null,
8723                                         /* selection */ AsyncRegistrationContract.ID + " = ? ",
8724                                         /* selectionArgs */ new String[] {
8725                                             asyncRegistrationID.toString()
8726                                         },
8727                                         /* groupBy */ null,
8728                                         /* having */ null,
8729                                         /* orderedBy */ null)
8730                                 .getCount())
8731                 .isEqualTo(0);
8732     }
8733 
8734     @Test
testDeleteAsyncRegistration_missingRecord()8735     public void testDeleteAsyncRegistration_missingRecord() {
8736         mDatastoreManager.runInTransaction(
8737                 (dao) ->
8738                         assertThrows(
8739                                 "Async Registration already deleted",
8740                                 DatastoreException.class,
8741                                 () -> dao.deleteAsyncRegistration("missingAsyncRegId")));
8742     }
8743 
8744     @Test
deleteAsyncRegistrations_success()8745     public void deleteAsyncRegistrations_success() {
8746         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8747         AsyncRegistration ar1 =
8748                 new AsyncRegistration.Builder()
8749                         .setId("1")
8750                         .setRegistrant(Uri.parse("android-app://installed-registrant1"))
8751                         .setTopOrigin(Uri.parse("android-app://installed-registrant1"))
8752                         .setAdIdPermission(false)
8753                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
8754                         .setRequestTime(1)
8755                         .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID)
8756                         .build();
8757 
8758         AsyncRegistration ar2 =
8759                 new AsyncRegistration.Builder()
8760                         .setId("2")
8761                         .setRegistrant(Uri.parse("android-app://installed-registrant2"))
8762                         .setTopOrigin(Uri.parse("android-app://installed-registrant2"))
8763                         .setAdIdPermission(false)
8764                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
8765                         .setRequestTime(Long.MAX_VALUE)
8766                         .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID)
8767                         .build();
8768 
8769         AsyncRegistration ar3 =
8770                 new AsyncRegistration.Builder()
8771                         .setId("3")
8772                         .setRegistrant(Uri.parse("android-app://installed-registrant3"))
8773                         .setTopOrigin(Uri.parse("android-app://installed-registrant3"))
8774                         .setAdIdPermission(false)
8775                         .setType(AsyncRegistration.RegistrationType.APP_SOURCE)
8776                         .setRequestTime(Long.MAX_VALUE)
8777                         .setRegistrationId(ValidAsyncRegistrationParams.REGISTRATION_ID)
8778                         .build();
8779 
8780         List<AsyncRegistration> asyncRegistrationList = List.of(ar1, ar2, ar3);
8781         asyncRegistrationList.forEach(
8782                 asyncRegistration -> {
8783                     ContentValues values = new ContentValues();
8784                     values.put(AsyncRegistrationContract.ID, asyncRegistration.getId());
8785                     values.put(
8786                             AsyncRegistrationContract.REQUEST_TIME,
8787                             asyncRegistration.getRequestTime());
8788                     values.put(
8789                             AsyncRegistrationContract.REGISTRANT,
8790                             asyncRegistration.getRegistrant().toString());
8791                     values.put(
8792                             AsyncRegistrationContract.TOP_ORIGIN,
8793                             asyncRegistration.getTopOrigin().toString());
8794                     values.put(
8795                             AsyncRegistrationContract.REGISTRATION_ID,
8796                             asyncRegistration.getRegistrationId());
8797                     db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values);
8798                 });
8799 
8800         mDatastoreManager.runInTransaction(
8801                 (dao) -> dao.deleteAsyncRegistrations(List.of("1", "3")));
8802 
8803         assertThat(
8804                         db.query(
8805                                         /* table */ AsyncRegistrationContract.TABLE,
8806                                         /* columns */ null,
8807                                         /* selection */ null,
8808                                         /* selectionArgs */ null,
8809                                         /* groupBy */ null,
8810                                         /* having */ null,
8811                                         /* orderedBy */ null)
8812                                 .getCount())
8813                 .isEqualTo(1);
8814 
8815         assertThat(
8816                         db.query(
8817                                         /* table */ AsyncRegistrationContract.TABLE,
8818                                         /* columns */ null,
8819                                         /* selection */ AsyncRegistrationContract.ID + " = ? ",
8820                                         /* selectionArgs */ new String[] {"2"},
8821                                         /* groupBy */ null,
8822                                         /* having */ null,
8823                                         /* orderedBy */ null)
8824                                 .getCount())
8825                 .isEqualTo(1);
8826     }
8827 
8828     /** Test that retry count in AsyncRegistration is updated correctly. */
8829     @Test
testUpdateAsyncRegistrationRetryCount()8830     public void testUpdateAsyncRegistrationRetryCount() {
8831         AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
8832         String asyncRegistrationId = asyncRegistration.getId();
8833         long originalRetryCount = asyncRegistration.getRetryCount();
8834 
8835         mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
8836         mDatastoreManager.runInTransaction(
8837                 (dao) -> {
8838                     asyncRegistration.incrementRetryCount();
8839                     dao.updateRetryCount(asyncRegistration);
8840                 });
8841 
8842         try (Cursor cursor =
8843                 MeasurementDbHelper.getInstance()
8844                         .getReadableDatabase()
8845                         .query(
8846                                 AsyncRegistrationContract.TABLE,
8847                                 null,
8848                                 AsyncRegistrationContract.ID + " = ? ",
8849                                 new String[] {asyncRegistrationId},
8850                                 null,
8851                                 null,
8852                                 null)) {
8853             assertTrue(cursor.moveToNext());
8854             AsyncRegistration updateAsyncRegistration =
8855                     SqliteObjectMapper.constructAsyncRegistration(cursor);
8856             assertNotNull(updateAsyncRegistration);
8857             assertTrue(updateAsyncRegistration.getRetryCount() == originalRetryCount + 1);
8858         }
8859     }
8860 
8861     @Test
getSource_fetchesMatchingSourceFromDb()8862     public void getSource_fetchesMatchingSourceFromDb() {
8863         // Setup - insert 2 sources with different IDs
8864         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8865         String sourceId1 = "source1";
8866         Source source1WithoutDestinations =
8867                 SourceFixture.getMinimalValidSourceBuilder()
8868                         .setId(sourceId1)
8869                         .setAppDestinations(null)
8870                         .setWebDestinations(null)
8871                         .build();
8872         Source source1WithDestinations =
8873                 SourceFixture.getMinimalValidSourceBuilder()
8874                         .setId(sourceId1)
8875                         .setAppDestinations(null)
8876                         .setWebDestinations(null)
8877                         .build();
8878         insertInDb(db, source1WithDestinations);
8879         String sourceId2 = "source2";
8880         Source source2WithoutDestinations =
8881                 SourceFixture.getMinimalValidSourceBuilder()
8882                         .setId(sourceId2)
8883                         .setAppDestinations(null)
8884                         .setWebDestinations(null)
8885                         .build();
8886         Source source2WithDestinations =
8887                 SourceFixture.getMinimalValidSourceBuilder()
8888                         .setId(sourceId2)
8889                         .setAppDestinations(null)
8890                         .setWebDestinations(null)
8891                         .build();
8892         insertInDb(db, source2WithDestinations);
8893 
8894         // Execution
8895         mDatastoreManager.runInTransaction(
8896                 (dao) -> {
8897                     assertEquals(source1WithoutDestinations, dao.getSource(sourceId1));
8898                     assertEquals(source2WithoutDestinations, dao.getSource(sourceId2));
8899                 });
8900     }
8901 
8902     @Test
getSourceRegistrant_fetchesMatchingSourceFromDb()8903     public void getSourceRegistrant_fetchesMatchingSourceFromDb() {
8904         // Setup - insert 2 sources with different IDs
8905         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8906         String sourceId1 = "source1";
8907         String registrant1 = "android-app://registrant.app1";
8908         Source source1WithDestinations =
8909                 SourceFixture.getMinimalValidSourceBuilder()
8910                         .setId(sourceId1)
8911                         .setAppDestinations(null)
8912                         .setWebDestinations(null)
8913                         .setRegistrant(Uri.parse(registrant1))
8914                         .build();
8915         insertInDb(db, source1WithDestinations);
8916 
8917         String sourceId2 = "source2";
8918         String registrant2 = "android-app://registrant.app1";
8919         Source source2WithDestinations =
8920                 SourceFixture.getMinimalValidSourceBuilder()
8921                         .setId(sourceId2)
8922                         .setAppDestinations(null)
8923                         .setWebDestinations(null)
8924                         .setRegistrant(Uri.parse(registrant2))
8925                         .build();
8926         insertInDb(db, source2WithDestinations);
8927 
8928         // Execution
8929         mDatastoreManager.runInTransaction(
8930                 (dao) -> {
8931                     assertEquals(registrant1, dao.getSourceRegistrant(sourceId1));
8932                     assertEquals(registrant2, dao.getSourceRegistrant(sourceId2));
8933                 });
8934     }
8935 
8936     @Test
getSource_nonExistingInDb_throwsException()8937     public void getSource_nonExistingInDb_throwsException() {
8938         // Setup - insert 2 sources with different IDs
8939         mFlags = mock(Flags.class);
8940         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
8941         doReturn(true).when(mFlags).getMeasurementEnableDatastoreManagerThrowDatastoreException();
8942         doReturn(1.0f).when(mFlags).getMeasurementThrowUnknownExceptionSamplingRate();
8943         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8944         String sourceId1 = "source1";
8945         Source source1WithDestinations =
8946                 SourceFixture.getMinimalValidSourceBuilder()
8947                         .setId(sourceId1)
8948                         .setAppDestinations(null)
8949                         .setWebDestinations(null)
8950                         .build();
8951         insertInDb(db, source1WithDestinations);
8952 
8953         // Execution
8954         try {
8955             mDatastoreManager.runInTransaction(
8956                     (dao) -> {
8957                         dao.getSource("random_source_id");
8958                     });
8959             fail();
8960         } catch (IllegalStateException e) {
8961             Throwable cause = e.getCause();
8962             assertEquals(DatastoreException.class, cause.getClass());
8963             assertEquals("Source retrieval failed. Id: random_source_id", cause.getMessage());
8964         }
8965     }
8966 
8967     @Test
getSource_nonExistingInDbNoSampling_swallowException()8968     public void getSource_nonExistingInDbNoSampling_swallowException() {
8969         // Setup - insert 2 sources with different IDs
8970         mFlags = mock(Flags.class);
8971         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
8972         doReturn(true).when(mFlags).getMeasurementEnableDatastoreManagerThrowDatastoreException();
8973         doReturn(0.0f).when(mFlags).getMeasurementThrowUnknownExceptionSamplingRate();
8974         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
8975         String sourceId1 = "source1";
8976         Source source1WithDestinations =
8977                 SourceFixture.getMinimalValidSourceBuilder()
8978                         .setId(sourceId1)
8979                         .setAppDestinations(null)
8980                         .setWebDestinations(null)
8981                         .build();
8982         insertInDb(db, source1WithDestinations);
8983 
8984         // Execution
8985         assertFalse(
8986                 mDatastoreManager.runInTransaction(
8987                         (dao) -> {
8988                             dao.getSource("random_source_id");
8989                         }));
8990     }
8991 
8992     @Test
getSource_nonExistingInDbThrowingDisabled_swallowException()8993     public void getSource_nonExistingInDbThrowingDisabled_swallowException() {
8994         // Setup - insert 2 sources with different IDs
8995         mFlags = mock(Flags.class);
8996         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
8997         doReturn(false).when(mFlags).getMeasurementEnableDatastoreManagerThrowDatastoreException();
8998         doReturn(1.0f).when(mFlags).getMeasurementThrowUnknownExceptionSamplingRate();
8999         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
9000         String sourceId1 = "source1";
9001         Source source1WithDestinations =
9002                 SourceFixture.getMinimalValidSourceBuilder()
9003                         .setId(sourceId1)
9004                         .setAppDestinations(null)
9005                         .setWebDestinations(null)
9006                         .build();
9007         insertInDb(db, source1WithDestinations);
9008 
9009         // Execution
9010         assertFalse(
9011                 mDatastoreManager.runInTransaction(
9012                         (dao) -> {
9013                             dao.getSource("random_source_id");
9014                         }));
9015     }
9016 
9017     @Test
fetchMatchingAggregateReports_returnsMatchingReports()9018     public void fetchMatchingAggregateReports_returnsMatchingReports() {
9019         // setup - create reports for 3*3 combinations of source and trigger
9020         List<Source> sources =
9021                 Arrays.asList(
9022                         SourceFixture.getMinimalValidSourceBuilder()
9023                                 .setEventId(new UnsignedLong(1L))
9024                                 .setId("source1")
9025                                 .build(),
9026                         SourceFixture.getMinimalValidSourceBuilder()
9027                                 .setEventId(new UnsignedLong(2L))
9028                                 .setId("source2")
9029                                 .build(),
9030                         SourceFixture.getMinimalValidSourceBuilder()
9031                                 .setEventId(new UnsignedLong(3L))
9032                                 .setId("source3")
9033                                 .build());
9034         List<Trigger> triggers =
9035                 Arrays.asList(
9036                         TriggerFixture.getValidTriggerBuilder().setId("trigger1").build(),
9037                         TriggerFixture.getValidTriggerBuilder().setId("trigger2").build(),
9038                         TriggerFixture.getValidTriggerBuilder().setId("trigger3").build());
9039         List<AggregateReport> reports =
9040                 ImmutableList.of(
9041                         createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(0)),
9042                         createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(1)),
9043                         createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(2)),
9044                         createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(0)),
9045                         createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(1)),
9046                         createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(2)),
9047                         createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(0)),
9048                         createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(1)),
9049                         createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(2)));
9050 
9051         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9052         sources.forEach(source -> insertSource(source, source.getId()));
9053         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
9054         reports.forEach(
9055                 report ->
9056                         mDatastoreManager.runInTransaction(
9057                                 (dao) -> dao.insertAggregateReport(report)));
9058 
9059         mDatastoreManager.runInTransaction(
9060                 (dao) -> {
9061                     // Execution
9062                     List<AggregateReport> aggregateReports =
9063                             dao.fetchMatchingAggregateReports(
9064                                     Arrays.asList(sources.get(1).getId(), "nonMatchingSource"),
9065                                     Arrays.asList(triggers.get(2).getId(), "nonMatchingTrigger"));
9066                     assertEquals(5, aggregateReports.size());
9067 
9068                     aggregateReports =
9069                             dao.fetchMatchingAggregateReports(
9070                                     Arrays.asList(sources.get(0).getId(), sources.get(1).getId()),
9071                                     Collections.emptyList());
9072                     assertEquals(6, aggregateReports.size());
9073 
9074                     aggregateReports =
9075                             dao.fetchMatchingAggregateReports(
9076                                     Collections.emptyList(),
9077                                     Arrays.asList(
9078                                             triggers.get(0).getId(), triggers.get(2).getId()));
9079                     assertEquals(6, aggregateReports.size());
9080 
9081                     aggregateReports =
9082                             dao.fetchMatchingAggregateReports(
9083                                     Arrays.asList(
9084                                             sources.get(0).getId(),
9085                                             sources.get(1).getId(),
9086                                             sources.get(2).getId()),
9087                                     Arrays.asList(
9088                                             triggers.get(0).getId(),
9089                                             triggers.get(1).getId(),
9090                                             triggers.get(2).getId()));
9091                     assertEquals(9, aggregateReports.size());
9092                 });
9093     }
9094 
9095     @Test
fetchMatchingEventReports_returnsMatchingReports()9096     public void fetchMatchingEventReports_returnsMatchingReports() throws JSONException {
9097         // setup - create reports for 3*3 combinations of source and trigger
9098         List<Source> sources =
9099                 Arrays.asList(
9100                         SourceFixture.getMinimalValidSourceBuilder()
9101                                 .setEventId(new UnsignedLong(1L))
9102                                 .setId("source1")
9103                                 .build(),
9104                         SourceFixture.getMinimalValidSourceBuilder()
9105                                 .setEventId(new UnsignedLong(2L))
9106                                 .setId("source2")
9107                                 .build(),
9108                         SourceFixture.getMinimalValidSourceBuilder()
9109                                 .setEventId(new UnsignedLong(3L))
9110                                 .setId("source3")
9111                                 .build());
9112         List<Trigger> triggers =
9113                 Arrays.asList(
9114                         TriggerFixture.getValidTriggerBuilder()
9115                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
9116                                 .setId("trigger1")
9117                                 .build(),
9118                         TriggerFixture.getValidTriggerBuilder()
9119                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
9120                                 .setId("trigger2")
9121                                 .build(),
9122                         TriggerFixture.getValidTriggerBuilder()
9123                                 .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
9124                                 .setId("trigger3")
9125                                 .build());
9126         List<EventReport> reports =
9127                 ImmutableList.of(
9128                         createEventReportForSourceAndTrigger(sources.get(0), triggers.get(0)),
9129                         createEventReportForSourceAndTrigger(sources.get(0), triggers.get(1)),
9130                         createEventReportForSourceAndTrigger(sources.get(0), triggers.get(2)),
9131                         createEventReportForSourceAndTrigger(sources.get(1), triggers.get(0)),
9132                         createEventReportForSourceAndTrigger(sources.get(1), triggers.get(1)),
9133                         createEventReportForSourceAndTrigger(sources.get(1), triggers.get(2)),
9134                         createEventReportForSourceAndTrigger(sources.get(2), triggers.get(0)),
9135                         createEventReportForSourceAndTrigger(sources.get(2), triggers.get(1)),
9136                         createEventReportForSourceAndTrigger(sources.get(2), triggers.get(2)));
9137 
9138         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9139         sources.forEach(source -> insertSource(source, source.getId()));
9140         triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
9141         reports.forEach(
9142                 report ->
9143                         mDatastoreManager.runInTransaction((dao) -> dao.insertEventReport(report)));
9144 
9145         mDatastoreManager.runInTransaction(
9146                 (dao) -> {
9147                     // Execution
9148                     List<EventReport> eventReports =
9149                             dao.fetchMatchingEventReports(
9150                                     Arrays.asList(sources.get(1).getId(), "nonMatchingSource"),
9151                                     Arrays.asList(triggers.get(2).getId(), "nonMatchingTrigger"));
9152                     assertEquals(5, eventReports.size());
9153 
9154                     eventReports =
9155                             dao.fetchMatchingEventReports(
9156                                     Arrays.asList(sources.get(0).getId(), sources.get(1).getId()),
9157                                     Collections.emptyList());
9158                     assertEquals(6, eventReports.size());
9159 
9160                     eventReports =
9161                             dao.fetchMatchingEventReports(
9162                                     Collections.emptyList(),
9163                                     Arrays.asList(
9164                                             triggers.get(0).getId(), triggers.get(2).getId()));
9165                     assertEquals(6, eventReports.size());
9166 
9167                     eventReports =
9168                             dao.fetchMatchingEventReports(
9169                                     Arrays.asList(
9170                                             sources.get(0).getId(),
9171                                             sources.get(1).getId(),
9172                                             sources.get(2).getId()),
9173                                     Arrays.asList(
9174                                             triggers.get(0).getId(),
9175                                             triggers.get(1).getId(),
9176                                             triggers.get(2).getId()));
9177                     assertEquals(9, eventReports.size());
9178                 });
9179     }
9180 
9181     @Test
fetchMatchingSources_bringsMatchingSources()9182     public void fetchMatchingSources_bringsMatchingSources() {
9183         // Setup
9184         Source source1 =
9185                 SourceFixture.getMinimalValidSourceBuilder()
9186                         .setEventId(new UnsignedLong(1L))
9187                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9188                         .setEventTime(5000)
9189                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9190                         .setId("source1")
9191                         .build();
9192         Source source2 =
9193                 SourceFixture.getMinimalValidSourceBuilder()
9194                         .setEventId(new UnsignedLong(2L))
9195                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9196                         .setEventTime(10000)
9197                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9198                         .setId("source2")
9199                         .build();
9200         Source source3 =
9201                 SourceFixture.getMinimalValidSourceBuilder()
9202                         .setEventId(new UnsignedLong(3L))
9203                         .setPublisher(WebUtil.validUri("https://subdomain2.site1.test"))
9204                         .setEventTime(15000)
9205                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9206                         .setId("source3")
9207                         .build();
9208         Source source4 =
9209                 SourceFixture.getMinimalValidSourceBuilder()
9210                         .setEventId(new UnsignedLong(4L))
9211                         .setPublisher(WebUtil.validUri("https://subdomain2.site2.test"))
9212                         .setEventTime(15000)
9213                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9214                         .setId("source4")
9215                         .build();
9216         Source source5 =
9217                 SourceFixture.getMinimalValidSourceBuilder()
9218                         .setEventId(new UnsignedLong(5L))
9219                         .setPublisher(WebUtil.validUri("https://subdomain2.site1.test"))
9220                         .setEventTime(20000)
9221                         .setRegistrant(Uri.parse("android-app://com.registrant2"))
9222                         .setId("source5")
9223                         .build();
9224         List<Source> sources = List.of(source1, source2, source3, source4, source5);
9225 
9226         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9227         sources.forEach(source -> insertInDb(db, source));
9228 
9229         // Execution
9230         mDatastoreManager.runInTransaction(
9231                 dao -> {
9232                     // --- DELETE behaviour ---
9233                     // Delete Nothing
9234                     // No matches
9235                     List<String> actualSources =
9236                             dao.fetchMatchingSources(
9237                                     Uri.parse("android-app://com.registrant1"),
9238                                     Instant.ofEpochMilli(0),
9239                                     Instant.ofEpochMilli(50000),
9240                                     List.of(),
9241                                     List.of(),
9242                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9243                     assertEquals(0, actualSources.size());
9244 
9245                     // 1 & 2 match registrant1 and "https://subdomain1.site1.test" publisher
9246                     // origin
9247                     actualSources =
9248                             dao.fetchMatchingSources(
9249                                     Uri.parse("android-app://com.registrant1"),
9250                                     Instant.ofEpochMilli(0),
9251                                     Instant.ofEpochMilli(50000),
9252                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9253                                     List.of(),
9254                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9255                     assertEquals(2, actualSources.size());
9256 
9257                     // Only 2 matches registrant1 and "https://subdomain1.site1.test"
9258                     // publisher origin within
9259                     // the range
9260                     actualSources =
9261                             dao.fetchMatchingSources(
9262                                     Uri.parse("android-app://com.registrant1"),
9263                                     Instant.ofEpochMilli(8000),
9264                                     Instant.ofEpochMilli(50000),
9265                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9266                                     List.of(),
9267                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9268                     assertEquals(1, actualSources.size());
9269 
9270                     // 1,2 & 3 matches registrant1 and "https://site1.test" publisher origin
9271                     actualSources =
9272                             dao.fetchMatchingSources(
9273                                     Uri.parse("android-app://com.registrant1"),
9274                                     Instant.ofEpochMilli(0),
9275                                     Instant.ofEpochMilli(50000),
9276                                     List.of(),
9277                                     List.of(WebUtil.validUri("https://site1.test")),
9278                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9279                     assertEquals(3, actualSources.size());
9280 
9281                     // 3 matches origin and 4 matches domain URI
9282                     actualSources =
9283                             dao.fetchMatchingSources(
9284                                     Uri.parse("android-app://com.registrant1"),
9285                                     Instant.ofEpochMilli(10000),
9286                                     Instant.ofEpochMilli(20000),
9287                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
9288                                     List.of(WebUtil.validUri("https://site2.test")),
9289                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9290                     assertEquals(2, actualSources.size());
9291 
9292                     // --- PRESERVE (anti-match exception registrant) behaviour ---
9293                     // Preserve Nothing
9294                     // 1,2,3 & 4 are match registrant1
9295                     actualSources =
9296                             dao.fetchMatchingSources(
9297                                     Uri.parse("android-app://com.registrant1"),
9298                                     Instant.ofEpochMilli(0),
9299                                     Instant.ofEpochMilli(50000),
9300                                     List.of(),
9301                                     List.of(),
9302                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9303                     assertEquals(4, actualSources.size());
9304 
9305                     // 3 & 4 match registrant1 and don't match
9306                     // "https://subdomain1.site1.test" publisher origin
9307                     actualSources =
9308                             dao.fetchMatchingSources(
9309                                     Uri.parse("android-app://com.registrant1"),
9310                                     Instant.ofEpochMilli(0),
9311                                     Instant.ofEpochMilli(50000),
9312                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9313                                     List.of(),
9314                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9315                     assertEquals(2, actualSources.size());
9316 
9317                     // 3 & 4 match registrant1, in range and don't match
9318                     // "https://subdomain1.site1.test"
9319                     actualSources =
9320                             dao.fetchMatchingSources(
9321                                     Uri.parse("android-app://com.registrant1"),
9322                                     Instant.ofEpochMilli(8000),
9323                                     Instant.ofEpochMilli(50000),
9324                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9325                                     List.of(),
9326                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9327                     assertEquals(2, actualSources.size());
9328 
9329                     // Only 4 matches registrant1, in range and don't match
9330                     // "https://site1.test"
9331                     actualSources =
9332                             dao.fetchMatchingSources(
9333                                     Uri.parse("android-app://com.registrant1"),
9334                                     Instant.ofEpochMilli(0),
9335                                     Instant.ofEpochMilli(50000),
9336                                     List.of(),
9337                                     List.of(WebUtil.validUri("https://site1.test")),
9338                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9339                     assertEquals(1, actualSources.size());
9340 
9341                     // only 2 is registrant1 based, in range and does not match either
9342                     // site2.test or subdomain2.site1.test
9343                     actualSources =
9344                             dao.fetchMatchingSources(
9345                                     Uri.parse("android-app://com.registrant1"),
9346                                     Instant.ofEpochMilli(10000),
9347                                     Instant.ofEpochMilli(20000),
9348                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
9349                                     List.of(WebUtil.validUri("https://site2.test")),
9350                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9351                     assertEquals(1, actualSources.size());
9352                 });
9353     }
9354 
9355     @Test
fetchFlexSourceIdsFor_bringsMatchingSources_expectedSourceReturned()9356     public void fetchFlexSourceIdsFor_bringsMatchingSources_expectedSourceReturned()
9357             throws JSONException {
9358         // Setup
9359         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
9360         Source source1 =
9361                 SourceFixture.getMinimalValidSourceBuilder()
9362                         .setEventId(new UnsignedLong(1L))
9363                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9364                         .setEventTime(5000)
9365                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9366                         .setId("source1")
9367                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9368                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9369                         .setPrivacyParameters(
9370                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9371                         .build();
9372         source1.buildTriggerSpecs();
9373         Source source2 =
9374                 SourceFixture.getMinimalValidSourceBuilder()
9375                         .setEventId(new UnsignedLong(2L))
9376                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9377                         .setEventTime(10000)
9378                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9379                         .setId("source2")
9380                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9381                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9382                         .setPrivacyParameters(
9383                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9384                         .build();
9385         source2.buildTriggerSpecs();
9386 
9387         List<TriggerSpecs> triggerSpecsList = List.of(
9388                 source1.getTriggerSpecs(),
9389                 source2.getTriggerSpecs());
9390 
9391         for (TriggerSpecs triggerSpecs : triggerSpecsList) {
9392             insertAttributedTrigger(
9393                     triggerSpecs,
9394                     EventReportFixture.getBaseEventReportBuild()
9395                             .setTriggerId("123456")
9396                             .setTriggerData(new UnsignedLong(2L))
9397                             .setTriggerPriority(1L)
9398                             .setTriggerValue(1L)
9399                             .build());
9400             insertAttributedTrigger(
9401                     triggerSpecs,
9402                     EventReportFixture.getBaseEventReportBuild()
9403                             .setTriggerId("234567")
9404                             .setTriggerData(new UnsignedLong(2L))
9405                             .setTriggerPriority(1L)
9406                             .setTriggerValue(1L)
9407                             .build());
9408             insertAttributedTrigger(
9409                     triggerSpecs,
9410                     EventReportFixture.getBaseEventReportBuild()
9411                             .setTriggerId("345678")
9412                             .setTriggerData(new UnsignedLong(2L))
9413                             .setTriggerPriority(1L)
9414                             .setTriggerValue(1L)
9415                             .build());
9416         }
9417 
9418         Source source3 =
9419                 SourceFixture.getMinimalValidSourceBuilder()
9420                         .setEventId(new UnsignedLong(2L))
9421                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9422                         .setEventTime(10000)
9423                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9424                         .setId("source3")
9425                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9426                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9427                         .setPrivacyParameters(
9428                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9429                         .build();
9430         source3.buildTriggerSpecs();
9431 
9432         insertAttributedTrigger(
9433                 source3.getTriggerSpecs(),
9434                 EventReportFixture.getBaseEventReportBuild()
9435                         .setTriggerId("123456")
9436                         .setTriggerData(new UnsignedLong(2L))
9437                         .setTriggerPriority(1L)
9438                         .setTriggerValue(1L)
9439                         .build());
9440 
9441         List<Source> sources = List.of(source1, source2, source3);
9442 
9443         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9444         sources.forEach(source -> insertInDb(db, source));
9445 
9446         // Execution
9447         mDatastoreManager.runInTransaction(
9448                 dao -> {
9449                     Set<String> actualSources =
9450                             dao.fetchFlexSourceIdsFor(Collections.singletonList("123456"));
9451                     Set<String> expected = Set.of("source1", "source2", "source3");
9452                     assertEquals(expected, actualSources);
9453                 });
9454 
9455         mDatastoreManager.runInTransaction(
9456                 dao -> {
9457                     Set<String> actualSources =
9458                             dao.fetchFlexSourceIdsFor(Collections.singletonList("234567"));
9459                     Set<String> expected = Set.of("source1", "source2");
9460                     assertEquals(expected, actualSources);
9461                 });
9462 
9463         mDatastoreManager.runInTransaction(
9464                 dao -> {
9465                     Set<String> actualSources =
9466                             dao.fetchFlexSourceIdsFor(Collections.singletonList("23456"));
9467                     assertEquals(Collections.emptySet(), actualSources);
9468                 });
9469 
9470         mDatastoreManager.runInTransaction(
9471                 dao -> {
9472                     Set<String> actualSources = dao.fetchFlexSourceIdsFor(new ArrayList<>());
9473                     assertEquals(Collections.emptySet(), actualSources);
9474                 });
9475     }
9476 
9477     @Test
fetchFlexSourceIdsFor_moreThanSqliteMaxCompoundSelect_expectedSourceReturned()9478     public void fetchFlexSourceIdsFor_moreThanSqliteMaxCompoundSelect_expectedSourceReturned()
9479             throws JSONException {
9480         // Setup
9481         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
9482         Source source1 =
9483                 SourceFixture.getMinimalValidSourceBuilder()
9484                         .setEventId(new UnsignedLong(1L))
9485                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9486                         .setEventTime(5000)
9487                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9488                         .setId("source1")
9489                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9490                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9491                         .setPrivacyParameters(
9492                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9493                         .build();
9494         source1.buildTriggerSpecs();
9495         Source source2 =
9496                 SourceFixture.getMinimalValidSourceBuilder()
9497                         .setEventId(new UnsignedLong(2L))
9498                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9499                         .setEventTime(10000)
9500                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9501                         .setId("source2")
9502                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9503                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9504                         .setPrivacyParameters(
9505                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9506                         .build();
9507         source2.buildTriggerSpecs();
9508 
9509         List<TriggerSpecs> triggerSpecsList = List.of(
9510                 source1.getTriggerSpecs(),
9511                 source2.getTriggerSpecs());
9512 
9513         for (TriggerSpecs triggerSpecs : triggerSpecsList) {
9514             insertAttributedTrigger(
9515                     triggerSpecs,
9516                     EventReportFixture.getBaseEventReportBuild()
9517                             .setTriggerId("123456")
9518                             .setTriggerData(new UnsignedLong(2L))
9519                             .setTriggerPriority(1L)
9520                             .setTriggerValue(1L)
9521                             .build());
9522             insertAttributedTrigger(
9523                     triggerSpecs,
9524                     EventReportFixture.getBaseEventReportBuild()
9525                             .setTriggerId("234567")
9526                             .setTriggerData(new UnsignedLong(2L))
9527                             .setTriggerPriority(1L)
9528                             .setTriggerValue(1L)
9529                             .build());
9530             insertAttributedTrigger(
9531                     triggerSpecs,
9532                     EventReportFixture.getBaseEventReportBuild()
9533                             .setTriggerId("345678")
9534                             .setTriggerData(new UnsignedLong(2L))
9535                             .setTriggerPriority(1L)
9536                             .setTriggerValue(1L)
9537                             .build());
9538         }
9539 
9540         Source source3 =
9541                 SourceFixture.getMinimalValidSourceBuilder()
9542                         .setEventId(new UnsignedLong(2L))
9543                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9544                         .setEventTime(10000)
9545                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9546                         .setId("source3")
9547                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9548                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9549                         .setPrivacyParameters(
9550                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9551                         .build();
9552         source3.buildTriggerSpecs();
9553 
9554         insertAttributedTrigger(
9555                 source3.getTriggerSpecs(),
9556                 EventReportFixture.getBaseEventReportBuild()
9557                         .setTriggerId("123456")
9558                         .setTriggerData(new UnsignedLong(2L))
9559                         .setTriggerPriority(1L)
9560                         .setTriggerValue(1L)
9561                         .build());
9562 
9563         List<Source> sources = List.of(source1, source2, source3);
9564 
9565         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9566         sources.forEach(source -> insertInDb(db, source));
9567 
9568         // Execution
9569         mDatastoreManager.runInTransaction(
9570                 dao -> {
9571                     Set<String> actualSources =
9572                             dao.fetchFlexSourceIdsFor(getRandomIdsWith(1000, "123456"));
9573                     Set<String> expected = Set.of("source1", "source2", "source3");
9574                     assertEquals(expected, actualSources);
9575                 });
9576 
9577         mDatastoreManager.runInTransaction(
9578                 dao -> {
9579                     Set<String> actualSources =
9580                             dao.fetchFlexSourceIdsFor(getRandomIdsWith(1000, "234567"));
9581                     Set<String> expected = Set.of("source1", "source2");
9582                     assertEquals(expected, actualSources);
9583                 });
9584 
9585         mDatastoreManager.runInTransaction(
9586                 dao -> {
9587                     Set<String> actualSources =
9588                             dao.fetchFlexSourceIdsFor(getRandomIdsWith(1000, "23456"));
9589                     assertEquals(Collections.emptySet(), actualSources);
9590                 });
9591 
9592         mDatastoreManager.runInTransaction(
9593                 dao -> {
9594                     Set<String> actualSources = dao.fetchFlexSourceIdsFor(new ArrayList<>());
9595                     assertEquals(Collections.emptySet(), actualSources);
9596                 });
9597     }
9598 
9599     @Test
fetchFlexSourceIdsFor_emptyInputSourceId_noSourceReturned()9600     public void fetchFlexSourceIdsFor_emptyInputSourceId_noSourceReturned()
9601             throws JSONException {
9602         // Setup
9603         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
9604         Source source1 =
9605                 SourceFixture.getMinimalValidSourceBuilder()
9606                         .setEventId(new UnsignedLong(1L))
9607                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9608                         .setEventTime(5000)
9609                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9610                         .setId("source1")
9611                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9612                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9613                         .setPrivacyParameters(
9614                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9615                         .build();
9616         source1.buildTriggerSpecs();
9617 
9618         Source source2 =
9619                 SourceFixture.getMinimalValidSourceBuilder()
9620                         .setEventId(new UnsignedLong(2L))
9621                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9622                         .setEventTime(10000)
9623                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9624                         .setId("source2")
9625                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9626                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9627                         .setPrivacyParameters(
9628                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9629                         .build();
9630         source2.buildTriggerSpecs();
9631 
9632         List<TriggerSpecs> triggerSpecsList = List.of(
9633                 source1.getTriggerSpecs(),
9634                 source2.getTriggerSpecs());
9635 
9636         for (TriggerSpecs triggerSpecs : triggerSpecsList) {
9637             insertAttributedTrigger(
9638                     triggerSpecs,
9639                     EventReportFixture.getBaseEventReportBuild()
9640                             .setTriggerId("123456")
9641                             .setTriggerData(new UnsignedLong(2L))
9642                             .setTriggerPriority(1L)
9643                             .setTriggerValue(1L)
9644                             .build());
9645             insertAttributedTrigger(
9646                     triggerSpecs,
9647                     EventReportFixture.getBaseEventReportBuild()
9648                             .setTriggerId("234567")
9649                             .setTriggerData(new UnsignedLong(2L))
9650                             .setTriggerPriority(1L)
9651                             .setTriggerValue(1L)
9652                             .build());
9653             insertAttributedTrigger(
9654                     triggerSpecs,
9655                     EventReportFixture.getBaseEventReportBuild()
9656                             .setTriggerId("345678")
9657                             .setTriggerData(new UnsignedLong(2L))
9658                             .setTriggerPriority(1L)
9659                             .setTriggerValue(1L)
9660                             .build());
9661         }
9662 
9663         Source source3 =
9664                 SourceFixture.getMinimalValidSourceBuilder()
9665                         .setEventId(new UnsignedLong(2L))
9666                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9667                         .setEventTime(10000)
9668                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9669                         .setId("source3")
9670                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9671                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9672                         .setPrivacyParameters(
9673                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9674                         .build();
9675         source3.buildTriggerSpecs();
9676 
9677         insertAttributedTrigger(
9678                 source3.getTriggerSpecs(),
9679                 EventReportFixture.getBaseEventReportBuild()
9680                         .setTriggerId("123456")
9681                         .setTriggerData(new UnsignedLong(2L))
9682                         .setTriggerPriority(1L)
9683                         .setTriggerValue(1L)
9684                         .build());
9685 
9686         List<Source> sources = List.of(source1, source2, source3);
9687 
9688         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9689         sources.forEach(source -> insertInDb(db, source));
9690 
9691         // Execution
9692         mDatastoreManager.runInTransaction(
9693                 dao -> {
9694                     Set<String> actualSources =
9695                             dao.fetchFlexSourceIdsFor(Collections.singletonList("123456"));
9696                     Set<String> expected = Set.of("source1", "source2", "source3");
9697                     assertEquals(expected, actualSources);
9698                 });
9699 
9700         mDatastoreManager.runInTransaction(
9701                 dao -> {
9702                     Set<String> actualSources =
9703                             dao.fetchFlexSourceIdsFor(Collections.singletonList("234567"));
9704                     Set<String> expected = Set.of("source1", "source2");
9705                     assertEquals(expected, actualSources);
9706                 });
9707 
9708         mDatastoreManager.runInTransaction(
9709                 dao -> {
9710                     Set<String> actualSources =
9711                             dao.fetchFlexSourceIdsFor(Collections.singletonList("23456"));
9712                     assertEquals(Collections.emptySet(), actualSources);
9713                 });
9714     }
9715 
9716     @Test
fetchFlexSourceIdsFor_doesNotMatchV1Sources()9717     public void fetchFlexSourceIdsFor_doesNotMatchV1Sources() throws JSONException {
9718         // Setup
9719         EventReport eventReport1 =
9720                 EventReportFixture.getBaseEventReportBuild()
9721                         .setTriggerId("123456")
9722                         .setTriggerData(new UnsignedLong(2L))
9723                         .setTriggerPriority(1L)
9724                         .setTriggerValue(1L)
9725                         .build();
9726         EventReport eventReport2 =
9727                 EventReportFixture.getBaseEventReportBuild()
9728                         .setTriggerId("234567")
9729                         .setTriggerData(new UnsignedLong(2L))
9730                         .setTriggerPriority(1L)
9731                         .setTriggerValue(1L)
9732                         .build();
9733         EventReport eventReport3 =
9734                 EventReportFixture.getBaseEventReportBuild()
9735                         .setTriggerId("345678")
9736                         .setTriggerData(new UnsignedLong(2L))
9737                         .setTriggerPriority(1L)
9738                         .setTriggerValue(1L)
9739                         .build();
9740 
9741         TriggerSpecs testTriggerSpecs = SourceFixture.getValidTriggerSpecsCountBased();
9742 
9743         // Flex source
9744         Source source1 =
9745                 SourceFixture.getMinimalValidSourceBuilder()
9746                         .setEventId(new UnsignedLong(1L))
9747                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9748                         .setEventTime(5000)
9749                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9750                         .setId("source1")
9751                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9752                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9753                         .setPrivacyParameters(
9754                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9755                         .build();
9756         source1.buildTriggerSpecs();
9757 
9758         insertAttributedTrigger(source1.getTriggerSpecs(), eventReport1);
9759         insertAttributedTrigger(source1.getTriggerSpecs(), eventReport2);
9760         insertAttributedTrigger(source1.getTriggerSpecs(), eventReport3);
9761 
9762         // Non-flex (V1) source
9763         Source source2 =
9764                 SourceFixture.getMinimalValidSourceBuilder()
9765                         .setEventId(new UnsignedLong(2L))
9766                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9767                         .setEventTime(10000)
9768                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9769                         .setId("source2")
9770                         .build();
9771         source2.buildAttributedTriggers();
9772 
9773         insertAttributedTrigger(source2.getAttributedTriggers(), eventReport1);
9774         insertAttributedTrigger(source2.getAttributedTriggers(), eventReport2);
9775         insertAttributedTrigger(source2.getAttributedTriggers(), eventReport3);
9776 
9777         // Flex source
9778         Source source3 =
9779                 SourceFixture.getMinimalValidSourceBuilder()
9780                         .setEventId(new UnsignedLong(2L))
9781                         .setPublisher(WebUtil.validUri("https://subdomain1.site1.test"))
9782                         .setEventTime(10000)
9783                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9784                         .setId("source3")
9785                         .setTriggerSpecsString(testTriggerSpecs.encodeToJson())
9786                         .setMaxEventLevelReports(testTriggerSpecs.getMaxReports())
9787                         .setPrivacyParameters(
9788                                 testTriggerSpecs.encodePrivacyParametersToJsonString())
9789                         .build();
9790         source3.buildTriggerSpecs();
9791 
9792         insertAttributedTrigger(source3.getTriggerSpecs(), eventReport1);
9793 
9794         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9795         List.of(source1, source2, source3).forEach(source -> insertInDb(db, source));
9796 
9797         // Execution
9798         mDatastoreManager.runInTransaction(
9799                 dao -> {
9800                     Set<String> actualSources =
9801                             dao.fetchFlexSourceIdsFor(Collections.singletonList("123456"));
9802                     Set<String> expected = Set.of("source1", "source3");
9803                     assertEquals(expected, actualSources);
9804                 });
9805 
9806         mDatastoreManager.runInTransaction(
9807                 dao -> {
9808                     Set<String> actualSources =
9809                             dao.fetchFlexSourceIdsFor(
9810                                     new ArrayList<>(Collections.singletonList("234567")));
9811                     Set<String> expected = Set.of("source1");
9812                     assertEquals(expected, actualSources);
9813                 });
9814 
9815         mDatastoreManager.runInTransaction(
9816                 dao -> {
9817                     Set<String> actualSources =
9818                             dao.fetchFlexSourceIdsFor(Collections.singletonList("23456"));
9819                     assertEquals(Collections.emptySet(), actualSources);
9820                 });
9821     }
9822 
9823     @Test
fetchMatchingTriggers_bringsMatchingTriggers()9824     public void fetchMatchingTriggers_bringsMatchingTriggers() {
9825         // Setup
9826         Trigger trigger1 =
9827                 TriggerFixture.getValidTriggerBuilder()
9828                         .setAttributionDestination(
9829                                 WebUtil.validUri("https://subdomain1.site1.test"))
9830                         .setTriggerTime(5000)
9831                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9832                         .setId("trigger1")
9833                         .build();
9834         Trigger trigger2 =
9835                 TriggerFixture.getValidTriggerBuilder()
9836                         .setAttributionDestination(
9837                                 WebUtil.validUri("https://subdomain1.site1.test"))
9838                         .setTriggerTime(10000)
9839                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9840                         .setId("trigger2")
9841                         .build();
9842         Trigger trigger3 =
9843                 TriggerFixture.getValidTriggerBuilder()
9844                         .setAttributionDestination(
9845                                 WebUtil.validUri("https://subdomain2.site1.test"))
9846                         .setTriggerTime(15000)
9847                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9848                         .setId("trigger3")
9849                         .build();
9850         Trigger trigger4 =
9851                 TriggerFixture.getValidTriggerBuilder()
9852                         .setAttributionDestination(
9853                                 WebUtil.validUri("https://subdomain2.site2.test"))
9854                         .setTriggerTime(15000)
9855                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
9856                         .setId("trigger4")
9857                         .build();
9858         Trigger trigger5 =
9859                 TriggerFixture.getValidTriggerBuilder()
9860                         .setAttributionDestination(
9861                                 WebUtil.validUri("https://subdomain2.site1.test"))
9862                         .setTriggerTime(20000)
9863                         .setRegistrant(Uri.parse("android-app://com.registrant2"))
9864                         .setId("trigger5")
9865                         .build();
9866         List<Trigger> triggers = List.of(trigger1, trigger2, trigger3, trigger4, trigger5);
9867 
9868         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
9869         triggers.forEach(
9870                 trigger -> {
9871                     ContentValues values = new ContentValues();
9872                     values.put(TriggerContract.ID, trigger.getId());
9873                     values.put(
9874                             TriggerContract.ATTRIBUTION_DESTINATION,
9875                             trigger.getAttributionDestination().toString());
9876                     values.put(TriggerContract.TRIGGER_TIME, trigger.getTriggerTime());
9877                     values.put(TriggerContract.ENROLLMENT_ID, trigger.getEnrollmentId());
9878                     values.put(TriggerContract.REGISTRANT, trigger.getRegistrant().toString());
9879                     values.put(TriggerContract.STATUS, trigger.getStatus());
9880                     db.insert(TriggerContract.TABLE, /* nullColumnHack */ null, values);
9881                 });
9882 
9883         // Execution
9884         mDatastoreManager.runInTransaction(
9885                 dao -> {
9886                     // --- DELETE behaviour ---
9887                     // Delete Nothing
9888                     // No Matches
9889                     Set<String> actualTriggers =
9890                             dao.fetchMatchingTriggers(
9891                                     Uri.parse("android-app://com.registrant1"),
9892                                     Instant.ofEpochMilli(0),
9893                                     Instant.ofEpochMilli(50000),
9894                                     List.of(),
9895                                     List.of(),
9896                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9897                     assertEquals(0, actualTriggers.size());
9898 
9899                     // 1 & 2 match registrant1 and "https://subdomain1.site1.test"
9900                     // destination origin
9901                     actualTriggers =
9902                             dao.fetchMatchingTriggers(
9903                                     Uri.parse("android-app://com.registrant1"),
9904                                     Instant.ofEpochMilli(0),
9905                                     Instant.ofEpochMilli(50000),
9906                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9907                                     List.of(),
9908                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9909                     assertEquals(2, actualTriggers.size());
9910 
9911                     // Only 2 matches registrant1 and "https://subdomain1.site1.test"
9912                     // destination origin within the range
9913                     actualTriggers =
9914                             dao.fetchMatchingTriggers(
9915                                     Uri.parse("android-app://com.registrant1"),
9916                                     Instant.ofEpochMilli(8000),
9917                                     Instant.ofEpochMilli(50000),
9918                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9919                                     List.of(),
9920                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9921                     assertEquals(1, actualTriggers.size());
9922 
9923                     // 1,2 & 3 matches registrant1 and "https://site1.test" destination
9924                     // origin
9925                     actualTriggers =
9926                             dao.fetchMatchingTriggers(
9927                                     Uri.parse("android-app://com.registrant1"),
9928                                     Instant.ofEpochMilli(0),
9929                                     Instant.ofEpochMilli(50000),
9930                                     List.of(),
9931                                     List.of(WebUtil.validUri("https://site1.test")),
9932                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9933                     assertEquals(3, actualTriggers.size());
9934 
9935                     // 3 matches origin and 4 matches domain URI
9936                     actualTriggers =
9937                             dao.fetchMatchingTriggers(
9938                                     Uri.parse("android-app://com.registrant1"),
9939                                     Instant.ofEpochMilli(10000),
9940                                     Instant.ofEpochMilli(20000),
9941                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
9942                                     List.of(WebUtil.validUri("https://site2.test")),
9943                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
9944                     assertEquals(2, actualTriggers.size());
9945 
9946                     // --- PRESERVE (anti-match exception registrant) behaviour ---
9947                     // Preserve Nothing
9948                     // 1,2,3 & 4 are match registrant1
9949                     actualTriggers =
9950                             dao.fetchMatchingTriggers(
9951                                     Uri.parse("android-app://com.registrant1"),
9952                                     Instant.ofEpochMilli(0),
9953                                     Instant.ofEpochMilli(50000),
9954                                     List.of(),
9955                                     List.of(),
9956                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9957                     assertEquals(4, actualTriggers.size());
9958 
9959                     // 3 & 4 match registrant1 and don't match
9960                     // "https://subdomain1.site1.test" destination origin
9961                     actualTriggers =
9962                             dao.fetchMatchingTriggers(
9963                                     Uri.parse("android-app://com.registrant1"),
9964                                     Instant.ofEpochMilli(0),
9965                                     Instant.ofEpochMilli(50000),
9966                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9967                                     List.of(),
9968                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9969                     assertEquals(2, actualTriggers.size());
9970 
9971                     // 3 & 4 match registrant1, in range and don't match
9972                     // "https://subdomain1.site1.test"
9973                     actualTriggers =
9974                             dao.fetchMatchingTriggers(
9975                                     Uri.parse("android-app://com.registrant1"),
9976                                     Instant.ofEpochMilli(8000),
9977                                     Instant.ofEpochMilli(50000),
9978                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
9979                                     List.of(),
9980                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9981                     assertEquals(2, actualTriggers.size());
9982 
9983                     // Only 4 matches registrant1, in range and don't match
9984                     // "https://site1.test"
9985                     actualTriggers =
9986                             dao.fetchMatchingTriggers(
9987                                     Uri.parse("android-app://com.registrant1"),
9988                                     Instant.ofEpochMilli(0),
9989                                     Instant.ofEpochMilli(50000),
9990                                     List.of(),
9991                                     List.of(WebUtil.validUri("https://site1.test")),
9992                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
9993                     assertEquals(1, actualTriggers.size());
9994 
9995                     // only 2 is registrant1 based, in range and does not match either
9996                     // site2.test or subdomain2.site1.test
9997                     actualTriggers =
9998                             dao.fetchMatchingTriggers(
9999                                     Uri.parse("android-app://com.registrant1"),
10000                                     Instant.ofEpochMilli(10000),
10001                                     Instant.ofEpochMilli(20000),
10002                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
10003                                     List.of(WebUtil.validUri("https://site2.test")),
10004                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10005                     assertEquals(1, actualTriggers.size());
10006                 });
10007     }
10008 
10009     @Test
fetchMatchingAsyncRegistrations_bringsMatchingAsyncRegistrations()10010     public void fetchMatchingAsyncRegistrations_bringsMatchingAsyncRegistrations() {
10011         // Setup
10012         AsyncRegistration asyncRegistration1 =
10013                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10014                         .setTopOrigin(
10015                                 WebUtil.validUri("https://subdomain1.site1.test"))
10016                         .setRequestTime(5000)
10017                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10018                         .setId("asyncRegistration1")
10019                         .build();
10020         AsyncRegistration asyncRegistration2 =
10021                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10022                         .setTopOrigin(
10023                                 WebUtil.validUri("https://subdomain1.site1.test"))
10024                         .setRequestTime(10000)
10025                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10026                         .setId("asyncRegistration2")
10027                         .build();
10028         AsyncRegistration asyncRegistration3 =
10029                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10030                         .setTopOrigin(
10031                                 WebUtil.validUri("https://subdomain2.site1.test"))
10032                         .setRequestTime(15000)
10033                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10034                         .setId("asyncRegistration3")
10035                         .build();
10036         AsyncRegistration asyncRegistration4 =
10037                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10038                         .setTopOrigin(
10039                                 WebUtil.validUri("https://subdomain2.site2.test"))
10040                         .setRequestTime(15000)
10041                         .setRegistrant(Uri.parse("android-app://com.registrant1"))
10042                         .setId("asyncRegistration4")
10043                         .build();
10044         AsyncRegistration asyncRegistration5 =
10045                 AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
10046                         .setTopOrigin(
10047                                 WebUtil.validUri("https://subdomain2.site1.test"))
10048                         .setRequestTime(20000)
10049                         .setRegistrant(Uri.parse("android-app://com.registrant2"))
10050                         .setId("asyncRegistration5")
10051                         .build();
10052         List<AsyncRegistration> asyncRegistrations = List.of(
10053                 asyncRegistration1, asyncRegistration2, asyncRegistration3, asyncRegistration4,
10054                 asyncRegistration5);
10055 
10056         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
10057         asyncRegistrations.forEach(
10058                 asyncRegistration -> {
10059                     ContentValues values = new ContentValues();
10060                     values.put(AsyncRegistrationContract.ID, asyncRegistration.getId());
10061                     values.put(
10062                             AsyncRegistrationContract.TOP_ORIGIN,
10063                             asyncRegistration.getTopOrigin().toString());
10064                     values.put(AsyncRegistrationContract.REQUEST_TIME,
10065                             asyncRegistration.getRequestTime());
10066                     values.put(AsyncRegistrationContract.REGISTRANT,
10067                             asyncRegistration.getRegistrant().toString());
10068                     values.put(AsyncRegistrationContract.REGISTRATION_ID,
10069                             UUID.randomUUID().toString());
10070                     db.insert(AsyncRegistrationContract.TABLE, /* nullColumnHack */ null, values);
10071                 });
10072 
10073         // Execution
10074         mDatastoreManager.runInTransaction(
10075                 dao -> {
10076                     // --- DELETE behaviour ---
10077                     // Delete Nothing
10078                     // No Matches
10079                     List<String> actualAsyncRegistrations =
10080                             dao.fetchMatchingAsyncRegistrations(
10081                                     Uri.parse("android-app://com.registrant1"),
10082                                     Instant.ofEpochMilli(0),
10083                                     Instant.ofEpochMilli(50000),
10084                                     List.of(),
10085                                     List.of(),
10086                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10087                     assertEquals(0, actualAsyncRegistrations.size());
10088 
10089                     // 1 & 2 match registrant1 and "https://subdomain1.site1.test" top-
10090                     // origin origin
10091                     actualAsyncRegistrations =
10092                             dao.fetchMatchingAsyncRegistrations(
10093                                     Uri.parse("android-app://com.registrant1"),
10094                                     Instant.ofEpochMilli(0),
10095                                     Instant.ofEpochMilli(50000),
10096                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10097                                     List.of(),
10098                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10099                     assertEquals(2, actualAsyncRegistrations.size());
10100 
10101                     // Only 2 matches registrant1 and "https://subdomain1.site1.test"
10102                     // top-origin origin within the range
10103                     actualAsyncRegistrations =
10104                             dao.fetchMatchingAsyncRegistrations(
10105                                     Uri.parse("android-app://com.registrant1"),
10106                                     Instant.ofEpochMilli(8000),
10107                                     Instant.ofEpochMilli(50000),
10108                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10109                                     List.of(),
10110                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10111                     assertEquals(1, actualAsyncRegistrations.size());
10112 
10113                     // 1,2 & 3 matches registrant1 and "https://site1.test" top-origin
10114                     // origin
10115                     actualAsyncRegistrations =
10116                             dao.fetchMatchingAsyncRegistrations(
10117                                     Uri.parse("android-app://com.registrant1"),
10118                                     Instant.ofEpochMilli(0),
10119                                     Instant.ofEpochMilli(50000),
10120                                     List.of(),
10121                                     List.of(WebUtil.validUri("https://site1.test")),
10122                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10123                     assertEquals(3, actualAsyncRegistrations.size());
10124 
10125                     // 3 matches origin and 4 matches domain URI
10126                     actualAsyncRegistrations =
10127                             dao.fetchMatchingAsyncRegistrations(
10128                                     Uri.parse("android-app://com.registrant1"),
10129                                     Instant.ofEpochMilli(10000),
10130                                     Instant.ofEpochMilli(20000),
10131                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
10132                                     List.of(WebUtil.validUri("https://site2.test")),
10133                                     DeletionRequest.MATCH_BEHAVIOR_DELETE);
10134                     assertEquals(2, actualAsyncRegistrations.size());
10135 
10136                     // --- PRESERVE (anti-match exception registrant) behaviour ---
10137                     // Preserve Nothing
10138                     // 1,2,3 & 4 are match registrant1
10139                     actualAsyncRegistrations =
10140                             dao.fetchMatchingAsyncRegistrations(
10141                                     Uri.parse("android-app://com.registrant1"),
10142                                     Instant.ofEpochMilli(0),
10143                                     Instant.ofEpochMilli(50000),
10144                                     List.of(),
10145                                     List.of(),
10146                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10147                     assertEquals(4, actualAsyncRegistrations.size());
10148 
10149                     // 3 & 4 match registrant1 and don't match
10150                     // "https://subdomain1.site1.test" top-origin origin
10151                     actualAsyncRegistrations =
10152                             dao.fetchMatchingAsyncRegistrations(
10153                                     Uri.parse("android-app://com.registrant1"),
10154                                     Instant.ofEpochMilli(0),
10155                                     Instant.ofEpochMilli(50000),
10156                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10157                                     List.of(),
10158                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10159                     assertEquals(2, actualAsyncRegistrations.size());
10160 
10161                     // 3 & 4 match registrant1, in range and don't match
10162                     // "https://subdomain1.site1.test"
10163                     actualAsyncRegistrations =
10164                             dao.fetchMatchingAsyncRegistrations(
10165                                     Uri.parse("android-app://com.registrant1"),
10166                                     Instant.ofEpochMilli(8000),
10167                                     Instant.ofEpochMilli(50000),
10168                                     List.of(WebUtil.validUri("https://subdomain1.site1.test")),
10169                                     List.of(),
10170                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10171                     assertEquals(2, actualAsyncRegistrations.size());
10172 
10173                     // Only 4 matches registrant1, in range and don't match
10174                     // "https://site1.test"
10175                     actualAsyncRegistrations =
10176                             dao.fetchMatchingAsyncRegistrations(
10177                                     Uri.parse("android-app://com.registrant1"),
10178                                     Instant.ofEpochMilli(0),
10179                                     Instant.ofEpochMilli(50000),
10180                                     List.of(),
10181                                     List.of(WebUtil.validUri("https://site1.test")),
10182                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10183                     assertEquals(1, actualAsyncRegistrations.size());
10184 
10185                     // only 2 is registrant1 based, in range and does not match either
10186                     // site2.test or subdomain2.site1.test
10187                     actualAsyncRegistrations =
10188                             dao.fetchMatchingAsyncRegistrations(
10189                                     Uri.parse("android-app://com.registrant1"),
10190                                     Instant.ofEpochMilli(10000),
10191                                     Instant.ofEpochMilli(20000),
10192                                     List.of(WebUtil.validUri("https://subdomain2.site1.test")),
10193                                     List.of(WebUtil.validUri("https://site2.test")),
10194                                     DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
10195                     assertEquals(1, actualAsyncRegistrations.size());
10196                 });
10197     }
10198 
10199     @Test
testUpdateSourceAggregateReportDedupKeys_updatesKeysInList()10200     public void testUpdateSourceAggregateReportDedupKeys_updatesKeysInList() {
10201         Source validSource = SourceFixture.getValidSource();
10202         assertTrue(validSource.getAggregateReportDedupKeys().equals(new ArrayList<UnsignedLong>()));
10203         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
10204 
10205         String sourceId = getFirstSourceIdFromDatastore();
10206         Source source =
10207                 mDatastoreManager
10208                         .runInTransactionWithResult(
10209                                 measurementDao -> measurementDao.getSource(sourceId))
10210                         .get();
10211 
10212         source.getAggregateReportDedupKeys().add(new UnsignedLong(10L));
10213         mDatastoreManager.runInTransaction(
10214                 (dao) -> dao.updateSourceAggregateReportDedupKeys(source));
10215 
10216         Source sourceAfterUpdate =
10217                 mDatastoreManager
10218                         .runInTransactionWithResult(
10219                                 measurementDao -> measurementDao.getSource(sourceId))
10220                         .get();
10221 
10222         assertTrue(sourceAfterUpdate.getAggregateReportDedupKeys().size() == 1);
10223         assertTrue(sourceAfterUpdate.getAggregateReportDedupKeys().get(0).getValue() == 10L);
10224     }
10225 
10226     @Test
testUpdateSourceAggregateReportDedupKeys_acceptsUnsignedLongValue()10227     public void testUpdateSourceAggregateReportDedupKeys_acceptsUnsignedLongValue() {
10228         Source validSource = SourceFixture.getValidSource();
10229         assertTrue(validSource.getAggregateReportDedupKeys().equals(new ArrayList<UnsignedLong>()));
10230         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
10231 
10232         String sourceId = getFirstSourceIdFromDatastore();
10233         Source source =
10234                 mDatastoreManager
10235                         .runInTransactionWithResult(
10236                                 measurementDao -> measurementDao.getSource(sourceId))
10237                         .get();
10238 
10239         UnsignedLong dedupKeyValue = new UnsignedLong("17293822569102704640");
10240         source.getAggregateReportDedupKeys().add(dedupKeyValue);
10241         mDatastoreManager.runInTransaction(
10242                 (dao) -> dao.updateSourceAggregateReportDedupKeys(source));
10243 
10244         Source sourceAfterUpdate =
10245                 mDatastoreManager
10246                         .runInTransactionWithResult(
10247                                 measurementDao -> measurementDao.getSource(sourceId))
10248                         .get();
10249 
10250         assertEquals(1, sourceAfterUpdate.getAggregateReportDedupKeys().size());
10251         assertEquals(dedupKeyValue, sourceAfterUpdate.getAggregateReportDedupKeys().get(0));
10252     }
10253 
10254     @Test
testUpdateSourceAggregatableNamedBudgetAndContribution_updatesBudgets()10255     public void testUpdateSourceAggregatableNamedBudgetAndContribution_updatesBudgets() {
10256         mFlags = mock(Flags.class);
10257         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
10258         when(mFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(true);
10259         when(mFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
10260 
10261         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
10262         aggregatableNamedBudgets.createContributionBudget("budget1", 400);
10263         aggregatableNamedBudgets.setContribution("budget1", 150);
10264         Source validSource =
10265                 insertSourceForAggregatableNamedBudgets(
10266                         aggregatableNamedBudgets,
10267                         SOURCE_EVENT_TIME,
10268                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
10269                         List.of(APP_ONE_DESTINATION));
10270         validSource.getAggregatableNamedBudgets().setContribution("budget1", 340);
10271         AggregatableNamedBudgets.BudgetAndContribution validSourceBudgetAndContribution =
10272                 new AggregatableNamedBudgets.BudgetAndContribution(400);
10273         validSourceBudgetAndContribution.setAggregateContribution(340);
10274         mDatastoreManager.runInTransaction(
10275                 (dao) ->
10276                         dao.updateSourceAggregatableNamedBudgetAndContribution(
10277                                 validSource.getId(), "budget1", validSourceBudgetAndContribution));
10278 
10279         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution =
10280                 mDatastoreManager
10281                         .runInTransactionWithResult(
10282                                 measurementDao ->
10283                                         measurementDao
10284                                                 .getSourceAggregatableNamedBudgetAndContribution(
10285                                                         validSource.getId(), "budget1"))
10286                         .get();
10287         AggregatableNamedBudgets.BudgetAndContribution expectedBudgetAndContribution =
10288                 new AggregatableNamedBudgets.BudgetAndContribution(400);
10289         expectedBudgetAndContribution.setAggregateContribution(340);
10290         assertThat(daoBudgetAndContribution).isEqualTo(expectedBudgetAndContribution);
10291     }
10292 
10293     @Test
10294     public void
testUpdateSourceAggregatableNamedBudgetAndContribution_budgetDoesNotExistCreatesRow()10295             testUpdateSourceAggregatableNamedBudgetAndContribution_budgetDoesNotExistCreatesRow() {
10296         mFlags = mock(Flags.class);
10297         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
10298         when(mFlags.getMeasurementEnableAggregatableNamedBudgets()).thenReturn(true);
10299         when(mFlags.getMeasurementDbSizeLimit()).thenReturn(MEASUREMENT_DB_SIZE_LIMIT);
10300 
10301         Source validSource = SourceFixture.getValidSource();
10302         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
10303         assertThat(validSource.getAggregatableNamedBudgets()).isNull();
10304 
10305         AggregatableNamedBudgets aggregatableNamedBudgets = new AggregatableNamedBudgets();
10306         Source finalSource =
10307                 Source.Builder.from(validSource)
10308                         .setAggregatableNamedBudgets(aggregatableNamedBudgets)
10309                         .build();
10310         AggregatableNamedBudgets.BudgetAndContribution sourceBudgetAndContribution =
10311                 new AggregatableNamedBudgets.BudgetAndContribution(100);
10312         sourceBudgetAndContribution.setAggregateContribution(70);
10313         mDatastoreManager.runInTransaction(
10314                 (dao) ->
10315                         dao.updateSourceAggregatableNamedBudgetAndContribution(
10316                                 finalSource.getId(), "budget1", sourceBudgetAndContribution));
10317 
10318         Source sourceAfterUpdate =
10319                 mDatastoreManager
10320                         .runInTransactionWithResult(
10321                                 measurementDao -> measurementDao.getSource(validSource.getId()))
10322                         .get();
10323 
10324         AggregatableNamedBudgets.BudgetAndContribution daoBudgetAndContribution =
10325                 mDatastoreManager
10326                         .runInTransactionWithResult(
10327                                 measurementDao ->
10328                                         measurementDao
10329                                                 .getSourceAggregatableNamedBudgetAndContribution(
10330                                                         sourceAfterUpdate.getId(), "budget1"))
10331                         .get();
10332         assertThat(daoBudgetAndContribution).isEqualTo(sourceBudgetAndContribution);
10333     }
10334 
10335     @Test
testPersistAndRetrieveSource_handlesPreExistingNegativeValues()10336     public void testPersistAndRetrieveSource_handlesPreExistingNegativeValues() {
10337         // Setup
10338         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
10339         Source validSource = SourceFixture.getValidSource();
10340         ContentValues values = new ContentValues();
10341         values.put(SourceContract.ID, validSource.getId());
10342         values.put(SourceContract.EVENT_ID, validSource.getEventId().toString());
10343         values.put(SourceContract.ENROLLMENT_ID, validSource.getEnrollmentId());
10344         values.put(SourceContract.REGISTRANT, validSource.getRegistrant().toString());
10345         values.put(SourceContract.PUBLISHER, validSource.getPublisher().toString());
10346         values.put(
10347                 SourceContract.REGISTRATION_ORIGIN, validSource.getRegistrationOrigin().toString());
10348         // Existing invalid values
10349         Long val1 = -123L;
10350         Long val2 = Long.MIN_VALUE;
10351         values.put(
10352                 SourceContract.AGGREGATE_REPORT_DEDUP_KEYS,
10353                 val1.toString() + "," + val2.toString());
10354         db.insert(SourceContract.TABLE, /* nullColumnHack */ null, values);
10355         maybeInsertSourceDestinations(db, validSource, validSource.getId());
10356 
10357         // Execution
10358         Optional<Source> source =
10359                 mDatastoreManager.runInTransactionWithResult(
10360                         measurementDao -> measurementDao.getSource(validSource.getId()));
10361         assertTrue(source.isPresent());
10362         List<UnsignedLong> aggReportDedupKeys = source.get().getAggregateReportDedupKeys();
10363         assertEquals(2, aggReportDedupKeys.size());
10364         assertEquals(val1, (Long) aggReportDedupKeys.get(0).getValue());
10365         assertEquals(val2, (Long) aggReportDedupKeys.get(1).getValue());
10366     }
10367 
10368     @Test
fetchTriggerMatchingSourcesForXna_filtersSourcesCorrectly()10369     public void fetchTriggerMatchingSourcesForXna_filtersSourcesCorrectly() {
10370         // Setup
10371         Uri matchingDestination = APP_ONE_DESTINATION;
10372         Uri nonMatchingDestination = APP_TWO_DESTINATION;
10373         String mmpMatchingEnrollmentId = "mmp1";
10374         String mmpNonMatchingEnrollmentId = "mmpx";
10375         String san1MatchingEnrollmentId = "san1EnrollmentId";
10376         String san2MatchingEnrollmentId = "san2EnrollmentId";
10377         String san3MatchingEnrollmentId = "san3EnrollmentId";
10378         String san4NonMatchingEnrollmentId = "san4EnrollmentId";
10379         String san5MatchingEnrollmentId = "san5EnrollmentId";
10380 
10381         Trigger trigger =
10382                 TriggerFixture.getValidTriggerBuilder()
10383                         .setAttributionDestination(matchingDestination)
10384                         .setEnrollmentId(mmpMatchingEnrollmentId)
10385                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
10386                         .setTriggerTime(TriggerFixture.ValidTriggerParams.TRIGGER_TIME)
10387                         .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
10388                         .setAggregateTriggerData(
10389                                 TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA)
10390                         .setAggregateValuesString(
10391                                 TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES_STRING)
10392                         .setFilters(TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING)
10393                         .setNotFilters(
10394                                 TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING)
10395                         .build();
10396         Source s1MmpMatchingWithDestinations =
10397                 createSourceBuilder()
10398                         .setId("s1")
10399                         .setEnrollmentId(mmpMatchingEnrollmentId)
10400                         .setAppDestinations(List.of(matchingDestination))
10401                         .build();
10402         Source s1MmpMatchingWithoutDestinations =
10403                 createSourceBuilder()
10404                         .setId("s1")
10405                         .setEnrollmentId(mmpMatchingEnrollmentId)
10406                         .setAppDestinations(null)
10407                         .setWebDestinations(null)
10408                         .setRegistrationId(s1MmpMatchingWithDestinations.getRegistrationId())
10409                         .build();
10410         Source s2MmpDiffDestination =
10411                 createSourceBuilder()
10412                         .setId("s2")
10413                         .setEnrollmentId(mmpMatchingEnrollmentId)
10414                         .setAppDestinations(List.of(nonMatchingDestination))
10415                         .build();
10416         Source s3MmpExpired =
10417                 createSourceBuilder()
10418                         .setId("s3")
10419                         .setEnrollmentId(mmpMatchingEnrollmentId)
10420                         .setAppDestinations(List.of(nonMatchingDestination))
10421                         // expired before trigger time
10422                         .setExpiryTime(trigger.getTriggerTime() - DAYS.toMillis(1))
10423                         .build();
10424         Source s4NonMatchingMmp =
10425                 createSourceBuilder()
10426                         .setId("s4")
10427                         .setEnrollmentId(mmpNonMatchingEnrollmentId)
10428                         .setAppDestinations(List.of(matchingDestination))
10429                         .build();
10430         Source s5MmpMatchingWithDestinations =
10431                 createSourceBuilder()
10432                         .setId("s5")
10433                         .setEnrollmentId(mmpMatchingEnrollmentId)
10434                         .setAppDestinations(List.of(matchingDestination))
10435                         .build();
10436         Source s5MmpMatchingWithoutDestinations =
10437                 createSourceBuilder()
10438                         .setId("s5")
10439                         .setEnrollmentId(mmpMatchingEnrollmentId)
10440                         .setAppDestinations(null)
10441                         .setWebDestinations(null)
10442                         .setRegistrationId(s5MmpMatchingWithDestinations.getRegistrationId())
10443                         .build();
10444         Source s6San1MatchingWithDestinations =
10445                 createSourceBuilder()
10446                         .setId("s6")
10447                         .setEnrollmentId(san1MatchingEnrollmentId)
10448                         .setAppDestinations(List.of(matchingDestination))
10449                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10450                         .build();
10451         Source s6San1MatchingWithoutDestinations =
10452                 createSourceBuilder()
10453                         .setId("s6")
10454                         .setEnrollmentId(san1MatchingEnrollmentId)
10455                         .setAppDestinations(null)
10456                         .setWebDestinations(null)
10457                         .setRegistrationId(s6San1MatchingWithDestinations.getRegistrationId())
10458                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10459                         .build();
10460         Source s7San1DiffDestination =
10461                 createSourceBuilder()
10462                         .setId("s7")
10463                         .setEnrollmentId(san1MatchingEnrollmentId)
10464                         .setAppDestinations(List.of(nonMatchingDestination))
10465                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10466                         .build();
10467         Source s8San2MatchingWithDestinations =
10468                 createSourceBuilder()
10469                         .setId("s8")
10470                         .setEnrollmentId(san2MatchingEnrollmentId)
10471                         .setAppDestinations(List.of(matchingDestination))
10472                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10473                         .build();
10474         Source s8San2MatchingWithoutDestinations =
10475                 createSourceBuilder()
10476                         .setId("s8")
10477                         .setEnrollmentId(san2MatchingEnrollmentId)
10478                         .setAppDestinations(null)
10479                         .setWebDestinations(null)
10480                         .setRegistrationId(s8San2MatchingWithDestinations.getRegistrationId())
10481                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10482                         .build();
10483         Source s9San3XnaIgnored =
10484                 createSourceBuilder()
10485                         .setId("s9")
10486                         .setEnrollmentId(san3MatchingEnrollmentId)
10487                         .setAppDestinations(List.of(matchingDestination))
10488                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10489                         .build();
10490         Source s10San3MatchingWithDestinations =
10491                 createSourceBuilder()
10492                         .setId("s10")
10493                         .setEnrollmentId(san3MatchingEnrollmentId)
10494                         .setAppDestinations(List.of(matchingDestination))
10495                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10496                         .build();
10497         Source s10San3MatchingWithoutDestinations =
10498                 createSourceBuilder()
10499                         .setId("s10")
10500                         .setEnrollmentId(san3MatchingEnrollmentId)
10501                         .setAppDestinations(null)
10502                         .setWebDestinations(null)
10503                         .setRegistrationId(s10San3MatchingWithDestinations.getRegistrationId())
10504                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10505                         .build();
10506         Source s11San4EnrollmentNonMatching =
10507                 createSourceBuilder()
10508                         .setId("s11")
10509                         .setEnrollmentId(san4NonMatchingEnrollmentId)
10510                         .setAppDestinations(List.of(matchingDestination))
10511                         .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
10512                         .build();
10513         Source s12San1NullSharedAggregationKeys =
10514                 createSourceBuilder()
10515                         .setId("s12")
10516                         .setEnrollmentId(san1MatchingEnrollmentId)
10517                         .setAppDestinations(List.of(matchingDestination))
10518                         .setSharedAggregationKeys(null)
10519                         .build();
10520         Source s13San1Expired =
10521                 createSourceBuilder()
10522                         .setId("s13")
10523                         .setEnrollmentId(san1MatchingEnrollmentId)
10524                         .setAppDestinations(List.of(matchingDestination))
10525                         // expired before trigger time
10526                         .setExpiryTime(trigger.getTriggerTime() - DAYS.toMillis(1))
10527                         .build();
10528         String registrationIdForTriggerAndOtherRegistration = UUID.randomUUID().toString();
10529         Source s14San5RegIdClasesWithMmp =
10530                 createSourceBuilder()
10531                         .setId("s14")
10532                         .setEnrollmentId(san5MatchingEnrollmentId)
10533                         .setAppDestinations(List.of(matchingDestination))
10534                         .setRegistrationId(registrationIdForTriggerAndOtherRegistration)
10535                         .build();
10536         Source s15MmpMatchingWithDestinations =
10537                 createSourceBuilder()
10538                         .setId("s15")
10539                         .setEnrollmentId(mmpMatchingEnrollmentId)
10540                         .setAppDestinations(List.of(matchingDestination))
10541                         .setRegistrationId(registrationIdForTriggerAndOtherRegistration)
10542                         .build();
10543         Source s15MmpMatchingWithoutDestinations =
10544                 createSourceBuilder()
10545                         .setId("s15")
10546                         .setEnrollmentId(mmpMatchingEnrollmentId)
10547                         .setAppDestinations(null)
10548                         .setWebDestinations(null)
10549                         .setRegistrationId(registrationIdForTriggerAndOtherRegistration)
10550                         .build();
10551         List<Source> sources =
10552                 Arrays.asList(
10553                         s1MmpMatchingWithDestinations,
10554                         s2MmpDiffDestination,
10555                         s3MmpExpired,
10556                         s4NonMatchingMmp,
10557                         s5MmpMatchingWithDestinations,
10558                         s6San1MatchingWithDestinations,
10559                         s7San1DiffDestination,
10560                         s8San2MatchingWithDestinations,
10561                         s9San3XnaIgnored,
10562                         s10San3MatchingWithDestinations,
10563                         s11San4EnrollmentNonMatching,
10564                         s12San1NullSharedAggregationKeys,
10565                         s13San1Expired,
10566                         s14San5RegIdClasesWithMmp,
10567                         s15MmpMatchingWithDestinations);
10568         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
10569         Objects.requireNonNull(db);
10570         // Insert all sources to the DB
10571         sources.forEach(source -> insertSource(source, source.getId()));
10572 
10573         // Insert XNA ignored sources
10574         ContentValues values = new ContentValues();
10575         values.put(XnaIgnoredSourcesContract.SOURCE_ID, s9San3XnaIgnored.getId());
10576         values.put(XnaIgnoredSourcesContract.ENROLLMENT_ID, mmpMatchingEnrollmentId);
10577         long row = db.insert(XnaIgnoredSourcesContract.TABLE, null, values);
10578         assertEquals(1, row);
10579 
10580         List<Source> expectedMatchingSources =
10581                 Arrays.asList(
10582                         s1MmpMatchingWithoutDestinations,
10583                         s5MmpMatchingWithoutDestinations,
10584                         s6San1MatchingWithoutDestinations,
10585                         s8San2MatchingWithoutDestinations,
10586                         s10San3MatchingWithoutDestinations,
10587                         s15MmpMatchingWithoutDestinations);
10588         Comparator<Source> sortingComparator = Comparator.comparing(Source::getId);
10589         expectedMatchingSources.sort(sortingComparator);
10590 
10591         // Execution
10592         mDatastoreManager.runInTransaction(
10593                 dao -> {
10594                     List<String> matchingSanEnrollmentIds =
10595                             Arrays.asList(
10596                                     san1MatchingEnrollmentId,
10597                                     san2MatchingEnrollmentId,
10598                                     san3MatchingEnrollmentId,
10599                                     san5MatchingEnrollmentId);
10600                     List<Source> actualMatchingSources =
10601                             dao.fetchTriggerMatchingSourcesForXna(
10602                                     trigger, matchingSanEnrollmentIds);
10603                     actualMatchingSources.sort(sortingComparator);
10604                     // Assertion
10605                     assertEquals(expectedMatchingSources, actualMatchingSources);
10606                 });
10607     }
10608 
10609     @Test
insertIgnoredSourceForEnrollment_success()10610     public void insertIgnoredSourceForEnrollment_success() {
10611         // Setup
10612         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
10613         // Need to insert sources before, to honor the foreign key constraint
10614         insertSource(createSourceBuilder().setId("s1").build(), "s1");
10615         insertSource(createSourceBuilder().setId("s2").build(), "s2");
10616 
10617         Pair<String, String> entry11 = new Pair<>("s1", "e1");
10618         Pair<String, String> entry21 = new Pair<>("s2", "e1");
10619         Pair<String, String> entry22 = new Pair<>("s2", "e2");
10620 
10621         mDatastoreManager.runInTransaction(
10622                 dao -> {
10623                     // Execution
10624                     dao.insertIgnoredSourceForEnrollment(entry11.first, entry11.second);
10625                     dao.insertIgnoredSourceForEnrollment(entry21.first, entry21.second);
10626                     dao.insertIgnoredSourceForEnrollment(entry22.first, entry22.second);
10627 
10628                     // Assertion
10629                     queryAndAssertSourceEntries(db, "e1", Arrays.asList("s1", "s2"));
10630                     queryAndAssertSourceEntries(db, "e2", Collections.singletonList("s2"));
10631                 });
10632     }
10633 
10634     @Test
countDistinctDebugAdIdsUsedByEnrollment_oneTriggerAndSource()10635     public void countDistinctDebugAdIdsUsedByEnrollment_oneTriggerAndSource() {
10636         // Setup
10637         Source.Builder sourceBuilder =
10638                 new Source.Builder()
10639                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
10640                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
10641                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
10642                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
10643                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
10644         Source s1 =
10645                 sourceBuilder
10646                         .setId("s1")
10647                         .setPublisherType(EventSurfaceType.WEB)
10648                         .setDebugAdId("debug-ad-id-1")
10649                         .setEnrollmentId("enrollment-id-1")
10650                         .build();
10651         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
10652 
10653         Trigger.Builder triggerBuilder =
10654                 new Trigger.Builder()
10655                         .setAttributionDestination(
10656                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
10657                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
10658                         .setRegistrationOrigin(
10659                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
10660                         .setAggregatableSourceRegistrationTimeConfig(
10661                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
10662         Trigger t1 =
10663                 triggerBuilder
10664                         .setId("t1")
10665                         .setDestinationType(EventSurfaceType.WEB)
10666                         .setDebugAdId("debug-ad-id-1")
10667                         .setEnrollmentId("enrollment-id-1")
10668                         .build();
10669         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
10670 
10671         // Assertion
10672         assertTrue(
10673                 mDatastoreManager.runInTransaction(
10674                         dao ->
10675                                 assertEquals(
10676                                         1,
10677                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
10678                                                 "enrollment-id-1"))));
10679     }
10680 
10681     @Test
countDistinctDebugAdIdsUsedByEnrollment_nullValuesPresent()10682     public void countDistinctDebugAdIdsUsedByEnrollment_nullValuesPresent() {
10683         // Setup
10684         Source.Builder sourceBuilder =
10685                 new Source.Builder()
10686                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
10687                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
10688                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
10689                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
10690                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
10691         // Source with debug AdId present
10692         Source s1 =
10693                 sourceBuilder
10694                         .setId("s1")
10695                         .setPublisherType(EventSurfaceType.WEB)
10696                         .setDebugAdId("debug-ad-id-1")
10697                         .setEnrollmentId("enrollment-id-1")
10698                         .build();
10699         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
10700         // Source with no debug AdId
10701         Source s2 =
10702                 sourceBuilder
10703                         .setId("s2")
10704                         .setPublisherType(EventSurfaceType.WEB)
10705                         .setEnrollmentId("enrollment-id-1")
10706                         .build();
10707         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
10708 
10709         Trigger.Builder triggerBuilder =
10710                 new Trigger.Builder()
10711                         .setAttributionDestination(
10712                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
10713                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
10714                         .setRegistrationOrigin(
10715                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
10716                         .setAggregatableSourceRegistrationTimeConfig(
10717                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
10718         // Trigger with debug AdId present
10719         Trigger t1 =
10720                 triggerBuilder
10721                         .setId("t1")
10722                         .setDestinationType(EventSurfaceType.WEB)
10723                         .setDebugAdId("debug-ad-id-1")
10724                         .setEnrollmentId("enrollment-id-1")
10725                         .build();
10726         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
10727         // Trigger with no debug AdId
10728         Trigger t2 =
10729                 triggerBuilder
10730                         .setId("t2")
10731                         .setDestinationType(EventSurfaceType.WEB)
10732                         .setEnrollmentId("enrollment-id-1")
10733                         .build();
10734         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
10735 
10736         // Assertion
10737         assertTrue(
10738                 mDatastoreManager.runInTransaction(
10739                         dao ->
10740                                 assertEquals(
10741                                         1,
10742                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
10743                                                 "enrollment-id-1"))));
10744     }
10745 
10746     @Test
countDistinctDebugAdIdsUsedByEnrollment_multipleSourcesAndTriggers()10747     public void countDistinctDebugAdIdsUsedByEnrollment_multipleSourcesAndTriggers() {
10748         // Setup
10749         Source.Builder sourceBuilder =
10750                 new Source.Builder()
10751                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
10752                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
10753                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
10754                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
10755                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
10756         // Multiple sources with same AdId
10757         Source s1 =
10758                 sourceBuilder
10759                         .setId("s1")
10760                         .setPublisherType(EventSurfaceType.WEB)
10761                         .setDebugAdId("debug-ad-id-1")
10762                         .setEnrollmentId("enrollment-id-1")
10763                         .build();
10764         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
10765         Source s2 =
10766                 sourceBuilder
10767                         .setId("s2")
10768                         .setPublisherType(EventSurfaceType.WEB)
10769                         .setDebugAdId("debug-ad-id-1")
10770                         .setEnrollmentId("enrollment-id-1")
10771                         .build();
10772         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
10773         Source s3 =
10774                 sourceBuilder
10775                         .setId("s3")
10776                         .setPublisherType(EventSurfaceType.WEB)
10777                         .setDebugAdId("debug-ad-id-1")
10778                         .setEnrollmentId("enrollment-id-1")
10779                         .build();
10780         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3));
10781 
10782         Trigger.Builder triggerBuilder =
10783                 new Trigger.Builder()
10784                         .setAttributionDestination(
10785                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
10786                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
10787                         .setRegistrationOrigin(
10788                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
10789                         .setAggregatableSourceRegistrationTimeConfig(
10790                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
10791         // Multiple triggers with same AdId
10792         Trigger t1 =
10793                 triggerBuilder
10794                         .setId("t1")
10795                         .setDestinationType(EventSurfaceType.WEB)
10796                         .setDebugAdId("debug-ad-id-1")
10797                         .setEnrollmentId("enrollment-id-1")
10798                         .build();
10799         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
10800         Trigger t2 =
10801                 triggerBuilder
10802                         .setId("t2")
10803                         .setDestinationType(EventSurfaceType.WEB)
10804                         .setDebugAdId("debug-ad-id-1")
10805                         .setEnrollmentId("enrollment-id-1")
10806                         .build();
10807         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
10808         Trigger t3 =
10809                 triggerBuilder
10810                         .setId("t3")
10811                         .setDestinationType(EventSurfaceType.WEB)
10812                         .setDebugAdId("debug-ad-id-1")
10813                         .setEnrollmentId("enrollment-id-1")
10814                         .build();
10815         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3));
10816 
10817         // Assertion
10818         assertTrue(
10819                 mDatastoreManager.runInTransaction(
10820                         dao ->
10821                                 assertEquals(
10822                                         1,
10823                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
10824                                                 "enrollment-id-1"))));
10825     }
10826 
10827     @Test
countDistinctDebugAdIdsUsedByEnrollment_multipleAdIdsPresent()10828     public void countDistinctDebugAdIdsUsedByEnrollment_multipleAdIdsPresent() {
10829         // Setup
10830         Source.Builder sourceBuilder =
10831                 new Source.Builder()
10832                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
10833                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
10834                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
10835                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
10836                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
10837         // Multiple sources with different AdIds but the same enrollmentId
10838         Source s1 =
10839                 sourceBuilder
10840                         .setId("s1")
10841                         .setPublisherType(EventSurfaceType.WEB)
10842                         .setDebugAdId("debug-ad-id-1")
10843                         .setEnrollmentId("enrollment-id-1")
10844                         .build();
10845         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
10846         Source s2 =
10847                 sourceBuilder
10848                         .setId("s2")
10849                         .setPublisherType(EventSurfaceType.WEB)
10850                         .setDebugAdId("debug-ad-id-2")
10851                         .setEnrollmentId("enrollment-id-1")
10852                         .build();
10853         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
10854         Source s3 =
10855                 sourceBuilder
10856                         .setId("s3")
10857                         .setPublisherType(EventSurfaceType.WEB)
10858                         .setDebugAdId("debug-ad-id-3")
10859                         .setEnrollmentId("enrollment-id-1")
10860                         .build();
10861         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3));
10862 
10863         Trigger.Builder triggerBuilder =
10864                 new Trigger.Builder()
10865                         .setAttributionDestination(
10866                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
10867                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
10868                         .setRegistrationOrigin(
10869                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
10870                         .setAggregatableSourceRegistrationTimeConfig(
10871                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
10872         // Multiple triggers with different AdIds but the same enrollmentId
10873         Trigger t1 =
10874                 triggerBuilder
10875                         .setId("t1")
10876                         .setDestinationType(EventSurfaceType.WEB)
10877                         .setDebugAdId("debug-ad-id-4")
10878                         .setEnrollmentId("enrollment-id-1")
10879                         .build();
10880         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
10881         Trigger t2 =
10882                 triggerBuilder
10883                         .setId("t2")
10884                         .setDestinationType(EventSurfaceType.WEB)
10885                         .setDebugAdId("debug-ad-id-5")
10886                         .setEnrollmentId("enrollment-id-1")
10887                         .build();
10888         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
10889         Trigger t3 =
10890                 triggerBuilder
10891                         .setId("t3")
10892                         .setDestinationType(EventSurfaceType.WEB)
10893                         .setDebugAdId("debug-ad-id-6")
10894                         .setEnrollmentId("enrollment-id-1")
10895                         .build();
10896         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3));
10897 
10898         // Assertion
10899         assertTrue(
10900                 mDatastoreManager.runInTransaction(
10901                         dao ->
10902                                 assertEquals(
10903                                         6,
10904                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
10905                                                 "enrollment-id-1"))));
10906     }
10907 
10908     @Test
countDistinctDebugAdIdsUsedByEnrollment_multipleEnrollmentIdsPresent()10909     public void countDistinctDebugAdIdsUsedByEnrollment_multipleEnrollmentIdsPresent() {
10910         // Setup
10911         Source.Builder sourceBuilder =
10912                 new Source.Builder()
10913                         .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
10914                         .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
10915                         .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
10916                         .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
10917                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
10918         // Multiple sources with different AdIds and differing enrollmentIds
10919         Source s1 =
10920                 sourceBuilder
10921                         .setId("s1")
10922                         .setPublisherType(EventSurfaceType.WEB)
10923                         .setDebugAdId("debug-ad-id-1")
10924                         .setEnrollmentId("enrollment-id-1")
10925                         .build();
10926         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s1));
10927         Source s2 =
10928                 sourceBuilder
10929                         .setId("s2")
10930                         .setPublisherType(EventSurfaceType.WEB)
10931                         .setDebugAdId("debug-ad-id-2")
10932                         .setEnrollmentId("enrollment-id-2")
10933                         .build();
10934         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s2));
10935         Source s3 =
10936                 sourceBuilder
10937                         .setId("s3")
10938                         .setPublisherType(EventSurfaceType.WEB)
10939                         .setDebugAdId("debug-ad-id-3")
10940                         .setEnrollmentId("enrollment-id-2")
10941                         .build();
10942         mDatastoreManager.runInTransaction(dao -> dao.insertSource(s3));
10943 
10944         Trigger.Builder triggerBuilder =
10945                 new Trigger.Builder()
10946                         .setAttributionDestination(
10947                                 TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION)
10948                         .setRegistrant(TriggerFixture.ValidTriggerParams.REGISTRANT)
10949                         .setRegistrationOrigin(
10950                                 TriggerFixture.ValidTriggerParams.REGISTRATION_ORIGIN)
10951                         .setAggregatableSourceRegistrationTimeConfig(
10952                                 Trigger.SourceRegistrationTimeConfig.INCLUDE);
10953         // Multiple triggers with different AdIds and differing enrollmentIds
10954         Trigger t1 =
10955                 triggerBuilder
10956                         .setId("t1")
10957                         .setDestinationType(EventSurfaceType.WEB)
10958                         .setDebugAdId("debug-ad-id-4")
10959                         .setEnrollmentId("enrollment-id-1")
10960                         .build();
10961         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t1));
10962         Trigger t2 =
10963                 triggerBuilder
10964                         .setId("t2")
10965                         .setDestinationType(EventSurfaceType.WEB)
10966                         .setDebugAdId("debug-ad-id-5")
10967                         .setEnrollmentId("enrollment-id-2")
10968                         .build();
10969         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t2));
10970         Trigger t3 =
10971                 triggerBuilder
10972                         .setId("t3")
10973                         .setDestinationType(EventSurfaceType.WEB)
10974                         .setDebugAdId("debug-ad-id-6")
10975                         .setEnrollmentId("enrollment-id-2")
10976                         .build();
10977         mDatastoreManager.runInTransaction(dao -> dao.insertTrigger(t3));
10978 
10979         // Assertion
10980         assertTrue(
10981                 mDatastoreManager.runInTransaction(
10982                         dao ->
10983                                 assertEquals(
10984                                         2,
10985                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
10986                                                 "enrollment-id-1"))));
10987         assertTrue(
10988                 mDatastoreManager.runInTransaction(
10989                         dao ->
10990                                 assertEquals(
10991                                         4,
10992                                         dao.countDistinctDebugAdIdsUsedByEnrollment(
10993                                                 "enrollment-id-2"))));
10994     }
10995 
10996     @Test
getPendingAggregateReportIdsByCoordinatorInWindow()10997     public void getPendingAggregateReportIdsByCoordinatorInWindow() {
10998         AggregateReport ar11 =
10999                 AggregateReportFixture.getValidAggregateReportBuilder()
11000                         .setId("11")
11001                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11002                         .build();
11003         AggregateReport ar12 =
11004                 AggregateReportFixture.getValidAggregateReportBuilder()
11005                         .setId("12")
11006                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11007                         .build();
11008         AggregateReport ar21 =
11009                 AggregateReportFixture.getValidAggregateReportBuilder()
11010                         .setId("21")
11011                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11012                         .build();
11013         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11014         AbstractDbIntegrationTest.insertToDb(ar11, db);
11015         AbstractDbIntegrationTest.insertToDb(ar12, db);
11016         AbstractDbIntegrationTest.insertToDb(ar21, db);
11017 
11018         Optional<Map<String, List<String>>> resOpt =
11019                 mDatastoreManager.runInTransactionWithResult(
11020                         (dao) ->
11021                                 dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11022                                         AggregateReportFixture.ValidAggregateReportParams
11023                                                 .TRIGGER_TIME,
11024                                         AggregateReportFixture.ValidAggregateReportParams
11025                                                         .TRIGGER_TIME
11026                                                 + DAYS.toMillis(30)));
11027         assertTrue(resOpt.isPresent());
11028         Map<String, List<String>> res = resOpt.get();
11029         assertEquals(2, res.size());
11030 
11031         // URL 1
11032         List<String> url1Ids = res.get("https://url1.test");
11033         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11034         assertEquals(2, url1Ids.size());
11035         assertEquals("11", url1Ids.get(0));
11036         assertEquals("12", url1Ids.get(1));
11037         // URL 2
11038         List<String> url2Ids = res.get("https://url2.test");
11039         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11040         assertEquals(1, url2Ids.size());
11041         assertEquals("21", url2Ids.get(0));
11042     }
11043 
11044     @Test
getPendingAggregateReportIdsByCoordinatorInWindowWithRetryLimit()11045     public void getPendingAggregateReportIdsByCoordinatorInWindowWithRetryLimit() {
11046         // Mocking that the flags return a Max Retry of 1
11047         Flags mockFlags = Mockito.mock(Flags.class);
11048         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11049         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11050         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11051 
11052         AggregateReport ar11 =
11053                 AggregateReportFixture.getValidAggregateReportBuilder()
11054                         .setId("11")
11055                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11056                         .build();
11057         AggregateReport ar12 =
11058                 AggregateReportFixture.getValidAggregateReportBuilder()
11059                         .setId("12")
11060                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11061                         .build();
11062         AggregateReport ar21 =
11063                 AggregateReportFixture.getValidAggregateReportBuilder()
11064                         .setId("21")
11065                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11066                         .build();
11067         AggregateReport ar31 =
11068                 AggregateReportFixture.getValidAggregateReportBuilder()
11069                         .setId("31")
11070                         .setAggregationCoordinatorOrigin(Uri.parse("https://url3.test"))
11071                         .build();
11072         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11073         AbstractDbIntegrationTest.insertToDb(ar11, db);
11074         AbstractDbIntegrationTest.insertToDb(ar12, db);
11075         AbstractDbIntegrationTest.insertToDb(ar21, db);
11076         AbstractDbIntegrationTest.insertToDb(ar31, db);
11077 
11078         Optional<Map<String, List<String>>> resOpt =
11079                 mDatastoreManager.runInTransactionWithResult(
11080                         (dao) -> {
11081                             return dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11082                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME,
11083                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME
11084                                             + DAYS.toMillis(30));
11085                         });
11086         assertTrue(resOpt.isPresent());
11087         Map<String, List<String>> res = resOpt.get();
11088         assertEquals(3, res.size());
11089 
11090         // URL 1
11091         List<String> url1Ids = res.get("https://url1.test");
11092         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11093         assertEquals(2, url1Ids.size());
11094         assertEquals("11", url1Ids.get(0));
11095         assertEquals("12", url1Ids.get(1));
11096         // URL 2
11097         List<String> url2Ids = res.get("https://url2.test");
11098         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11099         assertEquals(1, url2Ids.size());
11100         assertEquals("21", url2Ids.get(0));
11101 
11102         resOpt =
11103                 mDatastoreManager.runInTransactionWithResult(
11104                         (dao) -> {
11105                             // Adds records to KeyValueData table for Retry Count.
11106                             dao.incrementAndGetReportingRetryCount(
11107                                     ar31.getId(), DataType.AGGREGATE_REPORT_RETRY_COUNT);
11108                             return dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11109                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME,
11110                                     AggregateReportFixture.ValidAggregateReportParams.TRIGGER_TIME
11111                                             + DAYS.toMillis(30));
11112                         });
11113         res = resOpt.get();
11114 
11115         assertEquals(2, res.size());
11116 
11117         // URL 1
11118         url1Ids = res.get("https://url1.test");
11119         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11120         assertEquals(2, url1Ids.size());
11121         assertEquals("11", url1Ids.get(0));
11122         assertEquals("12", url1Ids.get(1));
11123         // URL 2
11124         url2Ids = res.get("https://url2.test");
11125         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11126         assertEquals(1, url2Ids.size());
11127         assertEquals("21", url2Ids.get(0));
11128     }
11129 
11130     @Test
getPendingAggregateDebugReportIdsByCoordinator()11131     public void getPendingAggregateDebugReportIdsByCoordinator() {
11132         AggregateReport ar11 =
11133                 AggregateReportFixture.getValidAggregateReportBuilder()
11134                         .setId("11")
11135                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11136                         .build();
11137         AggregateReport ar12 =
11138                 AggregateReportFixture.getValidAggregateReportBuilder()
11139                         .setId("12")
11140                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11141                         .build();
11142         AggregateReport ar21 =
11143                 AggregateReportFixture.getValidAggregateReportBuilder()
11144                         .setId("21")
11145                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11146                         .build();
11147         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11148         AbstractDbIntegrationTest.insertToDb(ar11, db);
11149         AbstractDbIntegrationTest.insertToDb(ar12, db);
11150         AbstractDbIntegrationTest.insertToDb(ar21, db);
11151 
11152         Optional<Map<String, List<String>>> resOpt =
11153                 mDatastoreManager.runInTransactionWithResult(
11154                         (dao) ->
11155                                 dao.getPendingAggregateReportIdsByCoordinatorInWindow(
11156                                         AggregateReportFixture.ValidAggregateReportParams
11157                                                 .TRIGGER_TIME,
11158                                         AggregateReportFixture.ValidAggregateReportParams
11159                                                         .TRIGGER_TIME
11160                                                 + DAYS.toMillis(30)));
11161         assertTrue(resOpt.isPresent());
11162         Map<String, List<String>> res = resOpt.get();
11163         assertEquals(2, res.size());
11164 
11165         // URL 1
11166         List<String> url1Ids = res.get("https://url1.test");
11167         assertEquals(2, url1Ids.size());
11168         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11169         assertEquals("11", url1Ids.get(0));
11170         assertEquals("12", url1Ids.get(1));
11171         // URL 2
11172         List<String> url2Ids = res.get("https://url2.test");
11173         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11174         assertEquals(1, url2Ids.size());
11175         assertEquals("21", url2Ids.get(0));
11176     }
11177 
11178     @Test
getPendingAggregateDebugReportIdsByCoordinatorWithRetryLimit()11179     public void getPendingAggregateDebugReportIdsByCoordinatorWithRetryLimit() {
11180         // Mocking that the flags return a Max Retry of 1
11181         Flags mockFlags = Mockito.mock(Flags.class);
11182         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11183         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11184         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11185 
11186         AggregateReport ar11 =
11187                 AggregateReportFixture.getValidAggregateReportBuilder()
11188                         .setId("11")
11189                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11190                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11191                         .build();
11192         AggregateReport ar12 =
11193                 AggregateReportFixture.getValidAggregateReportBuilder()
11194                         .setId("12")
11195                         .setAggregationCoordinatorOrigin(Uri.parse("https://url1.test"))
11196                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11197                         .build();
11198         AggregateReport ar21 =
11199                 AggregateReportFixture.getValidAggregateReportBuilder()
11200                         .setId("21")
11201                         .setAggregationCoordinatorOrigin(Uri.parse("https://url2.test"))
11202                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11203                         .build();
11204         AggregateReport ar31 =
11205                 AggregateReportFixture.getValidAggregateReportBuilder()
11206                         .setId("31")
11207                         .setAggregationCoordinatorOrigin(Uri.parse("https://url3.test"))
11208                         .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
11209                         .build();
11210         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11211         AbstractDbIntegrationTest.insertToDb(ar11, db);
11212         AbstractDbIntegrationTest.insertToDb(ar12, db);
11213         AbstractDbIntegrationTest.insertToDb(ar21, db);
11214         AbstractDbIntegrationTest.insertToDb(ar31, db);
11215 
11216         Optional<Map<String, List<String>>> resOpt =
11217                 mDatastoreManager.runInTransactionWithResult(
11218                         (dao) -> dao.getPendingAggregateDebugReportIdsByCoordinator());
11219         assertTrue(resOpt.isPresent());
11220         Map<String, List<String>> res = resOpt.get();
11221         assertEquals(3, res.size());
11222 
11223         resOpt =
11224                 mDatastoreManager.runInTransactionWithResult(
11225                         (dao) -> {
11226                             // Adds records to KeyValueData table for Retry Count.
11227                             dao.incrementAndGetReportingRetryCount(
11228                                     ar31.getId(), DataType.AGGREGATE_REPORT_RETRY_COUNT);
11229                             return dao.getPendingAggregateDebugReportIdsByCoordinator();
11230                         });
11231         assertTrue(resOpt.isPresent());
11232         res = resOpt.get();
11233         assertEquals(2, res.size());
11234 
11235         // URL 1
11236         List<String> url1Ids = res.get("https://url1.test");
11237         assertEquals(2, url1Ids.size());
11238         url1Ids.sort(String.CASE_INSENSITIVE_ORDER);
11239         assertEquals("11", url1Ids.get(0));
11240         assertEquals("12", url1Ids.get(1));
11241         // URL 2
11242         List<String> url2Ids = res.get("https://url2.test");
11243         url2Ids.sort(String.CASE_INSENSITIVE_ORDER);
11244         assertEquals(1, url2Ids.size());
11245         assertEquals("21", url2Ids.get(0));
11246     }
11247 
11248     @Test
getPendingEventReportIdsInWindowWithRetry()11249     public void getPendingEventReportIdsInWindowWithRetry() {
11250         // Mocking that the flags return a Max Retry of 1
11251         Flags mockFlags = Mockito.mock(Flags.class);
11252         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11253         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11254         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11255 
11256         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11257 
11258         EventReport er1 =
11259                 generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1);
11260         EventReport er2 =
11261                 generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2);
11262         List.of(er1, er2)
11263                 .forEach(
11264                         eventReport -> {
11265                             ContentValues values = new ContentValues();
11266                             values.put(EventReportContract.ID, eventReport.getId());
11267                             values.put(
11268                                     EventReportContract.ATTRIBUTION_DESTINATION,
11269                                     eventReport.getAttributionDestinations().get(0).toString());
11270                             values.put(
11271                                     EventReportContract.REPORT_TIME,
11272                                     EventReportFixture.ValidEventReportParams.TRIGGER_TIME
11273                                             + DAYS.toMillis(15));
11274                             values.put(EventReportContract.STATUS, EventReport.Status.PENDING);
11275                             db.insert(EventReportContract.TABLE, null, values);
11276                         });
11277         Optional<List<String>> resOpt =
11278                 mDatastoreManager.runInTransactionWithResult(
11279                         (dao) ->
11280                                 dao.getPendingEventReportIdsInWindow(
11281                                         EventReportFixture.ValidEventReportParams.TRIGGER_TIME,
11282                                         EventReportFixture.ValidEventReportParams.TRIGGER_TIME
11283                                                 + DAYS.toMillis(30)));
11284         assertTrue(resOpt.isPresent());
11285         List<String> res = resOpt.get();
11286         assertEquals(2, res.size());
11287         assertTrue(res.containsAll(List.of("1", "2")));
11288         resOpt =
11289                 mDatastoreManager.runInTransactionWithResult(
11290                         (dao) -> {
11291                             // Adds records to KeyValueData table for Retry Count.
11292                             dao.incrementAndGetReportingRetryCount(
11293                                     "1", DataType.EVENT_REPORT_RETRY_COUNT);
11294                             return dao.getPendingEventReportIdsInWindow(
11295                                     EventReportFixture.ValidEventReportParams.TRIGGER_TIME,
11296                                     EventReportFixture.ValidEventReportParams.TRIGGER_TIME
11297                                             + DAYS.toMillis(30));
11298                         });
11299         res = resOpt.get();
11300         assertEquals(1, res.size());
11301         assertEquals(List.of("2"), res);
11302     }
11303 
11304     @Test
getPendingEventDebugReportIdsWithRetryLimit()11305     public void getPendingEventDebugReportIdsWithRetryLimit() {
11306         // Mocking that the flags return a Max Retry of 1
11307         Flags mockFlags = Mockito.mock(Flags.class);
11308         ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
11309         ExtendedMockito.doReturn(1).when(mockFlags).getMeasurementReportingRetryLimit();
11310         ExtendedMockito.doReturn(true).when(mockFlags).getMeasurementReportingRetryLimitEnabled();
11311 
11312         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11313 
11314         EventReport er1 =
11315                 generateMockEventReport(WebUtil.validUrl("https://destination-1.test"), 1);
11316         EventReport er2 =
11317                 generateMockEventReport(WebUtil.validUrl("https://destination-2.test"), 2);
11318         List.of(er1, er2)
11319                 .forEach(
11320                         eventReport -> {
11321                             ContentValues values = new ContentValues();
11322                             values.put(EventReportContract.ID, eventReport.getId());
11323                             values.put(
11324                                     EventReportContract.ATTRIBUTION_DESTINATION,
11325                                     eventReport.getAttributionDestinations().get(0).toString());
11326                             values.put(
11327                                     EventReportContract.DEBUG_REPORT_STATUS,
11328                                     EventReport.DebugReportStatus.PENDING);
11329                             db.insert(EventReportContract.TABLE, null, values);
11330                         });
11331 
11332         Optional<List<String>> resOpt =
11333                 mDatastoreManager.runInTransactionWithResult(
11334                         (dao) -> dao.getPendingDebugEventReportIds());
11335         assertTrue(resOpt.isPresent());
11336         List<String> res = resOpt.get();
11337         assertEquals(2, res.size());
11338         assertTrue(res.containsAll(List.of("1", "2")));
11339         resOpt =
11340                 mDatastoreManager.runInTransactionWithResult(
11341                         (dao) -> {
11342                             // Adds records to KeyValueData table for Retry Count.
11343                             dao.incrementAndGetReportingRetryCount(
11344                                     "1", DataType.DEBUG_EVENT_REPORT_RETRY_COUNT);
11345                             return dao.getPendingDebugEventReportIds();
11346                         });
11347         res = resOpt.get();
11348         assertEquals(1, res.size());
11349         assertEquals(List.of("2"), res);
11350     }
11351 
11352     @Test
getNonExpiredAggregateEncryptionKeys()11353     public void getNonExpiredAggregateEncryptionKeys() {
11354         AggregateEncryptionKey ek11 =
11355                 new AggregateEncryptionKey.Builder()
11356                         .setId("11")
11357                         .setKeyId("11")
11358                         .setPublicKey("11")
11359                         .setExpiry(11)
11360                         .setAggregationCoordinatorOrigin(Uri.parse("https://1coordinator.test"))
11361                         .build();
11362         // ek12 will not be fetched because expiry (5) < 10
11363         AggregateEncryptionKey ek12 =
11364                 new AggregateEncryptionKey.Builder()
11365                         .setId("12")
11366                         .setKeyId("12")
11367                         .setPublicKey("12")
11368                         .setExpiry(5)
11369                         .setAggregationCoordinatorOrigin(Uri.parse("https://1coordinator.test"))
11370                         .build();
11371 
11372         AggregateEncryptionKey ek21 =
11373                 new AggregateEncryptionKey.Builder()
11374                         .setId("21")
11375                         .setKeyId("21")
11376                         .setPublicKey("21")
11377                         .setExpiry(10)
11378                         .setAggregationCoordinatorOrigin(Uri.parse("https://2coordinator.test"))
11379                         .build();
11380         AggregateEncryptionKey ek22 =
11381                 new AggregateEncryptionKey.Builder()
11382                         .setId("22")
11383                         .setKeyId("22")
11384                         .setPublicKey("22")
11385                         .setExpiry(15)
11386                         .setAggregationCoordinatorOrigin(Uri.parse("https://2coordinator.test"))
11387                         .build();
11388 
11389         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
11390         AbstractDbIntegrationTest.insertToDb(ek11, db);
11391         AbstractDbIntegrationTest.insertToDb(ek12, db);
11392         AbstractDbIntegrationTest.insertToDb(ek21, db);
11393         AbstractDbIntegrationTest.insertToDb(ek22, db);
11394 
11395         List<AggregateEncryptionKey> res1 =
11396                 mDatastoreManager
11397                         .runInTransactionWithResult(
11398                                 (dao) -> {
11399                                     return dao.getNonExpiredAggregateEncryptionKeys(
11400                                             Uri.parse("https://1coordinator.test"), 10);
11401                                 })
11402                         .orElseThrow();
11403         // ek12 will not be fetched because expiry (5) < 10
11404         assertEquals(1, res1.size());
11405         assertEquals(ek11, res1.get(0));
11406 
11407         List<AggregateEncryptionKey> res2 =
11408                 mDatastoreManager
11409                         .runInTransactionWithResult(
11410                                 (dao) -> {
11411                                     return dao.getNonExpiredAggregateEncryptionKeys(
11412                                             Uri.parse("https://2coordinator.test"), 10);
11413                                 })
11414                         .orElseThrow();
11415         res1.sort((x, y) -> String.CASE_INSENSITIVE_ORDER.compare(x.getId(), y.getId()));
11416         assertEquals(2, res2.size());
11417         assertEquals(ek21, res2.get(0));
11418         assertEquals(ek22, res2.get(1));
11419     }
11420 
11421     @Test
incrementReportRetryIncrements()11422     public void incrementReportRetryIncrements() {
11423         final String eventId = "TestIdEvent";
11424         final String aggregateId = "TestIdAggregate";
11425         final String debugId = "TestIdDebug";
11426 
11427         mDatastoreManager.runInTransaction(
11428                 (dao) -> {
11429                     dao.incrementAndGetReportingRetryCount(
11430                             eventId, DataType.EVENT_REPORT_RETRY_COUNT);
11431                     dao.incrementAndGetReportingRetryCount(
11432                             eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT);
11433                     dao.incrementAndGetReportingRetryCount(
11434                             aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT);
11435                     dao.incrementAndGetReportingRetryCount(
11436                             aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT);
11437                     dao.incrementAndGetReportingRetryCount(
11438                             debugId, DataType.DEBUG_REPORT_RETRY_COUNT);
11439                 });
11440         Optional<KeyValueData> eventCount =
11441                 mDatastoreManager.runInTransactionWithResult(
11442                         (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT));
11443         Optional<KeyValueData> debugEventCount =
11444                 mDatastoreManager.runInTransactionWithResult(
11445                         (dao) ->
11446                                 dao.getKeyValueData(
11447                                         eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT));
11448         Optional<KeyValueData> aggregateCount =
11449                 mDatastoreManager.runInTransactionWithResult(
11450                         (dao) ->
11451                                 dao.getKeyValueData(
11452                                         aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT));
11453         Optional<KeyValueData> debugAggregateCount =
11454                 mDatastoreManager.runInTransactionWithResult(
11455                         (dao) ->
11456                                 dao.getKeyValueData(
11457                                         aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT));
11458         Optional<KeyValueData> debugCount =
11459                 mDatastoreManager.runInTransactionWithResult(
11460                         (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT));
11461 
11462         assertTrue(eventCount.isPresent());
11463         assertEquals(1, (eventCount.get().getReportRetryCount()));
11464         assertTrue(debugEventCount.isPresent());
11465         assertEquals(1, (debugEventCount.get().getReportRetryCount()));
11466         assertTrue(aggregateCount.isPresent());
11467         assertEquals(1, (aggregateCount.get().getReportRetryCount()));
11468         assertTrue(debugAggregateCount.isPresent());
11469         assertEquals(1, (debugAggregateCount.get().getReportRetryCount()));
11470         assertTrue(debugCount.isPresent());
11471         assertEquals(1, (debugCount.get().getReportRetryCount()));
11472 
11473         mDatastoreManager.runInTransaction(
11474                 (dao) -> {
11475                     dao.incrementAndGetReportingRetryCount(
11476                             eventId, DataType.EVENT_REPORT_RETRY_COUNT);
11477                     dao.incrementAndGetReportingRetryCount(
11478                             aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT);
11479                 });
11480         eventCount =
11481                 mDatastoreManager.runInTransactionWithResult(
11482                         (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT));
11483         aggregateCount =
11484                 mDatastoreManager.runInTransactionWithResult(
11485                         (dao) ->
11486                                 dao.getKeyValueData(
11487                                         aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT));
11488         debugEventCount =
11489                 mDatastoreManager.runInTransactionWithResult(
11490                         (dao) ->
11491                                 dao.getKeyValueData(
11492                                         eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT));
11493         debugAggregateCount =
11494                 mDatastoreManager.runInTransactionWithResult(
11495                         (dao) ->
11496                                 dao.getKeyValueData(
11497                                         aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT));
11498         debugCount =
11499                 mDatastoreManager.runInTransactionWithResult(
11500                         (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT));
11501 
11502         assertTrue(eventCount.isPresent());
11503         assertEquals(2, (eventCount.get().getReportRetryCount()));
11504         assertTrue(debugEventCount.isPresent());
11505         assertEquals(1, (debugEventCount.get().getReportRetryCount()));
11506         assertTrue(aggregateCount.isPresent());
11507         assertEquals(2, (aggregateCount.get().getReportRetryCount()));
11508         assertTrue(debugAggregateCount.isPresent());
11509         assertEquals(1, (debugAggregateCount.get().getReportRetryCount()));
11510         assertTrue(debugCount.isPresent());
11511         assertEquals(1, (debugCount.get().getReportRetryCount()));
11512 
11513         mDatastoreManager.runInTransaction(
11514                 (dao) -> {
11515                     dao.incrementAndGetReportingRetryCount(
11516                             eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT);
11517                     dao.incrementAndGetReportingRetryCount(
11518                             aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT);
11519                     dao.incrementAndGetReportingRetryCount(
11520                             debugId, DataType.DEBUG_REPORT_RETRY_COUNT);
11521                 });
11522         eventCount =
11523                 mDatastoreManager.runInTransactionWithResult(
11524                         (dao) -> dao.getKeyValueData(eventId, DataType.EVENT_REPORT_RETRY_COUNT));
11525         aggregateCount =
11526                 mDatastoreManager.runInTransactionWithResult(
11527                         (dao) ->
11528                                 dao.getKeyValueData(
11529                                         aggregateId, DataType.AGGREGATE_REPORT_RETRY_COUNT));
11530         debugEventCount =
11531                 mDatastoreManager.runInTransactionWithResult(
11532                         (dao) ->
11533                                 dao.getKeyValueData(
11534                                         eventId, DataType.DEBUG_EVENT_REPORT_RETRY_COUNT));
11535         debugAggregateCount =
11536                 mDatastoreManager.runInTransactionWithResult(
11537                         (dao) ->
11538                                 dao.getKeyValueData(
11539                                         aggregateId, DataType.DEBUG_AGGREGATE_REPORT_RETRY_COUNT));
11540         debugCount =
11541                 mDatastoreManager.runInTransactionWithResult(
11542                         (dao) -> dao.getKeyValueData(debugId, DataType.DEBUG_REPORT_RETRY_COUNT));
11543 
11544         assertTrue(eventCount.isPresent());
11545         assertEquals(2, (eventCount.get().getReportRetryCount()));
11546         assertTrue(debugEventCount.isPresent());
11547         assertEquals(2, (debugEventCount.get().getReportRetryCount()));
11548         assertTrue(aggregateCount.isPresent());
11549         assertEquals(2, (aggregateCount.get().getReportRetryCount()));
11550         assertTrue(debugAggregateCount.isPresent());
11551         assertEquals(2, (debugAggregateCount.get().getReportRetryCount()));
11552         assertTrue(debugCount.isPresent());
11553         assertEquals(2, (debugCount.get().getReportRetryCount()));
11554     }
11555 
11556     @Test
countNavigationSourcesPerReportingOriginQuery()11557     public void countNavigationSourcesPerReportingOriginQuery() {
11558         final String registrationId1 = "registrationId1";
11559         final String registrationId2 = "registrationId2";
11560         Source source1 =
11561                 SourceFixture.getValidSourceBuilder()
11562                         .setRegistrationId(registrationId1)
11563                         .setSourceType(Source.SourceType.NAVIGATION)
11564                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
11565                         .build();
11566         Source source2 =
11567                 SourceFixture.getValidSourceBuilder()
11568                         .setRegistrationId(registrationId1)
11569                         .setSourceType(Source.SourceType.EVENT)
11570                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
11571                         .build();
11572         Source source3 =
11573                 SourceFixture.getValidSourceBuilder()
11574                         .setRegistrationId(registrationId1)
11575                         .setSourceType(Source.SourceType.NAVIGATION)
11576                         .setRegistrationOrigin(REGISTRATION_ORIGIN)
11577                         .build();
11578         Source source4 =
11579                 SourceFixture.getValidSourceBuilder()
11580                         .setRegistrationId(registrationId2)
11581                         .setSourceType(Source.SourceType.EVENT)
11582                         .setRegistrationOrigin(REGISTRATION_ORIGIN_2)
11583                         .build();
11584         Source source5 =
11585                 SourceFixture.getValidSourceBuilder()
11586                         .setRegistrationId(registrationId2)
11587                         .setSourceType(Source.SourceType.NAVIGATION)
11588                         .setRegistrationOrigin(REGISTRATION_ORIGIN_2)
11589                         .build();
11590         Arrays.asList(source1, source2, source3, source4, source5).stream()
11591                 .forEach(source -> insertSource(source));
11592         assertThat(
11593                         mDatastoreManager.runInTransactionWithResult(
11594                                 (dao) ->
11595                                         dao.countNavigationSourcesPerReportingOrigin(
11596                                                 REGISTRATION_ORIGIN, registrationId1)))
11597                 .isEqualTo(Optional.of(2L));
11598         assertThat(
11599                         mDatastoreManager.runInTransactionWithResult(
11600                                 (dao) ->
11601                                         dao.countNavigationSourcesPerReportingOrigin(
11602                                                 REGISTRATION_ORIGIN_2, registrationId2)))
11603                 .isEqualTo(Optional.of(1L));
11604         assertThat(
11605                         mDatastoreManager.runInTransactionWithResult(
11606                                 (dao) ->
11607                                         dao.countNavigationSourcesPerReportingOrigin(
11608                                                 REGISTRATION_ORIGIN, registrationId2)))
11609                 .isEqualTo(Optional.of(0L));
11610     }
11611 
verifySourceStatus(@onNull Source source, @Source.Status int status)11612     private void verifySourceStatus(@NonNull Source source, @Source.Status int status) {
11613         assertThat(
11614                         mDatastoreManager
11615                                 .runInTransactionWithResult(
11616                                         measurementDao -> measurementDao.getSource(source.getId()))
11617                                 .get()
11618                                 .getStatus())
11619                 .isEqualTo(status);
11620     }
11621 
11622     @Test
11623     public void
testUpdateSourcesForAttributionScope_diffMaxViewStates_ignoresSourcesDeletesReports()11624             testUpdateSourcesForAttributionScope_diffMaxViewStates_ignoresSourcesDeletesReports() {
11625         mFlags = mock(Flags.class);
11626         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
11627         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
11628         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
11629 
11630         Source source1 =
11631                 insertSourceForAttributionScope(
11632                         List.of("1"),
11633                         ATTRIBUTION_SCOPE_LIMIT,
11634                         MAX_EVENT_STATES,
11635                         SOURCE_EVENT_TIME,
11636                         List.of(WEB_ONE_DESTINATION),
11637                         null);
11638         EventReport pastFakeEventReport =
11639                 new EventReport.Builder()
11640                         .setId("1")
11641                         .setSourceId(source1.getId())
11642                         .setSourceEventId(source1.getEventId())
11643                         .setReportTime(SOURCE_EVENT_TIME)
11644                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
11645                         .setTriggerTime(SOURCE_EVENT_TIME)
11646                         .setSourceType(source1.getSourceType())
11647                         .setStatus(EventReport.Status.PENDING)
11648                         .setRegistrationOrigin(source1.getRegistrationOrigin())
11649                         .build();
11650         EventReport fakeEventReport1 =
11651                 new EventReport.Builder()
11652                         .setId("2")
11653                         .setSourceId(source1.getId())
11654                         .setSourceEventId(source1.getEventId())
11655                         .setReportTime(SOURCE_EVENT_TIME + 1000)
11656                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
11657                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
11658                         .setSourceType(source1.getSourceType())
11659                         .setStatus(EventReport.Status.PENDING)
11660                         .setRegistrationOrigin(source1.getRegistrationOrigin())
11661                         .build();
11662         // Deleted fake event report for comparison.
11663         EventReport deletedFakeEventReport1 =
11664                 new EventReport.Builder()
11665                         .setId("3")
11666                         .setSourceId(source1.getId())
11667                         .setSourceEventId(source1.getEventId())
11668                         .setReportTime(SOURCE_EVENT_TIME + 1000)
11669                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
11670                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
11671                         .setSourceType(source1.getSourceType())
11672                         .setStatus(EventReport.Status.MARKED_TO_DELETE)
11673                         .setRegistrationOrigin(source1.getRegistrationOrigin())
11674                         .build();
11675         mDatastoreManager.runInTransaction(
11676                 (dao) -> {
11677                     dao.insertEventReport(pastFakeEventReport);
11678                     dao.insertEventReport(fakeEventReport1);
11679                     dao.updateSourcesForAttributionScope(source1);
11680                 });
11681         Arrays.asList(source1).stream()
11682                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11683         assertThat(
11684                         mDatastoreManager
11685                                 .runInTransactionWithResult(
11686                                         measurementDao ->
11687                                                 measurementDao.getSourceEventReports(source1))
11688                                 .get())
11689                 .containsExactly(pastFakeEventReport, fakeEventReport1);
11690 
11691         Source source2 =
11692                 insertSourceForAttributionScope(
11693                         List.of("2"),
11694                         ATTRIBUTION_SCOPE_LIMIT,
11695                         MAX_EVENT_STATES + 1,
11696                         SOURCE_EVENT_TIME + 1,
11697                         List.of(WEB_ONE_DESTINATION),
11698                         null);
11699         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
11700         Arrays.asList(source1).stream()
11701                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11702         Arrays.asList(source2).stream()
11703                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11704         assertThat(
11705                         mDatastoreManager
11706                                 .runInTransactionWithResult(
11707                                         measurementDao ->
11708                                                 measurementDao.getSourceEventReports(source1))
11709                                 .get())
11710                 .containsExactly(pastFakeEventReport, deletedFakeEventReport1);
11711 
11712         Source source3 =
11713                 insertSourceForAttributionScope(
11714                         List.of("3"),
11715                         ATTRIBUTION_SCOPE_LIMIT,
11716                         MAX_EVENT_STATES,
11717                         SOURCE_EVENT_TIME + 2,
11718                         List.of(WEB_TWO_DESTINATION),
11719                         null);
11720         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
11721         Arrays.asList(source1).stream()
11722                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11723         Arrays.asList(source2, source3).stream()
11724                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11725 
11726         Source source4 =
11727                 insertSourceForAttributionScope(
11728                         List.of("5"),
11729                         ATTRIBUTION_SCOPE_LIMIT,
11730                         MAX_EVENT_STATES,
11731                         SOURCE_EVENT_TIME + 3,
11732                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11733                         null);
11734         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
11735         Arrays.asList(source1, source2).stream()
11736                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11737         Arrays.asList(source3, source4).stream()
11738                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11739 
11740         Source source5 =
11741                 insertSourceForAttributionScope(
11742                         List.of("4"),
11743                         ATTRIBUTION_SCOPE_LIMIT,
11744                         MAX_EVENT_STATES + 1,
11745                         SOURCE_EVENT_TIME + 4,
11746                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11747                         null);
11748         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source5));
11749         Arrays.asList(source1, source2, source3, source4).stream()
11750                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11751         Arrays.asList(source5).stream()
11752                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11753 
11754         // Sources for different reporting origin with different max event states.
11755         Source source6 =
11756                 insertSourceForAttributionScope(
11757                         List.of("4"),
11758                         ATTRIBUTION_SCOPE_LIMIT,
11759                         MAX_EVENT_STATES,
11760                         SOURCE_EVENT_TIME + 5,
11761                         List.of(WEB_ONE_DESTINATION),
11762                         null,
11763                         REGISTRATION_ORIGIN_2);
11764         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source6));
11765         Arrays.asList(source1, source2, source3, source4).stream()
11766                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11767         Arrays.asList(source5, source6).stream()
11768                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11769 
11770         // Sources for different reporting origin with the same max event states.
11771         Source source7 =
11772                 insertSourceForAttributionScope(
11773                         List.of("4"),
11774                         ATTRIBUTION_SCOPE_LIMIT + 1,
11775                         MAX_EVENT_STATES,
11776                         SOURCE_EVENT_TIME + 5,
11777                         List.of(WEB_TWO_DESTINATION),
11778                         null,
11779                         REGISTRATION_ORIGIN_2);
11780         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source7));
11781         Arrays.asList(source1, source2, source3, source4).stream()
11782                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11783         Arrays.asList(source5, source6, source7).stream()
11784                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11785 
11786         Source source8 =
11787                 insertSourceForAttributionScope(
11788                         List.of("4"),
11789                         ATTRIBUTION_SCOPE_LIMIT,
11790                         MAX_EVENT_STATES + 1,
11791                         SOURCE_EVENT_TIME + 5,
11792                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11793                         null);
11794         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source8));
11795         Arrays.asList(source1, source2, source3, source4).stream()
11796                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11797         Arrays.asList(source5, source6, source7, source8).stream()
11798                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11799     }
11800 
11801     @Test
testUpdateSourcesForAttributionScope_smallerLimit_ignoresSourcesDeletesReports()11802     public void testUpdateSourcesForAttributionScope_smallerLimit_ignoresSourcesDeletesReports() {
11803         mFlags = mock(Flags.class);
11804         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
11805         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
11806         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
11807         Source source1 =
11808                 insertSourceForAttributionScope(
11809                         List.of("1"),
11810                         ATTRIBUTION_SCOPE_LIMIT,
11811                         MAX_EVENT_STATES,
11812                         SOURCE_EVENT_TIME,
11813                         List.of(WEB_ONE_DESTINATION),
11814                         null);
11815         EventReport pastFakeEventReport =
11816                 new EventReport.Builder()
11817                         .setId("1")
11818                         .setSourceId(source1.getId())
11819                         .setSourceEventId(source1.getEventId())
11820                         .setReportTime(SOURCE_EVENT_TIME)
11821                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
11822                         .setTriggerTime(SOURCE_EVENT_TIME)
11823                         .setSourceType(source1.getSourceType())
11824                         .setStatus(EventReport.Status.PENDING)
11825                         .setRegistrationOrigin(source1.getRegistrationOrigin())
11826                         .build();
11827         EventReport fakeEventReport1 =
11828                 new EventReport.Builder()
11829                         .setId("2")
11830                         .setSourceId(source1.getId())
11831                         .setSourceEventId(source1.getEventId())
11832                         .setReportTime(SOURCE_EVENT_TIME + 1000)
11833                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
11834                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
11835                         .setSourceType(source1.getSourceType())
11836                         .setStatus(EventReport.Status.PENDING)
11837                         .setRegistrationOrigin(source1.getRegistrationOrigin())
11838                         .build();
11839         // Delete fake event report for comparison.
11840         EventReport deletedFakeEventReport1 =
11841                 new EventReport.Builder()
11842                         .setId("3")
11843                         .setSourceId(source1.getId())
11844                         .setSourceEventId(source1.getEventId())
11845                         .setReportTime(SOURCE_EVENT_TIME + 1000)
11846                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
11847                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
11848                         .setSourceType(source1.getSourceType())
11849                         .setStatus(EventReport.Status.MARKED_TO_DELETE)
11850                         .setRegistrationOrigin(source1.getRegistrationOrigin())
11851                         .build();
11852         mDatastoreManager.runInTransaction(
11853                 (dao) -> {
11854                     dao.insertEventReport(pastFakeEventReport);
11855                     dao.insertEventReport(fakeEventReport1);
11856                     dao.updateSourcesForAttributionScope(source1);
11857                 });
11858         Arrays.asList(source1).stream()
11859                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11860         assertThat(
11861                         mDatastoreManager
11862                                 .runInTransactionWithResult(
11863                                         measurementDao ->
11864                                                 measurementDao.getSourceEventReports(source1))
11865                                 .get())
11866                 .containsExactly(pastFakeEventReport, fakeEventReport1);
11867 
11868         Source source2 =
11869                 insertSourceForAttributionScope(
11870                         List.of("2"),
11871                         /* attributionScopeLimit= */ 8L,
11872                         MAX_EVENT_STATES,
11873                         SOURCE_EVENT_TIME + 1,
11874                         List.of(WEB_ONE_DESTINATION),
11875                         null);
11876         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
11877         Arrays.asList(source1).stream()
11878                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11879         Arrays.asList(source2).stream()
11880                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11881         assertThat(
11882                         mDatastoreManager
11883                                 .runInTransactionWithResult(
11884                                         measurementDao ->
11885                                                 measurementDao.getSourceEventReports(source1))
11886                                 .get())
11887                 .containsExactly(pastFakeEventReport, deletedFakeEventReport1);
11888 
11889         Source source3 =
11890                 insertSourceForAttributionScope(
11891                         List.of("3"),
11892                         /* attributionScopeLimit= */ 4L,
11893                         MAX_EVENT_STATES,
11894                         SOURCE_EVENT_TIME + 2,
11895                         List.of(WEB_TWO_DESTINATION),
11896                         null);
11897         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
11898         Arrays.asList(source1).stream()
11899                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11900         Arrays.asList(source2, source3).stream()
11901                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11902 
11903         Source source4 =
11904                 insertSourceForAttributionScope(
11905                         List.of("3"),
11906                         /* attributionScopeLimit= */ 7L,
11907                         MAX_EVENT_STATES,
11908                         SOURCE_EVENT_TIME + 3,
11909                         List.of(WEB_TWO_DESTINATION),
11910                         null);
11911         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
11912         Arrays.asList(source1, source3).stream()
11913                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11914         Arrays.asList(source2, source4).stream()
11915                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11916 
11917         Source source5 =
11918                 insertSourceForAttributionScope(
11919                         List.of("3"),
11920                         /* attributionScopeLimit= */ 4L,
11921                         MAX_EVENT_STATES,
11922                         SOURCE_EVENT_TIME + 2,
11923                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11924                         null);
11925         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source5));
11926         Arrays.asList(source1, source3).stream()
11927                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11928         Arrays.asList(source2, source4, source5).stream()
11929                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11930 
11931         Source source6 =
11932                 insertSourceForAttributionScope(
11933                         List.of("3"),
11934                         /* attributionScopeLimit= */ 6L,
11935                         MAX_EVENT_STATES,
11936                         SOURCE_EVENT_TIME + 2,
11937                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11938                         null);
11939         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source6));
11940         Arrays.asList(source1, source3, source5).stream()
11941                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11942         Arrays.asList(source2, source4, source6).stream()
11943                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11944 
11945         // Sources for different reporting origin with different max event states.
11946         Source source7 =
11947                 insertSourceForAttributionScope(
11948                         List.of("4"),
11949                         /* attributionScopeLimit= */ 6L,
11950                         MAX_EVENT_STATES,
11951                         SOURCE_EVENT_TIME + 3,
11952                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11953                         null,
11954                         REGISTRATION_ORIGIN_2);
11955         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source7));
11956         Arrays.asList(source1, source3, source5).stream()
11957                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11958         Arrays.asList(source2, source4, source6, source7).stream()
11959                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11960 
11961         // Sources for different reporting origin with the same max event states.
11962         Source source8 =
11963                 insertSourceForAttributionScope(
11964                         List.of("4"),
11965                         /* attributionScopeLimit= */ 4L,
11966                         MAX_EVENT_STATES,
11967                         SOURCE_EVENT_TIME + 3,
11968                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11969                         null,
11970                         REGISTRATION_ORIGIN_2);
11971         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source8));
11972         Arrays.asList(source1, source3, source5).stream()
11973                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11974         Arrays.asList(source2, source4, source6, source7, source8).stream()
11975                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11976 
11977         Source source9 =
11978                 insertSourceForAttributionScope(
11979                         List.of("4"),
11980                         /* attributionScopeLimit= */ 5L,
11981                         MAX_EVENT_STATES,
11982                         SOURCE_EVENT_TIME + 4,
11983                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION),
11984                         null);
11985         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source9));
11986         Arrays.stream(new Source[] {source1, source3, source5})
11987                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
11988         Arrays.stream(new Source[] {source2, source4, source6, source7, source8, source9})
11989                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
11990     }
11991 
11992     @Test
11993     public void
testUpdateSourcesForAttributionScope_scopedSource_ignoresNonScopedAndDeletesReports()11994             testUpdateSourcesForAttributionScope_scopedSource_ignoresNonScopedAndDeletesReports() {
11995         mFlags = mock(Flags.class);
11996         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
11997         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
11998         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
11999 
12000         Source source1 =
12001                 insertSourceForAttributionScope(
12002                         null, null, null, SOURCE_EVENT_TIME, List.of(WEB_ONE_DESTINATION), null);
12003         EventReport pastFakeEventReport =
12004                 new EventReport.Builder()
12005                         .setId("1")
12006                         .setSourceId(source1.getId())
12007                         .setSourceEventId(source1.getEventId())
12008                         .setReportTime(SOURCE_EVENT_TIME)
12009                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12010                         .setTriggerTime(SOURCE_EVENT_TIME)
12011                         .setSourceType(source1.getSourceType())
12012                         .setStatus(EventReport.Status.PENDING)
12013                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12014                         .build();
12015         EventReport fakeEventReport1 =
12016                 new EventReport.Builder()
12017                         .setId("2")
12018                         .setSourceId(source1.getId())
12019                         .setSourceEventId(source1.getEventId())
12020                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12021                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12022                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12023                         .setSourceType(source1.getSourceType())
12024                         .setStatus(EventReport.Status.PENDING)
12025                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12026                         .build();
12027         // Deleted fake event report for comparison.
12028         EventReport deletedFakeEventReport1 =
12029                 new EventReport.Builder()
12030                         .setId("3")
12031                         .setSourceId(source1.getId())
12032                         .setSourceEventId(source1.getEventId())
12033                         .setReportTime(SOURCE_EVENT_TIME + 1000)
12034                         .setAttributionDestinations(List.of(WEB_ONE_DESTINATION))
12035                         .setTriggerTime(SOURCE_EVENT_TIME + 1000)
12036                         .setSourceType(source1.getSourceType())
12037                         .setStatus(EventReport.Status.MARKED_TO_DELETE)
12038                         .setRegistrationOrigin(source1.getRegistrationOrigin())
12039                         .build();
12040         mDatastoreManager.runInTransaction(
12041                 (dao) -> {
12042                     dao.insertEventReport(pastFakeEventReport);
12043                     dao.insertEventReport(fakeEventReport1);
12044                     dao.updateSourcesForAttributionScope(source1);
12045                 });
12046         Arrays.asList(source1).stream()
12047                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12048         assertThat(
12049                         mDatastoreManager
12050                                 .runInTransactionWithResult(
12051                                         measurementDao ->
12052                                                 measurementDao.getSourceEventReports(source1))
12053                                 .get())
12054                 .containsExactly(pastFakeEventReport, fakeEventReport1);
12055 
12056         Source source2 =
12057                 insertSourceForAttributionScope(
12058                         List.of("2"),
12059                         ATTRIBUTION_SCOPE_LIMIT,
12060                         MAX_EVENT_STATES,
12061                         SOURCE_EVENT_TIME + 1,
12062                         List.of(WEB_ONE_DESTINATION),
12063                         null);
12064         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12065         Arrays.asList(source1).stream()
12066                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12067         Arrays.asList(source2).stream()
12068                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12069         assertThat(
12070                         mDatastoreManager
12071                                 .runInTransactionWithResult(
12072                                         measurementDao ->
12073                                                 measurementDao.getSourceEventReports(source1))
12074                                 .get())
12075                 .containsExactly(pastFakeEventReport, deletedFakeEventReport1);
12076 
12077         Source source3 =
12078                 insertSourceForAttributionScope(
12079                         List.of("3"),
12080                         ATTRIBUTION_SCOPE_LIMIT,
12081                         MAX_EVENT_STATES,
12082                         SOURCE_EVENT_TIME + 2,
12083                         List.of(WEB_TWO_DESTINATION),
12084                         null);
12085         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12086         Arrays.asList(source1).stream()
12087                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12088         Arrays.asList(source2, source3).stream()
12089                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12090     }
12091 
12092     @Test
testUpdateSourcesForAttributionScope_newNonScopedSource_removesScopes()12093     public void testUpdateSourcesForAttributionScope_newNonScopedSource_removesScopes() {
12094         mFlags = mock(Flags.class);
12095         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
12096         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
12097         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
12098         Consumer<? super Source> verifyAttributionScopeEmptyFn =
12099                 source -> {
12100                     assertThat(
12101                                     mDatastoreManager
12102                                             .runInTransactionWithResult(
12103                                                     measurementDao ->
12104                                                             measurementDao
12105                                                                     .getSourceAttributionScopes(
12106                                                                             source.getId()))
12107                                             .get())
12108                             .isEmpty();
12109                     Source savedSource =
12110                             mDatastoreManager
12111                                     .runInTransactionWithResult(
12112                                             measurementDao ->
12113                                                     measurementDao.getSource(source.getId()))
12114                                     .get();
12115                     assertThat(savedSource.getAttributionScopeLimit()).isNull();
12116                     assertThat(savedSource.getMaxEventStates()).isNull();
12117                 };
12118 
12119         Consumer<? super Source> verifyAttributionScopeUnchangedFn =
12120                 source -> {
12121                     assertThat(
12122                                     mDatastoreManager
12123                                             .runInTransactionWithResult(
12124                                                     measurementDao ->
12125                                                             measurementDao
12126                                                                     .getSourceAttributionScopes(
12127                                                                             source.getId()))
12128                                             .get())
12129                             .containsExactlyElementsIn(source.getAttributionScopes());
12130                     Source savedSource =
12131                             mDatastoreManager
12132                                     .runInTransactionWithResult(
12133                                             measurementDao ->
12134                                                     measurementDao.getSource(source.getId()))
12135                                     .get();
12136                     assertThat(savedSource.getAttributionScopeLimit())
12137                             .isEqualTo(savedSource.getAttributionScopeLimit());
12138                     assertThat(savedSource.getMaxEventStates())
12139                             .isEqualTo(savedSource.getMaxEventStates());
12140                 };
12141 
12142         Source source1 =
12143                 insertSourceForAttributionScope(
12144                         List.of("1"),
12145                         ATTRIBUTION_SCOPE_LIMIT,
12146                         MAX_EVENT_STATES,
12147                         SOURCE_EVENT_TIME,
12148                         List.of(WEB_ONE_DESTINATION),
12149                         null);
12150         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source1));
12151         Arrays.asList(source1).stream()
12152                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12153         Arrays.asList(source1).stream().forEach(verifyAttributionScopeUnchangedFn);
12154 
12155         Source source2 =
12156                 insertSourceForAttributionScope(
12157                         List.of("2"),
12158                         ATTRIBUTION_SCOPE_LIMIT,
12159                         MAX_EVENT_STATES,
12160                         SOURCE_EVENT_TIME + 1,
12161                         List.of(WEB_ONE_DESTINATION),
12162                         null);
12163         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12164         Arrays.asList(source1, source2).stream()
12165                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12166         Arrays.asList(source1, source2).stream().forEach(verifyAttributionScopeUnchangedFn);
12167 
12168         Source source3 =
12169                 insertSourceForAttributionScope(
12170                         null,
12171                         null,
12172                         null,
12173                         SOURCE_EVENT_TIME + 2,
12174                         List.of(WEB_TWO_DESTINATION),
12175                         null);
12176         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12177         Arrays.asList(source1, source2, source3).stream()
12178                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12179         // Source3 is for a different destination, attribution scopes should not be cleared.
12180         Arrays.asList(source1, source2).stream().forEach(verifyAttributionScopeUnchangedFn);
12181 
12182         Source source4 =
12183                 insertSourceForAttributionScope(
12184                         null,
12185                         null,
12186                         null,
12187                         SOURCE_EVENT_TIME + 3,
12188                         List.of(WEB_ONE_DESTINATION),
12189                         null);
12190         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
12191         Arrays.asList(source1, source2, source3, source4).stream()
12192                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12193         // Source4 is the same destination, clear attribution scopes for older sources.
12194         Arrays.asList(source1, source2).stream().forEach(verifyAttributionScopeEmptyFn);
12195     }
12196 
12197     @Test
testUpdateSourcesForAttributionScope_scopesNotSelected_ignoreSources()12198     public void testUpdateSourcesForAttributionScope_scopesNotSelected_ignoreSources() {
12199         mFlags = mock(Flags.class);
12200         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
12201         doReturn(true).when(mFlags).getMeasurementEnableAttributionScope();
12202         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
12203         // Below are the sources registered with attribution scopes and destinations.
12204         // For each destination, two sources are registered, and only one's scopes are to be
12205         // deleted.
12206         // For registration R1:
12207         // S1: attribution scopes -> [0, ""], destinations -> [D1]
12208         // S2: attribution scopes -> [3, 4, 5], destinations -> [D1]
12209         // S3: attribution scopes -> [0, 1], destinations -> [D2]
12210         // S4: attribution scopes -> [1, 3], destinations -> [D2]
12211         // S5: attribution scopes -> [1, 2], destinations -> [D3]
12212         // S6: attribution scopes -> [2, 3, 4], destinations -> [D3]
12213         // S7: attribution scopes -> [2], destinations -> [D4]
12214         // S8: attribution scopes -> [1, 2], destinations -> [D4], shares same timestamp as S7.
12215         // S12: attribution scopes -> [3, 4], destinations -> [D1, D2, D3, D4]
12216         // For registration R2 to test interplay cross reporting origin:
12217         // If the reporting origin were R1, the attribution scopes for S9, S10, and S11 would
12218         // have been removed.
12219         // S9: attribution scopes -> [0], destinations -> [D1]
12220         // S10: attribution scopes -> [0, 1], destinations -> [D2]
12221         // S11: attribution scopes -> [1, 2], destinations -> [D3]
12222         // The selected attribution scopes for each destination are:
12223         // D1: [3, 4, 5] => Scope for S1 to be removed.
12224         // D2: [1, 3, 4] => Scope for S3 to be removed.
12225         // D3: [2, 3, 4] => Scope for S5 to be removed.
12226         // D4: [2, 3, 4] => Scope for S8 to be removed. S7 and S8 share the same timestamp; the
12227         // attribution scope with the higher value will be selected.
12228 
12229         // S1: attribution scopes -> [0], destinations -> [D1]
12230         Source source1 =
12231                 insertSourceForAttributionScope(
12232                         List.of("0", ""),
12233                         ATTRIBUTION_SCOPE_LIMIT,
12234                         MAX_EVENT_STATES,
12235                         SOURCE_EVENT_TIME,
12236                         null,
12237                         List.of(APP_ONE_DESTINATION));
12238         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source1));
12239         Arrays.stream(new Source[] {source1})
12240                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12241 
12242         // S2: attribution scopes -> [3, 4, 5], destinations -> [D1]
12243         Source source2 =
12244                 insertSourceForAttributionScope(
12245                         List.of("3", "4", "5"),
12246                         ATTRIBUTION_SCOPE_LIMIT,
12247                         MAX_EVENT_STATES,
12248                         SOURCE_EVENT_TIME + 1,
12249                         null,
12250                         List.of(APP_ONE_DESTINATION));
12251         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source2));
12252         Arrays.stream(new Source[] {source1})
12253                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12254         Arrays.stream(new Source[] {source2})
12255                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12256 
12257         // S3: attribution scopes -> [0, 1], destinations -> [D2]
12258         Source source3 =
12259                 insertSourceForAttributionScope(
12260                         List.of("0", "1"),
12261                         ATTRIBUTION_SCOPE_LIMIT,
12262                         MAX_EVENT_STATES,
12263                         SOURCE_EVENT_TIME + 2,
12264                         List.of(WEB_ONE_DESTINATION),
12265                         null);
12266         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source3));
12267         Arrays.stream(new Source[] {source1})
12268                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12269         Arrays.stream(new Source[] {source2, source3})
12270                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12271 
12272         // S4: attribution scopes -> [1, 3], destinations -> [D2]
12273         Source source4 =
12274                 insertSourceForAttributionScope(
12275                         List.of("1", "3"),
12276                         ATTRIBUTION_SCOPE_LIMIT,
12277                         MAX_EVENT_STATES,
12278                         SOURCE_EVENT_TIME + 3,
12279                         List.of(WEB_ONE_DESTINATION),
12280                         null);
12281         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source4));
12282         Arrays.stream(new Source[] {source1})
12283                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12284         Arrays.stream(new Source[] {source2, source3, source4})
12285                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12286 
12287         // S5: attribution scopes -> [1, 2], destinations -> [D3]
12288         Source source5 =
12289                 insertSourceForAttributionScope(
12290                         List.of("1", "2"),
12291                         ATTRIBUTION_SCOPE_LIMIT,
12292                         MAX_EVENT_STATES,
12293                         SOURCE_EVENT_TIME + 4,
12294                         List.of(WEB_TWO_DESTINATION),
12295                         null);
12296         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source5));
12297         Arrays.stream(new Source[] {source1})
12298                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12299         Arrays.stream(new Source[] {source2, source3, source4, source5})
12300                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12301 
12302         // S6: attribution scopes -> [2, 3, 4], destinations -> [D3]
12303         Source source6 =
12304                 insertSourceForAttributionScope(
12305                         List.of("2", "3", "4"),
12306                         ATTRIBUTION_SCOPE_LIMIT,
12307                         MAX_EVENT_STATES,
12308                         SOURCE_EVENT_TIME + 5,
12309                         List.of(WEB_TWO_DESTINATION),
12310                         null);
12311         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source6));
12312         Arrays.stream(new Source[] {source1, source5})
12313                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12314         Arrays.stream(new Source[] {source2, source3, source4, source6})
12315                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12316 
12317         // S7: attribution scopes -> [2], destinations -> [D4]
12318         Source source7 =
12319                 insertSourceForAttributionScope(
12320                         List.of("2"),
12321                         ATTRIBUTION_SCOPE_LIMIT,
12322                         MAX_EVENT_STATES,
12323                         SOURCE_EVENT_TIME + 6,
12324                         List.of(WEB_THREE_DESTINATION),
12325                         null);
12326         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source7));
12327         Arrays.stream(new Source[] {source1, source5})
12328                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12329         Arrays.stream(new Source[] {source2, source3, source4, source6, source7})
12330                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12331 
12332         // S8: attribution scopes -> [1, 2], destinations -> [D4], shares same timestamp as S7.
12333         Source source8 =
12334                 insertSourceForAttributionScope(
12335                         List.of("1", "2"),
12336                         ATTRIBUTION_SCOPE_LIMIT,
12337                         MAX_EVENT_STATES,
12338                         SOURCE_EVENT_TIME + 6,
12339                         List.of(WEB_THREE_DESTINATION),
12340                         null);
12341         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source8));
12342         Arrays.stream(new Source[] {source1, source5})
12343                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12344         Arrays.stream(new Source[] {source2, source3, source4, source6, source7, source8})
12345                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12346 
12347         // S9: attribution scopes -> [0], destinations -> [D1], reporting origin -> R2
12348         Source source9 =
12349                 insertSourceForAttributionScope(
12350                         List.of("0"),
12351                         ATTRIBUTION_SCOPE_LIMIT,
12352                         MAX_EVENT_STATES,
12353                         SOURCE_EVENT_TIME,
12354                         null,
12355                         List.of(APP_ONE_DESTINATION),
12356                         REGISTRATION_ORIGIN_2);
12357         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source9));
12358         Arrays.stream(new Source[] {source1, source5})
12359                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12360         Arrays.stream(new Source[] {source2, source3, source4, source6, source7, source8, source9})
12361                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12362 
12363         // S10: attribution scopes -> [0, 1], destinations -> [D2], reporting origin -> R2
12364         Source source10 =
12365                 insertSourceForAttributionScope(
12366                         List.of("0", "1"),
12367                         ATTRIBUTION_SCOPE_LIMIT,
12368                         MAX_EVENT_STATES,
12369                         SOURCE_EVENT_TIME + 2,
12370                         List.of(WEB_ONE_DESTINATION),
12371                         null,
12372                         REGISTRATION_ORIGIN_2);
12373         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source10));
12374         Arrays.stream(new Source[] {source1, source5})
12375                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12376         Arrays.stream(
12377                         new Source[] {
12378                             source2, source3, source4, source6, source7, source8, source9, source10
12379                         })
12380                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12381 
12382         // S11: attribution scopes -> [1, 2], destinations -> [D3], reporting origin -> R2
12383         Source source11 =
12384                 insertSourceForAttributionScope(
12385                         List.of("1", "2"),
12386                         ATTRIBUTION_SCOPE_LIMIT,
12387                         MAX_EVENT_STATES,
12388                         SOURCE_EVENT_TIME + 4,
12389                         List.of(WEB_TWO_DESTINATION),
12390                         null,
12391                         REGISTRATION_ORIGIN_2);
12392         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source11));
12393         Arrays.stream(new Source[] {source1, source5})
12394                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12395         Arrays.stream(
12396                         new Source[] {
12397                             source2, source3, source4, source6, source7, source8, source9, source10,
12398                             source11
12399                         })
12400                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12401 
12402         // S12: attribution scopes -> [3, 4], destinations -> [D1, D2, D3, D4]
12403         Source source12 =
12404                 insertSourceForAttributionScope(
12405                         List.of("3", "4"),
12406                         ATTRIBUTION_SCOPE_LIMIT,
12407                         MAX_EVENT_STATES,
12408                         SOURCE_EVENT_TIME + 8,
12409                         List.of(WEB_ONE_DESTINATION, WEB_TWO_DESTINATION, WEB_THREE_DESTINATION),
12410                         List.of(APP_ONE_DESTINATION));
12411         mDatastoreManager.runInTransaction((dao) -> dao.updateSourcesForAttributionScope(source12));
12412         Arrays.stream(new Source[] {source1, source3, source5, source8})
12413                 .forEach(source -> verifySourceStatus(source, Source.Status.IGNORED));
12414         Arrays.stream(
12415                         new Source[] {
12416                             source2, source4, source6, source7, source9, source10, source11,
12417                             source12
12418                         })
12419                 .forEach(source -> verifySourceStatus(source, Source.Status.ACTIVE));
12420     }
12421 
12422     @Test
getLatestReportTimeInBatchWindow_singleAggregateReport_returnsSingleReportTime()12423     public void getLatestReportTimeInBatchWindow_singleAggregateReport_returnsSingleReportTime() {
12424         Source source =
12425                 SourceFixture.getMinimalValidSourceBuilder()
12426                         .setEventId(new UnsignedLong(1L))
12427                         .setId("source1")
12428                         .build();
12429 
12430         long scheduledReportTime = 1L;
12431         AggregateReport report =
12432                 generateMockAggregateReport(
12433                         WebUtil.validUrl("https://destination-1.test"),
12434                         1,
12435                         "source1",
12436                         scheduledReportTime);
12437 
12438         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12439         Objects.requireNonNull(db);
12440         insertSource(source, source.getId());
12441         getAggregateReportConsumer(db).accept(report);
12442 
12443         Long result =
12444                 mDatastoreManager
12445                         .runInTransactionWithResult(
12446                                 measurementDao ->
12447                                         measurementDao.getLatestReportTimeInBatchWindow(
12448                                                 mFlags
12449                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12450                         .orElseThrow();
12451 
12452         assertEquals(scheduledReportTime, result.longValue());
12453     }
12454 
12455     @Test
testGetLatestReportTimeInBatchWindow_singleEventReport_returnsSingleReportTime()12456     public void testGetLatestReportTimeInBatchWindow_singleEventReport_returnsSingleReportTime() {
12457         long reportTime = 1L;
12458         EventReport report =
12459                 generateMockEventReport(
12460                         WebUtil.validUrl("https://destination-1.test"), 1, reportTime);
12461 
12462         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12463         Objects.requireNonNull(db);
12464         getEventReportConsumer(db).accept(report);
12465 
12466         Long result =
12467                 mDatastoreManager
12468                         .runInTransactionWithResult(
12469                                 measurementDao ->
12470                                         measurementDao.getLatestReportTimeInBatchWindow(
12471                                                 mFlags
12472                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12473                         .orElseThrow();
12474 
12475         assertEquals(reportTime, result.longValue());
12476     }
12477 
12478     @Test
12479     public void
testGetLatestReportTimeInBatchWindow_twoAggregateReport_bothInBatchWindow_returnsTwoReportTimes()12480             testGetLatestReportTimeInBatchWindow_twoAggregateReport_bothInBatchWindow_returnsTwoReportTimes() {
12481         String sourceId = "source1";
12482         Source source =
12483                 SourceFixture.getMinimalValidSourceBuilder()
12484                         .setEventId(new UnsignedLong(1L))
12485                         .setId(sourceId)
12486                         .build();
12487 
12488         long firstScheduledReportTime = 1L;
12489         long secondScheduledReportTime =
12490                 firstScheduledReportTime
12491                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
12492                         - 1L;
12493 
12494         String destination = WebUtil.validUrl("https://destination-1.test");
12495         List<AggregateReport> reports =
12496                 Arrays.asList(
12497                         generateMockAggregateReport(
12498                                 destination, 1, sourceId, firstScheduledReportTime),
12499                         generateMockAggregateReport(
12500                                 destination, 2, sourceId, secondScheduledReportTime));
12501 
12502         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12503         Objects.requireNonNull(db);
12504         insertSource(source, source.getId());
12505         reports.forEach(getAggregateReportConsumer(db));
12506 
12507         Long result =
12508                 mDatastoreManager
12509                         .runInTransactionWithResult(
12510                                 measurementDao ->
12511                                         measurementDao.getLatestReportTimeInBatchWindow(
12512                                                 mFlags
12513                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12514                         .orElseThrow();
12515 
12516         assertEquals(secondScheduledReportTime, result.longValue());
12517     }
12518 
12519     @Test
12520     public void
testGetLatestReportTimeInBatchWindow_twoAggReport_oneAfterBatchWindow_returnOneReportTime()12521             testGetLatestReportTimeInBatchWindow_twoAggReport_oneAfterBatchWindow_returnOneReportTime() {
12522         String sourceId = "source1";
12523         Source source =
12524                 SourceFixture.getMinimalValidSourceBuilder()
12525                         .setEventId(new UnsignedLong(1L))
12526                         .setId(sourceId)
12527                         .build();
12528 
12529         long firstScheduledReportTime = 1L;
12530         long secondScheduledReportTime =
12531                 firstScheduledReportTime
12532                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
12533                         + 1L;
12534 
12535         String destination = WebUtil.validUrl("https://destination-1.test");
12536         List<AggregateReport> reports =
12537                 Arrays.asList(
12538                         generateMockAggregateReport(
12539                                 destination, 1, sourceId, firstScheduledReportTime),
12540                         generateMockAggregateReport(
12541                                 destination, 2, sourceId, secondScheduledReportTime));
12542 
12543         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12544         Objects.requireNonNull(db);
12545         insertSource(source, source.getId());
12546         reports.forEach(getAggregateReportConsumer(db));
12547 
12548         Long result =
12549                 mDatastoreManager
12550                         .runInTransactionWithResult(
12551                                 measurementDao ->
12552                                         measurementDao.getLatestReportTimeInBatchWindow(
12553                                                 mFlags
12554                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12555                         .orElseThrow();
12556 
12557         // secondScheduledReportTime should not be returned as it was outside the batch window.
12558         assertEquals(firstScheduledReportTime, result.longValue());
12559     }
12560 
12561     @Test
12562     public void
testGetLatestReportTimeInBatchWindow_twoEventReport_bothInBatchWindow_returnSecondReportTime()12563             testGetLatestReportTimeInBatchWindow_twoEventReport_bothInBatchWindow_returnSecondReportTime() {
12564         long firstScheduledReportTime = 1L;
12565         long secondScheduledReportTime =
12566                 firstScheduledReportTime
12567                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
12568                         - 1L;
12569 
12570         String destination = WebUtil.validUrl("https://destination-1.test");
12571         List<EventReport> reports =
12572                 Arrays.asList(
12573                         generateMockEventReport(destination, 1, firstScheduledReportTime),
12574                         generateMockEventReport(destination, 2, secondScheduledReportTime));
12575 
12576         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12577         Objects.requireNonNull(db);
12578 
12579         Consumer<EventReport> eventReportConsumer = getEventReportConsumer(db);
12580         reports.forEach(eventReportConsumer);
12581 
12582         Long result =
12583                 mDatastoreManager
12584                         .runInTransactionWithResult(
12585                                 measurementDao ->
12586                                         measurementDao.getLatestReportTimeInBatchWindow(
12587                                                 mFlags
12588                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12589                         .orElseThrow();
12590 
12591         assertEquals(secondScheduledReportTime, result.longValue());
12592     }
12593 
12594     @Test
12595     public void
testGetLatestReportTimeInBatchWindow_twoEventReport_oneAfterBatchWindow_returnFirstReportTime()12596             testGetLatestReportTimeInBatchWindow_twoEventReport_oneAfterBatchWindow_returnFirstReportTime() {
12597         long firstScheduledReportTime = 1L;
12598         long secondScheduledReportTime =
12599                 firstScheduledReportTime
12600                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
12601                         + 1L;
12602 
12603         String destination = WebUtil.validUrl("https://destination-1.test");
12604         List<EventReport> reports =
12605                 Arrays.asList(
12606                         generateMockEventReport(destination, 1, firstScheduledReportTime),
12607                         generateMockEventReport(destination, 2, secondScheduledReportTime));
12608 
12609         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12610         Objects.requireNonNull(db);
12611 
12612         Consumer<EventReport> eventReportConsumer = getEventReportConsumer(db);
12613         reports.forEach(eventReportConsumer);
12614 
12615         Long result =
12616                 mDatastoreManager
12617                         .runInTransactionWithResult(
12618                                 measurementDao ->
12619                                         measurementDao.getLatestReportTimeInBatchWindow(
12620                                                 mFlags
12621                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12622                         .orElseThrow();
12623 
12624         // secondScheduledReportTime should not be returned as it was outside the batch window.
12625         assertEquals(firstScheduledReportTime, result.longValue());
12626     }
12627 
12628     @Test
12629     public void
testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_bothInBatchWindow_returnSecondReportTime()12630             testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_bothInBatchWindow_returnSecondReportTime() {
12631         long firstScheduledReportTime = 1L;
12632         long secondScheduledReportTime =
12633                 firstScheduledReportTime
12634                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
12635                         - 1L;
12636 
12637         Source source =
12638                 SourceFixture.getMinimalValidSourceBuilder()
12639                         .setEventId(new UnsignedLong(1L))
12640                         .setId("source1")
12641                         .build();
12642 
12643         String destination = WebUtil.validUrl("https://destination-1.test");
12644         AggregateReport aggregateReport =
12645                 generateMockAggregateReport(destination, 1, "source1", firstScheduledReportTime);
12646         EventReport eventReport =
12647                 generateMockEventReport(destination, 1, secondScheduledReportTime);
12648 
12649         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12650         Objects.requireNonNull(db);
12651         insertSource(source, source.getId());
12652         getEventReportConsumer(db).accept(eventReport);
12653         getAggregateReportConsumer(db).accept(aggregateReport);
12654 
12655         Long result =
12656                 mDatastoreManager
12657                         .runInTransactionWithResult(
12658                                 measurementDao ->
12659                                         measurementDao.getLatestReportTimeInBatchWindow(
12660                                                 mFlags
12661                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12662                         .orElseThrow();
12663 
12664         assertEquals(secondScheduledReportTime, result.longValue());
12665     }
12666 
12667     @Test
12668     public void
testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_oneAfterBatchWindow_returnFirstReportTime()12669             testGetLatestReportTimeInBatchWindow_oneAggReport_oneEventReport_oneAfterBatchWindow_returnFirstReportTime() {
12670         long firstScheduledReportTime = 1L;
12671         long secondScheduledReportTime =
12672                 firstScheduledReportTime
12673                         + MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS
12674                         + 1L;
12675 
12676         String sourceId = "source1";
12677         Source source =
12678                 SourceFixture.getMinimalValidSourceBuilder()
12679                         .setEventId(new UnsignedLong(1L))
12680                         .setId(sourceId)
12681                         .build();
12682 
12683         String destination = WebUtil.validUrl("https://destination-1.test");
12684         AggregateReport aggregateReport =
12685                 generateMockAggregateReport(destination, 1, sourceId, firstScheduledReportTime);
12686         EventReport eventReport =
12687                 generateMockEventReport(destination, 1, secondScheduledReportTime);
12688 
12689         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12690         Objects.requireNonNull(db);
12691         insertSource(source, source.getId());
12692         getEventReportConsumer(db).accept(eventReport);
12693         getAggregateReportConsumer(db).accept(aggregateReport);
12694 
12695         Long result =
12696                 mDatastoreManager
12697                         .runInTransactionWithResult(
12698                                 measurementDao ->
12699                                         measurementDao.getLatestReportTimeInBatchWindow(
12700                                                 mFlags
12701                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12702                         .orElseThrow();
12703 
12704         assertEquals(firstScheduledReportTime, result.longValue());
12705     }
12706 
12707     @Test
12708     public void
testGetLatestReportTimeInBatchWindow_manyAggReport_manyEventReport_returnLatestReportTime()12709             testGetLatestReportTimeInBatchWindow_manyAggReport_manyEventReport_returnLatestReportTime() {
12710         String sourceId = "source1";
12711         Source source =
12712                 SourceFixture.getMinimalValidSourceBuilder()
12713                         .setEventId(new UnsignedLong(1L))
12714                         .setId(sourceId)
12715                         .build();
12716         List<EventReport> eventReports = new ArrayList<>();
12717         List<AggregateReport> aggregateReports = new ArrayList<>();
12718         int firstReportTime = 1;
12719         String destination = WebUtil.validUrl("https://destination-1.test");
12720         for (int i = firstReportTime; i < MEASUREMENT_MAX_EVENT_REPORTS_PER_DESTINATION; i++) {
12721             eventReports.add(
12722                     generateMockEventReport(destination, /* id= */ i, /* reportTime= */ i));
12723         }
12724 
12725         for (int i = firstReportTime; i < MEASUREMENT_MAX_AGGREGATE_REPORTS_PER_SOURCE; i++) {
12726             aggregateReports.add(
12727                     generateMockAggregateReport(
12728                             destination, /* id= */ i, sourceId, /* reportTime= */ i));
12729         }
12730 
12731         // Add one more aggregate report that is scheduled at the very edge of the batch window.
12732         // The report time for this report should be returned as the report time.
12733         long lastReportTime = MEASUREMENT_REPORTING_JOB_SERVICE_BATCH_WINDOW_MILLIS;
12734         int lastId = MEASUREMENT_MAX_AGGREGATE_REPORTS_PER_SOURCE;
12735         aggregateReports.add(
12736                 generateMockAggregateReport(destination, lastId, sourceId, lastReportTime));
12737 
12738         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
12739         Objects.requireNonNull(db);
12740         insertSource(source, source.getId());
12741         eventReports.forEach(getEventReportConsumer(db));
12742         aggregateReports.forEach(getAggregateReportConsumer(db));
12743 
12744         Long result =
12745                 mDatastoreManager
12746                         .runInTransactionWithResult(
12747                                 measurementDao ->
12748                                         measurementDao.getLatestReportTimeInBatchWindow(
12749                                                 mFlags
12750                                                         .getMeasurementReportingJobServiceBatchWindowMillis()))
12751                         .orElseThrow();
12752 
12753         assertEquals(lastReportTime, result.longValue());
12754     }
12755 
12756     @Test
testGetLatestReportTimeInBatchWindow_noReports_returnNull()12757     public void testGetLatestReportTimeInBatchWindow_noReports_returnNull() {
12758         Optional<Long> results =
12759                 mDatastoreManager.runInTransactionWithResult(
12760                         measurementDao ->
12761                                 measurementDao.getLatestReportTimeInBatchWindow(
12762                                         mFlags
12763                                                 .getMeasurementReportingJobServiceBatchWindowMillis()));
12764 
12765         assertTrue(results.isEmpty());
12766     }
12767 
12768     @Test
testInsertSource_withDestinationLimitPriorityEnabled_fetchesTheSetValue()12769     public void testInsertSource_withDestinationLimitPriorityEnabled_fetchesTheSetValue() {
12770         // Setup
12771         mFlags = mock(Flags.class);
12772         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
12773         doReturn(true).when(mFlags).getMeasurementEnableSourceDestinationLimitPriority();
12774         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
12775 
12776         // Execution
12777         Source validSource =
12778                 SourceFixture.getValidSourceBuilder()
12779                         .setDestinationLimitPriority(
12780                                 SourceFixture.ValidSourceParams.DESTINATION_LIMIT_PRIORITY)
12781                         .build();
12782         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
12783 
12784         // Assertion
12785         String sourceId = getFirstSourceIdFromDatastore();
12786         Source source =
12787                 mDatastoreManager
12788                         .runInTransactionWithResult(
12789                                 measurementDao -> measurementDao.getSource(sourceId))
12790                         .orElseThrow(() -> new IllegalStateException("Source is null"));
12791         assertEquals(
12792                 SourceFixture.ValidSourceParams.DESTINATION_LIMIT_PRIORITY,
12793                 source.getDestinationLimitPriority());
12794     }
12795 
12796     @Test
testInsertAggregateDebugReportRecords_sourceAndTriggerIdPresent_succeeds()12797     public void testInsertAggregateDebugReportRecords_sourceAndTriggerIdPresent_succeeds() {
12798         // insert source & trigger to honor the foreign key constraint
12799         Source source =
12800                 SourceFixture.getMinimalValidSourceBuilder()
12801                         .setId("S1")
12802                         .setEventId(new UnsignedLong(3L))
12803                         .setEnrollmentId("1")
12804                         .build();
12805         Trigger trigger =
12806                 TriggerFixture.getValidTriggerBuilder().setId("T1").setEnrollmentId("2").build();
12807         AggregateDebugReportRecord validAggregateDebugReportRecord =
12808                 new AggregateDebugReportRecord.Builder(
12809                                 /* reportGenerationTime= */ 1000L,
12810                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
12811                                 /* registrantApp= */ Uri.parse("com.test1.myapp"),
12812                                 /* registrationOrigin= */ Uri.parse("https://destination.test"),
12813                                 /* contributions= */ 9)
12814                         .setSourceId("S1")
12815                         .setTriggerId("T1")
12816                         .build();
12817 
12818         // Execution
12819         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
12820         Objects.requireNonNull(db);
12821         insertSource(source, source.getId());
12822         AbstractDbIntegrationTest.insertToDb(trigger, db);
12823         mDatastoreManager.runInTransaction(
12824                 (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
12825 
12826         // Assertion
12827         try (Cursor cursor =
12828                 MeasurementDbHelper.getInstance()
12829                         .getReadableDatabase()
12830                         .query(
12831                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
12832                                 null,
12833                                 null,
12834                                 null,
12835                                 null,
12836                                 null,
12837                                 null)) {
12838             assertThat(cursor.moveToNext()).isTrue();
12839             assertThat(validAggregateDebugReportRecord.getReportGenerationTime())
12840                     .isEqualTo(
12841                             cursor.getInt(
12842                                     cursor.getColumnIndex(
12843                                             AggregatableDebugReportBudgetTrackerContract
12844                                                     .REPORT_GENERATION_TIME)));
12845             assertThat(validAggregateDebugReportRecord.getTopLevelRegistrant().toString())
12846                     .isEqualTo(
12847                             cursor.getString(
12848                                     cursor.getColumnIndex(
12849                                             AggregatableDebugReportBudgetTrackerContract
12850                                                     .TOP_LEVEL_REGISTRANT)));
12851             assertThat(validAggregateDebugReportRecord.getRegistrantApp().toString())
12852                     .isEqualTo(
12853                             cursor.getString(
12854                                     cursor.getColumnIndex(
12855                                             AggregatableDebugReportBudgetTrackerContract
12856                                                     .REGISTRANT_APP)));
12857             assertThat(validAggregateDebugReportRecord.getRegistrationOrigin().toString())
12858                     .isEqualTo(
12859                             cursor.getString(
12860                                     cursor.getColumnIndex(
12861                                             AggregatableDebugReportBudgetTrackerContract
12862                                                     .REGISTRATION_ORIGIN)));
12863             assertThat(validAggregateDebugReportRecord.getSourceId())
12864                     .isEqualTo(
12865                             cursor.getString(
12866                                     cursor.getColumnIndex(
12867                                             AggregatableDebugReportBudgetTrackerContract
12868                                                     .SOURCE_ID)));
12869             assertThat(validAggregateDebugReportRecord.getTriggerId())
12870                     .isEqualTo(
12871                             cursor.getString(
12872                                     cursor.getColumnIndex(
12873                                             AggregatableDebugReportBudgetTrackerContract
12874                                                     .TRIGGER_ID)));
12875             assertThat(validAggregateDebugReportRecord.getContributions())
12876                     .isEqualTo(
12877                             cursor.getInt(
12878                                     cursor.getColumnIndex(
12879                                             AggregatableDebugReportBudgetTrackerContract
12880                                                     .CONTRIBUTIONS)));
12881         }
12882     }
12883 
12884     @Test
testInsertAggregateDebugReportRecords_nullTriggerId_succeeds()12885     public void testInsertAggregateDebugReportRecords_nullTriggerId_succeeds() {
12886         // insert source & trigger to honor the foreign key constraint
12887         Source source =
12888                 SourceFixture.getMinimalValidSourceBuilder()
12889                         .setId("S1")
12890                         .setEventId(new UnsignedLong(3L))
12891                         .setEnrollmentId("1")
12892                         .build();
12893         AggregateDebugReportRecord validAggregateDebugReportRecord =
12894                 new AggregateDebugReportRecord.Builder(
12895                                 /* reportGenerationTime= */ 1000L,
12896                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
12897                                 /* registrantApp= */ Uri.parse("com.test1.myapp"),
12898                                 /* registrationOrigin= */ Uri.parse("https://destination.test"),
12899                                 /* contributions= */ 9)
12900                         .setSourceId("S1")
12901                         .build();
12902 
12903         // Execution
12904         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
12905         Objects.requireNonNull(db);
12906         insertSource(source, source.getId());
12907         mDatastoreManager.runInTransaction(
12908                 (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
12909 
12910         // Assertion
12911         try (Cursor cursor =
12912                 MeasurementDbHelper.getInstance()
12913                         .getReadableDatabase()
12914                         .query(
12915                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
12916                                 null,
12917                                 null,
12918                                 null,
12919                                 null,
12920                                 null,
12921                                 null)) {
12922             assertThat(cursor.moveToNext()).isTrue();
12923             assertThat(validAggregateDebugReportRecord.getReportGenerationTime())
12924                     .isEqualTo(
12925                             cursor.getInt(
12926                                     cursor.getColumnIndex(
12927                                             AggregatableDebugReportBudgetTrackerContract
12928                                                     .REPORT_GENERATION_TIME)));
12929             assertThat(validAggregateDebugReportRecord.getTopLevelRegistrant().toString())
12930                     .isEqualTo(
12931                             cursor.getString(
12932                                     cursor.getColumnIndex(
12933                                             AggregatableDebugReportBudgetTrackerContract
12934                                                     .TOP_LEVEL_REGISTRANT)));
12935             assertThat(validAggregateDebugReportRecord.getRegistrantApp().toString())
12936                     .isEqualTo(
12937                             cursor.getString(
12938                                     cursor.getColumnIndex(
12939                                             AggregatableDebugReportBudgetTrackerContract
12940                                                     .REGISTRANT_APP)));
12941             assertThat(validAggregateDebugReportRecord.getRegistrationOrigin().toString())
12942                     .isEqualTo(
12943                             cursor.getString(
12944                                     cursor.getColumnIndex(
12945                                             AggregatableDebugReportBudgetTrackerContract
12946                                                     .REGISTRATION_ORIGIN)));
12947             assertThat(validAggregateDebugReportRecord.getSourceId())
12948                     .isEqualTo(
12949                             cursor.getString(
12950                                     cursor.getColumnIndex(
12951                                             AggregatableDebugReportBudgetTrackerContract
12952                                                     .SOURCE_ID)));
12953             assertThat(validAggregateDebugReportRecord.getTriggerId())
12954                     .isEqualTo(
12955                             cursor.getString(
12956                                     cursor.getColumnIndex(
12957                                             AggregatableDebugReportBudgetTrackerContract
12958                                                     .TRIGGER_ID)));
12959             assertThat(validAggregateDebugReportRecord.getContributions())
12960                     .isEqualTo(
12961                             cursor.getInt(
12962                                     cursor.getColumnIndex(
12963                                             AggregatableDebugReportBudgetTrackerContract
12964                                                     .CONTRIBUTIONS)));
12965         }
12966     }
12967 
12968     @Test
testInsertAggregateDebugReportRecords_nullSourceAndTriggerId_succeeds()12969     public void testInsertAggregateDebugReportRecords_nullSourceAndTriggerId_succeeds() {
12970         AggregateDebugReportRecord validAggregateDebugReportRecord =
12971                 new AggregateDebugReportRecord.Builder(
12972                                 /* reportGenerationTime= */ 1000L,
12973                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
12974                                 /* registrantApp= */ Uri.parse("com.test1.myapp"),
12975                                 /* registrationOrigin= */ Uri.parse("https://destination.test"),
12976                                 /* contributions= */ 9)
12977                         .build();
12978 
12979         // Execution
12980         mDatastoreManager.runInTransaction(
12981                 (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
12982 
12983         // Assertion
12984         try (Cursor cursor =
12985                 MeasurementDbHelper.getInstance()
12986                         .getReadableDatabase()
12987                         .query(
12988                                 AggregatableDebugReportBudgetTrackerContract.TABLE,
12989                                 null,
12990                                 null,
12991                                 null,
12992                                 null,
12993                                 null,
12994                                 null)) {
12995             assertThat(cursor.moveToNext()).isTrue();
12996             assertThat(validAggregateDebugReportRecord.getReportGenerationTime())
12997                     .isEqualTo(
12998                             cursor.getInt(
12999                                     cursor.getColumnIndex(
13000                                             AggregatableDebugReportBudgetTrackerContract
13001                                                     .REPORT_GENERATION_TIME)));
13002             assertThat(validAggregateDebugReportRecord.getTopLevelRegistrant().toString())
13003                     .isEqualTo(
13004                             cursor.getString(
13005                                     cursor.getColumnIndex(
13006                                             AggregatableDebugReportBudgetTrackerContract
13007                                                     .TOP_LEVEL_REGISTRANT)));
13008             assertThat(validAggregateDebugReportRecord.getRegistrantApp().toString())
13009                     .isEqualTo(
13010                             cursor.getString(
13011                                     cursor.getColumnIndex(
13012                                             AggregatableDebugReportBudgetTrackerContract
13013                                                     .REGISTRANT_APP)));
13014             assertThat(validAggregateDebugReportRecord.getRegistrationOrigin().toString())
13015                     .isEqualTo(
13016                             cursor.getString(
13017                                     cursor.getColumnIndex(
13018                                             AggregatableDebugReportBudgetTrackerContract
13019                                                     .REGISTRATION_ORIGIN)));
13020             assertThat(validAggregateDebugReportRecord.getSourceId())
13021                     .isEqualTo(
13022                             cursor.getString(
13023                                     cursor.getColumnIndex(
13024                                             AggregatableDebugReportBudgetTrackerContract
13025                                                     .SOURCE_ID)));
13026             assertThat(validAggregateDebugReportRecord.getTriggerId())
13027                     .isEqualTo(
13028                             cursor.getString(
13029                                     cursor.getColumnIndex(
13030                                             AggregatableDebugReportBudgetTrackerContract
13031                                                     .TRIGGER_ID)));
13032             assertThat(validAggregateDebugReportRecord.getContributions())
13033                     .isEqualTo(
13034                             cursor.getInt(
13035                                     cursor.getColumnIndex(
13036                                             AggregatableDebugReportBudgetTrackerContract
13037                                                     .CONTRIBUTIONS)));
13038         }
13039     }
13040 
13041     @Test
testGetTotalAggregateDebugReportBudget()13042     public void testGetTotalAggregateDebugReportBudget() {
13043         // insert source & trigger to honor the foreign key constraint
13044         Source source =
13045                 SourceFixture.getMinimalValidSourceBuilder()
13046                         .setId("S1")
13047                         .setEventId(new UnsignedLong(3L))
13048                         .setEnrollmentId("1")
13049                         .build();
13050         Trigger trigger =
13051                 TriggerFixture.getValidTriggerBuilder().setId("T1").setEnrollmentId("2").build();
13052         SQLiteDatabase db = MeasurementDbHelper.getInstance().getWritableDatabase();
13053         Objects.requireNonNull(db);
13054         insertSource(source, source.getId());
13055         AbstractDbIntegrationTest.insertToDb(trigger, db);
13056 
13057         // test case 1 (publisher query): outside time window + same publisher
13058         AggregateDebugReportRecord validAggregateDebugReportRecord1 =
13059                 new AggregateDebugReportRecord.Builder(
13060                                 /* reportGenerationTime= */ 1000L,
13061                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13062                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13063                                 /* registrationOrigin= */ Uri.parse("https://destination0.test"),
13064                                 /* contributions= */ 9)
13065                         .setSourceId("S1")
13066                         .setTriggerId("T1")
13067                         .build();
13068 
13069         // test case 2 (publisher query): inside time window + same publisher + different origin
13070         AggregateDebugReportRecord validAggregateDebugReportRecord2 =
13071                 new AggregateDebugReportRecord.Builder(
13072                                 /* reportGenerationTime= */ 2000L,
13073                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13074                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13075                                 /* registrationOrigin= */ Uri.parse("https://destination1.test"),
13076                                 /* contributions= */ 16)
13077                         .setSourceId("S1")
13078                         .setTriggerId("T1")
13079                         .build();
13080         AggregateDebugReportRecord validAggregateDebugReportRecord3 =
13081                 new AggregateDebugReportRecord.Builder(
13082                                 /* reportGenerationTime= */ 3000L,
13083                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13084                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13085                                 /* registrationOrigin= */ Uri.parse("https://destination2.test"),
13086                                 /* contributions= */ 25)
13087                         .setSourceId("S1")
13088                         .setTriggerId("T1")
13089                         .build();
13090         AggregateDebugReportRecord validAggregateDebugReportRecord4 =
13091                 new AggregateDebugReportRecord.Builder(
13092                                 /* reportGenerationTime= */ 4000L,
13093                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_1,
13094                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13095                                 /* registrationOrigin= */ Uri.parse("https://destination3.test"),
13096                                 /* contributions= */ 36)
13097                         .setSourceId("S1")
13098                         .setTriggerId("T1")
13099                         .build();
13100 
13101         // test case 3 (publisher + origin query): inside time window + same publisher + same origin
13102         AggregateDebugReportRecord validAggregateDebugReportRecord5 =
13103                 new AggregateDebugReportRecord.Builder(
13104                                 /* reportGenerationTime= */ 5000L,
13105                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_2,
13106                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13107                                 /* registrationOrigin= */ Uri.parse("https://destination4.test"),
13108                                 /* contributions= */ 49)
13109                         .setSourceId("S1")
13110                         .setTriggerId("T1")
13111                         .build();
13112         AggregateDebugReportRecord validAggregateDebugReportRecord6 =
13113                 new AggregateDebugReportRecord.Builder(
13114                                 /* reportGenerationTime= */ 6000L,
13115                                 /* topLevelRegistrant= */ TOP_LEVEL_REGISTRANT_2,
13116                                 /* registrantApp= */ Uri.parse("com.test.myapp"),
13117                                 /* registrationOrigin= */ Uri.parse("https://destination4.test"),
13118                                 /* contributions= */ 64)
13119                         .setSourceId("S1")
13120                         .setTriggerId("T1")
13121                         .build();
13122         List<AggregateDebugReportRecord> validAggregateDebugReportRecords =
13123                 Arrays.asList(
13124                         validAggregateDebugReportRecord1,
13125                         validAggregateDebugReportRecord2,
13126                         validAggregateDebugReportRecord3,
13127                         validAggregateDebugReportRecord4,
13128                         validAggregateDebugReportRecord5,
13129                         validAggregateDebugReportRecord6);
13130 
13131         for (AggregateDebugReportRecord validAggregateDebugReportRecord :
13132                 validAggregateDebugReportRecords) {
13133             mDatastoreManager.runInTransaction(
13134                     (dao) -> dao.insertAggregateDebugReportRecord(validAggregateDebugReportRecord));
13135         }
13136 
13137         // test case 1
13138         int budget1 =
13139                 mDatastoreManager
13140                         .runInTransactionWithResult(
13141                                 (dao) ->
13142                                         dao.sumAggregateDebugReportBudgetXPublisherXWindow(
13143                                                 /* publisher= */ TOP_LEVEL_REGISTRANT_1,
13144                                                 /* publisherType= */ EventSurfaceType.APP,
13145                                                 /* windowStartTime= */ 1000L))
13146                         .get();
13147         assertThat(budget1).isEqualTo((16 + 25 + 36));
13148 
13149         // test case 2
13150         int budget2 =
13151                 mDatastoreManager
13152                         .runInTransactionWithResult(
13153                                 (dao) ->
13154                                         dao.sumAggregateDebugReportBudgetXPublisherXWindow(
13155                                                 /* publisher= */ TOP_LEVEL_REGISTRANT_1,
13156                                                 /* publisherType= */ EventSurfaceType.APP,
13157                                                 /* windowStartTime= */ 1001L))
13158                         .get();
13159         assertThat(budget2).isEqualTo((16 + 25 + 36));
13160 
13161         // test case 3
13162         int budget3 =
13163                 mDatastoreManager
13164                         .runInTransactionWithResult(
13165                                 (dao) ->
13166                                         dao.sumAggregateDebugReportBudgetXOriginXPublisherXWindow(
13167                                                 /* publisher= */ TOP_LEVEL_REGISTRANT_2,
13168                                                 /* publisherType= */ EventSurfaceType.APP,
13169                                                 /* origin= */ Uri.parse(
13170                                                         "https://destination4.test"),
13171                                                 /* windowStartTime= */ 1001L))
13172                         .get();
13173         assertThat(budget3).isEqualTo((49 + 64));
13174     }
13175 
13176     @Test
updateSourceAggregateDebugContributions_updateFromPreValue_success()13177     public void updateSourceAggregateDebugContributions_updateFromPreValue_success() {
13178         // Setup
13179         mFlags = mock(Flags.class);
13180         ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
13181         doReturn(true).when(mFlags).getMeasurementEnableAggregateDebugReporting();
13182         doReturn(MEASUREMENT_DB_SIZE_LIMIT).when(mFlags).getMeasurementDbSizeLimit();
13183         int initialContributions = 1024;
13184         Source source =
13185                 SourceFixture.getValidSourceBuilder()
13186                         .setAggregateDebugReportContributions(initialContributions)
13187                         .build();
13188         insertSource(source, source.getId());
13189 
13190         // Verify persisted value
13191         assertThat(getFirstSourceFromDb().getAggregateDebugReportContributions())
13192                 .isEqualTo(initialContributions);
13193 
13194         // Execution
13195         // Update the value
13196         int expectedUpdatedContributions = 65536;
13197         source.setAggregateDebugContributions(expectedUpdatedContributions);
13198         mDatastoreManager.runInTransaction(
13199                 measurementDao -> measurementDao.updateSourceAggregateDebugContributions(source));
13200 
13201         // Verification
13202         assertThat(getFirstSourceFromDb().getAggregateDebugReportContributions())
13203                 .isEqualTo(expectedUpdatedContributions);
13204     }
13205 
getFirstSourceFromDb()13206     private Source getFirstSourceFromDb() {
13207         return mDatastoreManager
13208                 .runInTransactionWithResult(
13209                         measurementDao ->
13210                                 measurementDao.getSource(
13211                                         getFirstIdFromDatastore(
13212                                                 SourceContract.TABLE, SourceContract.ID)))
13213                 .orElseThrow();
13214     }
13215 
getAggregateReportConsumer(SQLiteDatabase db)13216     private static Consumer<AggregateReport> getAggregateReportConsumer(SQLiteDatabase db) {
13217         Consumer<AggregateReport> aggregateReportConsumer =
13218                 aggregateReport -> {
13219                     ContentValues values = new ContentValues();
13220                     values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId());
13221                     values.put(
13222                             MeasurementTables.AggregateReport.SOURCE_ID,
13223                             aggregateReport.getSourceId());
13224                     values.put(
13225                             MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
13226                             aggregateReport.getAttributionDestination().toString());
13227                     values.put(
13228                             MeasurementTables.AggregateReport.SCHEDULED_REPORT_TIME,
13229                             aggregateReport.getScheduledReportTime());
13230                     values.put(
13231                             MeasurementTables.AggregateReport.STATUS, aggregateReport.getStatus());
13232                     db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
13233                 };
13234         return aggregateReportConsumer;
13235     }
13236 
getEventReportConsumer(SQLiteDatabase db)13237     private static Consumer<EventReport> getEventReportConsumer(SQLiteDatabase db) {
13238         Consumer<EventReport> eventReportConsumer =
13239                 eventReport -> {
13240                     ContentValues values = new ContentValues();
13241                     values.put(EventReportContract.ID, eventReport.getId());
13242                     values.put(
13243                             EventReportContract.ATTRIBUTION_DESTINATION,
13244                             eventReport.getAttributionDestinations().get(0).toString());
13245                     values.put(EventReportContract.REPORT_TIME, eventReport.getReportTime());
13246                     values.put(EventReportContract.STATUS, EventReport.Status.PENDING);
13247 
13248                     db.insert(EventReportContract.TABLE, null, values);
13249                 };
13250         return eventReportConsumer;
13251     }
13252 
insertInDb(SQLiteDatabase db, Source source)13253     private void insertInDb(SQLiteDatabase db, Source source) {
13254         ContentValues values = new ContentValues();
13255         values.put(SourceContract.ID, source.getId());
13256         values.put(SourceContract.STATUS, Source.Status.ACTIVE);
13257         values.put(SourceContract.EVENT_TIME, source.getEventTime());
13258         values.put(SourceContract.EXPIRY_TIME, source.getExpiryTime());
13259         values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId());
13260         values.put(SourceContract.PUBLISHER, source.getPublisher().toString());
13261         values.put(SourceContract.REGISTRANT, source.getRegistrant().toString());
13262         values.put(SourceContract.REGISTRATION_ORIGIN, source.getRegistrationOrigin().toString());
13263         if (source.getAttributedTriggers() != null) {
13264             values.put(SourceContract.EVENT_ATTRIBUTION_STATUS, source.attributedTriggersToJson());
13265         }
13266         if (source.getTriggerSpecs() != null) {
13267             values.put(SourceContract.TRIGGER_SPECS, source.getTriggerSpecs().encodeToJson());
13268         }
13269         db.insert(SourceContract.TABLE, null, values);
13270 
13271         // Insert source destinations
13272         if (source.getAppDestinations() != null) {
13273             for (Uri appDestination : source.getAppDestinations()) {
13274                 ContentValues destinationValues = new ContentValues();
13275                 destinationValues.put(
13276                         MeasurementTables.SourceDestination.SOURCE_ID, source.getId());
13277                 destinationValues.put(
13278                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.APP);
13279                 destinationValues.put(
13280                         MeasurementTables.SourceDestination.DESTINATION, appDestination.toString());
13281                 db.insert(MeasurementTables.SourceDestination.TABLE, null, destinationValues);
13282             }
13283         }
13284 
13285         if (source.getWebDestinations() != null) {
13286             for (Uri webDestination : source.getWebDestinations()) {
13287                 ContentValues destinationValues = new ContentValues();
13288                 destinationValues.put(
13289                         MeasurementTables.SourceDestination.SOURCE_ID, source.getId());
13290                 destinationValues.put(
13291                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.WEB);
13292                 destinationValues.put(
13293                         MeasurementTables.SourceDestination.DESTINATION, webDestination.toString());
13294                 db.insert(MeasurementTables.SourceDestination.TABLE, null, destinationValues);
13295             }
13296         }
13297     }
13298 
queryAndAssertSourceEntries( SQLiteDatabase db, String enrollmentId, List<String> expectedSourceIds)13299     private void queryAndAssertSourceEntries(
13300             SQLiteDatabase db, String enrollmentId, List<String> expectedSourceIds) {
13301         try (Cursor cursor =
13302                 db.query(
13303                         XnaIgnoredSourcesContract.TABLE,
13304                         new String[] {XnaIgnoredSourcesContract.SOURCE_ID},
13305                         XnaIgnoredSourcesContract.ENROLLMENT_ID + " = ?",
13306                         new String[] {enrollmentId},
13307                         null,
13308                         null,
13309                         null)) {
13310             assertEquals(expectedSourceIds.size(), cursor.getCount());
13311             for (int i = 0; i < expectedSourceIds.size() && cursor.moveToNext(); i++) {
13312                 assertEquals(expectedSourceIds.get(i), cursor.getString(0));
13313             }
13314         }
13315     }
13316 
createSourceBuilder()13317     private Source.Builder createSourceBuilder() {
13318         return new Source.Builder()
13319                 .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
13320                 .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
13321                 .setAppDestinations(SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATIONS)
13322                 .setWebDestinations(SourceFixture.ValidSourceParams.WEB_DESTINATIONS)
13323                 .setEnrollmentId(SourceFixture.ValidSourceParams.ENROLLMENT_ID)
13324                 .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
13325                 .setEventTime(SOURCE_EVENT_TIME)
13326                 .setExpiryTime(SourceFixture.ValidSourceParams.EXPIRY_TIME)
13327                 .setPriority(SourceFixture.ValidSourceParams.PRIORITY)
13328                 .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
13329                 .setInstallAttributionWindow(
13330                         SourceFixture.ValidSourceParams.INSTALL_ATTRIBUTION_WINDOW)
13331                 .setInstallCooldownWindow(SourceFixture.ValidSourceParams.INSTALL_COOLDOWN_WINDOW)
13332                 .setAttributionMode(SourceFixture.ValidSourceParams.ATTRIBUTION_MODE)
13333                 .setAggregateSource(SourceFixture.ValidSourceParams.buildAggregateSource())
13334                 .setFilterDataString(SourceFixture.ValidSourceParams.buildFilterDataString())
13335                 .setSharedFilterDataKeys(SourceFixture.ValidSourceParams.SHARED_FILTER_DATA_KEYS)
13336                 .setIsDebugReporting(true)
13337                 .setRegistrationId(UUID.randomUUID().toString())
13338                 .setSharedAggregationKeys(SHARED_AGGREGATE_KEYS)
13339                 .setInstallTime(SourceFixture.ValidSourceParams.INSTALL_TIME)
13340                 .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
13341     }
13342 
createAggregateReportForSourceAndTrigger( Source source, Trigger trigger)13343     private AggregateReport createAggregateReportForSourceAndTrigger(
13344             Source source, Trigger trigger) {
13345         return createAggregateReportForSourceAndTrigger(
13346                 UUID.randomUUID().toString(), source, trigger);
13347     }
13348 
createEventReportForSourceAndTrigger(Source source, Trigger trigger)13349     private EventReport createEventReportForSourceAndTrigger(Source source, Trigger trigger)
13350             throws JSONException {
13351         return createEventReportForSourceAndTrigger(UUID.randomUUID().toString(), source, trigger);
13352     }
13353 
createAggregateReportForSourceAndTrigger( String reportId, Source source, Trigger trigger)13354     private AggregateReport createAggregateReportForSourceAndTrigger(
13355             String reportId, Source source, Trigger trigger) {
13356         return AggregateReportFixture.getValidAggregateReportBuilder()
13357                 .setId(reportId)
13358                 .setSourceId(source.getId())
13359                 .setTriggerId(trigger.getId())
13360                 .setTriggerTime(trigger.getTriggerTime())
13361                 .build();
13362     }
13363 
createEventReportForSourceAndTrigger( String reportId, Source source, Trigger trigger)13364     private EventReport createEventReportForSourceAndTrigger(
13365             String reportId, Source source, Trigger trigger) throws JSONException {
13366         EventTrigger eventTrigger = trigger.parseEventTriggers(
13367                 FakeFlagsFactory.getFlagsForTest()).get(0);
13368         return new EventReport.Builder()
13369                 .populateFromSourceAndTrigger(
13370                         source,
13371                         trigger,
13372                         eventTrigger.getTriggerData(),
13373                         eventTrigger,
13374                         new Pair<>(null, null),
13375                         new EventReportWindowCalcDelegate(mFlags),
13376                         new SourceNoiseHandler(mFlags),
13377                         source.getAttributionDestinations(trigger.getDestinationType()))
13378                 .setId(reportId)
13379                 .setSourceEventId(source.getEventId())
13380                 .setSourceId(source.getId())
13381                 .setTriggerId(trigger.getId())
13382                 .build();
13383     }
13384 
createEventReportForSourceAndTriggerForUninstall( String reportId, Source source, Trigger trigger)13385     private EventReport createEventReportForSourceAndTriggerForUninstall(
13386             String reportId, Source source, Trigger trigger) throws JSONException {
13387         EventTrigger eventTrigger =
13388                 trigger.parseEventTriggers(FakeFlagsFactory.getFlagsForTest()).get(0);
13389         return new EventReport.Builder()
13390                 .setTriggerTime(trigger.getTriggerTime())
13391                 .setSourceEventId(source.getEventId())
13392                 .setEnrollmentId(source.getEnrollmentId())
13393                 .setStatus(EventReport.Status.PENDING)
13394                 .setSourceType(source.getSourceType())
13395                 .setDebugReportStatus(EventReport.DebugReportStatus.NONE)
13396                 .setRegistrationOrigin(trigger.getRegistrationOrigin())
13397                 .setTriggerPriority(eventTrigger.getTriggerPriority())
13398                 .setTriggerData(eventTrigger.getTriggerData())
13399                 .setId(reportId)
13400                 .setAttributionDestinations(
13401                         source.getAttributionDestinations(trigger.getDestinationType()))
13402                 .setSourceId(source.getId())
13403                 .setTriggerId(trigger.getId())
13404                 .build();
13405     }
13406 
createDebugReport()13407     private DebugReport createDebugReport() {
13408         return createDebugReport(UUID.randomUUID().toString(), REGISTRANT, INSERTION_TIME);
13409     }
13410 
createDebugReport(String id, Uri registrant, long insertionTime)13411     private DebugReport createDebugReport(String id, Uri registrant, long insertionTime) {
13412         return new DebugReport.Builder()
13413                 .setId(id)
13414                 .setType("trigger-event-deduplicated")
13415                 .setBody(
13416                         " {\n"
13417                                 + "      \"attribution_destination\":"
13418                                 + " \"https://destination.example\",\n"
13419                                 + "      \"source_event_id\": \"45623\"\n"
13420                                 + "    }")
13421                 .setEnrollmentId("1")
13422                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
13423                 .setRegistrant(registrant)
13424                 .setInsertionTime(insertionTime)
13425                 .build();
13426     }
13427 
buildDebugReportWithInstalledRegistrant(String id)13428     private DebugReport buildDebugReportWithInstalledRegistrant(String id) {
13429         return new DebugReport.Builder()
13430                 .setId(id)
13431                 .setType("trigger-event-deduplicated")
13432                 .setBody(
13433                         " {\n"
13434                                 + "      \"attribution_destination\":"
13435                                 + " \"https://destination.example\",\n"
13436                                 + "      \"source_event_id\": \"45623\"\n"
13437                                 + "    }")
13438                 .setEnrollmentId("1")
13439                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
13440                 .setRegistrant(INSTALLED_REGISTRANT)
13441                 .setInsertionTime(INSERTION_TIME)
13442                 .build();
13443     }
13444 
buildDebugReportWithNotInstalledRegistrant(String id)13445     private DebugReport buildDebugReportWithNotInstalledRegistrant(String id) {
13446         return new DebugReport.Builder()
13447                 .setId(id)
13448                 .setType("trigger-event-deduplicated")
13449                 .setBody(
13450                         " {\n"
13451                                 + "      \"attribution_destination\":"
13452                                 + " \"https://destination.example\",\n"
13453                                 + "      \"source_event_id\": \"45623\"\n"
13454                                 + "    }")
13455                 .setEnrollmentId("1")
13456                 .setRegistrationOrigin(REGISTRATION_ORIGIN)
13457                 .setRegistrant(NOT_INSTALLED_REGISTRANT)
13458                 .setInsertionTime(INSERTION_TIME)
13459                 .build();
13460     }
13461 
buildRegistrant(String appName)13462     private Uri buildRegistrant(String appName) {
13463         return Uri.parse("android-app://" + appName);
13464     }
13465 
insertAsyncRecordForPackageName(Uri... registrants)13466     private void insertAsyncRecordForPackageName(Uri... registrants) {
13467         for (Uri registrant : registrants) {
13468             AsyncRegistration validRecord =
13469                     AsyncRegistrationFixture.getValidAsyncRegistrationBuilder()
13470                             .setRegistrant(registrant)
13471                             .build();
13472 
13473             mDatastoreManager.runInTransaction((dao) -> dao.insertAsyncRegistration(validRecord));
13474         }
13475     }
13476 
insertSourceForPackageName(Uri... registrants)13477     private void insertSourceForPackageName(Uri... registrants) {
13478         for (Uri registrant : registrants) {
13479             Source validSource =
13480                     SourceFixture.getValidSourceBuilder().setRegistrant(registrant).build();
13481 
13482             mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
13483         }
13484     }
13485 
insertSourceForAttributionScope( List<String> attributionScopes, Long attributionScopeLimit, Long maxEventStates, long eventTime, List<Uri> webDestinations, List<Uri> appDestinations)13486     private Source insertSourceForAttributionScope(
13487             List<String> attributionScopes,
13488             Long attributionScopeLimit,
13489             Long maxEventStates,
13490             long eventTime,
13491             List<Uri> webDestinations,
13492             List<Uri> appDestinations) {
13493         return insertSourceForAttributionScope(
13494                 attributionScopes,
13495                 attributionScopeLimit,
13496                 maxEventStates,
13497                 eventTime,
13498                 webDestinations,
13499                 appDestinations,
13500                 SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
13501     }
13502 
insertSourceForAttributionScope( List<String> attributionScopes, Long attributionScopeLimit, Long maxEventStates, long eventTime, List<Uri> webDestinations, List<Uri> appDestinations, @NonNull Uri reportingOrigin)13503     private Source insertSourceForAttributionScope(
13504             List<String> attributionScopes,
13505             Long attributionScopeLimit,
13506             Long maxEventStates,
13507             long eventTime,
13508             List<Uri> webDestinations,
13509             List<Uri> appDestinations,
13510             @NonNull Uri reportingOrigin) {
13511         return insertSourceForAttributionScope(
13512                 attributionScopes,
13513                 attributionScopeLimit,
13514                 maxEventStates,
13515                 eventTime,
13516                 webDestinations,
13517                 appDestinations,
13518                 reportingOrigin,
13519                 SourceFixture.ValidSourceParams.REGISTRATION_ID,
13520                 Source.SourceType.EVENT,
13521                 Source.Status.ACTIVE);
13522     }
13523 
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)13524     private Source insertSourceForAttributionScope(
13525             List<String> attributionScopes,
13526             Long attributionScopeLimit,
13527             Long maxEventStates,
13528             long eventTime,
13529             List<Uri> webDestinations,
13530             List<Uri> appDestinations,
13531             @NonNull Uri reportingOrigin,
13532             String registrationId,
13533             Source.SourceType sourceType,
13534             @Source.Status int status) {
13535         Source validSource =
13536                 SourceFixture.getValidSourceBuilder()
13537                         .setEventTime(eventTime)
13538                         .setAttributionScopeLimit(attributionScopeLimit)
13539                         .setMaxEventStates(maxEventStates)
13540                         .setWebDestinations(webDestinations)
13541                         .setAppDestinations(appDestinations)
13542                         .setAttributionScopes(attributionScopes)
13543                         .setRegistrationOrigin(reportingOrigin)
13544                         .setSourceType(sourceType)
13545                         .setStatus(status)
13546                         .setRegistrationId(registrationId)
13547                         .build();
13548 
13549         mDatastoreManager.runInTransaction(
13550                 (dao) -> {
13551                     dao.insertSource(validSource);
13552                     Source insertedSource = dao.getSource(validSource.getId());
13553                     boolean attributionScopeEnabled = mFlags.getMeasurementEnableAttributionScope();
13554                     assertThat(insertedSource.getMaxEventStates())
13555                             .isEqualTo(attributionScopeEnabled ? maxEventStates : null);
13556                     assertThat(insertedSource.getAttributionScopeLimit())
13557                             .isEqualTo(attributionScopeEnabled ? attributionScopeLimit : null);
13558                     assertThat(dao.getSourceAttributionScopes(validSource.getId()))
13559                             .containsExactlyElementsIn(
13560                                     (!attributionScopeEnabled || attributionScopes == null)
13561                                             ? List.of()
13562                                             : attributionScopes);
13563                 });
13564         return validSource;
13565     }
13566 
insertSourceForAggregatableNamedBudgets( AggregatableNamedBudgets aggregatableNamedBudgets, long eventTime, List<Uri> webDestinations, List<Uri> appDestinations)13567     private Source insertSourceForAggregatableNamedBudgets(
13568             AggregatableNamedBudgets aggregatableNamedBudgets,
13569             long eventTime,
13570             List<Uri> webDestinations,
13571             List<Uri> appDestinations) {
13572         Source validSource =
13573                 SourceFixture.getValidSourceBuilder()
13574                         .setEventTime(eventTime)
13575                         .setWebDestinations(webDestinations)
13576                         .setAppDestinations(appDestinations)
13577                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN)
13578                         .setSourceType(Source.SourceType.EVENT)
13579                         .setStatus(Source.Status.ACTIVE)
13580                         .setRegistrationId(SourceFixture.ValidSourceParams.REGISTRATION_ID)
13581                         .setAggregatableNamedBudgets(aggregatableNamedBudgets)
13582                         .build();
13583 
13584         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
13585         return validSource;
13586     }
13587 
insertSourceForPopulatingAggregatableNamedBudgets( long eventTime, List<Uri> webDestinations, List<Uri> appDestinations)13588     private Source insertSourceForPopulatingAggregatableNamedBudgets(
13589             long eventTime, List<Uri> webDestinations, List<Uri> appDestinations) {
13590         Source validSource =
13591                 SourceFixture.getValidSourceBuilder()
13592                         .setEventTime(eventTime)
13593                         .setWebDestinations(webDestinations)
13594                         .setAppDestinations(appDestinations)
13595                         .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN)
13596                         .setSourceType(Source.SourceType.EVENT)
13597                         .setStatus(Source.Status.ACTIVE)
13598                         .setRegistrationId(SourceFixture.ValidSourceParams.REGISTRATION_ID)
13599                         .build();
13600 
13601         mDatastoreManager.runInTransaction((dao) -> dao.insertSource(validSource));
13602         return validSource;
13603     }
13604 
insertTriggerForPackageName(Uri... registrants)13605     private void insertTriggerForPackageName(Uri... registrants) {
13606         for (Uri registrant : registrants) {
13607             Trigger validTrigger =
13608                     TriggerFixture.getValidTriggerBuilder().setRegistrant(registrant).build();
13609 
13610             mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(validTrigger));
13611         }
13612     }
13613 
setupSourceAndTriggerData()13614     private void setupSourceAndTriggerData() {
13615         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13616         List<Source> sourcesList = new ArrayList<>();
13617         sourcesList.add(
13618                 SourceFixture.getMinimalValidSourceBuilder()
13619                         .setId("S1")
13620                         .setRegistrant(APP_TWO_SOURCES)
13621                         .setPublisher(APP_TWO_PUBLISHER)
13622                         .setPublisherType(EventSurfaceType.APP)
13623                         .build());
13624         sourcesList.add(
13625                 SourceFixture.getMinimalValidSourceBuilder()
13626                         .setId("S2")
13627                         .setRegistrant(APP_TWO_SOURCES)
13628                         .setPublisher(APP_TWO_PUBLISHER)
13629                         .setPublisherType(EventSurfaceType.APP)
13630                         .build());
13631         sourcesList.add(
13632                 SourceFixture.getMinimalValidSourceBuilder()
13633                         .setId("S3")
13634                         .setRegistrant(APP_ONE_SOURCE)
13635                         .setPublisher(APP_ONE_PUBLISHER)
13636                         .setPublisherType(EventSurfaceType.APP)
13637                         .build());
13638         sourcesList.add(
13639                 SourceFixture.getMinimalValidSourceBuilder()
13640                         .setId("S4")
13641                         .setRegistrant(APP_ONE_SOURCE)
13642                         .setPublisher(APP_ONE_PUBLISHER)
13643                         .setPublisherType(EventSurfaceType.APP)
13644                         .setStatus(Source.Status.MARKED_TO_DELETE)
13645                         .build());
13646         sourcesList.add(
13647                 SourceFixture.getMinimalValidSourceBuilder()
13648                         .setId("S5")
13649                         .setRegistrant(APP_TWO_SOURCES)
13650                         .setPublisher(APP_TWO_PUBLISHER)
13651                         .setPublisherType(EventSurfaceType.APP)
13652                         .setStatus(Source.Status.MARKED_TO_DELETE)
13653                         .build());
13654         sourcesList.forEach(source -> insertSource(source, source.getId()));
13655         List<Trigger> triggersList = new ArrayList<>();
13656         triggersList.add(
13657                 TriggerFixture.getValidTriggerBuilder()
13658                         .setId("T1")
13659                         .setRegistrant(APP_TWO_DESTINATION)
13660                         .build());
13661         triggersList.add(
13662                 TriggerFixture.getValidTriggerBuilder()
13663                         .setId("T2")
13664                         .setRegistrant(APP_TWO_DESTINATION)
13665                         .build());
13666         triggersList.add(
13667                 TriggerFixture.getValidTriggerBuilder()
13668                         .setId("T3")
13669                         .setRegistrant(APP_ONE_DESTINATION)
13670                         .build());
13671 
13672         // Add web triggers.
13673         triggersList.add(
13674                 TriggerFixture.getValidTriggerBuilder()
13675                         .setId("T4")
13676                         .setRegistrant(APP_BROWSER)
13677                         .setAttributionDestination(WEB_ONE_DESTINATION)
13678                         .build());
13679         triggersList.add(
13680                 TriggerFixture.getValidTriggerBuilder()
13681                         .setId("T5")
13682                         .setRegistrant(APP_BROWSER)
13683                         .setAttributionDestination(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN)
13684                         .build());
13685         triggersList.add(
13686                 TriggerFixture.getValidTriggerBuilder()
13687                         .setId("T7")
13688                         .setRegistrant(APP_BROWSER)
13689                         .setAttributionDestination(WEB_ONE_DESTINATION_DIFFERENT_SUBDOMAIN_2)
13690                         .build());
13691         triggersList.add(
13692                 TriggerFixture.getValidTriggerBuilder()
13693                         .setId("T8")
13694                         .setRegistrant(APP_BROWSER)
13695                         .setAttributionDestination(WEB_TWO_DESTINATION)
13696                         .build());
13697         triggersList.add(
13698                 TriggerFixture.getValidTriggerBuilder()
13699                         .setId("T9")
13700                         .setRegistrant(APP_BROWSER)
13701                         .setAttributionDestination(WEB_TWO_DESTINATION_WITH_PATH)
13702                         .build());
13703 
13704         for (Trigger trigger : triggersList) {
13705             ContentValues values = new ContentValues();
13706             values.put("_id", trigger.getId());
13707             values.put("registrant", trigger.getRegistrant().toString());
13708             values.put("attribution_destination", trigger.getAttributionDestination().toString());
13709             long row = db.insert("msmt_trigger", null, values);
13710             assertNotEquals("Trigger insertion failed", -1, row);
13711         }
13712     }
13713 
createWebTrigger(Uri attributionDestination)13714     private Trigger createWebTrigger(Uri attributionDestination) {
13715         return TriggerFixture.getValidTriggerBuilder()
13716                 .setId("ID" + mValueId++)
13717                 .setAttributionDestination(attributionDestination)
13718                 .setRegistrant(APP_BROWSER)
13719                 .build();
13720     }
13721 
createAppTrigger(Uri registrant, Uri destination)13722     private Trigger createAppTrigger(Uri registrant, Uri destination) {
13723         return TriggerFixture.getValidTriggerBuilder()
13724                 .setId("ID" + mValueId++)
13725                 .setAttributionDestination(destination)
13726                 .setRegistrant(registrant)
13727                 .build();
13728     }
13729 
addTriggersToDatabase(List<Trigger> triggersList)13730     private void addTriggersToDatabase(List<Trigger> triggersList) {
13731         SQLiteDatabase db = MeasurementDbHelper.getInstance().safeGetWritableDatabase();
13732 
13733         for (Trigger trigger : triggersList) {
13734             ContentValues values = new ContentValues();
13735             values.put("_id", trigger.getId());
13736             values.put("registrant", trigger.getRegistrant().toString());
13737             values.put("attribution_destination", trigger.getAttributionDestination().toString());
13738             long row = db.insert("msmt_trigger", null, values);
13739             assertNotEquals("Trigger insertion failed", -1, row);
13740         }
13741     }
13742 
setupSourceDataForPublisherTypeWeb()13743     private void setupSourceDataForPublisherTypeWeb() {
13744         List<Source> sourcesList = new ArrayList<>();
13745         sourcesList.add(
13746                 SourceFixture.getMinimalValidSourceBuilder()
13747                         .setId("W1")
13748                         .setPublisher(WEB_PUBLISHER_ONE)
13749                         .setPublisherType(EventSurfaceType.WEB)
13750                         .build());
13751         sourcesList.add(
13752                 SourceFixture.getMinimalValidSourceBuilder()
13753                         .setId("W21")
13754                         .setPublisher(WEB_PUBLISHER_TWO)
13755                         .setPublisherType(EventSurfaceType.WEB)
13756                         .build());
13757         sourcesList.add(
13758                 SourceFixture.getMinimalValidSourceBuilder()
13759                         .setId("W22")
13760                         .setPublisher(WEB_PUBLISHER_TWO)
13761                         .setPublisherType(EventSurfaceType.WEB)
13762                         .build());
13763         sourcesList.add(
13764                 SourceFixture.getMinimalValidSourceBuilder()
13765                         .setId("W23")
13766                         .setPublisher(WEB_PUBLISHER_TWO)
13767                         .setPublisherType(EventSurfaceType.WEB)
13768                         .setStatus(Source.Status.MARKED_TO_DELETE)
13769                         .build());
13770         sourcesList.add(
13771                 SourceFixture.getMinimalValidSourceBuilder()
13772                         .setId("S3")
13773                         .setPublisher(WEB_PUBLISHER_THREE)
13774                         .setPublisherType(EventSurfaceType.WEB)
13775                         .build());
13776         sourcesList.forEach(source -> insertSource(source, source.getId()));
13777     }
13778 
createSourceForIATest( String id, long currentTime, long priority, int eventTimePastDays, boolean expiredIAWindow, String enrollmentId)13779     private Source.Builder createSourceForIATest(
13780             String id,
13781             long currentTime,
13782             long priority,
13783             int eventTimePastDays,
13784             boolean expiredIAWindow,
13785             String enrollmentId) {
13786         return createSourceForIATest(
13787                 id,
13788                 currentTime,
13789                 priority,
13790                 eventTimePastDays,
13791                 expiredIAWindow,
13792                 enrollmentId,
13793                 REGISTRATION_ORIGIN);
13794     }
13795 
createSourceForIATest( String id, long currentTime, long priority, int eventTimePastDays, boolean expiredIAWindow, String enrollmentId, Uri registrationOrigin)13796     private Source.Builder createSourceForIATest(
13797             String id,
13798             long currentTime,
13799             long priority,
13800             int eventTimePastDays,
13801             boolean expiredIAWindow,
13802             String enrollmentId,
13803             Uri registrationOrigin) {
13804         return new Source.Builder()
13805                 .setId(id)
13806                 .setPublisher(Uri.parse("android-app://com.example.sample"))
13807                 .setRegistrant(Uri.parse("android-app://com.example.sample"))
13808                 .setEnrollmentId(enrollmentId)
13809                 .setExpiryTime(currentTime + DAYS.toMillis(30))
13810                 .setInstallAttributionWindow(DAYS.toMillis(expiredIAWindow ? 0 : 30))
13811                 .setAppDestinations(List.of(INSTALLED_PACKAGE))
13812                 .setEventTime(
13813                         currentTime
13814                                 - DAYS.toMillis(eventTimePastDays == -1 ? 10 : eventTimePastDays))
13815                 .setPriority(priority == -1 ? 100 : priority)
13816                 .setRegistrationOrigin(registrationOrigin);
13817     }
13818 
generateMockAggregateReport(String attributionDestination, int id)13819     private AggregateReport generateMockAggregateReport(String attributionDestination, int id) {
13820         return new AggregateReport.Builder()
13821                 .setId(String.valueOf(id))
13822                 .setAttributionDestination(Uri.parse(attributionDestination))
13823                 .build();
13824     }
13825 
generateMockAggregateReport( String attributionDestination, int id, String sourceId, String api)13826     private AggregateReport generateMockAggregateReport(
13827             String attributionDestination, int id, String sourceId, String api) {
13828         return new AggregateReport.Builder()
13829                 .setId(String.valueOf(id))
13830                 .setSourceId(sourceId)
13831                 .setAttributionDestination(Uri.parse(attributionDestination))
13832                 .setApi(api)
13833                 .build();
13834     }
13835 
generateMockAggregateReport( String attributionDestination, int id, String sourceId, long reportTime)13836     private AggregateReport generateMockAggregateReport(
13837             String attributionDestination, int id, String sourceId, long reportTime) {
13838         return new AggregateReport.Builder()
13839                 .setId(String.valueOf(id))
13840                 .setSourceId(sourceId)
13841                 .setAttributionDestination(Uri.parse(attributionDestination))
13842                 .setScheduledReportTime(reportTime)
13843                 .build();
13844     }
13845 
generateMockEventReport(String attributionDestination, int id)13846     private EventReport generateMockEventReport(String attributionDestination, int id) {
13847         return new EventReport.Builder()
13848                 .setId(String.valueOf(id))
13849                 .setAttributionDestinations(List.of(Uri.parse(attributionDestination)))
13850                 .build();
13851     }
13852 
generateMockEventReport( String attributionDestination, int id, long reportTime)13853     private EventReport generateMockEventReport(
13854             String attributionDestination, int id, long reportTime) {
13855         return new EventReport.Builder()
13856                 .setId(String.valueOf(id))
13857                 .setAttributionDestinations(List.of(Uri.parse(attributionDestination)))
13858                 .setReportTime(reportTime)
13859                 .build();
13860     }
13861 
assertAggregateReportCount( List<String> attributionDestinations, int destinationType, List<Integer> expectedCounts)13862     private void assertAggregateReportCount(
13863             List<String> attributionDestinations,
13864             int destinationType,
13865             List<Integer> expectedCounts) {
13866         IntStream.range(0, attributionDestinations.size())
13867                 .forEach(
13868                         i -> {
13869                             DatastoreManager.ThrowingCheckedFunction<Integer>
13870                                     aggregateReportCountPerDestination =
13871                                             measurementDao ->
13872                                                     measurementDao
13873                                                             .getNumAggregateReportsPerDestination(
13874                                                                     Uri.parse(
13875                                                                             attributionDestinations
13876                                                                                     .get(i)),
13877                                                                     destinationType);
13878                             assertEquals(
13879                                     expectedCounts.get(i),
13880                                     mDatastoreManager
13881                                             .runInTransactionWithResult(
13882                                                     aggregateReportCountPerDestination)
13883                                             .orElseThrow());
13884                         });
13885     }
13886 
assertEventReportCount( List<String> attributionDestinations, int destinationType, List<Integer> expectedCounts)13887     private void assertEventReportCount(
13888             List<String> attributionDestinations,
13889             int destinationType,
13890             List<Integer> expectedCounts) {
13891         IntStream.range(0, attributionDestinations.size())
13892                 .forEach(
13893                         i -> {
13894                             DatastoreManager.ThrowingCheckedFunction<Integer>
13895                                     numEventReportsPerDestination =
13896                                             measurementDao ->
13897                                                     measurementDao.getNumEventReportsPerDestination(
13898                                                             Uri.parse(
13899                                                                     attributionDestinations.get(i)),
13900                                                             destinationType);
13901                             assertEquals(
13902                                     expectedCounts.get(i),
13903                                     mDatastoreManager
13904                                             .runInTransactionWithResult(
13905                                                     numEventReportsPerDestination)
13906                                             .orElseThrow());
13907                         });
13908     }
13909 
createAppDestinationVariants(int destinationNum)13910     private List<String> createAppDestinationVariants(int destinationNum) {
13911         return Arrays.asList(
13912                 "android-app://subdomain.destination-" + destinationNum + ".app/abcd",
13913                 "android-app://subdomain.destination-" + destinationNum + ".app",
13914                 "android-app://destination-" + destinationNum + ".app/abcd",
13915                 "android-app://destination-" + destinationNum + ".app",
13916                 "android-app://destination-" + destinationNum + ".ap");
13917     }
13918 
createWebDestinationVariants(int destinationNum)13919     private List<String> createWebDestinationVariants(int destinationNum) {
13920         return Arrays.asList(
13921                 "https://subdomain.destination-" + destinationNum + ".com/abcd",
13922                 "https://subdomain.destination-" + destinationNum + ".com",
13923                 "https://destination-" + destinationNum + ".com/abcd",
13924                 "https://destination-" + destinationNum + ".com",
13925                 "https://destination-" + destinationNum + ".co");
13926     }
13927 
getInstallAttributionStatus(String sourceDbId, SQLiteDatabase db)13928     private boolean getInstallAttributionStatus(String sourceDbId, SQLiteDatabase db) {
13929         Cursor cursor =
13930                 db.query(
13931                         SourceContract.TABLE,
13932                         new String[] {SourceContract.IS_INSTALL_ATTRIBUTED},
13933                         SourceContract.ID + " = ? ",
13934                         new String[] {sourceDbId},
13935                         null,
13936                         null,
13937                         null,
13938                         null);
13939         assertTrue(cursor.moveToFirst());
13940         return cursor.getInt(0) == 1;
13941     }
13942 
getInstallAttributionInstallTime(String sourceDbId, SQLiteDatabase db)13943     private Long getInstallAttributionInstallTime(String sourceDbId, SQLiteDatabase db) {
13944         Cursor cursor =
13945                 db.query(
13946                         SourceContract.TABLE,
13947                         new String[] {SourceContract.INSTALL_TIME},
13948                         SourceContract.ID + " = ? ",
13949                         new String[] {sourceDbId},
13950                         null,
13951                         null,
13952                         null,
13953                         null);
13954         assertTrue(cursor.moveToFirst());
13955         if (!cursor.isNull(0)) {
13956             return cursor.getLong(0);
13957         }
13958         return null;
13959     }
13960 
removeSources(List<String> dbIds, SQLiteDatabase db)13961     private void removeSources(List<String> dbIds, SQLiteDatabase db) {
13962         db.delete(
13963                 SourceContract.TABLE,
13964                 SourceContract.ID + " IN ( ? )",
13965                 new String[] {String.join(",", dbIds)});
13966     }
13967 
maybeInsertSourceDestinations( SQLiteDatabase db, Source source, String sourceId)13968     private static void maybeInsertSourceDestinations(
13969             SQLiteDatabase db, Source source, String sourceId) {
13970         if (source.getAppDestinations() != null) {
13971             for (Uri appDestination : source.getAppDestinations()) {
13972                 ContentValues values = new ContentValues();
13973                 values.put(MeasurementTables.SourceDestination.SOURCE_ID, sourceId);
13974                 values.put(
13975                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.APP);
13976                 values.put(
13977                         MeasurementTables.SourceDestination.DESTINATION, appDestination.toString());
13978                 long row = db.insert(MeasurementTables.SourceDestination.TABLE, null, values);
13979                 assertNotEquals("Source app destination insertion failed", -1, row);
13980             }
13981         }
13982         if (source.getWebDestinations() != null) {
13983             for (Uri webDestination : source.getWebDestinations()) {
13984                 ContentValues values = new ContentValues();
13985                 values.put(MeasurementTables.SourceDestination.SOURCE_ID, sourceId);
13986                 values.put(
13987                         MeasurementTables.SourceDestination.DESTINATION_TYPE, EventSurfaceType.WEB);
13988                 values.put(
13989                         MeasurementTables.SourceDestination.DESTINATION, webDestination.toString());
13990                 long row = db.insert(MeasurementTables.SourceDestination.TABLE, null, values);
13991                 assertNotEquals("Source web destination insertion failed", -1, row);
13992             }
13993         }
13994     }
13995 
getAttributionBuilder(Source source, Trigger trigger)13996     private static Attribution.Builder getAttributionBuilder(Source source, Trigger trigger) {
13997         return new Attribution.Builder()
13998                 .setEnrollmentId(source.getEnrollmentId())
13999                 .setDestinationOrigin(source.getWebDestinations().get(0).toString())
14000                 .setDestinationSite(source.getAppDestinations().get(0).toString())
14001                 .setSourceOrigin(source.getPublisher().toString())
14002                 .setSourceSite(source.getPublisher().toString())
14003                 .setRegistrant(source.getRegistrant().toString())
14004                 .setTriggerTime(
14005                         trigger.getTriggerTime() - MEASUREMENT_RATE_LIMIT_WINDOW_MILLISECONDS + 1)
14006                 .setRegistrationOrigin(trigger.getRegistrationOrigin());
14007     }
14008 
14009     /** Create {@link Attribution} object from SQLite datastore. */
constructAttributionFromCursor(Cursor cursor)14010     private static Attribution constructAttributionFromCursor(Cursor cursor) {
14011         Attribution.Builder builder = new Attribution.Builder();
14012         int index = cursor.getColumnIndex(AttributionContract.ID);
14013         if (index > -1 && !cursor.isNull(index)) {
14014             builder.setId(cursor.getString(index));
14015         }
14016         index = cursor.getColumnIndex(AttributionContract.SCOPE);
14017         if (index > -1 && !cursor.isNull(index)) {
14018             builder.setScope(cursor.getInt(index));
14019         }
14020         index = cursor.getColumnIndex(AttributionContract.SOURCE_SITE);
14021         if (index > -1 && !cursor.isNull(index)) {
14022             builder.setSourceSite(cursor.getString(index));
14023         }
14024         index = cursor.getColumnIndex(AttributionContract.SOURCE_ORIGIN);
14025         if (index > -1 && !cursor.isNull(index)) {
14026             builder.setSourceOrigin(cursor.getString(index));
14027         }
14028         index = cursor.getColumnIndex(AttributionContract.DESTINATION_SITE);
14029         if (index > -1 && !cursor.isNull(index)) {
14030             builder.setDestinationSite(cursor.getString(index));
14031         }
14032         index = cursor.getColumnIndex(AttributionContract.DESTINATION_ORIGIN);
14033         if (index > -1 && !cursor.isNull(index)) {
14034             builder.setDestinationOrigin(cursor.getString(index));
14035         }
14036         index = cursor.getColumnIndex(AttributionContract.ENROLLMENT_ID);
14037         if (index > -1 && !cursor.isNull(index)) {
14038             builder.setEnrollmentId(cursor.getString(index));
14039         }
14040         index = cursor.getColumnIndex(AttributionContract.TRIGGER_TIME);
14041         if (index > -1 && !cursor.isNull(index)) {
14042             builder.setTriggerTime(cursor.getLong(index));
14043         }
14044         index = cursor.getColumnIndex(AttributionContract.REGISTRANT);
14045         if (index > -1 && !cursor.isNull(index)) {
14046             builder.setRegistrant(cursor.getString(index));
14047         }
14048         index = cursor.getColumnIndex(AttributionContract.REGISTRATION_ORIGIN);
14049         if (index > -1 && !cursor.isNull(index)) {
14050             builder.setRegistrationOrigin(Uri.parse(cursor.getString(index)));
14051         }
14052         return builder.build();
14053     }
14054 
getAttribution(String attributionId, SQLiteDatabase db)14055     private Attribution getAttribution(String attributionId, SQLiteDatabase db) {
14056         try (Cursor cursor =
14057                 db.query(
14058                         AttributionContract.TABLE,
14059                         /* columns= */ null,
14060                         AttributionContract.ID + " = ? ",
14061                         new String[] {attributionId},
14062                         /* groupBy= */ null,
14063                         /* having= */ null,
14064                         /* orderBy= */ null,
14065                         /* limit= */ null)) {
14066             if (cursor.getCount() == 0) {
14067                 return null;
14068             }
14069             cursor.moveToNext();
14070             return constructAttributionFromCursor(cursor);
14071         }
14072     }
14073 
insertAttributedTrigger(TriggerSpecs triggerSpecs, EventReport eventReport)14074     private static void insertAttributedTrigger(TriggerSpecs triggerSpecs,
14075             EventReport eventReport) {
14076         triggerSpecs.getAttributedTriggers().add(
14077                 new AttributedTrigger(
14078                         eventReport.getTriggerId(),
14079                         eventReport.getTriggerPriority(),
14080                         eventReport.getTriggerData(),
14081                         eventReport.getTriggerValue(),
14082                         eventReport.getTriggerTime(),
14083                         eventReport.getTriggerDedupKey(),
14084                         eventReport.getTriggerDebugKey(),
14085                         false));
14086     }
14087 
insertAttributedTrigger(List<AttributedTrigger> attributedTriggers, EventReport eventReport)14088     private static void insertAttributedTrigger(List<AttributedTrigger> attributedTriggers,
14089             EventReport eventReport) {
14090         attributedTriggers.add(
14091                 new AttributedTrigger(
14092                         eventReport.getTriggerId(),
14093                         eventReport.getTriggerData(),
14094                         eventReport.getTriggerDedupKey()));
14095     }
14096 
getRandomIdsWith(int numIds, String idToInclude)14097     private static List<String> getRandomIdsWith(int numIds, String idToInclude) {
14098         List<String> result = new ArrayList<>();
14099         result.add(idToInclude);
14100         for (int i = 0; i < numIds; i++) {
14101             result.add(UUID.randomUUID().toString());
14102         }
14103         return result;
14104     }
14105 
getFirstSourceIdFromDatastore()14106     private static String getFirstSourceIdFromDatastore() {
14107         return getFirstIdFromDatastore(SourceContract.TABLE, SourceContract.ID);
14108     }
14109 
getFirstIdFromDatastore(String tableName, String idColumn)14110     private static String getFirstIdFromDatastore(String tableName, String idColumn) {
14111         try (Cursor cursor =
14112                 MeasurementDbHelper.getInstance()
14113                         .getReadableDatabase()
14114                         .query(tableName, new String[] {idColumn}, null, null, null, null, null)) {
14115             assertTrue(cursor.moveToNext());
14116             return cursor.getString(cursor.getColumnIndex(idColumn));
14117         }
14118     }
14119 
getNullableUriList(List<Uri> uris)14120     private static List<Uri> getNullableUriList(List<Uri> uris) {
14121         return uris == null ? null : uris;
14122     }
14123 
getMatchingSourceIds(Trigger trigger)14124     private List<String> getMatchingSourceIds(Trigger trigger) {
14125         List<Source> result =
14126                 mDatastoreManager
14127                         .runInTransactionWithResult(
14128                                 measurementDao -> measurementDao.getMatchingActiveSources(trigger))
14129                         .orElseThrow();
14130         return result.stream().map(Source::getId).collect(Collectors.toList());
14131     }
14132 
getMatchingSources(Trigger trigger)14133     private List<Source> getMatchingSources(Trigger trigger) {
14134         return mDatastoreManager
14135                 .runInTransactionWithResult(
14136                         measurementDao -> measurementDao.getMatchingActiveSources(trigger))
14137                 .orElseThrow();
14138     }
14139 }
14140