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