Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
4 /**
5 * Tests the DownloadImport object.
6 */
8 "use strict";
10 ////////////////////////////////////////////////////////////////////////////////
11 //// Globals
13 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
14 "resource://gre/modules/Sqlite.jsm");
15 XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
16 "resource://gre/modules/DownloadImport.jsm");
18 // Importable states
19 const DOWNLOAD_NOTSTARTED = -1;
20 const DOWNLOAD_DOWNLOADING = 0;
21 const DOWNLOAD_PAUSED = 4;
22 const DOWNLOAD_QUEUED = 5;
24 // Non importable states
25 const DOWNLOAD_FAILED = 2;
26 const DOWNLOAD_CANCELED = 3;
27 const DOWNLOAD_BLOCKED_PARENTAL = 6;
28 const DOWNLOAD_SCANNING = 7;
29 const DOWNLOAD_DIRTY = 8;
30 const DOWNLOAD_BLOCKED_POLICY = 9;
32 // The TEST_DATA_TAINTED const is a version of TEST_DATA_SHORT in which the
33 // beginning of the data was changed (with the TEST_DATA_REPLACEMENT value).
34 // We use this to test that the entityID is properly imported and the download
35 // can be resumed from where it was paused.
36 // For simplification purposes, the test requires that TEST_DATA_SHORT and
37 // TEST_DATA_TAINTED have the same length.
38 const TEST_DATA_REPLACEMENT = "-changed- ";
39 const TEST_DATA_TAINTED = TEST_DATA_REPLACEMENT +
40 TEST_DATA_SHORT.substr(TEST_DATA_REPLACEMENT.length);
41 const TEST_DATA_LENGTH = TEST_DATA_SHORT.length;
43 // The length of the partial file that we'll write to disk as an existing
44 // ongoing download.
45 const TEST_DATA_PARTIAL_LENGTH = TEST_DATA_REPLACEMENT.length;
47 // The value of the "maxBytes" column stored in the DB about the downloads.
48 // It's intentionally different than TEST_DATA_LENGTH to test that each value
49 // is seen when expected.
50 const MAXBYTES_IN_DB = TEST_DATA_LENGTH - 10;
52 let gDownloadsRowToImport;
53 let gDownloadsRowNonImportable;
55 /**
56 * Creates a database with an empty moz_downloads table and leaves an
57 * open connection to it.
58 *
59 * @param aPath
60 * String containing the path of the database file to be created.
61 * @param aSchemaVersion
62 * Number with the version of the database schema to set.
63 *
64 * @return {Promise}
65 * @resolves The open connection to the database.
66 * @rejects If an error occurred during the database creation.
67 */
68 function promiseEmptyDatabaseConnection({aPath, aSchemaVersion}) {
69 return Task.spawn(function () {
70 let connection = yield Sqlite.openConnection({ path: aPath });
72 yield connection.execute("CREATE TABLE moz_downloads ("
73 + "id INTEGER PRIMARY KEY,"
74 + "name TEXT,"
75 + "source TEXT,"
76 + "target TEXT,"
77 + "tempPath TEXT,"
78 + "startTime INTEGER,"
79 + "endTime INTEGER,"
80 + "state INTEGER,"
81 + "referrer TEXT,"
82 + "entityID TEXT,"
83 + "currBytes INTEGER NOT NULL DEFAULT 0,"
84 + "maxBytes INTEGER NOT NULL DEFAULT -1,"
85 + "mimeType TEXT,"
86 + "preferredApplication TEXT,"
87 + "preferredAction INTEGER NOT NULL DEFAULT 0,"
88 + "autoResume INTEGER NOT NULL DEFAULT 0,"
89 + "guid TEXT)");
91 yield connection.setSchemaVersion(aSchemaVersion);
93 throw new Task.Result(connection);
94 });
95 }
97 /**
98 * Inserts a new entry in the database with the given columns' values.
99 *
100 * @param aConnection
101 * The database connection.
102 * @param aDownloadRow
103 * An object representing the values for each column of the row
104 * being inserted.
105 *
106 * @return {Promise}
107 * @resolves When the operation completes.
108 * @rejects If there's an error inserting the row.
109 */
110 function promiseInsertRow(aConnection, aDownloadRow) {
111 // We can't use the aDownloadRow obj directly in the execute statement
112 // because the obj bind code in Sqlite.jsm doesn't allow objects
113 // with extra properties beyond those being binded. So we might as well
114 // use an array as it is simpler.
115 let values = [
116 aDownloadRow.source, aDownloadRow.target, aDownloadRow.tempPath,
117 aDownloadRow.startTime.getTime() * 1000, aDownloadRow.state,
118 aDownloadRow.referrer, aDownloadRow.entityID, aDownloadRow.maxBytes,
119 aDownloadRow.mimeType, aDownloadRow.preferredApplication,
120 aDownloadRow.preferredAction, aDownloadRow.autoResume
121 ];
123 return aConnection.execute("INSERT INTO moz_downloads ("
124 + "name, source, target, tempPath, startTime,"
125 + "endTime, state, referrer, entityID, currBytes,"
126 + "maxBytes, mimeType, preferredApplication,"
127 + "preferredAction, autoResume, guid)"
128 + "VALUES ("
129 + "'', ?, ?, ?, ?, " //name,
130 + "0, ?, ?, ?, 0, " //endTime, currBytes
131 + " ?, ?, ?, " //
132 + " ?, ?, '')", //and guid are not imported
133 values);
134 }
136 /**
137 * Retrieves the number of rows in the moz_downloads table of the
138 * database.
139 *
140 * @param aConnection
141 * The database connection.
142 *
143 * @return {Promise}
144 * @resolves With the number of rows.
145 * @rejects Never.
146 */
147 function promiseTableCount(aConnection) {
148 return aConnection.execute("SELECT COUNT(*) FROM moz_downloads")
149 .then(res => res[0].getResultByName("COUNT(*)"))
150 .then(null, Cu.reportError);
151 }
153 /**
154 * Briefly opens a network channel to a given URL to retrieve
155 * the entityID of this url, as generated by the network code.
156 *
157 * @param aUrl
158 * The URL to retrieve the entityID.
159 *
160 * @return {Promise}
161 * @resolves The EntityID of the given URL.
162 * @rejects When there's a problem accessing the URL.
163 */
164 function promiseEntityID(aUrl) {
165 let deferred = Promise.defer();
166 let entityID = "";
167 let channel = NetUtil.newChannel(NetUtil.newURI(aUrl));
169 channel.asyncOpen({
170 onStartRequest: function (aRequest) {
171 if (aRequest instanceof Ci.nsIResumableChannel) {
172 entityID = aRequest.entityID;
173 }
174 aRequest.cancel(Cr.NS_BINDING_ABORTED);
175 },
177 onStopRequest: function (aRequest, aContext, aStatusCode) {
178 if (aStatusCode == Cr.NS_BINDING_ABORTED) {
179 deferred.resolve(entityID);
180 } else {
181 deferred.reject("Unexpected status code received");
182 }
183 },
185 onDataAvailable: function () {}
186 }, null);
188 return deferred.promise;
189 }
191 /**
192 * Gets a file path to a temporary writeable download target, in the
193 * correct format as expected to be stored in the downloads database,
194 * which is file:///absolute/path/to/file
195 *
196 * @param aLeafName
197 * A hint leaf name for the file.
198 *
199 * @return String The path to the download target.
200 */
201 function getDownloadTarget(aLeafName) {
202 return NetUtil.newURI(getTempFile(aLeafName)).spec;
203 }
205 /**
206 * Generates a temporary partial file to use as an in-progress
207 * download. The file is written to disk with a part of the total expected
208 * download content pre-written.
209 *
210 * @param aLeafName
211 * A hint leaf name for the file.
212 * @param aTainted
213 * A boolean value. When true, the partial content of the file
214 * will be different from the expected content of the original source
215 * file. See the declaration of TEST_DATA_TAINTED for more information.
216 *
217 * @return {Promise}
218 * @resolves When the operation completes, and returns a string with the path
219 * to the generated file.
220 * @rejects If there's an error writing the file.
221 */
222 function getPartialFile(aLeafName, aTainted = false) {
223 let tempDownload = getTempFile(aLeafName);
224 let partialContent = aTainted
225 ? TEST_DATA_TAINTED.substr(0, TEST_DATA_PARTIAL_LENGTH)
226 : TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH);
228 return OS.File.writeAtomic(tempDownload.path, partialContent,
229 { tmpPath: tempDownload.path + ".tmp",
230 flush: true })
231 .then(() => tempDownload.path);
232 }
234 /**
235 * Generates a Date object to be used as the startTime for the download rows
236 * in the DB. A date that is obviously different from the current time is
237 * generated to make sure this stored data and a `new Date()` can't collide.
238 *
239 * @param aOffset
240 * A offset from the base generated date is used to differentiate each
241 * row in the database.
242 *
243 * @return A Date object.
244 */
245 function getStartTime(aOffset) {
246 return new Date(1000000 + (aOffset * 10000));
247 }
249 /**
250 * Performs various checks on an imported Download object to make sure
251 * all properties are properly set as expected from the import procedure.
252 *
253 * @param aDownload
254 * The Download object to be checked.
255 * @param aDownloadRow
256 * An object that represents a row from the original database table,
257 * with extra properties describing expected values that are not
258 * explictly part of the database.
259 *
260 * @return {Promise}
261 * @resolves When the operation completes
262 * @rejects Never
263 */
264 function checkDownload(aDownload, aDownloadRow) {
265 return Task.spawn(function() {
266 do_check_eq(aDownload.source.url, aDownloadRow.source);
267 do_check_eq(aDownload.source.referrer, aDownloadRow.referrer);
269 do_check_eq(aDownload.target.path,
270 NetUtil.newURI(aDownloadRow.target)
271 .QueryInterface(Ci.nsIFileURL).file.path);
273 do_check_eq(aDownload.target.partFilePath, aDownloadRow.tempPath);
275 if (aDownloadRow.expectedResume) {
276 do_check_true(!aDownload.stopped || aDownload.succeeded);
277 yield promiseDownloadStopped(aDownload);
279 do_check_true(aDownload.succeeded);
280 do_check_eq(aDownload.progress, 100);
281 // If the download has resumed, a new startTime will be set.
282 // By calling toJSON we're also testing that startTime is a Date object.
283 do_check_neq(aDownload.startTime.toJSON(),
284 aDownloadRow.startTime.toJSON());
285 } else {
286 do_check_false(aDownload.succeeded);
287 do_check_eq(aDownload.startTime.toJSON(),
288 aDownloadRow.startTime.toJSON());
289 }
291 do_check_eq(aDownload.stopped, true);
293 let serializedSaver = aDownload.saver.toSerializable();
294 if (typeof(serializedSaver) == "object") {
295 do_check_eq(serializedSaver.type, "copy");
296 } else {
297 do_check_eq(serializedSaver, "copy");
298 }
300 if (aDownloadRow.entityID) {
301 do_check_eq(aDownload.saver.entityID, aDownloadRow.entityID);
302 }
304 do_check_eq(aDownload.currentBytes, aDownloadRow.expectedCurrentBytes);
305 do_check_eq(aDownload.totalBytes, aDownloadRow.expectedTotalBytes);
307 if (aDownloadRow.expectedContent) {
308 let fileToCheck = aDownloadRow.expectedResume
309 ? aDownload.target.path
310 : aDownload.target.partFilePath;
311 yield promiseVerifyContents(fileToCheck, aDownloadRow.expectedContent);
312 }
314 do_check_eq(aDownload.contentType, aDownloadRow.expectedContentType);
315 do_check_eq(aDownload.launcherPath, aDownloadRow.preferredApplication);
317 do_check_eq(aDownload.launchWhenSucceeded,
318 aDownloadRow.preferredAction != Ci.nsIMIMEInfo.saveToDisk);
319 });
320 }
322 ////////////////////////////////////////////////////////////////////////////////
323 //// Preparation tasks
325 /**
326 * Prepares the list of downloads to be added to the database that should
327 * be imported by the import procedure.
328 */
329 add_task(function prepareDownloadsToImport() {
331 let sourceUrl = httpUrl("source.txt");
332 let sourceEntityId = yield promiseEntityID(sourceUrl);
334 gDownloadsRowToImport = [
335 // Paused download with autoResume and a partial file. By
336 // setting the correct entityID the download can resume from
337 // where it stopped, and to test that this works properly we
338 // intentionally set different data in the beginning of the
339 // partial file to make sure it was not replaced.
340 {
341 source: sourceUrl,
342 target: getDownloadTarget("inprogress1.txt"),
343 tempPath: yield getPartialFile("inprogress1.txt.part", true),
344 startTime: getStartTime(1),
345 state: DOWNLOAD_PAUSED,
346 referrer: httpUrl("referrer1"),
347 entityID: sourceEntityId,
348 maxBytes: MAXBYTES_IN_DB,
349 mimeType: "mimeType1",
350 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
351 preferredApplication: "prerredApplication1",
352 autoResume: 1,
354 // Even though the information stored in the DB said
355 // maxBytes was MAXBYTES_IN_DB, the download turned out to be
356 // a different length. Here we make sure the totalBytes property
357 // was correctly set with the actual value. The same consideration
358 // applies to the contentType.
359 expectedCurrentBytes: TEST_DATA_LENGTH,
360 expectedTotalBytes: TEST_DATA_LENGTH,
361 expectedResume: true,
362 expectedContentType: "text/plain",
363 expectedContent: TEST_DATA_TAINTED,
364 },
366 // Paused download with autoResume and a partial file,
367 // but missing entityID. This means that the download will
368 // start from beginning, and the entire original content of the
369 // source file should replace the different data that was stored
370 // in the partial file.
371 {
372 source: sourceUrl,
373 target: getDownloadTarget("inprogress2.txt"),
374 tempPath: yield getPartialFile("inprogress2.txt.part", true),
375 startTime: getStartTime(2),
376 state: DOWNLOAD_PAUSED,
377 referrer: httpUrl("referrer2"),
378 entityID: "",
379 maxBytes: MAXBYTES_IN_DB,
380 mimeType: "mimeType2",
381 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
382 preferredApplication: "prerredApplication2",
383 autoResume: 1,
385 expectedCurrentBytes: TEST_DATA_LENGTH,
386 expectedTotalBytes: TEST_DATA_LENGTH,
387 expectedResume: true,
388 expectedContentType: "text/plain",
389 expectedContent: TEST_DATA_SHORT
390 },
392 // Paused download with no autoResume and a partial file.
393 {
394 source: sourceUrl,
395 target: getDownloadTarget("inprogress3.txt"),
396 tempPath: yield getPartialFile("inprogress3.txt.part"),
397 startTime: getStartTime(3),
398 state: DOWNLOAD_PAUSED,
399 referrer: httpUrl("referrer3"),
400 entityID: "",
401 maxBytes: MAXBYTES_IN_DB,
402 mimeType: "mimeType3",
403 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
404 preferredApplication: "prerredApplication3",
405 autoResume: 0,
407 // Since this download has not been resumed, the actual data
408 // about its total size and content type is not known.
409 // Therefore, we're going by the information imported from the DB.
410 expectedCurrentBytes: TEST_DATA_PARTIAL_LENGTH,
411 expectedTotalBytes: MAXBYTES_IN_DB,
412 expectedResume: false,
413 expectedContentType: "mimeType3",
414 expectedContent: TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH),
415 },
417 // Paused download with autoResume and no partial file.
418 {
419 source: sourceUrl,
420 target: getDownloadTarget("inprogress4.txt"),
421 tempPath: "",
422 startTime: getStartTime(4),
423 state: DOWNLOAD_PAUSED,
424 referrer: httpUrl("referrer4"),
425 entityID: "",
426 maxBytes: MAXBYTES_IN_DB,
427 mimeType: "text/plain",
428 preferredAction: Ci.nsIMIMEInfo.useHelperApp,
429 preferredApplication: "prerredApplication4",
430 autoResume: 1,
432 expectedCurrentBytes: TEST_DATA_LENGTH,
433 expectedTotalBytes: TEST_DATA_LENGTH,
434 expectedResume: true,
435 expectedContentType: "text/plain",
436 expectedContent: TEST_DATA_SHORT
437 },
439 // Paused download with no autoResume and no partial file.
440 {
441 source: sourceUrl,
442 target: getDownloadTarget("inprogress5.txt"),
443 tempPath: "",
444 startTime: getStartTime(5),
445 state: DOWNLOAD_PAUSED,
446 referrer: httpUrl("referrer4"),
447 entityID: "",
448 maxBytes: MAXBYTES_IN_DB,
449 mimeType: "text/plain",
450 preferredAction: Ci.nsIMIMEInfo.useSystemDefault,
451 preferredApplication: "prerredApplication5",
452 autoResume: 0,
454 expectedCurrentBytes: 0,
455 expectedTotalBytes: MAXBYTES_IN_DB,
456 expectedResume: false,
457 expectedContentType: "text/plain",
458 },
460 // Queued download with no autoResume and no partial file.
461 // Even though autoResume=0, queued downloads always autoResume.
462 {
463 source: sourceUrl,
464 target: getDownloadTarget("inprogress6.txt"),
465 tempPath: "",
466 startTime: getStartTime(6),
467 state: DOWNLOAD_QUEUED,
468 referrer: httpUrl("referrer6"),
469 entityID: "",
470 maxBytes: MAXBYTES_IN_DB,
471 mimeType: "text/plain",
472 preferredAction: Ci.nsIMIMEInfo.useHelperApp,
473 preferredApplication: "prerredApplication6",
474 autoResume: 0,
476 expectedCurrentBytes: TEST_DATA_LENGTH,
477 expectedTotalBytes: TEST_DATA_LENGTH,
478 expectedResume: true,
479 expectedContentType: "text/plain",
480 expectedContent: TEST_DATA_SHORT
481 },
483 // Notstarted download with no autoResume and no partial file.
484 // Even though autoResume=0, notstarted downloads always autoResume.
485 {
486 source: sourceUrl,
487 target: getDownloadTarget("inprogress7.txt"),
488 tempPath: "",
489 startTime: getStartTime(7),
490 state: DOWNLOAD_NOTSTARTED,
491 referrer: httpUrl("referrer7"),
492 entityID: "",
493 maxBytes: MAXBYTES_IN_DB,
494 mimeType: "text/plain",
495 preferredAction: Ci.nsIMIMEInfo.useHelperApp,
496 preferredApplication: "prerredApplication7",
497 autoResume: 0,
499 expectedCurrentBytes: TEST_DATA_LENGTH,
500 expectedTotalBytes: TEST_DATA_LENGTH,
501 expectedResume: true,
502 expectedContentType: "text/plain",
503 expectedContent: TEST_DATA_SHORT
504 },
506 // Downloading download with no autoResume and a partial file.
507 // Even though autoResume=0, downloading downloads always autoResume.
508 {
509 source: sourceUrl,
510 target: getDownloadTarget("inprogress8.txt"),
511 tempPath: yield getPartialFile("inprogress8.txt.part", true),
512 startTime: getStartTime(8),
513 state: DOWNLOAD_DOWNLOADING,
514 referrer: httpUrl("referrer8"),
515 entityID: sourceEntityId,
516 maxBytes: MAXBYTES_IN_DB,
517 mimeType: "text/plain",
518 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
519 preferredApplication: "prerredApplication8",
520 autoResume: 0,
522 expectedCurrentBytes: TEST_DATA_LENGTH,
523 expectedTotalBytes: TEST_DATA_LENGTH,
524 expectedResume: true,
525 expectedContentType: "text/plain",
526 expectedContent: TEST_DATA_TAINTED
527 },
528 ];
529 });
531 /**
532 * Prepares the list of downloads to be added to the database that should
533 * *not* be imported by the import procedure.
534 */
535 add_task(function prepareNonImportableDownloads()
536 {
537 gDownloadsRowNonImportable = [
538 // Download with no source (should never happen in normal circumstances).
539 {
540 source: "",
541 target: "nonimportable1.txt",
542 tempPath: "",
543 startTime: getStartTime(1),
544 state: DOWNLOAD_PAUSED,
545 referrer: "",
546 entityID: "",
547 maxBytes: MAXBYTES_IN_DB,
548 mimeType: "mimeType1",
549 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
550 preferredApplication: "prerredApplication1",
551 autoResume: 1
552 },
554 // state = DOWNLOAD_FAILED
555 {
556 source: httpUrl("source.txt"),
557 target: "nonimportable2.txt",
558 tempPath: "",
559 startTime: getStartTime(2),
560 state: DOWNLOAD_FAILED,
561 referrer: "",
562 entityID: "",
563 maxBytes: MAXBYTES_IN_DB,
564 mimeType: "mimeType2",
565 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
566 preferredApplication: "prerredApplication2",
567 autoResume: 1
568 },
570 // state = DOWNLOAD_CANCELED
571 {
572 source: httpUrl("source.txt"),
573 target: "nonimportable3.txt",
574 tempPath: "",
575 startTime: getStartTime(3),
576 state: DOWNLOAD_CANCELED,
577 referrer: "",
578 entityID: "",
579 maxBytes: MAXBYTES_IN_DB,
580 mimeType: "mimeType3",
581 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
582 preferredApplication: "prerredApplication3",
583 autoResume: 1
584 },
586 // state = DOWNLOAD_BLOCKED_PARENTAL
587 {
588 source: httpUrl("source.txt"),
589 target: "nonimportable4.txt",
590 tempPath: "",
591 startTime: getStartTime(4),
592 state: DOWNLOAD_BLOCKED_PARENTAL,
593 referrer: "",
594 entityID: "",
595 maxBytes: MAXBYTES_IN_DB,
596 mimeType: "mimeType4",
597 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
598 preferredApplication: "prerredApplication4",
599 autoResume: 1
600 },
602 // state = DOWNLOAD_SCANNING
603 {
604 source: httpUrl("source.txt"),
605 target: "nonimportable5.txt",
606 tempPath: "",
607 startTime: getStartTime(5),
608 state: DOWNLOAD_SCANNING,
609 referrer: "",
610 entityID: "",
611 maxBytes: MAXBYTES_IN_DB,
612 mimeType: "mimeType5",
613 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
614 preferredApplication: "prerredApplication5",
615 autoResume: 1
616 },
618 // state = DOWNLOAD_DIRTY
619 {
620 source: httpUrl("source.txt"),
621 target: "nonimportable6.txt",
622 tempPath: "",
623 startTime: getStartTime(6),
624 state: DOWNLOAD_DIRTY,
625 referrer: "",
626 entityID: "",
627 maxBytes: MAXBYTES_IN_DB,
628 mimeType: "mimeType6",
629 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
630 preferredApplication: "prerredApplication6",
631 autoResume: 1
632 },
634 // state = DOWNLOAD_BLOCKED_POLICY
635 {
636 source: httpUrl("source.txt"),
637 target: "nonimportable7.txt",
638 tempPath: "",
639 startTime: getStartTime(7),
640 state: DOWNLOAD_BLOCKED_POLICY,
641 referrer: "",
642 entityID: "",
643 maxBytes: MAXBYTES_IN_DB,
644 mimeType: "mimeType7",
645 preferredAction: Ci.nsIMIMEInfo.saveToDisk,
646 preferredApplication: "prerredApplication7",
647 autoResume: 1
648 },
649 ];
650 });
652 ////////////////////////////////////////////////////////////////////////////////
653 //// Test
655 /**
656 * Creates a temporary Sqlite database with download data and perform an
657 * import of that data to the new Downloads API to verify that the import
658 * worked correctly.
659 */
660 add_task(function test_downloadImport()
661 {
662 let connection = null;
663 let downloadsSqlite = getTempFile("downloads.sqlite").path;
665 try {
666 // Set up the database.
667 connection = yield promiseEmptyDatabaseConnection({
668 aPath: downloadsSqlite,
669 aSchemaVersion: 9
670 });
672 // Insert both the importable and non-importable
673 // downloads together.
674 for (let downloadRow of gDownloadsRowToImport) {
675 yield promiseInsertRow(connection, downloadRow);
676 }
678 for (let downloadRow of gDownloadsRowNonImportable) {
679 yield promiseInsertRow(connection, downloadRow);
680 }
682 // Check that every item was inserted.
683 do_check_eq((yield promiseTableCount(connection)),
684 gDownloadsRowToImport.length +
685 gDownloadsRowNonImportable.length);
686 } finally {
687 // Close the connection so that DownloadImport can open it.
688 yield connection.close();
689 }
691 // Import items.
692 let list = yield promiseNewList(false);
693 yield new DownloadImport(list, downloadsSqlite).import();
694 let items = yield list.getAll();
696 do_check_eq(items.length, gDownloadsRowToImport.length);
698 for (let i = 0; i < gDownloadsRowToImport.length; i++) {
699 yield checkDownload(items[i], gDownloadsRowToImport[i]);
700 }
701 })