summaryrefslogtreecommitdiffstats
path: root/toolkit/components/downloads/test/unit/test_DownloadStore.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/downloads/test/unit/test_DownloadStore.js')
-rw-r--r--toolkit/components/downloads/test/unit/test_DownloadStore.js458
1 files changed, 458 insertions, 0 deletions
diff --git a/toolkit/components/downloads/test/unit/test_DownloadStore.js b/toolkit/components/downloads/test/unit/test_DownloadStore.js
new file mode 100644
index 0000000000..9353b63060
--- /dev/null
+++ b/toolkit/components/downloads/test/unit/test_DownloadStore.js
@@ -0,0 +1,458 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the DownloadStore object.
+ */
+
+"use strict";
+
+// Globals
+
+ChromeUtils.defineESModuleGetters(this, {
+ DownloadError: "resource://gre/modules/DownloadCore.sys.mjs",
+ DownloadStore: "resource://gre/modules/DownloadStore.sys.mjs",
+});
+
+/**
+ * Returns a new DownloadList object with an associated DownloadStore.
+ *
+ * @param aStorePath
+ * String pointing to the file to be associated with the DownloadStore,
+ * or undefined to use a non-existing temporary file. In this case, the
+ * temporary file is deleted when the test file execution finishes.
+ *
+ * @return {Promise}
+ * @resolves Array [ Newly created DownloadList , associated DownloadStore ].
+ * @rejects JavaScript exception.
+ */
+function promiseNewListAndStore(aStorePath) {
+ return promiseNewList().then(function (aList) {
+ let path = aStorePath || getTempFile(TEST_STORE_FILE_NAME).path;
+ let store = new DownloadStore(aList, path);
+ return [aList, store];
+ });
+}
+
+// Tests
+
+/**
+ * Saves downloads to a file, then reloads them.
+ */
+add_task(async function test_save_reload() {
+ let [listForSave, storeForSave] = await promiseNewListAndStore();
+ let [listForLoad, storeForLoad] = await promiseNewListAndStore(
+ storeForSave.path
+ );
+ let referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ NetUtil.newURI(TEST_REFERRER_URL)
+ );
+
+ listForSave.add(await promiseNewDownload(httpUrl("source.txt")));
+ listForSave.add(
+ await Downloads.createDownload({
+ source: { url: httpUrl("empty.txt"), referrerInfo },
+ target: getTempFile(TEST_TARGET_FILE_NAME),
+ })
+ );
+
+ // If we used a callback to adjust the channel, the download should
+ // not be serialized because we can't recreate it across sessions.
+ let adjustedDownload = await Downloads.createDownload({
+ source: {
+ url: httpUrl("empty.txt"),
+ adjustChannel: () => Promise.resolve(),
+ },
+ target: getTempFile(TEST_TARGET_FILE_NAME),
+ });
+ listForSave.add(adjustedDownload);
+
+ let legacyDownload = await promiseStartLegacyDownload();
+ await legacyDownload.cancel();
+ listForSave.add(legacyDownload);
+
+ await storeForSave.save();
+ await storeForLoad.load();
+
+ // Remove the adjusted download because it should not appear here.
+ listForSave.remove(adjustedDownload);
+
+ let itemsForSave = await listForSave.getAll();
+ let itemsForLoad = await listForLoad.getAll();
+
+ Assert.equal(itemsForSave.length, itemsForLoad.length);
+
+ // Downloads should be reloaded in the same order.
+ for (let i = 0; i < itemsForSave.length; i++) {
+ // The reloaded downloads are different objects.
+ Assert.notEqual(itemsForSave[i], itemsForLoad[i]);
+
+ // The reloaded downloads have the same properties.
+ Assert.equal(itemsForSave[i].source.url, itemsForLoad[i].source.url);
+ Assert.equal(
+ !!itemsForSave[i].source.referrerInfo,
+ !!itemsForLoad[i].source.referrerInfo
+ );
+ if (
+ itemsForSave[i].source.referrerInfo &&
+ itemsForLoad[i].source.referrerInfo
+ ) {
+ Assert.ok(
+ itemsForSave[i].source.referrerInfo.equals(
+ itemsForLoad[i].source.referrerInfo
+ )
+ );
+ }
+ Assert.equal(itemsForSave[i].target.path, itemsForLoad[i].target.path);
+ Assert.equal(
+ itemsForSave[i].saver.toSerializable(),
+ itemsForLoad[i].saver.toSerializable()
+ );
+ }
+});
+
+/**
+ * Checks that saving an empty list deletes any existing file.
+ */
+add_task(async function test_save_empty() {
+ let [, store] = await promiseNewListAndStore();
+
+ await IOUtils.write(store.path, new Uint8Array());
+
+ await store.save();
+
+ let successful;
+ try {
+ await IOUtils.read(store.path);
+ successful = true;
+ } catch (ex) {
+ successful = ex.name != "NotFoundError";
+ }
+
+ ok(!successful, "File should not exist");
+
+ // If the file does not exist, saving should not generate exceptions.
+ await store.save();
+});
+
+/**
+ * Checks that loading from a missing file results in an empty list.
+ */
+add_task(async function test_load_empty() {
+ let [list, store] = await promiseNewListAndStore();
+
+ let succeesful;
+ try {
+ await IOUtils.read(store.path);
+ succeesful = true;
+ } catch (ex) {
+ succeesful = ex.name != "NotFoundError";
+ }
+
+ ok(!succeesful, "File should not exist");
+
+ let items = await list.getAll();
+ Assert.equal(items.length, 0);
+});
+
+/**
+ * Loads downloads from a string in a predefined format. The purpose of this
+ * test is to verify that the JSON format used in previous versions can be
+ * loaded, assuming the file is reloaded on the same platform.
+ */
+add_task(async function test_load_string_predefined() {
+ let [list, store] = await promiseNewListAndStore();
+
+ // The platform-dependent file name should be generated dynamically.
+ let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
+ let filePathLiteral = JSON.stringify(targetPath);
+ let sourceUriLiteral = JSON.stringify(httpUrl("source.txt"));
+ let emptyUriLiteral = JSON.stringify(httpUrl("empty.txt"));
+ let referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ NetUtil.newURI(TEST_REFERRER_URL)
+ );
+ let referrerInfoLiteral = JSON.stringify(
+ E10SUtils.serializeReferrerInfo(referrerInfo)
+ );
+
+ let string =
+ '{"list":[{"source":' +
+ sourceUriLiteral +
+ "," +
+ '"target":' +
+ filePathLiteral +
+ "}," +
+ '{"source":{"url":' +
+ emptyUriLiteral +
+ "," +
+ '"referrerInfo":' +
+ referrerInfoLiteral +
+ "}," +
+ '"target":' +
+ filePathLiteral +
+ "}]}";
+
+ await IOUtils.write(store.path, new TextEncoder().encode(string), {
+ tmpPath: store.path + ".tmp",
+ });
+
+ await store.load();
+
+ let items = await list.getAll();
+
+ Assert.equal(items.length, 2);
+
+ Assert.equal(items[0].source.url, httpUrl("source.txt"));
+ Assert.equal(items[0].target.path, targetPath);
+
+ Assert.equal(items[1].source.url, httpUrl("empty.txt"));
+
+ checkEqualReferrerInfos(items[1].source.referrerInfo, referrerInfo);
+ Assert.equal(items[1].target.path, targetPath);
+});
+
+/**
+ * Loads downloads from a well-formed JSON string containing unrecognized data.
+ */
+add_task(async function test_load_string_unrecognized() {
+ let [list, store] = await promiseNewListAndStore();
+
+ // The platform-dependent file name should be generated dynamically.
+ let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
+ let filePathLiteral = JSON.stringify(targetPath);
+ let sourceUriLiteral = JSON.stringify(httpUrl("source.txt"));
+
+ let string =
+ '{"list":[{"source":null,' +
+ '"target":null},' +
+ '{"source":{"url":' +
+ sourceUriLiteral +
+ "}," +
+ '"target":{"path":' +
+ filePathLiteral +
+ "}," +
+ '"saver":{"type":"copy"}}]}';
+
+ await IOUtils.write(store.path, new TextEncoder().encode(string), {
+ tmpPath: store.path + ".tmp",
+ });
+
+ await store.load();
+
+ let items = await list.getAll();
+
+ Assert.equal(items.length, 1);
+
+ Assert.equal(items[0].source.url, httpUrl("source.txt"));
+ Assert.equal(items[0].target.path, targetPath);
+});
+
+/**
+ * Loads downloads from a malformed JSON string.
+ */
+add_task(async function test_load_string_malformed() {
+ let [list, store] = await promiseNewListAndStore();
+
+ let string =
+ '{"list":[{"source":null,"target":null},' +
+ '{"source":{"url":"about:blank"}}}';
+
+ await IOUtils.write(store.path, new TextEncoder().encode(string), {
+ tmpPath: store.path + ".tmp",
+ });
+
+ try {
+ await store.load();
+ do_throw("Exception expected when JSON data is malformed.");
+ } catch (ex) {
+ if (ex.name != "SyntaxError") {
+ throw ex;
+ }
+ info("The expected SyntaxError exception was thrown.");
+ }
+
+ let items = await list.getAll();
+
+ Assert.equal(items.length, 0);
+});
+
+/**
+ * Saves downloads with unknown properties to a file and then reloads
+ * them to ensure that these properties are preserved.
+ */
+add_task(async function test_save_reload_unknownProperties() {
+ let [listForSave, storeForSave] = await promiseNewListAndStore();
+ let [listForLoad, storeForLoad] = await promiseNewListAndStore(
+ storeForSave.path
+ );
+
+ let download1 = await promiseNewDownload(httpUrl("source.txt"));
+ // startTime should be ignored as it is a known property, and error
+ // is ignored by serialization
+ download1._unknownProperties = {
+ peanut: "butter",
+ orange: "marmalade",
+ startTime: 77,
+ error: { message: "Passed" },
+ };
+ listForSave.add(download1);
+
+ let download2 = await promiseStartLegacyDownload();
+ await download2.cancel();
+ download2._unknownProperties = { number: 5, object: { test: "string" } };
+ listForSave.add(download2);
+
+ let referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ NetUtil.newURI(TEST_REFERRER_URL)
+ );
+ let download3 = await Downloads.createDownload({
+ source: {
+ url: httpUrl("empty.txt"),
+ referrerInfo,
+ source1: "download3source1",
+ source2: "download3source2",
+ },
+ target: {
+ path: getTempFile(TEST_TARGET_FILE_NAME).path,
+ target1: "download3target1",
+ target2: "download3target2",
+ },
+ saver: {
+ type: "copy",
+ saver1: "download3saver1",
+ saver2: "download3saver2",
+ },
+ });
+ listForSave.add(download3);
+
+ await storeForSave.save();
+ await storeForLoad.load();
+
+ let itemsForSave = await listForSave.getAll();
+ let itemsForLoad = await listForLoad.getAll();
+
+ Assert.equal(itemsForSave.length, itemsForLoad.length);
+
+ Assert.equal(Object.keys(itemsForLoad[0]._unknownProperties).length, 2);
+ Assert.equal(itemsForLoad[0]._unknownProperties.peanut, "butter");
+ Assert.equal(itemsForLoad[0]._unknownProperties.orange, "marmalade");
+ Assert.equal(false, "startTime" in itemsForLoad[0]._unknownProperties);
+ Assert.equal(false, "error" in itemsForLoad[0]._unknownProperties);
+
+ Assert.equal(Object.keys(itemsForLoad[1]._unknownProperties).length, 2);
+ Assert.equal(itemsForLoad[1]._unknownProperties.number, 5);
+ Assert.equal(itemsForLoad[1]._unknownProperties.object.test, "string");
+
+ Assert.equal(
+ Object.keys(itemsForLoad[2].source._unknownProperties).length,
+ 2
+ );
+ Assert.equal(
+ itemsForLoad[2].source._unknownProperties.source1,
+ "download3source1"
+ );
+ Assert.equal(
+ itemsForLoad[2].source._unknownProperties.source2,
+ "download3source2"
+ );
+
+ Assert.equal(
+ Object.keys(itemsForLoad[2].target._unknownProperties).length,
+ 2
+ );
+ Assert.equal(
+ itemsForLoad[2].target._unknownProperties.target1,
+ "download3target1"
+ );
+ Assert.equal(
+ itemsForLoad[2].target._unknownProperties.target2,
+ "download3target2"
+ );
+
+ Assert.equal(Object.keys(itemsForLoad[2].saver._unknownProperties).length, 2);
+ Assert.equal(
+ itemsForLoad[2].saver._unknownProperties.saver1,
+ "download3saver1"
+ );
+ Assert.equal(
+ itemsForLoad[2].saver._unknownProperties.saver2,
+ "download3saver2"
+ );
+});
+
+/**
+ * Saves insecure downloads to a file, then reloads the file and checks if they
+ * are still there.
+ */
+add_task(async function test_insecure_download_deletion() {
+ let [listForSave, storeForSave] = await promiseNewListAndStore();
+ let [listForLoad, storeForLoad] = await promiseNewListAndStore(
+ storeForSave.path
+ );
+ let referrerInfo = new ReferrerInfo(
+ Ci.nsIReferrerInfo.EMPTY,
+ true,
+ NetUtil.newURI(TEST_REFERRER_URL)
+ );
+
+ const createTestDownload = async startTime => {
+ // Create a valid test download and start it so it creates a file
+ let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
+ let download = await Downloads.createDownload({
+ source: { url: httpUrl("empty.txt"), referrerInfo },
+ target: targetFile.path,
+ startTime: new Date().toISOString(),
+ contentType: "application/zip",
+ });
+ await download.start();
+
+ // Add an "Insecure Download" error and overwrite the start time for
+ // serialization
+ download.hasBlockedData = true;
+ download.error = DownloadError.fromSerializable({
+ becauseBlockedByReputationCheck: true,
+ reputationCheckVerdict: "Insecure",
+ });
+ download.startTime = startTime;
+
+ let targetPath = download.target.path;
+
+ // Add download to store, save, load and retrieve deserialized download list
+ listForSave.add(download);
+ await storeForSave.save();
+ await storeForLoad.load();
+ let loadedDownloadList = await listForLoad.getAll();
+
+ return [loadedDownloadList, targetPath];
+ };
+
+ // Insecure downloads that are older than 5 minutes should get removed from
+ // the download-store and the file should get deleted. (360000 = 6 minutes)
+ let [loadedDownloadList1, targetPath1] = await createTestDownload(
+ new Date(Date.now() - 360000)
+ );
+
+ Assert.equal(loadedDownloadList1.length, 0, "Download should be removed");
+ Assert.ok(
+ !(await IOUtils.exists(targetPath1)),
+ "The file should have been deleted."
+ );
+
+ // Insecure downloads that are newer than 5 minutes should stay in the
+ // download store and the file should remain.
+ let [loadedDownloadList2, targetPath2] = await createTestDownload(new Date());
+
+ Assert.equal(loadedDownloadList2.length, 1, "Download should be kept");
+ Assert.ok(
+ await IOUtils.exists(targetPath2),
+ "The file should have not been deleted."
+ );
+});