summaryrefslogtreecommitdiffstats
path: root/toolkit/components/downloads/test/unit/test_DownloadHistory.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/downloads/test/unit/test_DownloadHistory.js')
-rw-r--r--toolkit/components/downloads/test/unit/test_DownloadHistory.js273
1 files changed, 273 insertions, 0 deletions
diff --git a/toolkit/components/downloads/test/unit/test_DownloadHistory.js b/toolkit/components/downloads/test/unit/test_DownloadHistory.js
new file mode 100644
index 0000000000..69eb1c4728
--- /dev/null
+++ b/toolkit/components/downloads/test/unit/test_DownloadHistory.js
@@ -0,0 +1,273 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the DownloadHistory module.
+ */
+
+"use strict";
+
+const { DownloadHistory } = ChromeUtils.importESModule(
+ "resource://gre/modules/DownloadHistory.sys.mjs"
+);
+
+let baseDate = new Date("2000-01-01");
+
+/**
+ * Non-fatal assertion used to test whether the downloads in the list already
+ * match the expected state.
+ */
+function areEqual(a, b) {
+ if (a === b) {
+ Assert.equal(a, b);
+ return true;
+ }
+ info(a + " !== " + b);
+ return false;
+}
+
+/**
+ * This allows waiting for an expected list at various points during the test.
+ */
+class TestView {
+ constructor(expected) {
+ this.expected = [...expected];
+ this.downloads = [];
+ this.resolveWhenExpected = () => {};
+ }
+ onDownloadAdded(download, options = {}) {
+ if (options.insertBefore) {
+ let index = this.downloads.indexOf(options.insertBefore);
+ this.downloads.splice(index, 0, download);
+ } else {
+ this.downloads.push(download);
+ }
+ this.checkForExpectedDownloads();
+ }
+ onDownloadChanged(download) {
+ this.checkForExpectedDownloads();
+ }
+ onDownloadRemoved(download) {
+ let index = this.downloads.indexOf(download);
+ this.downloads.splice(index, 1);
+ this.checkForExpectedDownloads();
+ }
+ checkForExpectedDownloads() {
+ // Wait for all the expected downloads to be added or removed before doing
+ // the detailed tests. This is done to avoid creating irrelevant output.
+ if (this.downloads.length != this.expected.length) {
+ return;
+ }
+ for (let i = 0; i < this.downloads.length; i++) {
+ if (
+ this.downloads[i].source.url != this.expected[i].source.url ||
+ this.downloads[i].target.path != this.expected[i].target.path
+ ) {
+ return;
+ }
+ }
+ // Check and report the actual state of the downloads. Even if the items
+ // are in the expected order, the metadata for history downloads might not
+ // have been updated to the final state yet.
+ for (let i = 0; i < this.downloads.length; i++) {
+ let download = this.downloads[i];
+ let testDownload = this.expected[i];
+ info(
+ "Checking download source " +
+ download.source.url +
+ " with target " +
+ download.target.path
+ );
+ if (
+ !areEqual(download.succeeded, !!testDownload.succeeded) ||
+ !areEqual(download.canceled, !!testDownload.canceled) ||
+ !areEqual(download.hasPartialData, !!testDownload.hasPartialData) ||
+ !areEqual(!!download.error, !!testDownload.error)
+ ) {
+ return;
+ }
+ // If the above properties match, the error details should be correct.
+ if (download.error) {
+ if (testDownload.error.becauseSourceFailed) {
+ Assert.equal(download.error.message, "History download failed.");
+ }
+ Assert.equal(
+ download.error.becauseBlockedByParentalControls,
+ testDownload.error.becauseBlockedByParentalControls
+ );
+ Assert.equal(
+ download.error.becauseBlockedByReputationCheck,
+ testDownload.error.becauseBlockedByReputationCheck
+ );
+ }
+ }
+ this.resolveWhenExpected();
+ }
+ async waitForExpected() {
+ let promise = new Promise(resolve => (this.resolveWhenExpected = resolve));
+ this.checkForExpectedDownloads();
+ await promise;
+ }
+}
+
+/**
+ * Tests that various operations on session and history downloads are reflected
+ * by the DownloadHistoryList object, and that the order of results is correct.
+ */
+add_task(async function test_DownloadHistory() {
+ // Clean up at the beginning and at the end of the test.
+ async function cleanup() {
+ await PlacesUtils.history.clear();
+ }
+ registerCleanupFunction(cleanup);
+ await cleanup();
+
+ let testDownloads = [
+ // History downloads should appear in order at the beginning of the list.
+ { offset: 10, canceled: true },
+ { offset: 20, succeeded: true },
+ { offset: 30, error: { becauseSourceFailed: true } },
+ { offset: 40, error: { becauseBlockedByParentalControls: true } },
+ { offset: 50, error: { becauseBlockedByReputationCheck: true } },
+ // Session downloads should show up after all the history download, in the
+ // same order as they were added.
+ { offset: 45, canceled: true, inSession: true },
+ { offset: 35, canceled: true, hasPartialData: true, inSession: true },
+ { offset: 55, succeeded: true, inSession: true },
+ ];
+ const NEXT_OFFSET = 60;
+
+ let publicList = await promiseNewList();
+ let allList = await Downloads.getList(Downloads.ALL);
+
+ async function addTestDownload(properties) {
+ properties.source = {
+ url: httpUrl("source" + properties.offset),
+ isPrivate: properties.isPrivate,
+ };
+ let targetFile = getTempFile(TEST_TARGET_FILE_NAME + properties.offset);
+ properties.target = { path: targetFile.path };
+ properties.startTime = new Date(baseDate.getTime() + properties.offset);
+
+ let download = await Downloads.createDownload(properties);
+ if (properties.inSession) {
+ await allList.add(download);
+ }
+
+ if (properties.isPrivate) {
+ return;
+ }
+
+ // Add the download to history using the XPCOM service, then use the
+ // DownloadHistory module to save the associated metadata.
+ let promiseFileAnnotation = waitForAnnotation(
+ properties.source.url,
+ "downloads/destinationFileURI"
+ );
+ let promiseMetaAnnotation = waitForAnnotation(
+ properties.source.url,
+ "downloads/metaData"
+ );
+ let promiseVisit = promiseWaitForVisit(properties.source.url);
+ await DownloadHistory.addDownloadToHistory(download);
+ await promiseVisit;
+ await DownloadHistory.updateMetaData(download);
+ await Promise.all([promiseFileAnnotation, promiseMetaAnnotation]);
+ }
+
+ // Add all the test downloads to history.
+ for (let properties of testDownloads) {
+ await addTestDownload(properties);
+ }
+
+ // Initialize DownloadHistoryList only after having added the history and
+ // session downloads, and check that they are loaded in the correct order.
+ let historyList = await DownloadHistory.getList();
+ let view = new TestView(testDownloads);
+ await historyList.addView(view);
+ await view.waitForExpected();
+
+ // Remove a download from history and verify that the change is reflected.
+ let downloadToRemove = view.expected[1];
+ view.expected.splice(1, 1);
+ await PlacesUtils.history.remove(downloadToRemove.source.url);
+ await view.waitForExpected();
+
+ // Add a download to history and verify it's placed before session downloads,
+ // even if the start date is more recent.
+ let downloadToAdd = { offset: NEXT_OFFSET, canceled: true };
+ view.expected.splice(
+ view.expected.findIndex(d => d.inSession),
+ 0,
+ downloadToAdd
+ );
+ await addTestDownload(downloadToAdd);
+ await view.waitForExpected();
+
+ // Add a session download and verify it's placed after all session downloads,
+ // even if the start date is less recent.
+ let sessionDownloadToAdd = { offset: 0, inSession: true, succeeded: true };
+ view.expected.push(sessionDownloadToAdd);
+ await addTestDownload(sessionDownloadToAdd);
+ await view.waitForExpected();
+
+ // Add a session download for the same URI without a history entry, and verify
+ // it's visible and placed after all session downloads.
+ view.expected.push(sessionDownloadToAdd);
+ await publicList.add(await Downloads.createDownload(sessionDownloadToAdd));
+ await view.waitForExpected();
+
+ // Create a new DownloadHistoryList that also shows private downloads. Since
+ // we only have public downloads, the two lists should contain the same items.
+ let allHistoryList = await DownloadHistory.getList({ type: Downloads.ALL });
+ let allView = new TestView(view.expected);
+ await allHistoryList.addView(allView);
+ await allView.waitForExpected();
+
+ // Add a new private download and verify it appears only on the complete list.
+ let privateDownloadToAdd = {
+ offset: NEXT_OFFSET + 10,
+ inSession: true,
+ succeeded: true,
+ isPrivate: true,
+ };
+ allView.expected.push(privateDownloadToAdd);
+ await addTestDownload(privateDownloadToAdd);
+ await view.waitForExpected();
+ await allView.waitForExpected();
+
+ // Now test the maxHistoryResults parameter.
+ let allHistoryList2 = await DownloadHistory.getList({
+ type: Downloads.ALL,
+ maxHistoryResults: 3,
+ });
+ // Prepare the set of downloads to contain fewer history downloads by removing
+ // the oldest ones.
+ let allView2 = new TestView(allView.expected.slice(3));
+ await allHistoryList2.addView(allView2);
+ await allView2.waitForExpected();
+
+ // Create a dummy list and view like the previous limited one to just add and
+ // remove its view to make sure it doesn't break other lists' updates.
+ let dummyList = await DownloadHistory.getList({
+ type: Downloads.ALL,
+ maxHistoryResults: 3,
+ });
+ let dummyView = new TestView([]);
+ await dummyList.addView(dummyView);
+ await dummyList.removeView(dummyView);
+
+ // Clear history and check that session downloads with partial data remain.
+ // Private downloads are also not cleared when clearing history.
+ view.expected = view.expected.filter(d => d.hasPartialData);
+ allView.expected = allView.expected.filter(
+ d => d.hasPartialData || d.isPrivate
+ );
+ await PlacesUtils.history.clear();
+ await view.waitForExpected();
+ await allView.waitForExpected();
+
+ // Check that the dummy view above did not prevent the limited from updating.
+ allView2.expected = allView.expected;
+ await allView2.waitForExpected();
+});