michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: /** michael@0: * This file tests migration invariants from schema version 10 to the current michael@0: * schema version. michael@0: */ michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Constants michael@0: michael@0: const kGuidAnnotationName = "sync/guid"; michael@0: const kExpectedAnnotations = 5; michael@0: const kExpectedValidGuids = 2; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: // Set in test_initial_state to the value in the database. michael@0: var gItemGuid = []; michael@0: var gItemId = []; michael@0: var gPlaceGuid = []; michael@0: var gPlaceId = []; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Helpers michael@0: michael@0: /** michael@0: * Determines if a guid is valid or not. michael@0: * michael@0: * @return true if it is a valid guid, false otherwise. michael@0: */ michael@0: function isValidGuid(aGuid) michael@0: { michael@0: return /^[a-zA-Z0-9\-_]{12}$/.test(aGuid); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Test Functions michael@0: michael@0: function test_initial_state() michael@0: { michael@0: // Mostly sanity checks our starting DB to make sure it's setup as we expect michael@0: // it to be. michael@0: let dbFile = gProfD.clone(); michael@0: dbFile.append(kDBName); michael@0: let db = Services.storage.openUnsharedDatabase(dbFile); michael@0: michael@0: let stmt = db.createStatement("PRAGMA journal_mode"); michael@0: do_check_true(stmt.executeStep()); michael@0: // WAL journal mode should not be set on this database. michael@0: do_check_neq(stmt.getString(0).toLowerCase(), "wal"); michael@0: stmt.finalize(); michael@0: michael@0: do_check_false(db.indexExists("moz_bookmarks_guid_uniqueindex")); michael@0: do_check_false(db.indexExists("moz_places_guid_uniqueindex")); michael@0: michael@0: // There should be five item annotations for a bookmark guid. michael@0: stmt = db.createStatement( michael@0: "SELECT content AS guid, item_id " michael@0: + "FROM moz_items_annos " michael@0: + "WHERE anno_attribute_id = ( " michael@0: + "SELECT id " michael@0: + "FROM moz_anno_attributes " michael@0: + "WHERE name = :attr_name " michael@0: + ") " michael@0: ); michael@0: stmt.params.attr_name = kGuidAnnotationName; michael@0: while (stmt.executeStep()) { michael@0: gItemGuid.push(stmt.row.guid); michael@0: gItemId.push(stmt.row.item_id) michael@0: } michael@0: do_check_eq(gItemGuid.length, gItemId.length); michael@0: do_check_eq(gItemGuid.length, kExpectedAnnotations); michael@0: stmt.finalize(); michael@0: michael@0: // There should be five item annotations for a place guid. michael@0: stmt = db.createStatement( michael@0: "SELECT content AS guid, place_id " michael@0: + "FROM moz_annos " michael@0: + "WHERE anno_attribute_id = ( " michael@0: + "SELECT id " michael@0: + "FROM moz_anno_attributes " michael@0: + "WHERE name = :attr_name " michael@0: + ") " michael@0: ); michael@0: stmt.params.attr_name = kGuidAnnotationName; michael@0: while (stmt.executeStep()) { michael@0: gPlaceGuid.push(stmt.row.guid); michael@0: gPlaceId.push(stmt.row.place_id) michael@0: } michael@0: do_check_eq(gPlaceGuid.length, gPlaceId.length); michael@0: do_check_eq(gPlaceGuid.length, kExpectedAnnotations); michael@0: stmt.finalize(); michael@0: michael@0: // Check our schema version to make sure it is actually at 10. michael@0: do_check_eq(db.schemaVersion, 10); michael@0: michael@0: db.close(); michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_moz_bookmarks_guid_exists() michael@0: { michael@0: // This will throw if the column does not exist michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT guid " michael@0: + "FROM moz_bookmarks " michael@0: ); michael@0: stmt.finalize(); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_bookmark_guids_non_null() michael@0: { michael@0: // First, sanity check that we have a non-zero amount of bookmarks. michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT COUNT(1) " michael@0: + "FROM moz_bookmarks " michael@0: ); michael@0: do_check_true(stmt.executeStep()); michael@0: do_check_neq(stmt.getInt32(0), 0); michael@0: stmt.finalize(); michael@0: michael@0: // Now, make sure we have no NULL guid entry. michael@0: stmt = DBConn().createStatement( michael@0: "SELECT guid " michael@0: + "FROM moz_bookmarks " michael@0: + "WHERE guid IS NULL " michael@0: ); michael@0: do_check_false(stmt.executeStep()); michael@0: stmt.finalize(); michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_bookmark_guid_annotation_imported() michael@0: { michael@0: // Make sure we have the imported guid; not a newly generated one. michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT id " michael@0: + "FROM moz_bookmarks " michael@0: + "WHERE guid = :guid " michael@0: + "AND id = :item_id " michael@0: ); michael@0: let validGuids = 0; michael@0: let seenGuids = []; michael@0: for (let i = 0; i < gItemGuid.length; i++) { michael@0: let guid = gItemGuid[i]; michael@0: stmt.params.guid = guid; michael@0: stmt.params.item_id = gItemId[i]; michael@0: michael@0: // Check that it is a valid guid that we expect, and that it is not a michael@0: // duplicate (which would violate the unique constraint). michael@0: let valid = isValidGuid(guid) && seenGuids.indexOf(guid) == -1; michael@0: seenGuids.push(guid); michael@0: michael@0: if (valid) { michael@0: validGuids++; michael@0: do_check_true(stmt.executeStep()); michael@0: } michael@0: else { michael@0: do_check_false(stmt.executeStep()); michael@0: } michael@0: stmt.reset(); michael@0: } michael@0: do_check_eq(validGuids, kExpectedValidGuids); michael@0: stmt.finalize(); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_bookmark_guid_annotation_removed() michael@0: { michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT COUNT(1) " michael@0: + "FROM moz_items_annos " michael@0: + "WHERE anno_attribute_id = ( " michael@0: + "SELECT id " michael@0: + "FROM moz_anno_attributes " michael@0: + "WHERE name = :attr_name " michael@0: + ") " michael@0: ); michael@0: stmt.params.attr_name = kGuidAnnotationName; michael@0: do_check_true(stmt.executeStep()); michael@0: do_check_eq(stmt.getInt32(0), 0); michael@0: stmt.finalize(); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_moz_places_guid_exists() michael@0: { michael@0: // This will throw if the column does not exist michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT guid " michael@0: + "FROM moz_places " michael@0: ); michael@0: stmt.finalize(); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_place_guids_non_null() michael@0: { michael@0: // First, sanity check that we have a non-zero amount of places. michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT COUNT(1) " michael@0: + "FROM moz_places " michael@0: ); michael@0: do_check_true(stmt.executeStep()); michael@0: do_check_neq(stmt.getInt32(0), 0); michael@0: stmt.finalize(); michael@0: michael@0: // Now, make sure we have no NULL guid entry. michael@0: stmt = DBConn().createStatement( michael@0: "SELECT guid " michael@0: + "FROM moz_places " michael@0: + "WHERE guid IS NULL " michael@0: ); michael@0: do_check_false(stmt.executeStep()); michael@0: stmt.finalize(); michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_place_guid_annotation_imported() michael@0: { michael@0: // Make sure we have the imported guid; not a newly generated one. michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT id " michael@0: + "FROM moz_places " michael@0: + "WHERE guid = :guid " michael@0: + "AND id = :item_id " michael@0: ); michael@0: let validGuids = 0; michael@0: let seenGuids = []; michael@0: for (let i = 0; i < gPlaceGuid.length; i++) { michael@0: let guid = gPlaceGuid[i]; michael@0: stmt.params.guid = guid; michael@0: stmt.params.item_id = gPlaceId[i]; michael@0: michael@0: // Check that it is a valid guid that we expect, and that it is not a michael@0: // duplicate (which would violate the unique constraint). michael@0: let valid = isValidGuid(guid) && seenGuids.indexOf(guid) == -1; michael@0: seenGuids.push(guid); michael@0: michael@0: if (valid) { michael@0: validGuids++; michael@0: do_check_true(stmt.executeStep()); michael@0: } michael@0: else { michael@0: do_check_false(stmt.executeStep()); michael@0: } michael@0: stmt.reset(); michael@0: } michael@0: do_check_eq(validGuids, kExpectedValidGuids); michael@0: stmt.finalize(); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_place_guid_annotation_removed() michael@0: { michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT COUNT(1) " michael@0: + "FROM moz_annos " michael@0: + "WHERE anno_attribute_id = ( " michael@0: + "SELECT id " michael@0: + "FROM moz_anno_attributes " michael@0: + "WHERE name = :attr_name " michael@0: + ") " michael@0: ); michael@0: stmt.params.attr_name = kGuidAnnotationName; michael@0: do_check_true(stmt.executeStep()); michael@0: do_check_eq(stmt.getInt32(0), 0); michael@0: stmt.finalize(); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function test_moz_hosts() michael@0: { michael@0: // This will throw if the column does not exist michael@0: let stmt = DBConn().createStatement( michael@0: "SELECT host, frecency, typed, prefix " michael@0: + "FROM moz_hosts " michael@0: ); michael@0: stmt.finalize(); michael@0: michael@0: // moz_hosts is populated asynchronously, so query asynchronously to serialize michael@0: // to that. michael@0: // check the number of entries in moz_hosts equals the number of michael@0: // unique rev_host in moz_places michael@0: stmt = DBConn().createAsyncStatement( michael@0: "SELECT (SELECT COUNT(host) FROM moz_hosts), " + michael@0: "(SELECT COUNT(DISTINCT rev_host) " + michael@0: "FROM moz_places " + michael@0: "WHERE LENGTH(rev_host) > 1)"); michael@0: try { michael@0: stmt.executeAsync({ michael@0: handleResult: function (aResult) { michael@0: this._hasResults = true; michael@0: let row = aResult.getNextRow(); michael@0: let mozHostsCount = row.getResultByIndex(0); michael@0: let mozPlacesCount = row.getResultByIndex(1); michael@0: do_check_true(mozPlacesCount > 0); michael@0: do_check_eq(mozPlacesCount, mozHostsCount); michael@0: }, michael@0: handleError: function () {}, michael@0: handleCompletion: function (aReason) { michael@0: do_check_eq(aReason, Ci.mozIStorageStatementCallback.REASON_FINISHED); michael@0: do_check_true(this._hasResults); michael@0: run_next_test(); michael@0: } michael@0: }); michael@0: } michael@0: finally { michael@0: stmt.finalize(); michael@0: } michael@0: } michael@0: michael@0: function test_final_state() michael@0: { michael@0: // We open a new database mostly so that we can check that the settings were michael@0: // actually saved. michael@0: let dbFile = gProfD.clone(); michael@0: dbFile.append(kDBName); michael@0: let db = Services.storage.openUnsharedDatabase(dbFile); michael@0: michael@0: let (stmt = db.createStatement("PRAGMA journal_mode")) { michael@0: do_check_true(stmt.executeStep()); michael@0: // WAL journal mode should be set on this database. michael@0: do_check_eq(stmt.getString(0).toLowerCase(), "wal"); michael@0: stmt.finalize(); michael@0: } michael@0: michael@0: do_check_true(db.indexExists("moz_bookmarks_guid_uniqueindex")); michael@0: do_check_true(db.indexExists("moz_places_guid_uniqueindex")); michael@0: do_check_true(db.indexExists("moz_favicons_guid_uniqueindex")); michael@0: michael@0: do_check_eq(db.schemaVersion, CURRENT_SCHEMA_VERSION); michael@0: michael@0: db.close(); michael@0: run_next_test(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Test Runner michael@0: michael@0: [ michael@0: test_initial_state, michael@0: test_moz_bookmarks_guid_exists, michael@0: test_bookmark_guids_non_null, michael@0: test_bookmark_guid_annotation_imported, michael@0: test_bookmark_guid_annotation_removed, michael@0: test_moz_places_guid_exists, michael@0: test_place_guids_non_null, michael@0: test_place_guid_annotation_imported, michael@0: test_place_guid_annotation_removed, michael@0: test_moz_hosts, michael@0: test_final_state, michael@0: ].forEach(add_test); michael@0: michael@0: function run_test() michael@0: { michael@0: setPlacesDatabase("places_v10.sqlite"); michael@0: run_next_test(); michael@0: }