toolkit/components/jsdownloads/test/unit/test_DownloadImport.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/jsdownloads/test/unit/test_DownloadImport.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,701 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +/**
     1.8 + * Tests the DownloadImport object.
     1.9 + */
    1.10 +
    1.11 +"use strict";
    1.12 +
    1.13 +////////////////////////////////////////////////////////////////////////////////
    1.14 +//// Globals
    1.15 +
    1.16 +XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
    1.17 +                                  "resource://gre/modules/Sqlite.jsm");
    1.18 +XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
    1.19 +                                  "resource://gre/modules/DownloadImport.jsm");
    1.20 +
    1.21 +// Importable states
    1.22 +const DOWNLOAD_NOTSTARTED = -1;
    1.23 +const DOWNLOAD_DOWNLOADING = 0;
    1.24 +const DOWNLOAD_PAUSED = 4;
    1.25 +const DOWNLOAD_QUEUED = 5;
    1.26 +
    1.27 +// Non importable states
    1.28 +const DOWNLOAD_FAILED = 2;
    1.29 +const DOWNLOAD_CANCELED = 3;
    1.30 +const DOWNLOAD_BLOCKED_PARENTAL = 6;
    1.31 +const DOWNLOAD_SCANNING = 7;
    1.32 +const DOWNLOAD_DIRTY = 8;
    1.33 +const DOWNLOAD_BLOCKED_POLICY = 9;
    1.34 +
    1.35 +// The TEST_DATA_TAINTED const is a version of TEST_DATA_SHORT in which the
    1.36 +// beginning of the data was changed (with the TEST_DATA_REPLACEMENT value).
    1.37 +// We use this to test that the entityID is properly imported and the download
    1.38 +// can be resumed from where it was paused.
    1.39 +// For simplification purposes, the test requires that TEST_DATA_SHORT and
    1.40 +// TEST_DATA_TAINTED have the same length.
    1.41 +const TEST_DATA_REPLACEMENT = "-changed- ";
    1.42 +const TEST_DATA_TAINTED = TEST_DATA_REPLACEMENT +
    1.43 +                          TEST_DATA_SHORT.substr(TEST_DATA_REPLACEMENT.length);
    1.44 +const TEST_DATA_LENGTH = TEST_DATA_SHORT.length;
    1.45 +
    1.46 +// The length of the partial file that we'll write to disk as an existing
    1.47 +// ongoing download.
    1.48 +const TEST_DATA_PARTIAL_LENGTH = TEST_DATA_REPLACEMENT.length;
    1.49 +
    1.50 +// The value of the "maxBytes" column stored in the DB about the downloads.
    1.51 +// It's intentionally different than TEST_DATA_LENGTH to test that each value
    1.52 +// is seen when expected.
    1.53 +const MAXBYTES_IN_DB = TEST_DATA_LENGTH - 10;
    1.54 +
    1.55 +let gDownloadsRowToImport;
    1.56 +let gDownloadsRowNonImportable;
    1.57 +
    1.58 +/**
    1.59 + * Creates a database with an empty moz_downloads table and leaves an
    1.60 + * open connection to it.
    1.61 + *
    1.62 + * @param aPath
    1.63 + *        String containing the path of the database file to be created.
    1.64 + * @param aSchemaVersion
    1.65 + *        Number with the version of the database schema to set.
    1.66 + *
    1.67 + * @return {Promise}
    1.68 + * @resolves The open connection to the database.
    1.69 + * @rejects If an error occurred during the database creation.
    1.70 + */
    1.71 +function promiseEmptyDatabaseConnection({aPath, aSchemaVersion}) {
    1.72 +  return Task.spawn(function () {
    1.73 +    let connection = yield Sqlite.openConnection({ path: aPath });
    1.74 +
    1.75 +    yield connection.execute("CREATE TABLE moz_downloads ("
    1.76 +                             + "id INTEGER PRIMARY KEY,"
    1.77 +                             + "name TEXT,"
    1.78 +                             + "source TEXT,"
    1.79 +                             + "target TEXT,"
    1.80 +                             + "tempPath TEXT,"
    1.81 +                             + "startTime INTEGER,"
    1.82 +                             + "endTime INTEGER,"
    1.83 +                             + "state INTEGER,"
    1.84 +                             + "referrer TEXT,"
    1.85 +                             + "entityID TEXT,"
    1.86 +                             + "currBytes INTEGER NOT NULL DEFAULT 0,"
    1.87 +                             + "maxBytes INTEGER NOT NULL DEFAULT -1,"
    1.88 +                             + "mimeType TEXT,"
    1.89 +                             + "preferredApplication TEXT,"
    1.90 +                             + "preferredAction INTEGER NOT NULL DEFAULT 0,"
    1.91 +                             + "autoResume INTEGER NOT NULL DEFAULT 0,"
    1.92 +                             + "guid TEXT)");
    1.93 +
    1.94 +    yield connection.setSchemaVersion(aSchemaVersion);
    1.95 +
    1.96 +    throw new Task.Result(connection);
    1.97 +  });
    1.98 +}
    1.99 +
   1.100 +/**
   1.101 + * Inserts a new entry in the database with the given columns' values.
   1.102 + *
   1.103 + * @param aConnection
   1.104 + *        The database connection.
   1.105 + * @param aDownloadRow
   1.106 + *        An object representing the values for each column of the row
   1.107 + *        being inserted.
   1.108 + *
   1.109 + * @return {Promise}
   1.110 + * @resolves When the operation completes.
   1.111 + * @rejects If there's an error inserting the row.
   1.112 + */
   1.113 +function promiseInsertRow(aConnection, aDownloadRow) {
   1.114 +  // We can't use the aDownloadRow obj directly in the execute statement
   1.115 +  // because the obj bind code in Sqlite.jsm doesn't allow objects
   1.116 +  // with extra properties beyond those being binded. So we might as well
   1.117 +  // use an array as it is simpler.
   1.118 +  let values = [
   1.119 +    aDownloadRow.source, aDownloadRow.target, aDownloadRow.tempPath,
   1.120 +    aDownloadRow.startTime.getTime() * 1000, aDownloadRow.state,
   1.121 +    aDownloadRow.referrer, aDownloadRow.entityID, aDownloadRow.maxBytes,
   1.122 +    aDownloadRow.mimeType, aDownloadRow.preferredApplication,
   1.123 +    aDownloadRow.preferredAction, aDownloadRow.autoResume
   1.124 +  ];
   1.125 +
   1.126 +  return aConnection.execute("INSERT INTO moz_downloads ("
   1.127 +                            + "name, source, target, tempPath, startTime,"
   1.128 +                            + "endTime, state, referrer, entityID, currBytes,"
   1.129 +                            + "maxBytes, mimeType, preferredApplication,"
   1.130 +                            + "preferredAction, autoResume, guid)"
   1.131 +                            + "VALUES ("
   1.132 +                            + "'', ?, ?, ?, ?, " //name,
   1.133 +                            + "0, ?, ?, ?, 0, "  //endTime, currBytes
   1.134 +                            + " ?, ?, ?, "       //
   1.135 +                            + " ?, ?, '')",      //and guid are not imported
   1.136 +                            values);
   1.137 +}
   1.138 +
   1.139 +/**
   1.140 + * Retrieves the number of rows in the moz_downloads table of the
   1.141 + * database.
   1.142 + *
   1.143 + * @param aConnection
   1.144 + *        The database connection.
   1.145 + *
   1.146 + * @return {Promise}
   1.147 + * @resolves With the number of rows.
   1.148 + * @rejects Never.
   1.149 + */
   1.150 +function promiseTableCount(aConnection) {
   1.151 +  return aConnection.execute("SELECT COUNT(*) FROM moz_downloads")
   1.152 +                    .then(res => res[0].getResultByName("COUNT(*)"))
   1.153 +                    .then(null, Cu.reportError);
   1.154 +}
   1.155 +
   1.156 +/**
   1.157 + * Briefly opens a network channel to a given URL to retrieve
   1.158 + * the entityID of this url, as generated by the network code.
   1.159 + *
   1.160 + * @param aUrl
   1.161 + *        The URL to retrieve the entityID.
   1.162 + *
   1.163 + * @return {Promise}
   1.164 + * @resolves The EntityID of the given URL.
   1.165 + * @rejects When there's a problem accessing the URL.
   1.166 + */
   1.167 +function promiseEntityID(aUrl) {
   1.168 +  let deferred = Promise.defer();
   1.169 +  let entityID = "";
   1.170 +  let channel = NetUtil.newChannel(NetUtil.newURI(aUrl));
   1.171 +
   1.172 +  channel.asyncOpen({
   1.173 +    onStartRequest: function (aRequest) {
   1.174 +      if (aRequest instanceof Ci.nsIResumableChannel) {
   1.175 +        entityID = aRequest.entityID;
   1.176 +      }
   1.177 +      aRequest.cancel(Cr.NS_BINDING_ABORTED);
   1.178 +    },
   1.179 +
   1.180 +    onStopRequest: function (aRequest, aContext, aStatusCode) {
   1.181 +      if (aStatusCode == Cr.NS_BINDING_ABORTED) {
   1.182 +        deferred.resolve(entityID);
   1.183 +      } else {
   1.184 +        deferred.reject("Unexpected status code received");
   1.185 +      }
   1.186 +    },
   1.187 +
   1.188 +    onDataAvailable: function () {}
   1.189 +  }, null);
   1.190 +
   1.191 +  return deferred.promise;
   1.192 +}
   1.193 +
   1.194 +/**
   1.195 + * Gets a file path to a temporary writeable download target, in the
   1.196 + * correct format as expected to be stored in the downloads database,
   1.197 + * which is file:///absolute/path/to/file
   1.198 + *
   1.199 + * @param aLeafName
   1.200 + *        A hint leaf name for the file.
   1.201 + *
   1.202 + * @return String The path to the download target.
   1.203 + */
   1.204 +function getDownloadTarget(aLeafName) {
   1.205 +  return NetUtil.newURI(getTempFile(aLeafName)).spec;
   1.206 +}
   1.207 +
   1.208 +/**
   1.209 + * Generates a temporary partial file to use as an in-progress
   1.210 + * download. The file is written to disk with a part of the total expected
   1.211 + * download content pre-written.
   1.212 + *
   1.213 + * @param aLeafName
   1.214 + *        A hint leaf name for the file.
   1.215 + * @param aTainted
   1.216 + *        A boolean value. When true, the partial content of the file
   1.217 + *        will be different from the expected content of the original source
   1.218 + *        file. See the declaration of TEST_DATA_TAINTED for more information.
   1.219 + *
   1.220 + * @return {Promise}
   1.221 + * @resolves When the operation completes, and returns a string with the path
   1.222 + *           to the generated file.
   1.223 + * @rejects If there's an error writing the file.
   1.224 + */
   1.225 +function getPartialFile(aLeafName, aTainted = false) {
   1.226 +  let tempDownload = getTempFile(aLeafName);
   1.227 +  let partialContent = aTainted
   1.228 +                     ? TEST_DATA_TAINTED.substr(0, TEST_DATA_PARTIAL_LENGTH)
   1.229 +                     : TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH);
   1.230 +
   1.231 +  return OS.File.writeAtomic(tempDownload.path, partialContent,
   1.232 +                             { tmpPath: tempDownload.path + ".tmp",
   1.233 +                               flush: true })
   1.234 +                .then(() => tempDownload.path);
   1.235 +}
   1.236 +
   1.237 +/**
   1.238 + * Generates a Date object to be used as the startTime for the download rows
   1.239 + * in the DB. A date that is obviously different from the current time is
   1.240 + * generated to make sure this stored data and a `new Date()` can't collide.
   1.241 + *
   1.242 + * @param aOffset
   1.243 + *        A offset from the base generated date is used to differentiate each
   1.244 + *        row in the database.
   1.245 + *
   1.246 + * @return A Date object.
   1.247 + */
   1.248 +function getStartTime(aOffset) {
   1.249 +  return new Date(1000000 + (aOffset * 10000));
   1.250 +}
   1.251 +
   1.252 +/**
   1.253 + * Performs various checks on an imported Download object to make sure
   1.254 + * all properties are properly set as expected from the import procedure.
   1.255 + *
   1.256 + * @param aDownload
   1.257 + *        The Download object to be checked.
   1.258 + * @param aDownloadRow
   1.259 + *        An object that represents a row from the original database table,
   1.260 + *        with extra properties describing expected values that are not
   1.261 + *        explictly part of the database.
   1.262 + *
   1.263 + * @return {Promise}
   1.264 + * @resolves When the operation completes
   1.265 + * @rejects Never
   1.266 + */
   1.267 +function checkDownload(aDownload, aDownloadRow) {
   1.268 +  return Task.spawn(function() {
   1.269 +    do_check_eq(aDownload.source.url, aDownloadRow.source);
   1.270 +    do_check_eq(aDownload.source.referrer, aDownloadRow.referrer);
   1.271 +
   1.272 +    do_check_eq(aDownload.target.path,
   1.273 +                NetUtil.newURI(aDownloadRow.target)
   1.274 +                       .QueryInterface(Ci.nsIFileURL).file.path);
   1.275 +
   1.276 +    do_check_eq(aDownload.target.partFilePath, aDownloadRow.tempPath);
   1.277 +
   1.278 +    if (aDownloadRow.expectedResume) {
   1.279 +      do_check_true(!aDownload.stopped || aDownload.succeeded);
   1.280 +      yield promiseDownloadStopped(aDownload);
   1.281 +
   1.282 +      do_check_true(aDownload.succeeded);
   1.283 +      do_check_eq(aDownload.progress, 100);
   1.284 +      // If the download has resumed, a new startTime will be set.
   1.285 +      // By calling toJSON we're also testing that startTime is a Date object.
   1.286 +      do_check_neq(aDownload.startTime.toJSON(),
   1.287 +                   aDownloadRow.startTime.toJSON());
   1.288 +    } else {
   1.289 +      do_check_false(aDownload.succeeded);
   1.290 +      do_check_eq(aDownload.startTime.toJSON(),
   1.291 +                  aDownloadRow.startTime.toJSON());
   1.292 +    }
   1.293 +
   1.294 +    do_check_eq(aDownload.stopped, true);
   1.295 +
   1.296 +    let serializedSaver = aDownload.saver.toSerializable();
   1.297 +    if (typeof(serializedSaver) == "object") {
   1.298 +      do_check_eq(serializedSaver.type, "copy");
   1.299 +    } else {
   1.300 +      do_check_eq(serializedSaver, "copy");
   1.301 +    }
   1.302 +
   1.303 +    if (aDownloadRow.entityID) {
   1.304 +      do_check_eq(aDownload.saver.entityID, aDownloadRow.entityID);
   1.305 +    }
   1.306 +
   1.307 +    do_check_eq(aDownload.currentBytes, aDownloadRow.expectedCurrentBytes);
   1.308 +    do_check_eq(aDownload.totalBytes, aDownloadRow.expectedTotalBytes);
   1.309 +
   1.310 +    if (aDownloadRow.expectedContent) {
   1.311 +      let fileToCheck = aDownloadRow.expectedResume
   1.312 +                        ? aDownload.target.path
   1.313 +                        : aDownload.target.partFilePath;
   1.314 +      yield promiseVerifyContents(fileToCheck, aDownloadRow.expectedContent);
   1.315 +    }
   1.316 +
   1.317 +    do_check_eq(aDownload.contentType, aDownloadRow.expectedContentType);
   1.318 +    do_check_eq(aDownload.launcherPath, aDownloadRow.preferredApplication);
   1.319 +
   1.320 +    do_check_eq(aDownload.launchWhenSucceeded,
   1.321 +                aDownloadRow.preferredAction != Ci.nsIMIMEInfo.saveToDisk);
   1.322 +  });
   1.323 +}
   1.324 +
   1.325 +////////////////////////////////////////////////////////////////////////////////
   1.326 +//// Preparation tasks
   1.327 +
   1.328 +/**
   1.329 + * Prepares the list of downloads to be added to the database that should
   1.330 + * be imported by the import procedure.
   1.331 + */
   1.332 +add_task(function prepareDownloadsToImport() {
   1.333 +
   1.334 +  let sourceUrl = httpUrl("source.txt");
   1.335 +  let sourceEntityId = yield promiseEntityID(sourceUrl);
   1.336 +
   1.337 +  gDownloadsRowToImport = [
   1.338 +    // Paused download with autoResume and a partial file. By
   1.339 +    // setting the correct entityID the download can resume from
   1.340 +    // where it stopped, and to test that this works properly we
   1.341 +    // intentionally set different data in the beginning of the
   1.342 +    // partial file to make sure it was not replaced.
   1.343 +    {
   1.344 +      source: sourceUrl,
   1.345 +      target: getDownloadTarget("inprogress1.txt"),
   1.346 +      tempPath: yield getPartialFile("inprogress1.txt.part", true),
   1.347 +      startTime: getStartTime(1),
   1.348 +      state: DOWNLOAD_PAUSED,
   1.349 +      referrer: httpUrl("referrer1"),
   1.350 +      entityID: sourceEntityId,
   1.351 +      maxBytes: MAXBYTES_IN_DB,
   1.352 +      mimeType: "mimeType1",
   1.353 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.354 +      preferredApplication: "prerredApplication1",
   1.355 +      autoResume: 1,
   1.356 +
   1.357 +      // Even though the information stored in the DB said
   1.358 +      // maxBytes was MAXBYTES_IN_DB, the download turned out to be
   1.359 +      // a different length. Here we make sure the totalBytes property
   1.360 +      // was correctly set with the actual value. The same consideration
   1.361 +      // applies to the contentType.
   1.362 +      expectedCurrentBytes: TEST_DATA_LENGTH,
   1.363 +      expectedTotalBytes: TEST_DATA_LENGTH,
   1.364 +      expectedResume: true,
   1.365 +      expectedContentType: "text/plain",
   1.366 +      expectedContent: TEST_DATA_TAINTED,
   1.367 +    },
   1.368 +
   1.369 +    // Paused download with autoResume and a partial file,
   1.370 +    // but missing entityID. This means that the download will
   1.371 +    // start from beginning, and the entire original content of the
   1.372 +    // source file should replace the different data that was stored
   1.373 +    // in the partial file.
   1.374 +    {
   1.375 +      source: sourceUrl,
   1.376 +      target: getDownloadTarget("inprogress2.txt"),
   1.377 +      tempPath: yield getPartialFile("inprogress2.txt.part", true),
   1.378 +      startTime: getStartTime(2),
   1.379 +      state: DOWNLOAD_PAUSED,
   1.380 +      referrer: httpUrl("referrer2"),
   1.381 +      entityID: "",
   1.382 +      maxBytes: MAXBYTES_IN_DB,
   1.383 +      mimeType: "mimeType2",
   1.384 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.385 +      preferredApplication: "prerredApplication2",
   1.386 +      autoResume: 1,
   1.387 +
   1.388 +      expectedCurrentBytes: TEST_DATA_LENGTH,
   1.389 +      expectedTotalBytes: TEST_DATA_LENGTH,
   1.390 +      expectedResume: true,
   1.391 +      expectedContentType: "text/plain",
   1.392 +      expectedContent: TEST_DATA_SHORT
   1.393 +    },
   1.394 +
   1.395 +    // Paused download with no autoResume and a partial file.
   1.396 +    {
   1.397 +      source: sourceUrl,
   1.398 +      target: getDownloadTarget("inprogress3.txt"),
   1.399 +      tempPath: yield getPartialFile("inprogress3.txt.part"),
   1.400 +      startTime: getStartTime(3),
   1.401 +      state: DOWNLOAD_PAUSED,
   1.402 +      referrer: httpUrl("referrer3"),
   1.403 +      entityID: "",
   1.404 +      maxBytes: MAXBYTES_IN_DB,
   1.405 +      mimeType: "mimeType3",
   1.406 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.407 +      preferredApplication: "prerredApplication3",
   1.408 +      autoResume: 0,
   1.409 +
   1.410 +      // Since this download has not been resumed, the actual data
   1.411 +      // about its total size and content type is not known.
   1.412 +      // Therefore, we're going by the information imported from the DB.
   1.413 +      expectedCurrentBytes: TEST_DATA_PARTIAL_LENGTH,
   1.414 +      expectedTotalBytes: MAXBYTES_IN_DB,
   1.415 +      expectedResume: false,
   1.416 +      expectedContentType: "mimeType3",
   1.417 +      expectedContent: TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH),
   1.418 +    },
   1.419 +
   1.420 +    // Paused download with autoResume and no partial file.
   1.421 +    {
   1.422 +      source: sourceUrl,
   1.423 +      target: getDownloadTarget("inprogress4.txt"),
   1.424 +      tempPath: "",
   1.425 +      startTime: getStartTime(4),
   1.426 +      state: DOWNLOAD_PAUSED,
   1.427 +      referrer: httpUrl("referrer4"),
   1.428 +      entityID: "",
   1.429 +      maxBytes: MAXBYTES_IN_DB,
   1.430 +      mimeType: "text/plain",
   1.431 +      preferredAction: Ci.nsIMIMEInfo.useHelperApp,
   1.432 +      preferredApplication: "prerredApplication4",
   1.433 +      autoResume: 1,
   1.434 +
   1.435 +      expectedCurrentBytes: TEST_DATA_LENGTH,
   1.436 +      expectedTotalBytes: TEST_DATA_LENGTH,
   1.437 +      expectedResume: true,
   1.438 +      expectedContentType: "text/plain",
   1.439 +      expectedContent: TEST_DATA_SHORT
   1.440 +    },
   1.441 +
   1.442 +    // Paused download with no autoResume and no partial file.
   1.443 +    {
   1.444 +      source: sourceUrl,
   1.445 +      target: getDownloadTarget("inprogress5.txt"),
   1.446 +      tempPath: "",
   1.447 +      startTime: getStartTime(5),
   1.448 +      state: DOWNLOAD_PAUSED,
   1.449 +      referrer: httpUrl("referrer4"),
   1.450 +      entityID: "",
   1.451 +      maxBytes: MAXBYTES_IN_DB,
   1.452 +      mimeType: "text/plain",
   1.453 +      preferredAction: Ci.nsIMIMEInfo.useSystemDefault,
   1.454 +      preferredApplication: "prerredApplication5",
   1.455 +      autoResume: 0,
   1.456 +
   1.457 +      expectedCurrentBytes: 0,
   1.458 +      expectedTotalBytes: MAXBYTES_IN_DB,
   1.459 +      expectedResume: false,
   1.460 +      expectedContentType: "text/plain",
   1.461 +    },
   1.462 +
   1.463 +    // Queued download with no autoResume and no partial file.
   1.464 +    // Even though autoResume=0, queued downloads always autoResume.
   1.465 +    {
   1.466 +      source: sourceUrl,
   1.467 +      target: getDownloadTarget("inprogress6.txt"),
   1.468 +      tempPath: "",
   1.469 +      startTime: getStartTime(6),
   1.470 +      state: DOWNLOAD_QUEUED,
   1.471 +      referrer: httpUrl("referrer6"),
   1.472 +      entityID: "",
   1.473 +      maxBytes: MAXBYTES_IN_DB,
   1.474 +      mimeType: "text/plain",
   1.475 +      preferredAction: Ci.nsIMIMEInfo.useHelperApp,
   1.476 +      preferredApplication: "prerredApplication6",
   1.477 +      autoResume: 0,
   1.478 +
   1.479 +      expectedCurrentBytes: TEST_DATA_LENGTH,
   1.480 +      expectedTotalBytes: TEST_DATA_LENGTH,
   1.481 +      expectedResume: true,
   1.482 +      expectedContentType: "text/plain",
   1.483 +      expectedContent: TEST_DATA_SHORT
   1.484 +    },
   1.485 +
   1.486 +    // Notstarted download with no autoResume and no partial file.
   1.487 +    // Even though autoResume=0, notstarted downloads always autoResume.
   1.488 +    {
   1.489 +      source: sourceUrl,
   1.490 +      target: getDownloadTarget("inprogress7.txt"),
   1.491 +      tempPath: "",
   1.492 +      startTime: getStartTime(7),
   1.493 +      state: DOWNLOAD_NOTSTARTED,
   1.494 +      referrer: httpUrl("referrer7"),
   1.495 +      entityID: "",
   1.496 +      maxBytes: MAXBYTES_IN_DB,
   1.497 +      mimeType: "text/plain",
   1.498 +      preferredAction: Ci.nsIMIMEInfo.useHelperApp,
   1.499 +      preferredApplication: "prerredApplication7",
   1.500 +      autoResume: 0,
   1.501 +
   1.502 +      expectedCurrentBytes: TEST_DATA_LENGTH,
   1.503 +      expectedTotalBytes: TEST_DATA_LENGTH,
   1.504 +      expectedResume: true,
   1.505 +      expectedContentType: "text/plain",
   1.506 +      expectedContent: TEST_DATA_SHORT
   1.507 +    },
   1.508 +
   1.509 +    // Downloading download with no autoResume and a partial file.
   1.510 +    // Even though autoResume=0, downloading downloads always autoResume.
   1.511 +    {
   1.512 +      source: sourceUrl,
   1.513 +      target: getDownloadTarget("inprogress8.txt"),
   1.514 +      tempPath: yield getPartialFile("inprogress8.txt.part", true),
   1.515 +      startTime: getStartTime(8),
   1.516 +      state: DOWNLOAD_DOWNLOADING,
   1.517 +      referrer: httpUrl("referrer8"),
   1.518 +      entityID: sourceEntityId,
   1.519 +      maxBytes: MAXBYTES_IN_DB,
   1.520 +      mimeType: "text/plain",
   1.521 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.522 +      preferredApplication: "prerredApplication8",
   1.523 +      autoResume: 0,
   1.524 +
   1.525 +      expectedCurrentBytes: TEST_DATA_LENGTH,
   1.526 +      expectedTotalBytes: TEST_DATA_LENGTH,
   1.527 +      expectedResume: true,
   1.528 +      expectedContentType: "text/plain",
   1.529 +      expectedContent: TEST_DATA_TAINTED
   1.530 +    },
   1.531 +  ];
   1.532 +});
   1.533 +
   1.534 +/**
   1.535 + * Prepares the list of downloads to be added to the database that should
   1.536 + * *not* be imported by the import procedure.
   1.537 + */
   1.538 +add_task(function prepareNonImportableDownloads()
   1.539 +{
   1.540 +  gDownloadsRowNonImportable = [
   1.541 +    // Download with no source (should never happen in normal circumstances).
   1.542 +    {
   1.543 +      source: "",
   1.544 +      target: "nonimportable1.txt",
   1.545 +      tempPath: "",
   1.546 +      startTime: getStartTime(1),
   1.547 +      state: DOWNLOAD_PAUSED,
   1.548 +      referrer: "",
   1.549 +      entityID: "",
   1.550 +      maxBytes: MAXBYTES_IN_DB,
   1.551 +      mimeType: "mimeType1",
   1.552 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.553 +      preferredApplication: "prerredApplication1",
   1.554 +      autoResume: 1
   1.555 +    },
   1.556 +
   1.557 +    // state = DOWNLOAD_FAILED
   1.558 +    {
   1.559 +      source: httpUrl("source.txt"),
   1.560 +      target: "nonimportable2.txt",
   1.561 +      tempPath: "",
   1.562 +      startTime: getStartTime(2),
   1.563 +      state: DOWNLOAD_FAILED,
   1.564 +      referrer: "",
   1.565 +      entityID: "",
   1.566 +      maxBytes: MAXBYTES_IN_DB,
   1.567 +      mimeType: "mimeType2",
   1.568 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.569 +      preferredApplication: "prerredApplication2",
   1.570 +      autoResume: 1
   1.571 +    },
   1.572 +
   1.573 +    // state = DOWNLOAD_CANCELED
   1.574 +    {
   1.575 +      source: httpUrl("source.txt"),
   1.576 +      target: "nonimportable3.txt",
   1.577 +      tempPath: "",
   1.578 +      startTime: getStartTime(3),
   1.579 +      state: DOWNLOAD_CANCELED,
   1.580 +      referrer: "",
   1.581 +      entityID: "",
   1.582 +      maxBytes: MAXBYTES_IN_DB,
   1.583 +      mimeType: "mimeType3",
   1.584 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.585 +      preferredApplication: "prerredApplication3",
   1.586 +      autoResume: 1
   1.587 +    },
   1.588 +
   1.589 +    // state = DOWNLOAD_BLOCKED_PARENTAL
   1.590 +    {
   1.591 +      source: httpUrl("source.txt"),
   1.592 +      target: "nonimportable4.txt",
   1.593 +      tempPath: "",
   1.594 +      startTime: getStartTime(4),
   1.595 +      state: DOWNLOAD_BLOCKED_PARENTAL,
   1.596 +      referrer: "",
   1.597 +      entityID: "",
   1.598 +      maxBytes: MAXBYTES_IN_DB,
   1.599 +      mimeType: "mimeType4",
   1.600 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.601 +      preferredApplication: "prerredApplication4",
   1.602 +      autoResume: 1
   1.603 +    },
   1.604 +
   1.605 +    // state = DOWNLOAD_SCANNING
   1.606 +    {
   1.607 +      source: httpUrl("source.txt"),
   1.608 +      target: "nonimportable5.txt",
   1.609 +      tempPath: "",
   1.610 +      startTime: getStartTime(5),
   1.611 +      state: DOWNLOAD_SCANNING,
   1.612 +      referrer: "",
   1.613 +      entityID: "",
   1.614 +      maxBytes: MAXBYTES_IN_DB,
   1.615 +      mimeType: "mimeType5",
   1.616 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.617 +      preferredApplication: "prerredApplication5",
   1.618 +      autoResume: 1
   1.619 +    },
   1.620 +
   1.621 +    // state = DOWNLOAD_DIRTY
   1.622 +    {
   1.623 +      source: httpUrl("source.txt"),
   1.624 +      target: "nonimportable6.txt",
   1.625 +      tempPath: "",
   1.626 +      startTime: getStartTime(6),
   1.627 +      state: DOWNLOAD_DIRTY,
   1.628 +      referrer: "",
   1.629 +      entityID: "",
   1.630 +      maxBytes: MAXBYTES_IN_DB,
   1.631 +      mimeType: "mimeType6",
   1.632 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.633 +      preferredApplication: "prerredApplication6",
   1.634 +      autoResume: 1
   1.635 +    },
   1.636 +
   1.637 +    // state = DOWNLOAD_BLOCKED_POLICY
   1.638 +    {
   1.639 +      source: httpUrl("source.txt"),
   1.640 +      target: "nonimportable7.txt",
   1.641 +      tempPath: "",
   1.642 +      startTime: getStartTime(7),
   1.643 +      state: DOWNLOAD_BLOCKED_POLICY,
   1.644 +      referrer: "",
   1.645 +      entityID: "",
   1.646 +      maxBytes: MAXBYTES_IN_DB,
   1.647 +      mimeType: "mimeType7",
   1.648 +      preferredAction: Ci.nsIMIMEInfo.saveToDisk,
   1.649 +      preferredApplication: "prerredApplication7",
   1.650 +      autoResume: 1
   1.651 +    },
   1.652 +  ];
   1.653 +});
   1.654 +
   1.655 +////////////////////////////////////////////////////////////////////////////////
   1.656 +//// Test
   1.657 +
   1.658 +/**
   1.659 + * Creates a temporary Sqlite database with download data and perform an
   1.660 + * import of that data to the new Downloads API to verify that the import
   1.661 + * worked correctly.
   1.662 + */
   1.663 +add_task(function test_downloadImport()
   1.664 +{
   1.665 +  let connection = null;
   1.666 +  let downloadsSqlite = getTempFile("downloads.sqlite").path;
   1.667 +
   1.668 +  try {
   1.669 +    // Set up the database.
   1.670 +    connection = yield promiseEmptyDatabaseConnection({
   1.671 +      aPath: downloadsSqlite,
   1.672 +      aSchemaVersion: 9
   1.673 +    });
   1.674 +
   1.675 +    // Insert both the importable and non-importable
   1.676 +    // downloads together.
   1.677 +    for (let downloadRow of gDownloadsRowToImport) {
   1.678 +      yield promiseInsertRow(connection, downloadRow);
   1.679 +    }
   1.680 +
   1.681 +    for (let downloadRow of gDownloadsRowNonImportable) {
   1.682 +      yield promiseInsertRow(connection, downloadRow);
   1.683 +    }
   1.684 +
   1.685 +    // Check that every item was inserted.
   1.686 +    do_check_eq((yield promiseTableCount(connection)),
   1.687 +                gDownloadsRowToImport.length +
   1.688 +                gDownloadsRowNonImportable.length);
   1.689 +  } finally {
   1.690 +    // Close the connection so that DownloadImport can open it.
   1.691 +    yield connection.close();
   1.692 +  }
   1.693 +
   1.694 +  // Import items.
   1.695 +  let list = yield promiseNewList(false);
   1.696 +  yield new DownloadImport(list, downloadsSqlite).import();
   1.697 +  let items = yield list.getAll();
   1.698 +
   1.699 +  do_check_eq(items.length, gDownloadsRowToImport.length);
   1.700 +
   1.701 +  for (let i = 0; i < gDownloadsRowToImport.length; i++) {
   1.702 +    yield checkDownload(items[i], gDownloadsRowToImport[i]);
   1.703 +  }
   1.704 +})

mercurial