1 /* Any copyright is dedicated to the Public Domain.
2    http://creativecommons.org/publicdomain/zero/1.0/ */
3 
4 package org.mozilla.gecko.db;
5 
6 import android.content.ContentValues;
7 import android.database.Cursor;
8 import android.net.Uri;
9 
10 import org.junit.Test;
11 import org.junit.runner.RunWith;
12 import org.mozilla.gecko.background.testhelpers.TestRunner;
13 
14 import org.mozilla.gecko.db.BrowserContract.History;
15 
16 import static org.junit.Assert.*;
17 
18 @RunWith(TestRunner.class)
19 /**
20  * Testing insertion/deletion of visits as by-product of updating history records through BrowserProvider
21  */
22 public class BrowserProviderHistoryVisitsTest extends BrowserProviderHistoryVisitsTestBase {
23     @Test
24     /**
25      * Testing updating history records without affecting visits
26      */
testUpdateNoVisit()27     public void testUpdateNoVisit() throws Exception {
28         insertHistoryItem("https://www.mozilla.org", "testGUID");
29 
30         Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
31         assertNotNull(cursor);
32         assertEquals(0, cursor.getCount());
33         cursor.close();
34 
35         ContentValues historyUpdate = new ContentValues();
36         historyUpdate.put(History.TITLE, "Mozilla!");
37         assertEquals(1,
38                 historyClient.update(
39                         historyTestUri, historyUpdate, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
40                 )
41         );
42 
43         cursor = visitsClient.query(visitsTestUri, null, null, null, null);
44         assertNotNull(cursor);
45         assertEquals(0, cursor.getCount());
46         cursor.close();
47 
48         ContentValues historyToInsert = new ContentValues();
49         historyToInsert.put(History.URL, "https://www.eff.org");
50         assertEquals(1,
51                 historyClient.update(
52                         historyTestUri.buildUpon()
53                                 .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
54                         historyToInsert, null, null
55                 )
56         );
57 
58         cursor = visitsClient.query(visitsTestUri, null, null, null, null);
59         assertNotNull(cursor);
60         assertEquals(0, cursor.getCount());
61         cursor.close();
62     }
63 
64     @Test
65     /**
66      * Testing INCREMENT_VISITS flag for multiple history records at once
67      */
testUpdateMultipleHistoryIncrementVisit()68     public void testUpdateMultipleHistoryIncrementVisit() throws Exception {
69         insertHistoryItem("https://www.mozilla.org", "testGUID");
70         insertHistoryItem("https://www.mozilla.org", "testGUID2");
71 
72         // test that visits get inserted when updating existing history records
73         assertEquals(2, historyClient.update(
74                 historyTestUri.buildUpon()
75                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
76                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
77         ));
78 
79         Cursor cursor = visitsClient.query(
80                 visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
81         assertNotNull(cursor);
82         assertEquals(2, cursor.getCount());
83         assertTrue(cursor.moveToFirst());
84 
85         String guid1 = cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID));
86         cursor.moveToNext();
87         String guid2 = cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID));
88         cursor.close();
89 
90         assertNotEquals(guid1, guid2);
91 
92         assertTrue(guid1.equals("testGUID") || guid1.equals("testGUID2"));
93     }
94 
95     @Test
96     /**
97      * Testing INCREMENT_VISITS flag and its interplay with INSERT_IF_NEEDED
98      */
testUpdateHistoryIncrementVisit()99     public void testUpdateHistoryIncrementVisit() throws Exception {
100         insertHistoryItem("https://www.mozilla.org", "testGUID");
101 
102         // test that visit gets inserted when updating an existing histor record
103         assertEquals(1, historyClient.update(
104                 historyTestUri.buildUpon()
105                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
106             new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
107         ));
108 
109         Cursor cursor = visitsClient.query(
110                 visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
111         assertNotNull(cursor);
112         assertEquals(1, cursor.getCount());
113         assertTrue(cursor.moveToFirst());
114         assertEquals(
115                 "testGUID",
116                 cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID))
117         );
118         cursor.close();
119 
120         // test that visit gets inserted when updatingOrInserting a new history record
121         ContentValues historyItem = new ContentValues();
122         historyItem.put(History.URL, "https://www.eff.org");
123 
124         assertEquals(1, historyClient.update(
125                 historyTestUri.buildUpon()
126                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
127                         .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
128                 historyItem, null, null
129         ));
130 
131         cursor = historyClient.query(
132                 historyTestUri,
133                 new String[] {History.GUID}, History.URL + " = ?", new String[] {"https://www.eff.org"}, null
134         );
135         assertNotNull(cursor);
136         assertEquals(1, cursor.getCount());
137         assertTrue(cursor.moveToFirst());
138         String insertedGUID = cursor.getString(cursor.getColumnIndex(History.GUID));
139         cursor.close();
140 
141         cursor = visitsClient.query(
142                 visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
143         assertNotNull(cursor);
144         assertEquals(2, cursor.getCount());
145         assertTrue(cursor.moveToFirst());
146         assertEquals(insertedGUID,
147                 cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID))
148         );
149         cursor.close();
150     }
151 
152     @Test
153     /**
154      * Test that for locally generated visits, we store their timestamps in microseconds, and not in
155      * milliseconds like history does.
156      */
testTimestampConversionOnInsertion()157     public void testTimestampConversionOnInsertion() throws Exception {
158         insertHistoryItem("https://www.mozilla.org", "testGUID");
159 
160         Long lastVisited = System.currentTimeMillis();
161         ContentValues updatedVisitedTime = new ContentValues();
162         updatedVisitedTime.put(History.DATE_LAST_VISITED, lastVisited);
163 
164         // test with last visited date passed in
165         assertEquals(1, historyClient.update(
166                 historyTestUri.buildUpon()
167                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
168                 updatedVisitedTime, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
169         ));
170 
171         Cursor cursor = visitsClient.query(visitsTestUri, new String[] {BrowserContract.Visits.DATE_VISITED}, null, null, null);
172         assertNotNull(cursor);
173         assertEquals(1, cursor.getCount());
174         assertTrue(cursor.moveToFirst());
175 
176         assertEquals(lastVisited * 1000, cursor.getLong(cursor.getColumnIndex(BrowserContract.Visits.DATE_VISITED)));
177         cursor.close();
178 
179         // test without last visited date
180         assertEquals(1, historyClient.update(
181                 historyTestUri.buildUpon()
182                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
183                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
184         ));
185 
186         cursor = visitsClient.query(visitsTestUri, new String[] {BrowserContract.Visits.DATE_VISITED}, null, null, null);
187         assertNotNull(cursor);
188         assertEquals(2, cursor.getCount());
189         assertTrue(cursor.moveToFirst());
190 
191         // CP should generate time off of current time upon insertion and convert to microseconds.
192         // This also tests correct ordering (DESC on date).
193         assertTrue(lastVisited * 1000 < cursor.getLong(cursor.getColumnIndex(BrowserContract.Visits.DATE_VISITED)));
194         cursor.close();
195     }
196 
197     @Test
198     /**
199      * This should perform `DELETE FROM visits WHERE history_guid in IN (?, ?, ?, ..., ?)` sort of statement
200      * SQLite has a variable count limit (999 by default), so we're testing here that our deletion
201      * code does the right thing and chunks deletes to account for this limitation.
202      */
testDeletingLotsOfHistory()203     public void testDeletingLotsOfHistory() throws Exception {
204         Uri incrementUri = historyTestUri.buildUpon()
205                 .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build();
206 
207         // insert bunch of history records, and for each insert a visit
208         for (int i = 0; i < 2100; i++) {
209             final String url = "https://www.mozilla" + i + ".org";
210             insertHistoryItem(url, "testGUID" + i);
211             assertEquals(1, historyClient.update(incrementUri, new ContentValues(), History.URL + " = ?", new String[] {url}));
212         }
213 
214         // sanity check
215         Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
216         assertNotNull(cursor);
217         assertEquals(2100, cursor.getCount());
218         cursor.close();
219 
220         // delete all of the history items - this will trigger chunked deletion of visits as well
221         assertEquals(2100,
222                 historyClient.delete(historyTestUri, null, null)
223         );
224 
225         // check that all visits where deleted
226         cursor = visitsClient.query(visitsTestUri, null, null, null, null);
227         assertNotNull(cursor);
228         assertEquals(0, cursor.getCount());
229         cursor.close();
230     }
231 
232     @Test
233     /**
234      * Test visit deletion as by-product of history deletion - both explicit (from outside of Sync),
235      * and implicit (cascaded, from Sync).
236      */
testDeletingHistory()237     public void testDeletingHistory() throws Exception {
238         insertHistoryItem("https://www.mozilla.org", "testGUID");
239         insertHistoryItem("https://www.eff.org", "testGUID2");
240 
241         // insert some visits
242         assertEquals(1, historyClient.update(
243                 historyTestUri.buildUpon()
244                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
245                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
246         ));
247         assertEquals(1, historyClient.update(
248                 historyTestUri.buildUpon()
249                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
250                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
251         ));
252         assertEquals(1, historyClient.update(
253                 historyTestUri.buildUpon()
254                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
255                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.eff.org"}
256         ));
257 
258         Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
259         assertNotNull(cursor);
260         assertEquals(3, cursor.getCount());
261         cursor.close();
262 
263         // test that corresponding visit records are deleted if Sync isn't involved
264         assertEquals(1,
265                 historyClient.delete(historyTestUri, History.URL + " = ?", new String[] {"https://www.mozilla.org"})
266         );
267 
268         cursor = visitsClient.query(visitsTestUri, null, null, null, null);
269         assertNotNull(cursor);
270         assertEquals(1, cursor.getCount());
271         cursor.close();
272 
273         // test that corresponding visit records are deleted if Sync is involved
274         // insert some more visits
275         ContentValues moz = new ContentValues();
276         moz.put(History.URL, "https://www.mozilla.org");
277         moz.put(History.GUID, "testGUID3");
278         assertEquals(1, historyClient.update(
279                 historyTestUri.buildUpon()
280                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
281                         .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
282                 moz, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
283         ));
284         assertEquals(1, historyClient.update(
285                 historyTestUri.buildUpon()
286                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
287                         .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
288                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.eff.org"}
289         ));
290 
291         assertEquals(1,
292                 historyClient.delete(
293                         historyTestUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "true").build(),
294                         History.URL + " = ?", new String[] {"https://www.eff.org"})
295         );
296 
297         cursor = visitsClient.query(visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
298         assertNotNull(cursor);
299         assertEquals(1, cursor.getCount());
300         assertTrue(cursor.moveToFirst());
301         assertEquals("testGUID3", cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID)));
302         cursor.close();
303     }
304 
305     @Test
306     /**
307      * Test that changes to History GUID are cascaded to individual visits.
308      * See UPDATE CASCADED on Visit's HISTORY_GUID foreign key.
309      */
testHistoryGUIDUpdate()310     public void testHistoryGUIDUpdate() throws Exception {
311         insertHistoryItem("https://www.mozilla.org", "testGUID");
312         insertHistoryItem("https://www.eff.org", "testGUID2");
313 
314         // insert some visits
315         assertEquals(1, historyClient.update(
316                 historyTestUri.buildUpon()
317                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
318                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
319         ));
320         assertEquals(1, historyClient.update(
321                 historyTestUri.buildUpon()
322                         .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
323                 new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
324         ));
325 
326         // change testGUID -> testGUIDNew
327         ContentValues newGuid = new ContentValues();
328         newGuid.put(History.GUID, "testGUIDNew");
329         assertEquals(1, historyClient.update(
330                 historyTestUri, newGuid, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
331         ));
332 
333         Cursor cursor = visitsClient.query(visitsTestUri, null, BrowserContract.Visits.HISTORY_GUID + " = ?", new String[] {"testGUIDNew"}, null);
334         assertNotNull(cursor);
335         assertEquals(2, cursor.getCount());
336         cursor.close();
337     }
338 }