1 /*
2  * Copyright (C) 2023 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 package com.android.adservices.data.measurement.migration;
17 
18 import static com.android.adservices.common.DbTestUtil.getDbHelperForTest;
19 import static com.android.adservices.data.measurement.migration.MigrationTestHelper.populateDb;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 
24 import android.content.ContentValues;
25 import android.database.Cursor;
26 import android.database.sqlite.SQLiteDatabase;
27 import android.util.Pair;
28 
29 import com.android.adservices.data.measurement.MeasurementDbHelper;
30 import com.android.adservices.data.measurement.MeasurementTables;
31 import com.android.adservices.service.measurement.Attribution;
32 
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.mockito.junit.MockitoJUnitRunner;
36 
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.LinkedHashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.UUID;
44 
45 @RunWith(MockitoJUnitRunner.class)
46 public class MeasurementDbMigratorV29Test extends MeasurementDbMigratorTestBase {
47     private static final String ID_ONE = "0001";
48     private static final String ID_TWO = "0002";
49 
50     @Test
performMigration_v28ToV29WithData_maintainsDataIntegrity()51     public void performMigration_v28ToV29WithData_maintainsDataIntegrity() {
52         // Setup
53         MeasurementDbHelper dbHelper =
54                 new MeasurementDbHelper(
55                         sContext,
56                         MEASUREMENT_DATABASE_NAME_FOR_MIGRATION,
57                         28,
58                         getDbHelperForTest());
59         SQLiteDatabase db = dbHelper.getWritableDatabase();
60         String source1Id = UUID.randomUUID().toString();
61         // Source and Trigger objects must be inserted first to satisfy foreign key dependencies
62         populateDb(db, createFakeDataSourceAndTriggerV28(source1Id));
63         Pair<Map<String, List<ContentValues>>, List<ContentValues>> fakeDataPair =
64                 createFakeDataV28(source1Id);
65         Map<String, List<ContentValues>> fakeData = fakeDataPair.first;
66         List<ContentValues> expectedNewRows = fakeDataPair.second;
67         populateDb(db, fakeData);
68         // Execution
69         getTestSubject().performMigration(db, 28, 29);
70         // Assertion
71         // We expect actual data to include two new rows.
72         fakeData.computeIfPresent(MeasurementTables.AttributionContract.TABLE,
73                 (k, v) -> {
74                         v.addAll(expectedNewRows);
75                         return v;
76                 });
77         Map<String, Set<String>> columnsToBeSkipped = Map.of(
78                 MeasurementTables.AttributionContract.TABLE,
79                 Set.of(MeasurementTables.AttributionContract.ID));
80         MigrationTestHelper.verifyDataInDb(db, fakeData, new HashMap<>(), columnsToBeSkipped);
81         try (Cursor cursor =
82                 db.query(
83                         MeasurementTables.AttributionContract.TABLE,
84                         new String[] {
85                                 MeasurementTables.AttributionContract.ID,
86                                 MeasurementTables.AttributionContract.SCOPE
87                         },
88                         null,
89                         null,
90                         null,
91                         null,
92                         /* orderBy */ MeasurementTables.AttributionContract.ID)) {
93             assertEquals(4, cursor.getCount());
94             // We expect the migration to create two records of scope event and two of scope
95             // aggregate. Confirm record IDs for the first two records since they are known.
96             assertTrue(cursor.moveToPosition(0));
97             String id1 = cursor.getString(
98                     cursor.getColumnIndex(MeasurementTables.AttributionContract.ID));
99             assertEquals(ID_ONE, id1);
100             @Attribution.Scope int scope1 = cursor.getInt(
101                     cursor.getColumnIndex(MeasurementTables.AttributionContract.SCOPE));
102             assertEquals(Attribution.Scope.EVENT, scope1);
103 
104             assertTrue(cursor.moveToPosition(1));
105             String id2 = cursor.getString(
106                     cursor.getColumnIndex(MeasurementTables.AttributionContract.ID));
107             assertEquals(ID_TWO, id2);
108             @Attribution.Scope int scope2 = cursor.getInt(
109                     cursor.getColumnIndex(MeasurementTables.AttributionContract.SCOPE));
110             assertEquals(Attribution.Scope.EVENT, scope2);
111 
112             assertTrue(cursor.moveToPosition(2));
113             @Attribution.Scope int scope3 = cursor.getInt(
114                     cursor.getColumnIndex(MeasurementTables.AttributionContract.SCOPE));
115             assertEquals(Attribution.Scope.AGGREGATE, scope3);
116 
117             assertTrue(cursor.moveToPosition(3));
118             @Attribution.Scope int scope4 = cursor.getInt(
119                     cursor.getColumnIndex(MeasurementTables.AttributionContract.SCOPE));
120             assertEquals(Attribution.Scope.AGGREGATE, scope4);
121         }
122     }
123 
createFakeDataSourceAndTriggerV28(String source1Id)124     private Map<String, List<ContentValues>> createFakeDataSourceAndTriggerV28(String source1Id) {
125         Map<String, List<ContentValues>> tableRowsMap = new LinkedHashMap<>();
126         // Source Table
127         List<ContentValues> sourceRows = new ArrayList<>();
128         ContentValues source1 = ContentValueFixtures.generateSourceContentValuesV22();
129         source1.put(MeasurementTables.SourceContract.ID, source1Id);
130         sourceRows.add(source1);
131         sourceRows.add(ContentValueFixtures.generateSourceContentValuesV22());
132         tableRowsMap.put(MeasurementTables.SourceContract.TABLE, sourceRows);
133         // Trigger Table
134         List<ContentValues> triggerRows = new ArrayList<>();
135         ContentValues trigger1 = ContentValueFixtures.generateTriggerContentValuesV21();
136         trigger1.put(MeasurementTables.TriggerContract.ID, UUID.randomUUID().toString());
137         triggerRows.add(trigger1);
138         triggerRows.add(ContentValueFixtures.generateTriggerContentValuesV21());
139         tableRowsMap.put(MeasurementTables.TriggerContract.TABLE, triggerRows);
140         return tableRowsMap;
141     }
142 
createFakeDataV28( String source1Id)143     private Pair<Map<String, List<ContentValues>>, List<ContentValues>> createFakeDataV28(
144             String source1Id) {
145         Map<String, List<ContentValues>> tableRowsMap = new LinkedHashMap<>();
146         // Source Destination Table
147         List<ContentValues> sourceDestinationRows = new ArrayList<>();
148         ContentValues sourceDestination1 =
149                 ContentValueFixtures.generateSourceDestinationContentValuesV22();
150         sourceDestination1.put(MeasurementTables.SourceDestination.SOURCE_ID, source1Id);
151         sourceDestinationRows.add(sourceDestination1);
152         sourceDestinationRows.add(ContentValueFixtures.generateSourceDestinationContentValuesV22());
153         tableRowsMap.put(MeasurementTables.SourceDestination.TABLE, sourceDestinationRows);
154         // Attribution Table
155         List<ContentValues> attributionRows = new ArrayList<>();
156         ContentValues attribution1 = ContentValueFixtures.generateAttributionContentValuesV28();
157         ContentValues attribution2 = ContentValueFixtures.generateAttributionContentValuesV28();
158         attribution1.put(MeasurementTables.AttributionContract.ID, ID_ONE);
159         attribution2.put(MeasurementTables.AttributionContract.ID, ID_TWO);
160         attributionRows.add(attribution1);
161         attributionRows.add(attribution2);
162         tableRowsMap.put(MeasurementTables.AttributionContract.TABLE, attributionRows);
163         return Pair.create(
164                 tableRowsMap,
165                 List.of(
166                         attribution1,
167                         ContentValueFixtures.generateAttributionContentValuesV28()));
168     }
169 
170     @Override
getTargetVersion()171     int getTargetVersion() {
172         return 29;
173     }
174 
175     @Override
getTestSubject()176     AbstractMeasurementDbMigrator getTestSubject() {
177         return new MeasurementDbMigratorV29();
178     }
179 }
180