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: * Tests the DownloadImport object. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Sqlite", michael@0: "resource://gre/modules/Sqlite.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport", michael@0: "resource://gre/modules/DownloadImport.jsm"); michael@0: michael@0: // Importable states michael@0: const DOWNLOAD_NOTSTARTED = -1; michael@0: const DOWNLOAD_DOWNLOADING = 0; michael@0: const DOWNLOAD_PAUSED = 4; michael@0: const DOWNLOAD_QUEUED = 5; michael@0: michael@0: // Non importable states michael@0: const DOWNLOAD_FAILED = 2; michael@0: const DOWNLOAD_CANCELED = 3; michael@0: const DOWNLOAD_BLOCKED_PARENTAL = 6; michael@0: const DOWNLOAD_SCANNING = 7; michael@0: const DOWNLOAD_DIRTY = 8; michael@0: const DOWNLOAD_BLOCKED_POLICY = 9; michael@0: michael@0: // The TEST_DATA_TAINTED const is a version of TEST_DATA_SHORT in which the michael@0: // beginning of the data was changed (with the TEST_DATA_REPLACEMENT value). michael@0: // We use this to test that the entityID is properly imported and the download michael@0: // can be resumed from where it was paused. michael@0: // For simplification purposes, the test requires that TEST_DATA_SHORT and michael@0: // TEST_DATA_TAINTED have the same length. michael@0: const TEST_DATA_REPLACEMENT = "-changed- "; michael@0: const TEST_DATA_TAINTED = TEST_DATA_REPLACEMENT + michael@0: TEST_DATA_SHORT.substr(TEST_DATA_REPLACEMENT.length); michael@0: const TEST_DATA_LENGTH = TEST_DATA_SHORT.length; michael@0: michael@0: // The length of the partial file that we'll write to disk as an existing michael@0: // ongoing download. michael@0: const TEST_DATA_PARTIAL_LENGTH = TEST_DATA_REPLACEMENT.length; michael@0: michael@0: // The value of the "maxBytes" column stored in the DB about the downloads. michael@0: // It's intentionally different than TEST_DATA_LENGTH to test that each value michael@0: // is seen when expected. michael@0: const MAXBYTES_IN_DB = TEST_DATA_LENGTH - 10; michael@0: michael@0: let gDownloadsRowToImport; michael@0: let gDownloadsRowNonImportable; michael@0: michael@0: /** michael@0: * Creates a database with an empty moz_downloads table and leaves an michael@0: * open connection to it. michael@0: * michael@0: * @param aPath michael@0: * String containing the path of the database file to be created. michael@0: * @param aSchemaVersion michael@0: * Number with the version of the database schema to set. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves The open connection to the database. michael@0: * @rejects If an error occurred during the database creation. michael@0: */ michael@0: function promiseEmptyDatabaseConnection({aPath, aSchemaVersion}) { michael@0: return Task.spawn(function () { michael@0: let connection = yield Sqlite.openConnection({ path: aPath }); michael@0: michael@0: yield connection.execute("CREATE TABLE moz_downloads (" michael@0: + "id INTEGER PRIMARY KEY," michael@0: + "name TEXT," michael@0: + "source TEXT," michael@0: + "target TEXT," michael@0: + "tempPath TEXT," michael@0: + "startTime INTEGER," michael@0: + "endTime INTEGER," michael@0: + "state INTEGER," michael@0: + "referrer TEXT," michael@0: + "entityID TEXT," michael@0: + "currBytes INTEGER NOT NULL DEFAULT 0," michael@0: + "maxBytes INTEGER NOT NULL DEFAULT -1," michael@0: + "mimeType TEXT," michael@0: + "preferredApplication TEXT," michael@0: + "preferredAction INTEGER NOT NULL DEFAULT 0," michael@0: + "autoResume INTEGER NOT NULL DEFAULT 0," michael@0: + "guid TEXT)"); michael@0: michael@0: yield connection.setSchemaVersion(aSchemaVersion); michael@0: michael@0: throw new Task.Result(connection); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Inserts a new entry in the database with the given columns' values. michael@0: * michael@0: * @param aConnection michael@0: * The database connection. michael@0: * @param aDownloadRow michael@0: * An object representing the values for each column of the row michael@0: * being inserted. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the operation completes. michael@0: * @rejects If there's an error inserting the row. michael@0: */ michael@0: function promiseInsertRow(aConnection, aDownloadRow) { michael@0: // We can't use the aDownloadRow obj directly in the execute statement michael@0: // because the obj bind code in Sqlite.jsm doesn't allow objects michael@0: // with extra properties beyond those being binded. So we might as well michael@0: // use an array as it is simpler. michael@0: let values = [ michael@0: aDownloadRow.source, aDownloadRow.target, aDownloadRow.tempPath, michael@0: aDownloadRow.startTime.getTime() * 1000, aDownloadRow.state, michael@0: aDownloadRow.referrer, aDownloadRow.entityID, aDownloadRow.maxBytes, michael@0: aDownloadRow.mimeType, aDownloadRow.preferredApplication, michael@0: aDownloadRow.preferredAction, aDownloadRow.autoResume michael@0: ]; michael@0: michael@0: return aConnection.execute("INSERT INTO moz_downloads (" michael@0: + "name, source, target, tempPath, startTime," michael@0: + "endTime, state, referrer, entityID, currBytes," michael@0: + "maxBytes, mimeType, preferredApplication," michael@0: + "preferredAction, autoResume, guid)" michael@0: + "VALUES (" michael@0: + "'', ?, ?, ?, ?, " //name, michael@0: + "0, ?, ?, ?, 0, " //endTime, currBytes michael@0: + " ?, ?, ?, " // michael@0: + " ?, ?, '')", //and guid are not imported michael@0: values); michael@0: } michael@0: michael@0: /** michael@0: * Retrieves the number of rows in the moz_downloads table of the michael@0: * database. michael@0: * michael@0: * @param aConnection michael@0: * The database connection. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves With the number of rows. michael@0: * @rejects Never. michael@0: */ michael@0: function promiseTableCount(aConnection) { michael@0: return aConnection.execute("SELECT COUNT(*) FROM moz_downloads") michael@0: .then(res => res[0].getResultByName("COUNT(*)")) michael@0: .then(null, Cu.reportError); michael@0: } michael@0: michael@0: /** michael@0: * Briefly opens a network channel to a given URL to retrieve michael@0: * the entityID of this url, as generated by the network code. michael@0: * michael@0: * @param aUrl michael@0: * The URL to retrieve the entityID. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves The EntityID of the given URL. michael@0: * @rejects When there's a problem accessing the URL. michael@0: */ michael@0: function promiseEntityID(aUrl) { michael@0: let deferred = Promise.defer(); michael@0: let entityID = ""; michael@0: let channel = NetUtil.newChannel(NetUtil.newURI(aUrl)); michael@0: michael@0: channel.asyncOpen({ michael@0: onStartRequest: function (aRequest) { michael@0: if (aRequest instanceof Ci.nsIResumableChannel) { michael@0: entityID = aRequest.entityID; michael@0: } michael@0: aRequest.cancel(Cr.NS_BINDING_ABORTED); michael@0: }, michael@0: michael@0: onStopRequest: function (aRequest, aContext, aStatusCode) { michael@0: if (aStatusCode == Cr.NS_BINDING_ABORTED) { michael@0: deferred.resolve(entityID); michael@0: } else { michael@0: deferred.reject("Unexpected status code received"); michael@0: } michael@0: }, michael@0: michael@0: onDataAvailable: function () {} michael@0: }, null); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Gets a file path to a temporary writeable download target, in the michael@0: * correct format as expected to be stored in the downloads database, michael@0: * which is file:///absolute/path/to/file michael@0: * michael@0: * @param aLeafName michael@0: * A hint leaf name for the file. michael@0: * michael@0: * @return String The path to the download target. michael@0: */ michael@0: function getDownloadTarget(aLeafName) { michael@0: return NetUtil.newURI(getTempFile(aLeafName)).spec; michael@0: } michael@0: michael@0: /** michael@0: * Generates a temporary partial file to use as an in-progress michael@0: * download. The file is written to disk with a part of the total expected michael@0: * download content pre-written. michael@0: * michael@0: * @param aLeafName michael@0: * A hint leaf name for the file. michael@0: * @param aTainted michael@0: * A boolean value. When true, the partial content of the file michael@0: * will be different from the expected content of the original source michael@0: * file. See the declaration of TEST_DATA_TAINTED for more information. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the operation completes, and returns a string with the path michael@0: * to the generated file. michael@0: * @rejects If there's an error writing the file. michael@0: */ michael@0: function getPartialFile(aLeafName, aTainted = false) { michael@0: let tempDownload = getTempFile(aLeafName); michael@0: let partialContent = aTainted michael@0: ? TEST_DATA_TAINTED.substr(0, TEST_DATA_PARTIAL_LENGTH) michael@0: : TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH); michael@0: michael@0: return OS.File.writeAtomic(tempDownload.path, partialContent, michael@0: { tmpPath: tempDownload.path + ".tmp", michael@0: flush: true }) michael@0: .then(() => tempDownload.path); michael@0: } michael@0: michael@0: /** michael@0: * Generates a Date object to be used as the startTime for the download rows michael@0: * in the DB. A date that is obviously different from the current time is michael@0: * generated to make sure this stored data and a `new Date()` can't collide. michael@0: * michael@0: * @param aOffset michael@0: * A offset from the base generated date is used to differentiate each michael@0: * row in the database. michael@0: * michael@0: * @return A Date object. michael@0: */ michael@0: function getStartTime(aOffset) { michael@0: return new Date(1000000 + (aOffset * 10000)); michael@0: } michael@0: michael@0: /** michael@0: * Performs various checks on an imported Download object to make sure michael@0: * all properties are properly set as expected from the import procedure. michael@0: * michael@0: * @param aDownload michael@0: * The Download object to be checked. michael@0: * @param aDownloadRow michael@0: * An object that represents a row from the original database table, michael@0: * with extra properties describing expected values that are not michael@0: * explictly part of the database. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the operation completes michael@0: * @rejects Never michael@0: */ michael@0: function checkDownload(aDownload, aDownloadRow) { michael@0: return Task.spawn(function() { michael@0: do_check_eq(aDownload.source.url, aDownloadRow.source); michael@0: do_check_eq(aDownload.source.referrer, aDownloadRow.referrer); michael@0: michael@0: do_check_eq(aDownload.target.path, michael@0: NetUtil.newURI(aDownloadRow.target) michael@0: .QueryInterface(Ci.nsIFileURL).file.path); michael@0: michael@0: do_check_eq(aDownload.target.partFilePath, aDownloadRow.tempPath); michael@0: michael@0: if (aDownloadRow.expectedResume) { michael@0: do_check_true(!aDownload.stopped || aDownload.succeeded); michael@0: yield promiseDownloadStopped(aDownload); michael@0: michael@0: do_check_true(aDownload.succeeded); michael@0: do_check_eq(aDownload.progress, 100); michael@0: // If the download has resumed, a new startTime will be set. michael@0: // By calling toJSON we're also testing that startTime is a Date object. michael@0: do_check_neq(aDownload.startTime.toJSON(), michael@0: aDownloadRow.startTime.toJSON()); michael@0: } else { michael@0: do_check_false(aDownload.succeeded); michael@0: do_check_eq(aDownload.startTime.toJSON(), michael@0: aDownloadRow.startTime.toJSON()); michael@0: } michael@0: michael@0: do_check_eq(aDownload.stopped, true); michael@0: michael@0: let serializedSaver = aDownload.saver.toSerializable(); michael@0: if (typeof(serializedSaver) == "object") { michael@0: do_check_eq(serializedSaver.type, "copy"); michael@0: } else { michael@0: do_check_eq(serializedSaver, "copy"); michael@0: } michael@0: michael@0: if (aDownloadRow.entityID) { michael@0: do_check_eq(aDownload.saver.entityID, aDownloadRow.entityID); michael@0: } michael@0: michael@0: do_check_eq(aDownload.currentBytes, aDownloadRow.expectedCurrentBytes); michael@0: do_check_eq(aDownload.totalBytes, aDownloadRow.expectedTotalBytes); michael@0: michael@0: if (aDownloadRow.expectedContent) { michael@0: let fileToCheck = aDownloadRow.expectedResume michael@0: ? aDownload.target.path michael@0: : aDownload.target.partFilePath; michael@0: yield promiseVerifyContents(fileToCheck, aDownloadRow.expectedContent); michael@0: } michael@0: michael@0: do_check_eq(aDownload.contentType, aDownloadRow.expectedContentType); michael@0: do_check_eq(aDownload.launcherPath, aDownloadRow.preferredApplication); michael@0: michael@0: do_check_eq(aDownload.launchWhenSucceeded, michael@0: aDownloadRow.preferredAction != Ci.nsIMIMEInfo.saveToDisk); michael@0: }); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Preparation tasks michael@0: michael@0: /** michael@0: * Prepares the list of downloads to be added to the database that should michael@0: * be imported by the import procedure. michael@0: */ michael@0: add_task(function prepareDownloadsToImport() { michael@0: michael@0: let sourceUrl = httpUrl("source.txt"); michael@0: let sourceEntityId = yield promiseEntityID(sourceUrl); michael@0: michael@0: gDownloadsRowToImport = [ michael@0: // Paused download with autoResume and a partial file. By michael@0: // setting the correct entityID the download can resume from michael@0: // where it stopped, and to test that this works properly we michael@0: // intentionally set different data in the beginning of the michael@0: // partial file to make sure it was not replaced. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress1.txt"), michael@0: tempPath: yield getPartialFile("inprogress1.txt.part", true), michael@0: startTime: getStartTime(1), michael@0: state: DOWNLOAD_PAUSED, michael@0: referrer: httpUrl("referrer1"), michael@0: entityID: sourceEntityId, michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType1", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication1", michael@0: autoResume: 1, michael@0: michael@0: // Even though the information stored in the DB said michael@0: // maxBytes was MAXBYTES_IN_DB, the download turned out to be michael@0: // a different length. Here we make sure the totalBytes property michael@0: // was correctly set with the actual value. The same consideration michael@0: // applies to the contentType. michael@0: expectedCurrentBytes: TEST_DATA_LENGTH, michael@0: expectedTotalBytes: TEST_DATA_LENGTH, michael@0: expectedResume: true, michael@0: expectedContentType: "text/plain", michael@0: expectedContent: TEST_DATA_TAINTED, michael@0: }, michael@0: michael@0: // Paused download with autoResume and a partial file, michael@0: // but missing entityID. This means that the download will michael@0: // start from beginning, and the entire original content of the michael@0: // source file should replace the different data that was stored michael@0: // in the partial file. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress2.txt"), michael@0: tempPath: yield getPartialFile("inprogress2.txt.part", true), michael@0: startTime: getStartTime(2), michael@0: state: DOWNLOAD_PAUSED, michael@0: referrer: httpUrl("referrer2"), michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType2", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication2", michael@0: autoResume: 1, michael@0: michael@0: expectedCurrentBytes: TEST_DATA_LENGTH, michael@0: expectedTotalBytes: TEST_DATA_LENGTH, michael@0: expectedResume: true, michael@0: expectedContentType: "text/plain", michael@0: expectedContent: TEST_DATA_SHORT michael@0: }, michael@0: michael@0: // Paused download with no autoResume and a partial file. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress3.txt"), michael@0: tempPath: yield getPartialFile("inprogress3.txt.part"), michael@0: startTime: getStartTime(3), michael@0: state: DOWNLOAD_PAUSED, michael@0: referrer: httpUrl("referrer3"), michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType3", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication3", michael@0: autoResume: 0, michael@0: michael@0: // Since this download has not been resumed, the actual data michael@0: // about its total size and content type is not known. michael@0: // Therefore, we're going by the information imported from the DB. michael@0: expectedCurrentBytes: TEST_DATA_PARTIAL_LENGTH, michael@0: expectedTotalBytes: MAXBYTES_IN_DB, michael@0: expectedResume: false, michael@0: expectedContentType: "mimeType3", michael@0: expectedContent: TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH), michael@0: }, michael@0: michael@0: // Paused download with autoResume and no partial file. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress4.txt"), michael@0: tempPath: "", michael@0: startTime: getStartTime(4), michael@0: state: DOWNLOAD_PAUSED, michael@0: referrer: httpUrl("referrer4"), michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "text/plain", michael@0: preferredAction: Ci.nsIMIMEInfo.useHelperApp, michael@0: preferredApplication: "prerredApplication4", michael@0: autoResume: 1, michael@0: michael@0: expectedCurrentBytes: TEST_DATA_LENGTH, michael@0: expectedTotalBytes: TEST_DATA_LENGTH, michael@0: expectedResume: true, michael@0: expectedContentType: "text/plain", michael@0: expectedContent: TEST_DATA_SHORT michael@0: }, michael@0: michael@0: // Paused download with no autoResume and no partial file. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress5.txt"), michael@0: tempPath: "", michael@0: startTime: getStartTime(5), michael@0: state: DOWNLOAD_PAUSED, michael@0: referrer: httpUrl("referrer4"), michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "text/plain", michael@0: preferredAction: Ci.nsIMIMEInfo.useSystemDefault, michael@0: preferredApplication: "prerredApplication5", michael@0: autoResume: 0, michael@0: michael@0: expectedCurrentBytes: 0, michael@0: expectedTotalBytes: MAXBYTES_IN_DB, michael@0: expectedResume: false, michael@0: expectedContentType: "text/plain", michael@0: }, michael@0: michael@0: // Queued download with no autoResume and no partial file. michael@0: // Even though autoResume=0, queued downloads always autoResume. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress6.txt"), michael@0: tempPath: "", michael@0: startTime: getStartTime(6), michael@0: state: DOWNLOAD_QUEUED, michael@0: referrer: httpUrl("referrer6"), michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "text/plain", michael@0: preferredAction: Ci.nsIMIMEInfo.useHelperApp, michael@0: preferredApplication: "prerredApplication6", michael@0: autoResume: 0, michael@0: michael@0: expectedCurrentBytes: TEST_DATA_LENGTH, michael@0: expectedTotalBytes: TEST_DATA_LENGTH, michael@0: expectedResume: true, michael@0: expectedContentType: "text/plain", michael@0: expectedContent: TEST_DATA_SHORT michael@0: }, michael@0: michael@0: // Notstarted download with no autoResume and no partial file. michael@0: // Even though autoResume=0, notstarted downloads always autoResume. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress7.txt"), michael@0: tempPath: "", michael@0: startTime: getStartTime(7), michael@0: state: DOWNLOAD_NOTSTARTED, michael@0: referrer: httpUrl("referrer7"), michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "text/plain", michael@0: preferredAction: Ci.nsIMIMEInfo.useHelperApp, michael@0: preferredApplication: "prerredApplication7", michael@0: autoResume: 0, michael@0: michael@0: expectedCurrentBytes: TEST_DATA_LENGTH, michael@0: expectedTotalBytes: TEST_DATA_LENGTH, michael@0: expectedResume: true, michael@0: expectedContentType: "text/plain", michael@0: expectedContent: TEST_DATA_SHORT michael@0: }, michael@0: michael@0: // Downloading download with no autoResume and a partial file. michael@0: // Even though autoResume=0, downloading downloads always autoResume. michael@0: { michael@0: source: sourceUrl, michael@0: target: getDownloadTarget("inprogress8.txt"), michael@0: tempPath: yield getPartialFile("inprogress8.txt.part", true), michael@0: startTime: getStartTime(8), michael@0: state: DOWNLOAD_DOWNLOADING, michael@0: referrer: httpUrl("referrer8"), michael@0: entityID: sourceEntityId, michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "text/plain", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication8", michael@0: autoResume: 0, michael@0: michael@0: expectedCurrentBytes: TEST_DATA_LENGTH, michael@0: expectedTotalBytes: TEST_DATA_LENGTH, michael@0: expectedResume: true, michael@0: expectedContentType: "text/plain", michael@0: expectedContent: TEST_DATA_TAINTED michael@0: }, michael@0: ]; michael@0: }); michael@0: michael@0: /** michael@0: * Prepares the list of downloads to be added to the database that should michael@0: * *not* be imported by the import procedure. michael@0: */ michael@0: add_task(function prepareNonImportableDownloads() michael@0: { michael@0: gDownloadsRowNonImportable = [ michael@0: // Download with no source (should never happen in normal circumstances). michael@0: { michael@0: source: "", michael@0: target: "nonimportable1.txt", michael@0: tempPath: "", michael@0: startTime: getStartTime(1), michael@0: state: DOWNLOAD_PAUSED, michael@0: referrer: "", michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType1", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication1", michael@0: autoResume: 1 michael@0: }, michael@0: michael@0: // state = DOWNLOAD_FAILED michael@0: { michael@0: source: httpUrl("source.txt"), michael@0: target: "nonimportable2.txt", michael@0: tempPath: "", michael@0: startTime: getStartTime(2), michael@0: state: DOWNLOAD_FAILED, michael@0: referrer: "", michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType2", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication2", michael@0: autoResume: 1 michael@0: }, michael@0: michael@0: // state = DOWNLOAD_CANCELED michael@0: { michael@0: source: httpUrl("source.txt"), michael@0: target: "nonimportable3.txt", michael@0: tempPath: "", michael@0: startTime: getStartTime(3), michael@0: state: DOWNLOAD_CANCELED, michael@0: referrer: "", michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType3", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication3", michael@0: autoResume: 1 michael@0: }, michael@0: michael@0: // state = DOWNLOAD_BLOCKED_PARENTAL michael@0: { michael@0: source: httpUrl("source.txt"), michael@0: target: "nonimportable4.txt", michael@0: tempPath: "", michael@0: startTime: getStartTime(4), michael@0: state: DOWNLOAD_BLOCKED_PARENTAL, michael@0: referrer: "", michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType4", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication4", michael@0: autoResume: 1 michael@0: }, michael@0: michael@0: // state = DOWNLOAD_SCANNING michael@0: { michael@0: source: httpUrl("source.txt"), michael@0: target: "nonimportable5.txt", michael@0: tempPath: "", michael@0: startTime: getStartTime(5), michael@0: state: DOWNLOAD_SCANNING, michael@0: referrer: "", michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType5", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication5", michael@0: autoResume: 1 michael@0: }, michael@0: michael@0: // state = DOWNLOAD_DIRTY michael@0: { michael@0: source: httpUrl("source.txt"), michael@0: target: "nonimportable6.txt", michael@0: tempPath: "", michael@0: startTime: getStartTime(6), michael@0: state: DOWNLOAD_DIRTY, michael@0: referrer: "", michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType6", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication6", michael@0: autoResume: 1 michael@0: }, michael@0: michael@0: // state = DOWNLOAD_BLOCKED_POLICY michael@0: { michael@0: source: httpUrl("source.txt"), michael@0: target: "nonimportable7.txt", michael@0: tempPath: "", michael@0: startTime: getStartTime(7), michael@0: state: DOWNLOAD_BLOCKED_POLICY, michael@0: referrer: "", michael@0: entityID: "", michael@0: maxBytes: MAXBYTES_IN_DB, michael@0: mimeType: "mimeType7", michael@0: preferredAction: Ci.nsIMIMEInfo.saveToDisk, michael@0: preferredApplication: "prerredApplication7", michael@0: autoResume: 1 michael@0: }, michael@0: ]; michael@0: }); michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Test michael@0: michael@0: /** michael@0: * Creates a temporary Sqlite database with download data and perform an michael@0: * import of that data to the new Downloads API to verify that the import michael@0: * worked correctly. michael@0: */ michael@0: add_task(function test_downloadImport() michael@0: { michael@0: let connection = null; michael@0: let downloadsSqlite = getTempFile("downloads.sqlite").path; michael@0: michael@0: try { michael@0: // Set up the database. michael@0: connection = yield promiseEmptyDatabaseConnection({ michael@0: aPath: downloadsSqlite, michael@0: aSchemaVersion: 9 michael@0: }); michael@0: michael@0: // Insert both the importable and non-importable michael@0: // downloads together. michael@0: for (let downloadRow of gDownloadsRowToImport) { michael@0: yield promiseInsertRow(connection, downloadRow); michael@0: } michael@0: michael@0: for (let downloadRow of gDownloadsRowNonImportable) { michael@0: yield promiseInsertRow(connection, downloadRow); michael@0: } michael@0: michael@0: // Check that every item was inserted. michael@0: do_check_eq((yield promiseTableCount(connection)), michael@0: gDownloadsRowToImport.length + michael@0: gDownloadsRowNonImportable.length); michael@0: } finally { michael@0: // Close the connection so that DownloadImport can open it. michael@0: yield connection.close(); michael@0: } michael@0: michael@0: // Import items. michael@0: let list = yield promiseNewList(false); michael@0: yield new DownloadImport(list, downloadsSqlite).import(); michael@0: let items = yield list.getAll(); michael@0: michael@0: do_check_eq(items.length, gDownloadsRowToImport.length); michael@0: michael@0: for (let i = 0; i < gDownloadsRowToImport.length; i++) { michael@0: yield checkDownload(items[i], gDownloadsRowToImport[i]); michael@0: } michael@0: })