1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/tests/background/junit3/src/db/TestAndroidBrowserHistoryRepository.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,498 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +package org.mozilla.gecko.background.db; 1.8 + 1.9 +import java.util.ArrayList; 1.10 + 1.11 +import org.json.simple.JSONObject; 1.12 +import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate; 1.13 +import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate; 1.14 +import org.mozilla.gecko.background.sync.helpers.HistoryHelpers; 1.15 +import org.mozilla.gecko.db.BrowserContract; 1.16 +import org.mozilla.gecko.sync.Utils; 1.17 +import org.mozilla.gecko.sync.repositories.InactiveSessionException; 1.18 +import org.mozilla.gecko.sync.repositories.NullCursorException; 1.19 +import org.mozilla.gecko.sync.repositories.Repository; 1.20 +import org.mozilla.gecko.sync.repositories.RepositorySession; 1.21 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryDataAccessor; 1.22 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepository; 1.23 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepositorySession; 1.24 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepository; 1.25 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor; 1.26 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositorySession; 1.27 +import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers; 1.28 +import org.mozilla.gecko.sync.repositories.android.RepoUtils; 1.29 +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate; 1.30 +import org.mozilla.gecko.sync.repositories.domain.HistoryRecord; 1.31 +import org.mozilla.gecko.sync.repositories.domain.Record; 1.32 + 1.33 +import android.content.ContentValues; 1.34 +import android.content.Context; 1.35 +import android.database.Cursor; 1.36 +import android.net.Uri; 1.37 + 1.38 +public class TestAndroidBrowserHistoryRepository extends AndroidBrowserRepositoryTestCase { 1.39 + 1.40 + @Override 1.41 + protected AndroidBrowserRepository getRepository() { 1.42 + 1.43 + /** 1.44 + * Override this chain in order to avoid our test code having to create two 1.45 + * sessions all the time. 1.46 + */ 1.47 + return new AndroidBrowserHistoryRepository() { 1.48 + @Override 1.49 + protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) { 1.50 + AndroidBrowserHistoryRepositorySession session; 1.51 + session = new AndroidBrowserHistoryRepositorySession(this, context) { 1.52 + @Override 1.53 + protected synchronized void trackGUID(String guid) { 1.54 + System.out.println("Ignoring trackGUID call: this is a test!"); 1.55 + } 1.56 + }; 1.57 + delegate.onSessionCreated(session); 1.58 + } 1.59 + }; 1.60 + } 1.61 + 1.62 + @Override 1.63 + protected AndroidBrowserRepositoryDataAccessor getDataAccessor() { 1.64 + return new AndroidBrowserHistoryDataAccessor(getApplicationContext()); 1.65 + } 1.66 + 1.67 + @Override 1.68 + protected void closeDataAccessor(AndroidBrowserRepositoryDataAccessor dataAccessor) { 1.69 + if (!(dataAccessor instanceof AndroidBrowserHistoryDataAccessor)) { 1.70 + throw new IllegalArgumentException("Only expecting a history data accessor."); 1.71 + } 1.72 + ((AndroidBrowserHistoryDataAccessor) dataAccessor).closeExtender(); 1.73 + } 1.74 + 1.75 + @Override 1.76 + public void testFetchAll() { 1.77 + Record[] expected = new Record[2]; 1.78 + expected[0] = HistoryHelpers.createHistory3(); 1.79 + expected[1] = HistoryHelpers.createHistory2(); 1.80 + basicFetchAllTest(expected); 1.81 + } 1.82 + 1.83 + /* 1.84 + * Test storing identical records with different guids. 1.85 + * For bookmarks identical is defined by the following fields 1.86 + * being the same: title, uri, type, parentName 1.87 + */ 1.88 + @Override 1.89 + public void testStoreIdenticalExceptGuid() { 1.90 + storeIdenticalExceptGuid(HistoryHelpers.createHistory1()); 1.91 + } 1.92 + 1.93 + @Override 1.94 + public void testCleanMultipleRecords() { 1.95 + cleanMultipleRecords( 1.96 + HistoryHelpers.createHistory1(), 1.97 + HistoryHelpers.createHistory2(), 1.98 + HistoryHelpers.createHistory3(), 1.99 + HistoryHelpers.createHistory4(), 1.100 + HistoryHelpers.createHistory5() 1.101 + ); 1.102 + } 1.103 + 1.104 + @Override 1.105 + public void testGuidsSinceReturnMultipleRecords() { 1.106 + HistoryRecord record0 = HistoryHelpers.createHistory1(); 1.107 + HistoryRecord record1 = HistoryHelpers.createHistory2(); 1.108 + guidsSinceReturnMultipleRecords(record0, record1); 1.109 + } 1.110 + 1.111 + @Override 1.112 + public void testGuidsSinceReturnNoRecords() { 1.113 + guidsSinceReturnNoRecords(HistoryHelpers.createHistory3()); 1.114 + } 1.115 + 1.116 + @Override 1.117 + public void testFetchSinceOneRecord() { 1.118 + fetchSinceOneRecord(HistoryHelpers.createHistory1(), 1.119 + HistoryHelpers.createHistory2()); 1.120 + } 1.121 + 1.122 + @Override 1.123 + public void testFetchSinceReturnNoRecords() { 1.124 + fetchSinceReturnNoRecords(HistoryHelpers.createHistory3()); 1.125 + } 1.126 + 1.127 + @Override 1.128 + public void testFetchOneRecordByGuid() { 1.129 + fetchOneRecordByGuid(HistoryHelpers.createHistory1(), 1.130 + HistoryHelpers.createHistory2()); 1.131 + } 1.132 + 1.133 + @Override 1.134 + public void testFetchMultipleRecordsByGuids() { 1.135 + HistoryRecord record0 = HistoryHelpers.createHistory1(); 1.136 + HistoryRecord record1 = HistoryHelpers.createHistory2(); 1.137 + HistoryRecord record2 = HistoryHelpers.createHistory3(); 1.138 + fetchMultipleRecordsByGuids(record0, record1, record2); 1.139 + } 1.140 + 1.141 + @Override 1.142 + public void testFetchNoRecordByGuid() { 1.143 + fetchNoRecordByGuid(HistoryHelpers.createHistory1()); 1.144 + } 1.145 + 1.146 + @Override 1.147 + public void testWipe() { 1.148 + doWipe(HistoryHelpers.createHistory2(), HistoryHelpers.createHistory3()); 1.149 + } 1.150 + 1.151 + @Override 1.152 + public void testStore() { 1.153 + basicStoreTest(HistoryHelpers.createHistory1()); 1.154 + } 1.155 + 1.156 + @Override 1.157 + public void testRemoteNewerTimeStamp() { 1.158 + HistoryRecord local = HistoryHelpers.createHistory1(); 1.159 + HistoryRecord remote = HistoryHelpers.createHistory2(); 1.160 + remoteNewerTimeStamp(local, remote); 1.161 + } 1.162 + 1.163 + @Override 1.164 + public void testLocalNewerTimeStamp() { 1.165 + HistoryRecord local = HistoryHelpers.createHistory1(); 1.166 + HistoryRecord remote = HistoryHelpers.createHistory2(); 1.167 + localNewerTimeStamp(local, remote); 1.168 + } 1.169 + 1.170 + @Override 1.171 + public void testDeleteRemoteNewer() { 1.172 + HistoryRecord local = HistoryHelpers.createHistory1(); 1.173 + HistoryRecord remote = HistoryHelpers.createHistory2(); 1.174 + deleteRemoteNewer(local, remote); 1.175 + } 1.176 + 1.177 + @Override 1.178 + public void testDeleteLocalNewer() { 1.179 + HistoryRecord local = HistoryHelpers.createHistory1(); 1.180 + HistoryRecord remote = HistoryHelpers.createHistory2(); 1.181 + deleteLocalNewer(local, remote); 1.182 + } 1.183 + 1.184 + @Override 1.185 + public void testDeleteRemoteLocalNonexistent() { 1.186 + deleteRemoteLocalNonexistent(HistoryHelpers.createHistory2()); 1.187 + } 1.188 + 1.189 + /** 1.190 + * Exists to provide access to record string logic. 1.191 + */ 1.192 + protected class HelperHistorySession extends AndroidBrowserHistoryRepositorySession { 1.193 + public HelperHistorySession(Repository repository, Context context) { 1.194 + super(repository, context); 1.195 + } 1.196 + 1.197 + public boolean sameRecordString(HistoryRecord r1, HistoryRecord r2) { 1.198 + return buildRecordString(r1).equals(buildRecordString(r2)); 1.199 + } 1.200 + } 1.201 + 1.202 + /** 1.203 + * Verifies that two history records with the same URI but different 1.204 + * titles will be reconciled locally. 1.205 + */ 1.206 + public void testRecordStringCollisionAndEquality() { 1.207 + final AndroidBrowserHistoryRepository repo = new AndroidBrowserHistoryRepository(); 1.208 + final HelperHistorySession testSession = new HelperHistorySession(repo, getApplicationContext()); 1.209 + 1.210 + final long now = RepositorySession.now(); 1.211 + 1.212 + final HistoryRecord record0 = new HistoryRecord(null, "history", now + 1, false); 1.213 + final HistoryRecord record1 = new HistoryRecord(null, "history", now + 2, false); 1.214 + final HistoryRecord record2 = new HistoryRecord(null, "history", now + 3, false); 1.215 + 1.216 + record0.histURI = "http://example.com/foo"; 1.217 + record1.histURI = "http://example.com/foo"; 1.218 + record2.histURI = "http://example.com/bar"; 1.219 + record0.title = "Foo 0"; 1.220 + record1.title = "Foo 1"; 1.221 + record2.title = "Foo 2"; 1.222 + 1.223 + // Ensure that two records with the same URI produce the same record string, 1.224 + // and two records with different URIs do not. 1.225 + assertTrue(testSession.sameRecordString(record0, record1)); 1.226 + assertFalse(testSession.sameRecordString(record0, record2)); 1.227 + 1.228 + // Two records are congruent if they have the same URI and their 1.229 + // identifiers match (which is why these all have null GUIDs). 1.230 + assertTrue(record0.congruentWith(record0)); 1.231 + assertTrue(record0.congruentWith(record1)); 1.232 + assertTrue(record1.congruentWith(record0)); 1.233 + assertFalse(record0.congruentWith(record2)); 1.234 + assertFalse(record1.congruentWith(record2)); 1.235 + assertFalse(record2.congruentWith(record1)); 1.236 + assertFalse(record2.congruentWith(record0)); 1.237 + 1.238 + // None of these records are equal, because they have different titles. 1.239 + // (Except for being equal to themselves, of course.) 1.240 + assertTrue(record0.equalPayloads(record0)); 1.241 + assertTrue(record1.equalPayloads(record1)); 1.242 + assertTrue(record2.equalPayloads(record2)); 1.243 + assertFalse(record0.equalPayloads(record1)); 1.244 + assertFalse(record1.equalPayloads(record0)); 1.245 + assertFalse(record1.equalPayloads(record2)); 1.246 + } 1.247 + 1.248 + /* 1.249 + * Tests for adding some visits to a history record 1.250 + * and doing a fetch. 1.251 + */ 1.252 + @SuppressWarnings("unchecked") 1.253 + public void testAddOneVisit() { 1.254 + final RepositorySession session = createAndBeginSession(); 1.255 + 1.256 + HistoryRecord record0 = HistoryHelpers.createHistory3(); 1.257 + performWait(storeRunnable(session, record0)); 1.258 + 1.259 + // Add one visit to the count and put in a new 1.260 + // last visited date. 1.261 + ContentValues cv = new ContentValues(); 1.262 + int visits = record0.visits.size() + 1; 1.263 + long newVisitTime = System.currentTimeMillis(); 1.264 + cv.put(BrowserContract.History.VISITS, visits); 1.265 + cv.put(BrowserContract.History.DATE_LAST_VISITED, newVisitTime); 1.266 + final AndroidBrowserRepositoryDataAccessor dataAccessor = getDataAccessor(); 1.267 + dataAccessor.updateByGuid(record0.guid, cv); 1.268 + 1.269 + // Add expected visit to record for verification. 1.270 + JSONObject expectedVisit = new JSONObject(); 1.271 + expectedVisit.put("date", newVisitTime * 1000); // Microseconds. 1.272 + expectedVisit.put("type", 1L); 1.273 + record0.visits.add(expectedVisit); 1.274 + 1.275 + performWait(fetchRunnable(session, new String[] { record0.guid }, new ExpectFetchDelegate(new Record[] { record0 }))); 1.276 + closeDataAccessor(dataAccessor); 1.277 + } 1.278 + 1.279 + @SuppressWarnings("unchecked") 1.280 + public void testAddMultipleVisits() { 1.281 + final RepositorySession session = createAndBeginSession(); 1.282 + 1.283 + HistoryRecord record0 = HistoryHelpers.createHistory4(); 1.284 + performWait(storeRunnable(session, record0)); 1.285 + 1.286 + // Add three visits to the count and put in a new 1.287 + // last visited date. 1.288 + ContentValues cv = new ContentValues(); 1.289 + int visits = record0.visits.size() + 3; 1.290 + long newVisitTime = System.currentTimeMillis(); 1.291 + cv.put(BrowserContract.History.VISITS, visits); 1.292 + cv.put(BrowserContract.History.DATE_LAST_VISITED, newVisitTime); 1.293 + final AndroidBrowserRepositoryDataAccessor dataAccessor = getDataAccessor(); 1.294 + dataAccessor.updateByGuid(record0.guid, cv); 1.295 + 1.296 + // Now shift to microsecond timing for visits. 1.297 + long newMicroVisitTime = newVisitTime * 1000; 1.298 + 1.299 + // Add expected visits to record for verification 1.300 + JSONObject expectedVisit = new JSONObject(); 1.301 + expectedVisit.put("date", newMicroVisitTime); 1.302 + expectedVisit.put("type", 1L); 1.303 + record0.visits.add(expectedVisit); 1.304 + expectedVisit = new JSONObject(); 1.305 + expectedVisit.put("date", newMicroVisitTime - 1000); 1.306 + expectedVisit.put("type", 1L); 1.307 + record0.visits.add(expectedVisit); 1.308 + expectedVisit = new JSONObject(); 1.309 + expectedVisit.put("date", newMicroVisitTime - 2000); 1.310 + expectedVisit.put("type", 1L); 1.311 + record0.visits.add(expectedVisit); 1.312 + 1.313 + ExpectFetchDelegate delegate = new ExpectFetchDelegate(new Record[] { record0 }); 1.314 + performWait(fetchRunnable(session, new String[] { record0.guid }, delegate)); 1.315 + 1.316 + Record fetched = delegate.records.get(0); 1.317 + assertTrue(record0.equalPayloads(fetched)); 1.318 + closeDataAccessor(dataAccessor); 1.319 + } 1.320 + 1.321 + public void testInvalidHistoryItemIsSkipped() throws NullCursorException { 1.322 + final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession(); 1.323 + final AndroidBrowserRepositoryDataAccessor dbHelper = session.getDBHelper(); 1.324 + 1.325 + final long now = System.currentTimeMillis(); 1.326 + final HistoryRecord emptyURL = new HistoryRecord(Utils.generateGuid(), "history", now, false); 1.327 + final HistoryRecord noVisits = new HistoryRecord(Utils.generateGuid(), "history", now, false); 1.328 + final HistoryRecord aboutURL = new HistoryRecord(Utils.generateGuid(), "history", now, false); 1.329 + 1.330 + emptyURL.fennecDateVisited = now; 1.331 + emptyURL.fennecVisitCount = 1; 1.332 + emptyURL.histURI = ""; 1.333 + emptyURL.title = "Something"; 1.334 + 1.335 + noVisits.fennecDateVisited = now; 1.336 + noVisits.fennecVisitCount = 0; 1.337 + noVisits.histURI = "http://example.org/novisits"; 1.338 + noVisits.title = "Something Else"; 1.339 + 1.340 + aboutURL.fennecDateVisited = now; 1.341 + aboutURL.fennecVisitCount = 1; 1.342 + aboutURL.histURI = "about:home"; 1.343 + aboutURL.title = "Fennec Home"; 1.344 + 1.345 + Uri one = dbHelper.insert(emptyURL); 1.346 + Uri two = dbHelper.insert(noVisits); 1.347 + Uri tre = dbHelper.insert(aboutURL); 1.348 + assertNotNull(one); 1.349 + assertNotNull(two); 1.350 + assertNotNull(tre); 1.351 + 1.352 + // The records are in the DB. 1.353 + final Cursor all = dbHelper.fetchAll(); 1.354 + assertEquals(3, all.getCount()); 1.355 + all.close(); 1.356 + 1.357 + // But aren't returned by fetching. 1.358 + performWait(fetchAllRunnable(session, new Record[] {})); 1.359 + 1.360 + // And we'd ignore about:home if we downloaded it. 1.361 + assertTrue(session.shouldIgnore(aboutURL)); 1.362 + 1.363 + session.abort(); 1.364 + } 1.365 + 1.366 + public void testSqlInjectPurgeDelete() { 1.367 + // Some setup. 1.368 + RepositorySession session = createAndBeginSession(); 1.369 + final AndroidBrowserRepositoryDataAccessor db = getDataAccessor(); 1.370 + 1.371 + try { 1.372 + ContentValues cv = new ContentValues(); 1.373 + cv.put(BrowserContract.SyncColumns.IS_DELETED, 1); 1.374 + 1.375 + // Create and insert 2 history entries, 2nd one is evil (attempts injection). 1.376 + HistoryRecord h1 = HistoryHelpers.createHistory1(); 1.377 + HistoryRecord h2 = HistoryHelpers.createHistory2(); 1.378 + h2.guid = "' or '1'='1"; 1.379 + 1.380 + db.insert(h1); 1.381 + db.insert(h2); 1.382 + 1.383 + // Test 1 - updateByGuid() handles evil history entries correctly. 1.384 + db.updateByGuid(h2.guid, cv); 1.385 + 1.386 + // Query history table. 1.387 + Cursor cur = getAllHistory(); 1.388 + int numHistory = cur.getCount(); 1.389 + 1.390 + // Ensure only the evil history entry is marked for deletion. 1.391 + try { 1.392 + cur.moveToFirst(); 1.393 + while (!cur.isAfterLast()) { 1.394 + String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID); 1.395 + boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1; 1.396 + 1.397 + if (guid.equals(h2.guid)) { 1.398 + assertTrue(deleted); 1.399 + } else { 1.400 + assertFalse(deleted); 1.401 + } 1.402 + cur.moveToNext(); 1.403 + } 1.404 + } finally { 1.405 + cur.close(); 1.406 + } 1.407 + 1.408 + // Test 2 - Ensure purgeDelete()'s call to delete() deletes only 1 record. 1.409 + try { 1.410 + db.purgeDeleted(); 1.411 + } catch (NullCursorException e) { 1.412 + e.printStackTrace(); 1.413 + } 1.414 + 1.415 + cur = getAllHistory(); 1.416 + int numHistoryAfterDeletion = cur.getCount(); 1.417 + 1.418 + // Ensure we have only 1 deleted row. 1.419 + assertEquals(numHistoryAfterDeletion, numHistory - 1); 1.420 + 1.421 + // Ensure only the evil history is deleted. 1.422 + try { 1.423 + cur.moveToFirst(); 1.424 + while (!cur.isAfterLast()) { 1.425 + String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID); 1.426 + boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1; 1.427 + 1.428 + if (guid.equals(h2.guid)) { 1.429 + fail("Evil guid was not deleted!"); 1.430 + } else { 1.431 + assertFalse(deleted); 1.432 + } 1.433 + cur.moveToNext(); 1.434 + } 1.435 + } finally { 1.436 + cur.close(); 1.437 + } 1.438 + } finally { 1.439 + closeDataAccessor(db); 1.440 + session.abort(); 1.441 + } 1.442 + } 1.443 + 1.444 + protected Cursor getAllHistory() { 1.445 + Context context = getApplicationContext(); 1.446 + Cursor cur = context.getContentResolver().query(BrowserContractHelpers.HISTORY_CONTENT_URI, 1.447 + BrowserContractHelpers.HistoryColumns, null, null, null); 1.448 + return cur; 1.449 + } 1.450 + 1.451 + public void testDataAccessorBulkInsert() throws NullCursorException { 1.452 + final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession(); 1.453 + AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper(); 1.454 + 1.455 + ArrayList<HistoryRecord> records = new ArrayList<HistoryRecord>(); 1.456 + records.add(HistoryHelpers.createHistory1()); 1.457 + records.add(HistoryHelpers.createHistory2()); 1.458 + records.add(HistoryHelpers.createHistory3()); 1.459 + db.bulkInsert(records); 1.460 + 1.461 + performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(records.toArray(new Record[records.size()])))); 1.462 + session.abort(); 1.463 + } 1.464 + 1.465 + public void testDataExtenderIsClosedBeforeBegin() { 1.466 + // Create a session but don't begin() it. 1.467 + final AndroidBrowserRepositorySession session = (AndroidBrowserRepositorySession) createSession(); 1.468 + AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper(); 1.469 + 1.470 + // Confirm dataExtender is closed before beginning session. 1.471 + assertTrue(db.getHistoryDataExtender().isClosed()); 1.472 + } 1.473 + 1.474 + public void testDataExtenderIsClosedAfterFinish() throws InactiveSessionException { 1.475 + final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession(); 1.476 + AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper(); 1.477 + 1.478 + // Perform an action that opens the dataExtender. 1.479 + HistoryRecord h1 = HistoryHelpers.createHistory1(); 1.480 + db.insert(h1); 1.481 + assertFalse(db.getHistoryDataExtender().isClosed()); 1.482 + 1.483 + // Check dataExtender is closed upon finish. 1.484 + performWait(finishRunnable(session, new ExpectFinishDelegate())); 1.485 + assertTrue(db.getHistoryDataExtender().isClosed()); 1.486 + } 1.487 + 1.488 + public void testDataExtenderIsClosedAfterAbort() throws InactiveSessionException { 1.489 + final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession(); 1.490 + AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper(); 1.491 + 1.492 + // Perform an action that opens the dataExtender. 1.493 + HistoryRecord h1 = HistoryHelpers.createHistory1(); 1.494 + db.insert(h1); 1.495 + assertFalse(db.getHistoryDataExtender().isClosed()); 1.496 + 1.497 + // Check dataExtender is closed upon abort. 1.498 + session.abort(); 1.499 + assertTrue(db.getHistoryDataExtender().isClosed()); 1.500 + } 1.501 +}