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.deletion;
18 
19 import static com.android.adservices.data.measurement.deletion.MeasurementDataDeleter.ANDROID_APP_SCHEME;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyString;
26 import static org.mockito.ArgumentMatchers.eq;
27 import static org.mockito.Mockito.doNothing;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.spy;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.when;
33 
34 import android.adservices.measurement.DeletionParam;
35 import android.adservices.measurement.DeletionRequest;
36 import android.net.Uri;
37 import android.util.Pair;
38 
39 import com.android.adservices.common.AdServicesExtendedMockitoTestCase;
40 import com.android.adservices.data.measurement.DatastoreException;
41 import com.android.adservices.data.measurement.DatastoreManager;
42 import com.android.adservices.data.measurement.IMeasurementDao;
43 import com.android.adservices.data.measurement.ITransaction;
44 import com.android.adservices.service.FlagsFactory;
45 import com.android.adservices.service.measurement.EventReport;
46 import com.android.adservices.service.measurement.Source;
47 import com.android.adservices.service.measurement.SourceFixture;
48 import com.android.adservices.service.measurement.Trigger;
49 import com.android.adservices.service.measurement.TriggerFixture;
50 import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
51 import com.android.adservices.service.measurement.aggregation.AggregateHistogramContribution;
52 import com.android.adservices.service.measurement.aggregation.AggregateReport;
53 import com.android.adservices.service.measurement.aggregation.AggregateReportFixture;
54 import com.android.adservices.service.measurement.util.UnsignedLong;
55 import com.android.adservices.service.stats.AdServicesLogger;
56 import com.android.adservices.shared.errorlogging.AdServicesErrorLogger;
57 import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic;
58 
59 import com.google.common.truth.Truth;
60 
61 import org.json.JSONArray;
62 import org.json.JSONException;
63 import org.json.JSONObject;
64 import org.junit.Before;
65 import org.junit.Test;
66 import org.mockito.ArgumentCaptor;
67 import org.mockito.Mock;
68 
69 import java.math.BigInteger;
70 import java.time.Instant;
71 import java.util.ArrayList;
72 import java.util.Arrays;
73 import java.util.Collections;
74 import java.util.HashSet;
75 import java.util.List;
76 import java.util.Set;
77 import java.util.concurrent.TimeUnit;
78 
79 @SpyStatic(FlagsFactory.class)
80 public final class MeasurementDataDeleterTest extends AdServicesExtendedMockitoTestCase {
81     private static final List<AggregateHistogramContribution> CONTRIBUTIONS_1 =
82             Arrays.asList(
83                     new AggregateHistogramContribution.Builder()
84                             .setKey(new BigInteger("10"))
85                             .setValue(45)
86                             .build(),
87                     new AggregateHistogramContribution.Builder()
88                             .setKey(new BigInteger("100"))
89                             .setValue(87)
90                             .build());
91 
92 
93     private static final List<AggregateHistogramContribution> CONTRIBUTIONS_2 =
94             Arrays.asList(
95                     new AggregateHistogramContribution.Builder()
96                             .setKey(new BigInteger("500"))
97                             .setValue(2000)
98                             .build(),
99                     new AggregateHistogramContribution.Builder()
100                             .setKey(new BigInteger("10000"))
101                             .setValue(3454)
102                             .build());
103 
104     private static final AggregateReport AGGREGATE_REPORT_1;
105     private static final AggregateReport AGGREGATE_REPORT_2;
106 
107     static {
108         AggregateReport localAggregateReport1;
109         AggregateReport localAggregateReport2;
110         try {
111             localAggregateReport1 =
112                     AggregateReportFixture.getValidAggregateReportBuilder()
113                             .setId("reportId1")
114                             .setDebugCleartextPayload(
115                                     AggregateReport.generateDebugPayload(CONTRIBUTIONS_1))
116                             .setSourceId("source1")
117                             .setTriggerId("trigger1")
118                             .build();
119             localAggregateReport2 =
120                     AggregateReportFixture.getValidAggregateReportBuilder()
121                             .setId("reportId2")
122                             .setDebugCleartextPayload(
123                                     AggregateReport.generateDebugPayload(CONTRIBUTIONS_2))
124                             .setSourceId("source2")
125                             .setTriggerId("trigger2")
126                             .build();
127         } catch (JSONException e) {
128             localAggregateReport1 = null;
129             localAggregateReport2 = null;
130             fail("Failed to create aggregate report.");
131         }
132         AGGREGATE_REPORT_1 = localAggregateReport1;
133         AGGREGATE_REPORT_2 = localAggregateReport2;
134     }
135 
136     private static final Instant START = Instant.ofEpochMilli(5000);
137     private static final Instant END = Instant.ofEpochMilli(10000);
138     private static final String APP_PACKAGE_NAME = "app.package.name";
139     private static final String SDK_PACKAGE_NAME = "sdk.package.name";
140 
141     @Mock private IMeasurementDao mMeasurementDao;
142     @Mock private ITransaction mTransaction;
143     @Mock private AggregatableAttributionSource mAggregatableAttributionSource1;
144     @Mock private AggregatableAttributionSource mAggregatableAttributionSource2;
145     @Mock private EventReport mEventReport1;
146     @Mock private EventReport mEventReport2;
147     @Mock private EventReport mEventReport3;
148     @Mock private AggregateReport mAggregateReport1;
149     @Mock private AggregateReport mAggregateReport2;
150     @Mock private AggregateReport mAggregateReport3;
151     @Mock private List<Uri> mOriginUris;
152     @Mock private List<Uri> mDomainUris;
153     @Mock private AdServicesErrorLogger mErrorLogger;
154     @Mock private AdServicesLogger mLogger;
155 
156     private MeasurementDataDeleter mMeasurementDataDeleter;
157 
158     private class FakeDatastoreManager extends DatastoreManager {
FakeDatastoreManager()159         private FakeDatastoreManager() {
160             super(mErrorLogger);
161         }
162 
163         @Override
createNewTransaction()164         public ITransaction createNewTransaction() {
165             return mTransaction;
166         }
167 
168         @Override
getMeasurementDao()169         public IMeasurementDao getMeasurementDao() {
170             return mMeasurementDao;
171         }
172 
173         @Override
getDataStoreVersion()174         protected int getDataStoreVersion() {
175             return 0;
176         }
177     }
178 
179     @Before
setup()180     public void setup() throws Exception {
181         mocker.mockGetFlags(mMockFlags);
182         when(mMockFlags.getMeasurementEnableAraDeduplicationAlignmentV1()).thenReturn(true);
183         when(mMockFlags.getMeasurementFlexibleEventReportingApiEnabled()).thenReturn(false);
184         mMeasurementDataDeleter =
185                 spy(new MeasurementDataDeleter(new FakeDatastoreManager(), mMockFlags, mLogger));
186     }
187 
188     @Test
resetAggregateContributions_hasMatchingReports_resetsContributions()189     public void resetAggregateContributions_hasMatchingReports_resetsContributions()
190             throws DatastoreException {
191         // Setup
192         Source source1 =
193                 SourceFixture.getMinimalValidSourceBuilder()
194                         .setId("source1")
195                         .setAggregatableAttributionSource(mAggregatableAttributionSource1)
196                         .setAggregateContributions(32666)
197                         .build();
198         Source source2 =
199                 SourceFixture.getMinimalValidSourceBuilder()
200                         .setId("source2")
201                         .setAggregatableAttributionSource(mAggregatableAttributionSource2)
202                         .setAggregateContributions(6235)
203                         .build();
204 
205         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
206         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
207 
208         // Execute
209         mMeasurementDataDeleter.resetAggregateContributions(
210                 mMeasurementDao, Arrays.asList(AGGREGATE_REPORT_1, AGGREGATE_REPORT_2));
211 
212         // Verify
213         ArgumentCaptor<Source> sourceCaptor = ArgumentCaptor.forClass(Source.class);
214         verify(mMeasurementDao, times(2))
215                 .updateSourceAggregateContributions(sourceCaptor.capture());
216         assertEquals(2, sourceCaptor.getAllValues().size());
217         assertEquals(
218                 32534,
219                 sourceCaptor.getAllValues().get(0).getAggregateContributions()); // 32666-87-45
220         assertEquals(
221                 781,
222                 sourceCaptor.getAllValues().get(1).getAggregateContributions()); // 6235-3454-2000
223     }
224 
225     @Test
resetAggregateContributions_withSourceContributionsGoingBelowZero_resetsToZero()226     public void resetAggregateContributions_withSourceContributionsGoingBelowZero_resetsToZero()
227             throws DatastoreException {
228         // Setup
229         Source source1 =
230                 SourceFixture.getMinimalValidSourceBuilder()
231                         .setId("source1")
232                         .setAggregatableAttributionSource(mAggregatableAttributionSource1)
233                         .setAggregateContributions(10)
234                         .build();
235 
236         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
237 
238         // Execute
239         mMeasurementDataDeleter.resetAggregateContributions(
240                 mMeasurementDao, Collections.singletonList(AGGREGATE_REPORT_1));
241 
242         // Verify
243         ArgumentCaptor<Source> sourceCaptor = ArgumentCaptor.forClass(Source.class);
244         verify(mMeasurementDao, times(1))
245                 .updateSourceAggregateContributions(sourceCaptor.capture());
246         assertEquals(1, sourceCaptor.getAllValues().size());
247         assertEquals(0, sourceCaptor.getValue().getAggregateContributions());
248     }
249 
250     @Test
resetDedupKeys_matchingReports_removesDedupKeysFromSource()251     public void resetDedupKeys_matchingReports_removesDedupKeysFromSource()
252             throws DatastoreException, JSONException {
253         String attributionStatus1 = getAttributionStatus(
254                 List.of("trigger1", "trigger2", "trigger3"),
255                 List.of("4", "5", "6"),
256                 List.of("1", "2", "3"));
257         // Setup
258         Source source1 =
259                 SourceFixture.getMinimalValidSourceBuilder()
260                         .setId("sourceId1")
261                         .setEventAttributionStatus(attributionStatus1)
262                         .build();
263         String attributionStatus2 = getAttributionStatus(
264                 List.of("trigger11", "trigger22", "trigger33"),
265                 List.of("44", "55", "66"),
266                 List.of("11", "22", "33"));
267         Source source2 =
268                 SourceFixture.getMinimalValidSourceBuilder()
269                         .setId("sourceId2")
270                         .setEventAttributionStatus(attributionStatus2)
271                         .build();
272 
273         when(mEventReport1.getTriggerDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
274         when(mEventReport1.getTriggerId()).thenReturn("trigger1"); // S1 - T1
275         when(mEventReport2.getTriggerDedupKey()).thenReturn(new UnsignedLong("22")); // S2 - T2
276         when(mEventReport2.getTriggerId()).thenReturn("trigger22"); // S2 - T2
277         when(mEventReport3.getTriggerDedupKey()).thenReturn(new UnsignedLong("3")); // S1 - T3
278         when(mEventReport3.getTriggerId()).thenReturn("trigger3"); // S3 - T3
279         when(mEventReport1.getSourceId()).thenReturn(source1.getId());
280         when(mEventReport2.getSourceId()).thenReturn(source2.getId());
281         when(mEventReport3.getSourceId()).thenReturn(source1.getId());
282 
283         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
284         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
285 
286         // Execution
287         mMeasurementDataDeleter.resetDedupKeys(
288                 mMeasurementDao, List.of(mEventReport1, mEventReport2, mEventReport3));
289 
290         // Verification
291         ArgumentCaptor<String> attributionStatusArg = ArgumentCaptor.forClass(String.class);
292         verify(mMeasurementDao, times(2)).updateSourceAttributedTriggers(
293                 eq(source1.getId()), attributionStatusArg.capture());
294         List<String> attributionStatuses = attributionStatusArg.getAllValues();
295         assertEquals(
296                 getAttributionStatus(
297                         List.of("trigger2", "trigger3"), List.of("5", "6"), List.of("2", "3")),
298                 attributionStatuses.get(0));
299         assertEquals(
300                 getAttributionStatus(List.of("trigger2"), List.of("5"), List.of("2")),
301                 attributionStatuses.get(1));
302         String expectedAttributionStatus2 = getAttributionStatus(
303                 List.of("trigger11", "trigger33"), List.of("44", "66"), List.of("11", "33"));
304         verify(mMeasurementDao).updateSourceAttributedTriggers(
305                 eq(source2.getId()), eq(expectedAttributionStatus2));
306     }
307 
308     @Test
resetDedupKeys_matchingReportsDedupAlignFlagOff_removesDedupKeysFromSource()309     public void resetDedupKeys_matchingReportsDedupAlignFlagOff_removesDedupKeysFromSource()
310             throws DatastoreException {
311         // Setup
312         Source source1 =
313                 SourceFixture.getMinimalValidSourceBuilder()
314                         .setId("sourceId1")
315                         .setEventReportDedupKeys(
316                                 new ArrayList<>(
317                                         Arrays.asList(
318                                                 new UnsignedLong("1"),
319                                                 new UnsignedLong("2"),
320                                                 new UnsignedLong("3"))))
321                         .build();
322         Source source2 =
323                 SourceFixture.getMinimalValidSourceBuilder()
324                         .setId("sourceId2")
325                         .setEventReportDedupKeys(
326                                 new ArrayList<>(
327                                         Arrays.asList(
328                                                 new UnsignedLong("11"),
329                                                 new UnsignedLong("22"),
330                                                 new UnsignedLong("33"))))
331                         .build();
332 
333         when(mEventReport1.getTriggerDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
334         when(mEventReport2.getTriggerDedupKey()).thenReturn(new UnsignedLong("22")); // S2 - T2
335         when(mEventReport3.getTriggerDedupKey()).thenReturn(new UnsignedLong("3")); // S1 - T3
336         when(mEventReport1.getSourceId()).thenReturn(source1.getId());
337         when(mEventReport2.getSourceId()).thenReturn(source2.getId());
338         when(mEventReport3.getSourceId()).thenReturn(source1.getId());
339 
340         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
341         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
342 
343         when(mMockFlags.getMeasurementEnableAraDeduplicationAlignmentV1()).thenReturn(false);
344 
345         // Execution
346         mMeasurementDataDeleter.resetDedupKeys(
347                 mMeasurementDao, List.of(mEventReport1, mEventReport2, mEventReport3));
348 
349         // Verification
350         verify(mMeasurementDao, times(2)).updateSourceEventReportDedupKeys(source1);
351         verify(mMeasurementDao).updateSourceEventReportDedupKeys(source2);
352         assertEquals(
353                 Collections.singletonList(new UnsignedLong("2")),
354                 source1.getEventReportDedupKeys());
355         assertEquals(
356                 Arrays.asList(new UnsignedLong("11"), new UnsignedLong("33")),
357                 source2.getEventReportDedupKeys());
358     }
359 
360     @Test
resetDedupKeys_eventReportHasNullSourceIdDedup_ignoresRemoval()361     public void resetDedupKeys_eventReportHasNullSourceIdDedup_ignoresRemoval()
362             throws DatastoreException {
363         // Setup
364         when(mEventReport1.getSourceId()).thenReturn(null);
365         when(mEventReport1.getTriggerDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
366 
367         // Execution
368         mMeasurementDataDeleter.resetDedupKeys(mMeasurementDao, List.of(mEventReport1));
369 
370         // Verification
371         verify(mMeasurementDao, never()).getSource(anyString());
372         verify(mMeasurementDao, never()).updateSourceAttributedTriggers(anyString(), anyString());
373     }
374 
375     @Test
resetDedupKeys_eventReportHasNullSourceIdDedupFlagOff_ignoresRemoval()376     public void resetDedupKeys_eventReportHasNullSourceIdDedupFlagOff_ignoresRemoval()
377             throws DatastoreException {
378         // Setup
379         when(mEventReport1.getSourceId()).thenReturn(null);
380         when(mEventReport1.getTriggerDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
381 
382         when(mMockFlags.getMeasurementEnableAraDeduplicationAlignmentV1()).thenReturn(false);
383 
384         // Execution
385         mMeasurementDataDeleter.resetDedupKeys(mMeasurementDao, List.of(mEventReport1));
386 
387         // Verification
388         verify(mMeasurementDao, never()).getSource(anyString());
389         verify(mMeasurementDao, never()).updateSourceEventReportDedupKeys(any());
390     }
391 
392     @Test
resetAggregateReportDedupKeys_matchingReports_removesDedupKeysFromSource()393     public void resetAggregateReportDedupKeys_matchingReports_removesDedupKeysFromSource()
394             throws DatastoreException {
395         // Setup
396         Source source1 =
397                 SourceFixture.getMinimalValidSourceBuilder()
398                         .setId("sourceId1")
399                         .setAggregateReportDedupKeys(
400                                 new ArrayList<>(
401                                         Arrays.asList(
402                                                 new UnsignedLong("1"),
403                                                 new UnsignedLong("2"),
404                                                 new UnsignedLong("3"))))
405                         .build();
406         Source source2 =
407                 SourceFixture.getMinimalValidSourceBuilder()
408                         .setId("sourceId2")
409                         .setAggregateReportDedupKeys(
410                                 new ArrayList<>(
411                                         Arrays.asList(
412                                                 new UnsignedLong("11"),
413                                                 new UnsignedLong("22"),
414                                                 new UnsignedLong("33"))))
415                         .build();
416 
417         when(mAggregateReport1.getDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
418         when(mAggregateReport2.getDedupKey()).thenReturn(new UnsignedLong("22")); // S2 - T2
419         when(mAggregateReport3.getDedupKey()).thenReturn(new UnsignedLong("3")); // S1 - T3
420         when(mAggregateReport1.getSourceId()).thenReturn(source1.getId());
421         when(mAggregateReport2.getSourceId()).thenReturn(source2.getId());
422         when(mAggregateReport3.getSourceId()).thenReturn(source1.getId());
423 
424         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
425         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
426 
427         // Execution
428         mMeasurementDataDeleter.resetAggregateReportDedupKeys(
429                 mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2, mAggregateReport3));
430 
431         // Verification
432         verify(mMeasurementDao, times(2)).updateSourceAggregateReportDedupKeys(source1);
433         verify(mMeasurementDao).updateSourceAggregateReportDedupKeys(source2);
434         assertEquals(
435                 Collections.singletonList(new UnsignedLong("2")),
436                 source1.getAggregateReportDedupKeys());
437         assertEquals(
438                 Arrays.asList(new UnsignedLong("11"), new UnsignedLong("33")),
439                 source2.getAggregateReportDedupKeys());
440     }
441 
442     @Test
resetAggregateReportDedupKeys_aggregateReportHasNullSourceId_ignoresRemoval()443     public void resetAggregateReportDedupKeys_aggregateReportHasNullSourceId_ignoresRemoval()
444             throws DatastoreException {
445         // Setup
446         when(mAggregateReport1.getSourceId()).thenReturn(null);
447         when(mAggregateReport1.getDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
448 
449         // Execution
450         mMeasurementDataDeleter.resetAggregateReportDedupKeys(
451                 mMeasurementDao, List.of(mAggregateReport1));
452 
453         // Verification
454         verify(mMeasurementDao, never()).getSource(anyString());
455         verify(mMeasurementDao, never()).updateSourceAggregateReportDedupKeys(any());
456     }
457 
458     @Test
resetAggregateReportDedupKeys_aggregateReportHasNullDedupKey_ignoresRemoval()459     public void resetAggregateReportDedupKeys_aggregateReportHasNullDedupKey_ignoresRemoval()
460             throws DatastoreException {
461         // Setup
462         when(mAggregateReport1.getSourceId()).thenReturn(null);
463         when(mAggregateReport1.getDedupKey()).thenReturn(null); // S1 - T1
464 
465         // Execution
466         mMeasurementDataDeleter.resetAggregateReportDedupKeys(
467                 mMeasurementDao, List.of(mAggregateReport1));
468 
469         // Verification
470         verify(mMeasurementDao, never()).getSource(anyString());
471         verify(mMeasurementDao, never()).updateSourceAggregateReportDedupKeys(any());
472     }
473 
474     @Test
deleteAppUninstalledData_reinstallWindowDisabled_undoInstallAttributionCalled()475     public void deleteAppUninstalledData_reinstallWindowDisabled_undoInstallAttributionCalled()
476             throws DatastoreException {
477         // Setup
478         when(mMockFlags.getMeasurementEnableReinstallReattribution()).thenReturn(false);
479 
480         // Execution
481         mMeasurementDataDeleter.deleteAppUninstalledData(
482                 Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME), 0);
483 
484         // Assertions
485         verify(mMeasurementDao).undoInstallAttribution(any());
486     }
487 
488     @Test
deleteAppUninstalledData_reinstallWindowEnabled_undoInstallAttributionNotCalled()489     public void deleteAppUninstalledData_reinstallWindowEnabled_undoInstallAttributionNotCalled()
490             throws DatastoreException {
491         // Setup
492         when(mMockFlags.getMeasurementEnableReinstallReattribution()).thenReturn(true);
493 
494         // Execution
495         mMeasurementDataDeleter.deleteAppUninstalledData(
496                 Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME), 0);
497 
498         // Assertions
499         verify(mMeasurementDao, never()).undoInstallAttribution(any());
500     }
501 
502     @Test
deleteAppUninstalledData_uninstallEnabled_success()503     public void deleteAppUninstalledData_uninstallEnabled_success() throws DatastoreException {
504         // Setup
505         when(mMockFlags.getMeasurementEnableMinReportLifespanForUninstall()).thenReturn(true);
506         when(mMockFlags.getMeasurementMinReportLifespanForUninstallSeconds())
507                 .thenReturn(TimeUnit.DAYS.toMillis(1));
508 
509         Set<String> triggerIds = Set.of("triggerId1", "triggerId2");
510         List<String> sourceIds = List.of("sourceId1", "sourceId2");
511         List<String> asyncRegistrationIds = List.of("asyncRegId1", "asyncRegId2");
512         Source source1 = SourceFixture.getMinimalValidSourceBuilder().setId("sourceId1").build();
513         Source source2 = SourceFixture.getMinimalValidSourceBuilder().setId("sourceId2").build();
514         Trigger trigger1 = TriggerFixture.getValidTriggerBuilder().setId("triggerId1").build();
515         Trigger trigger2 = TriggerFixture.getValidTriggerBuilder().setId("triggerId2").build();
516         Uri packageName = Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME);
517         final DeletionParam deletionParam =
518                 new DeletionParam.Builder(
519                                 /* originUris= */ Collections.emptyList(),
520                                 /* domainUris= */ Collections.emptyList(),
521                                 /* start= */ Instant.MIN,
522                                 /* end= */ Instant.MAX,
523                                 /* appPackageName= */ packageName.getHost(),
524                                 /* sdkPackageName= */ "")
525                         .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_PRESERVE)
526                         .build();
527 
528         when(mEventReport1.getId()).thenReturn("eventReportId1");
529         when(mEventReport2.getId()).thenReturn("eventReportId2");
530         when(mAggregateReport1.getId()).thenReturn("aggregateReportId1");
531         when(mAggregateReport2.getId()).thenReturn("aggregateReportId2");
532 
533         doNothing()
534                 .when(mMeasurementDataDeleter)
535                 .resetAggregateContributions(
536                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
537         doNothing()
538                 .when(mMeasurementDataDeleter)
539                 .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
540         when(mMeasurementDao.fetchMatchingEventReports(sourceIds, triggerIds))
541                 .thenReturn(List.of(mEventReport1, mEventReport2));
542         when(mMeasurementDao.fetchMatchingAggregateReports(sourceIds, triggerIds))
543                 .thenReturn(List.of(mAggregateReport1, mAggregateReport2));
544         when(mMeasurementDao.fetchMatchingSources(
545                         packageName,
546                         deletionParam.getStart(),
547                         deletionParam.getEnd(),
548                         deletionParam.getOriginUris(),
549                         deletionParam.getDomainUris(),
550                         deletionParam.getMatchBehavior()))
551                 .thenReturn(Arrays.asList(source1.getId(), source2.getId()));
552         when(mMeasurementDao.fetchMatchingAsyncRegistrations(
553                         packageName,
554                         deletionParam.getStart(),
555                         deletionParam.getEnd(),
556                         deletionParam.getOriginUris(),
557                         deletionParam.getDomainUris(),
558                         deletionParam.getMatchBehavior()))
559                 .thenReturn(asyncRegistrationIds);
560         when(mMeasurementDao.fetchMatchingTriggers(
561                         packageName,
562                         deletionParam.getStart(),
563                         deletionParam.getEnd(),
564                         deletionParam.getOriginUris(),
565                         deletionParam.getDomainUris(),
566                         deletionParam.getMatchBehavior()))
567                 .thenReturn(Set.of(trigger1.getId(), trigger2.getId()));
568 
569         Pair<List<String>, List<String>> sourceIdsUninstall =
570                 new Pair<>(Arrays.asList(source1.getId()), Arrays.asList(source2.getId()));
571 
572         Pair<List<String>, List<String>> triggerIdsUninstall =
573                 new Pair<>(Arrays.asList(trigger1.getId()), Arrays.asList(trigger2.getId()));
574 
575         when(mMeasurementDao.fetchMatchingSourcesUninstall(packageName, 0))
576                 .thenReturn(sourceIdsUninstall);
577         when(mMeasurementDao.fetchMatchingTriggersUninstall(packageName, 0))
578                 .thenReturn(triggerIdsUninstall);
579 
580         when(mEventReport1.getSourceId()).thenReturn("sourceId1");
581         when(mEventReport2.getSourceId()).thenReturn("sourceId2");
582         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
583         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
584 
585         // Execution
586         boolean result = mMeasurementDataDeleter.deleteAppUninstalledData(packageName, 0);
587 
588         // Assertions
589         Truth.assertThat(result).isTrue();
590 
591         verify(mMeasurementDao).deleteSources(sourceIdsUninstall.first);
592         verify(mMeasurementDao).deleteTriggers(triggerIdsUninstall.first);
593 
594         verify(mMeasurementDao)
595                 .updateSourceStatus(sourceIdsUninstall.second, Source.Status.MARKED_TO_DELETE);
596         verify(mMeasurementDao)
597                 .updateTriggerStatus(triggerIdsUninstall.second, Trigger.Status.MARKED_TO_DELETE);
598     }
599 
600     @Test
delete_deletionModeAll_success()601     public void delete_deletionModeAll_success() throws DatastoreException {
602         // Setup
603         Set<String> triggerIds = Set.of("triggerId1", "triggerId2");
604         List<String> sourceIds = List.of("sourceId1", "sourceId2");
605         List<String> asyncRegistrationIds = List.of("asyncRegId1", "asyncRegId2");
606         Source source1 = SourceFixture.getMinimalValidSourceBuilder().setId("sourceId1").build();
607         Source source2 =
608                 SourceFixture.getMinimalValidSourceBuilder()
609                         .setId("sourceId2")
610                         .setAggregateReportDedupKeys(
611                                 List.of(new UnsignedLong(1L), new UnsignedLong(2L)))
612                         .build();
613         Trigger trigger1 = TriggerFixture.getValidTriggerBuilder().setId("triggerId1").build();
614         Trigger trigger2 = TriggerFixture.getValidTriggerBuilder().setId("triggerId2").build();
615         when(mEventReport1.getId()).thenReturn("eventReportId1");
616         when(mEventReport2.getId()).thenReturn("eventReportId2");
617         when(mAggregateReport1.getId()).thenReturn("aggregateReportId1");
618         when(mAggregateReport2.getId()).thenReturn("aggregateReportId2");
619         DeletionParam deletionParam =
620                 new DeletionParam.Builder(
621                                 mOriginUris,
622                                 mDomainUris,
623                                 START,
624                                 END,
625                                 APP_PACKAGE_NAME,
626                                 SDK_PACKAGE_NAME)
627                         .setDeletionMode(DeletionRequest.DELETION_MODE_ALL)
628                         .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
629                         .build();
630 
631         doNothing()
632                 .when(mMeasurementDataDeleter)
633                 .resetAggregateContributions(
634                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
635         doNothing()
636                 .when(mMeasurementDataDeleter)
637                 .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
638         when(mMeasurementDao.fetchMatchingEventReports(sourceIds, triggerIds))
639                 .thenReturn(List.of(mEventReport1, mEventReport2));
640         when(mMeasurementDao.fetchMatchingAggregateReports(sourceIds, triggerIds))
641                 .thenReturn(List.of(mAggregateReport1, mAggregateReport2));
642         when(mMeasurementDao.fetchMatchingSources(
643                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
644                         START,
645                         END,
646                         mOriginUris,
647                         mDomainUris,
648                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
649                 .thenReturn(Arrays.asList(source1.getId(), source2.getId()));
650         when(mMeasurementDao.fetchMatchingAsyncRegistrations(
651                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
652                         START,
653                         END,
654                         mOriginUris,
655                         mDomainUris,
656                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
657                 .thenReturn(asyncRegistrationIds);
658         when(mMeasurementDao.fetchMatchingTriggers(
659                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
660                         START,
661                         END,
662                         mOriginUris,
663                         mDomainUris,
664                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
665                 .thenReturn(Set.of(trigger1.getId(), trigger2.getId()));
666         when(mEventReport1.getSourceId()).thenReturn("sourceId1");
667         when(mEventReport2.getSourceId()).thenReturn("sourceId2");
668         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
669         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
670 
671         // Execution
672         boolean result = mMeasurementDataDeleter.delete(deletionParam);
673 
674         // Assertions
675         assertTrue(result);
676         verify(mMeasurementDataDeleter)
677                 .resetAggregateContributions(
678                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
679         verify(mMeasurementDataDeleter)
680                 .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
681         verify(mMeasurementDao).deleteAsyncRegistrations(asyncRegistrationIds);
682         verify(mMeasurementDao).deleteSources(sourceIds);
683         verify(mMeasurementDao).deleteTriggers(triggerIds);
684         verify(mMeasurementDataDeleter)
685                 .resetAggregateReportDedupKeys(
686                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
687     }
688 
689     @Test
delete_deletionModeAllFlexApi_success()690     public void delete_deletionModeAllFlexApi_success() throws DatastoreException, JSONException {
691         // Setup
692         // A mutable set -- the expected result of MeasurementDao::fetchMatchingTriggers
693         Set<String> triggerIds = new HashSet<>();
694         triggerIds.addAll(List.of("triggerId1", "triggerId2"));
695         // A list -- the expected result of MeasurementDao::fetchMatchingSources
696         List<String> sourceIds = List.of("sourceId1", "sourceId2");
697         // Expected triggers to delete
698         Set<String> extendedTriggerIds = Set.of(
699                 "triggerId1", "triggerId2", "triggerId3", "triggerId4");
700         // A mutable set -- the expected result of MeasurementDao::fetchFlexSourceIdsFor
701         Set<String> extendedSourceIds1 = new HashSet<>();
702         extendedSourceIds1.addAll(List.of("sourceId3", "sourceId4"));
703         // Expected parameter passed to MeasurementDao::fetchMatchingEventReports.
704         Set<String> extendedSourceIds2 = Set.of("sourceId1", "sourceId2", "sourceId3", "sourceId4");
705         List<String> asyncRegistrationIds = List.of("asyncRegId1", "asyncRegId2");
706         Source source1 = SourceFixture.getMinimalValidSourceBuilder().setId("sourceId1").build();
707         Source source2 =
708                 SourceFixture.getMinimalValidSourceBuilder()
709                         .setId("sourceId2")
710                         .setAggregateReportDedupKeys(
711                                 List.of(new UnsignedLong(1L), new UnsignedLong(2L)))
712                         .build();
713 
714         // Two Flex API sources
715 
716         // One trigger is included in triggerIds, the other is not. Both will be added to the set.
717         String attributionStatus3 = getAttributionStatus(
718                 List.of("triggerId2", "triggerId3"),
719                 List.of("4", "5"),
720                 List.of("1", "2"));
721         Source source3 =
722                 SourceFixture.getValidSourceBuilderWithFlexEventReport()
723                         .setId("sourceId3")
724                         .setEventAttributionStatus(attributionStatus3)
725                         .build();
726         // A trigger that is not included in triggerIds will be added to the set.
727         String attributionStatus4 = getAttributionStatus(
728                 List.of("triggerId4"),
729                 List.of("4"),
730                 List.of("1"));
731         Source source4 =
732                 SourceFixture.getValidSourceBuilderWithFlexEventReportValueSum()
733                         .setId("sourceId4")
734                         .setEventAttributionStatus(attributionStatus4)
735                         .build();
736 
737         when(mEventReport1.getId()).thenReturn("eventReportId1");
738         when(mEventReport2.getId()).thenReturn("eventReportId2");
739         when(mEventReport3.getId()).thenReturn("eventReportId3");
740         when(mAggregateReport1.getId()).thenReturn("aggregateReportId1");
741         when(mAggregateReport2.getId()).thenReturn("aggregateReportId2");
742         DeletionParam deletionParam =
743                 new DeletionParam.Builder(
744                                 mOriginUris,
745                                 mDomainUris,
746                                 START,
747                                 END,
748                                 APP_PACKAGE_NAME,
749                                 SDK_PACKAGE_NAME)
750                         .setDeletionMode(DeletionRequest.DELETION_MODE_ALL)
751                         .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
752                         .build();
753 
754         doNothing()
755                 .when(mMeasurementDataDeleter)
756                 .resetAggregateContributions(
757                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
758         doNothing()
759                 .when(mMeasurementDataDeleter)
760                 .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
761         // Due to Flex API, MeasurementDao::fetchMatchingEventReports will be called with
762         // extendedSourceIds.
763         when(mMeasurementDao.fetchMatchingEventReports(extendedSourceIds2, triggerIds))
764                 .thenReturn(List.of(mEventReport1, mEventReport2, mEventReport3));
765         when(mMeasurementDao.fetchMatchingAggregateReports(sourceIds, triggerIds))
766                 .thenReturn(List.of(mAggregateReport1, mAggregateReport2));
767         when(mMeasurementDao.fetchMatchingSources(
768                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
769                         START,
770                         END,
771                         mOriginUris,
772                         mDomainUris,
773                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
774                 .thenReturn(sourceIds);
775         when(mMeasurementDao.fetchMatchingAsyncRegistrations(
776                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
777                         START,
778                         END,
779                         mOriginUris,
780                         mDomainUris,
781                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
782                 .thenReturn(asyncRegistrationIds);
783         when(mMeasurementDao.fetchMatchingTriggers(
784                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
785                         START,
786                         END,
787                         mOriginUris,
788                         mDomainUris,
789                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
790                 .thenReturn(triggerIds);
791         when(mEventReport1.getSourceId()).thenReturn("sourceId1");
792         when(mEventReport2.getSourceId()).thenReturn("sourceId2");
793         when(mEventReport3.getSourceId()).thenReturn("sourceId3");
794         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
795         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
796         when(mMeasurementDao.getSource(source3.getId())).thenReturn(source3);
797         when(mMeasurementDao.getSource(source4.getId())).thenReturn(source4);
798 
799         // Flex API
800         when(mMockFlags.getMeasurementFlexibleEventReportingApiEnabled()).thenReturn(true);
801         when(mMeasurementDao.fetchFlexSourceIdsFor(triggerIds))
802                 .thenReturn(extendedSourceIds1);
803 
804         // Execution
805         boolean result = mMeasurementDataDeleter.delete(deletionParam);
806 
807         // Assertions
808         assertTrue(result);
809         verify(mMeasurementDataDeleter)
810                 .resetAggregateContributions(
811                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
812         verify(mMeasurementDataDeleter)
813                 .resetDedupKeys(
814                         mMeasurementDao, List.of(mEventReport1, mEventReport2, mEventReport3));
815         verify(mMeasurementDao).deleteAsyncRegistrations(asyncRegistrationIds);
816         verify(mMeasurementDao).deleteSources(sourceIds);
817         verify(mMeasurementDao).deleteTriggers(extendedTriggerIds);
818         verify(mMeasurementDataDeleter)
819                 .resetAggregateReportDedupKeys(
820                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
821         ArgumentCaptor<String> sourceIdArg = ArgumentCaptor.forClass(String.class);
822         ArgumentCaptor<String> attributionStatusArg = ArgumentCaptor.forClass(String.class);
823         verify(mMeasurementDao, times(2)).updateSourceAttributedTriggers(
824                 sourceIdArg.capture(), attributionStatusArg.capture());
825         assertEquals(Set.of("sourceId3", "sourceId4"), new HashSet(sourceIdArg.getAllValues()));
826         assertEquals(
827                 List.of(new JSONArray().toString(), new JSONArray().toString()),
828                 attributionStatusArg.getAllValues());
829     }
830 
831     @Test
delete_deletionModeExcludeInternalData_success()832     public void delete_deletionModeExcludeInternalData_success() throws DatastoreException {
833         // Setup
834         Set<String> triggerIds = Set.of("triggerId1", "triggerId2");
835         List<String> sourceIds = List.of("sourceId1", "sourceId2");
836         Source source1 = SourceFixture.getMinimalValidSourceBuilder().setId("sourceId1").build();
837         Source source2 = SourceFixture.getMinimalValidSourceBuilder().setId("sourceId2").build();
838         Trigger trigger1 = TriggerFixture.getValidTriggerBuilder().setId("triggerId1").build();
839         Trigger trigger2 = TriggerFixture.getValidTriggerBuilder().setId("triggerId2").build();
840         when(mEventReport1.getId()).thenReturn("eventReportId1");
841         when(mEventReport2.getId()).thenReturn("eventReportId2");
842         when(mEventReport1.getSourceId()).thenReturn("sourceId1");
843         when(mEventReport2.getSourceId()).thenReturn("sourceId2");
844         when(mAggregateReport1.getId()).thenReturn("aggregateReportId1");
845         when(mAggregateReport2.getId()).thenReturn("aggregateReportId2");
846         DeletionParam deletionParam =
847                 new DeletionParam.Builder(
848                                 mOriginUris,
849                                 mDomainUris,
850                                 START,
851                                 END,
852                                 APP_PACKAGE_NAME,
853                                 SDK_PACKAGE_NAME)
854                         .setDeletionMode(DeletionRequest.DELETION_MODE_EXCLUDE_INTERNAL_DATA)
855                         .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
856                         .build();
857 
858         doNothing()
859                 .when(mMeasurementDataDeleter)
860                 .resetAggregateContributions(
861                         mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
862         doNothing()
863                 .when(mMeasurementDataDeleter)
864                 .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
865         when(mMeasurementDao.fetchMatchingEventReports(sourceIds, triggerIds))
866                 .thenReturn(List.of(mEventReport1, mEventReport2));
867         when(mMeasurementDao.fetchMatchingAggregateReports(sourceIds, triggerIds))
868                 .thenReturn(List.of(mAggregateReport1, mAggregateReport2));
869         when(mMeasurementDao.fetchMatchingSources(
870                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
871                         START,
872                         END,
873                         mOriginUris,
874                         mDomainUris,
875                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
876                 .thenReturn(Arrays.asList(source1.getId(), source2.getId()));
877         when(mMeasurementDao.fetchMatchingTriggers(
878                         Uri.parse(ANDROID_APP_SCHEME + "://" + APP_PACKAGE_NAME),
879                         START,
880                         END,
881                         mOriginUris,
882                         mDomainUris,
883                         DeletionRequest.MATCH_BEHAVIOR_DELETE))
884                 .thenReturn(Set.of(trigger1.getId(), trigger2.getId()));
885         when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
886         when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
887 
888         // Execution
889         boolean result = mMeasurementDataDeleter.delete(deletionParam);
890 
891         // Assertions
892         assertTrue(result);
893         verify(mMeasurementDao)
894                 .markEventReportStatus(
895                         eq("eventReportId1"), eq(EventReport.Status.MARKED_TO_DELETE));
896         verify(mMeasurementDao)
897                 .markEventReportStatus(
898                         eq("eventReportId2"), eq(EventReport.Status.MARKED_TO_DELETE));
899         verify(mMeasurementDao)
900                 .markAggregateReportStatus(
901                         eq("aggregateReportId2"), eq(AggregateReport.Status.MARKED_TO_DELETE));
902         verify(mMeasurementDao)
903                 .markAggregateReportStatus(
904                         eq("aggregateReportId2"), eq(AggregateReport.Status.MARKED_TO_DELETE));
905         verify(mMeasurementDao)
906                 .updateSourceStatus(
907                         eq(List.of(source1.getId(), source2.getId())),
908                         eq(Source.Status.MARKED_TO_DELETE));
909         verify(mMeasurementDao)
910                 .updateTriggerStatus(
911                         eq(Set.of(trigger1.getId(), trigger2.getId())),
912                         eq(Trigger.Status.MARKED_TO_DELETE));
913     }
914 
getAttributionStatus(List<String> triggerIds, List<String> triggerData, List<String> dedupKeys)915     private static String getAttributionStatus(List<String> triggerIds, List<String> triggerData,
916             List<String> dedupKeys) {
917         try {
918             JSONArray attributionStatus = new JSONArray();
919             for (int i = 0; i < triggerIds.size(); i++) {
920                 attributionStatus.put(
921                         new JSONObject()
922                                 .put("trigger_id", triggerIds.get(i))
923                                 .put("trigger_data", triggerData.get(i))
924                                 .put("dedup_key", dedupKeys.get(i)));
925             }
926             return attributionStatus.toString();
927         } catch (JSONException ignored) {
928             return null;
929         }
930     }
931 }
932