Backed out changeset 7e1942ace2b2 (bug 1501214) for failing test_attachments_downloader.js on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Wed, 08 May 2019 21:12:06 +0300
changeset 534984 89ab5386f54bc1f39b5ea368cc3e6f0d2952e410
parent 534983 c61887431ba86c0c8846ee7773aafe53d0d1f782
child 534985 82ae6ce8f3a8e18ab661b8d85fc96649a28694b5
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1501214
milestone68.0a1
backs out7e1942ace2b27c0905642f3c2ea83c848ba947b1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 7e1942ace2b2 (bug 1501214) for failing test_attachments_downloader.js on a CLOSED TREE
services/common/docs/RemoteSettings.rst
services/settings/Attachments.jsm
services/settings/RemoteSettingsClient.jsm
services/settings/RemoteSettingsWorker.js
services/settings/RemoteSettingsWorker.jsm
services/settings/moz.build
services/settings/test/unit/test_attachments_downloader.js
services/settings/test/unit/test_attachments_downloader/65650a0f-7c22-4c10-9744-2d67e301f5f4.pem
services/settings/test/unit/xpcshell.ini
--- a/services/common/docs/RemoteSettings.rst
+++ b/services/common/docs/RemoteSettings.rst
@@ -52,20 +52,20 @@ This behaviour can be disabled using the
 Options
 -------
 
 * ``filters``, ``order``: The list can optionally be filtered or ordered:
 
     .. code-block:: js
 
         const subset = await RemoteSettings("a-key").get({
-          filters: {
-            property: "value"
-          },
-          order: "-weight"
+        filters: {
+            "property": "value"
+        },
+        order: "-weight"
         });
 
 * ``syncIfEmpty``: implicit synchronization if local data is empty (default: ``true``).
   Set it to ``false`` if your use-case can tolerate an empty list until the first synchronization happens.
 
     .. code-block:: js
 
         await RemoteSettings("a-key").get({ syncIfEmpty: false });
@@ -93,66 +93,29 @@ The ``sync`` event allows to be notified
 
 .. note::
 
     Currently, the synchronization of remote settings is triggered via push notifications, and also by its own timer every 24H (see the preference ``services.settings.poll_interval`` ).
 
 File attachments
 ----------------
 
-When an entry has a file attached to it, it has an ``attachment`` attribute, which contains the file related information (url, hash, size, mimetype, etc.).
-
-Remote files are not downloaded automatically. In order to keep attachments in sync, the provided helper can be leveraged like this:
+When an entry has a file attached to it, it has an ``attachment`` attribute, which contains the file related information (url, hash, size, mimetype, etc.). Remote files are not downloaded automatically.
 
 .. code-block:: js
 
-    const client = RemoteSettings("a-key");
-
-    client.on("sync", async ({ data: { created, updated, deleted } }) => {
-      const toDelete = deleted.filter(d => d.attachment);
-      const toDownload = created
-        .concat(updated.map(u => u.new))
-        .filter(d => d.attachment);
-
-      // Remove local files of deleted records
-      await Promise.all(toDelete.map(entry => client.attachments.delete(entry)));
-      // Download attachments
-      const fileURLs = await Promise.all(
-        toDownload.map(entry => client.attachments.download(entry, { retries: 2 }))
-      );
+    const data = await RemoteSettings("a-key").get();
 
-      // Open downloaded files...
-      const fileContents = await Promise.all(
-        fileURLs.map(async url => {
-          const r = await fetch(url);
-          return r.blob();
-        })
-      );
-    });
-
-The provided helper will:
-- fetch the remote binary content
-- check the file size
-- check the content SHA256 hash
-- do nothing if the file is already present and sound locally.
-
-.. important::
-
-    The following aspects are not taken care of (yet! help welcome):
-
-    - check available disk space
-    - preserve bandwidth
-    - resume downloads of large files
-
-.. notes::
-
-    The ``download()`` method does not return a file path but instead a ``file://`` URL which points to the locally-downloaded file.
-    This will allow us to package attachments as part of a Firefox release (see `Bug 1542177 <https://bugzilla.mozilla.org/show_bug.cgi?id=1542177>`_)
-    and return them to calling code as ``resource://`` from within a package archive.
-
+    data.filter(d => d.attachment)
+        .forEach(async ({ attachment: { url, filename, size } }) => {
+          if (size < OS.freeDiskSpace) {
+            // Planned feature, see Bug 1501214
+            await downloadLocally(url, filename);
+          }
+        });
 
 .. _services/initial-data:
 
 Initial data
 ------------
 
 It is possible to package a dump of the server records that will be loaded into the local database when no synchronization has happened yet.
 
@@ -365,8 +328,9 @@ For example, they leverage advanced cust
 
 Then, in order to access a specific client instance, the bucket must be specified:
 
 .. code-block:: js
 
     const collection = await RemoteSettings("addons", { bucketName: "blocklists" }).openCollection();
 
 And in the storage inspector, the IndexedDB internal store will be prefixed with ``blocklists`` instead of ``main`` (eg. ``blocklists/addons``).
+
deleted file mode 100644
--- a/services/settings/Attachments.jsm
+++ /dev/null
@@ -1,130 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-var EXPORTED_SYMBOLS = [
-  "Downloader",
-];
-
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "RemoteSettingsWorker",
-                               "resource://services-settings/RemoteSettingsWorker.jsm");
-ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
-
-
-class DownloadError extends Error {
-  constructor(url, resp) {
-    super(`Could not download ${url}`);
-    this.name = "DownloadError";
-    this.resp = resp;
-  }
-}
-
-class BadContentError extends Error {
-  constructor(path) {
-    super(`${path} content does not match server hash`);
-    this.name = "BadContentError";
-  }
-}
-
-class Downloader {
-  static get DownloadError() { return DownloadError; }
-  static get BadContentError() { return BadContentError; }
-
-  constructor(...folders) {
-    this.folders = ["settings", ...folders];
-    this._cdnURL = null;
-  }
-
-  /**
-   * Download the record attachment into the local profile directory
-   * and return a file:// URL that points to the local path.
-   *
-   * No-op if the file was already downloaded and not corrupted.
-   *
-   * @param {Object} record A Remote Settings entry with attachment.
-   * @param {Object} options Some download options.
-   * @param {Number} options.retries Number of times download should be retried (default: `3`)
-   * @throws {Downloader.DownloadError} if the file could not be fetched.
-   * @throws {Downloader.BadContentError} if the downloaded file integrity is not valid.
-   * @returns {String} the absolute file path to the downloaded attachment.
-   */
-  async download(record, options = {}) {
-    const { retries = 3 } = options;
-    const { attachment: { location, filename, hash, size } } = record;
-    const localFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, ...this.folders, filename);
-    const localFileUrl = `file://${localFilePath}`;
-    const remoteFileUrl = (await this._baseAttachmentsURL()) + location;
-
-    await this._makeDirs();
-
-    let retried = 0;
-    while (true) {
-      if (await RemoteSettingsWorker.checkFileHash(localFileUrl, size, hash)) {
-        return localFileUrl;
-      }
-      // File does not exist or is corrupted.
-      if (retried > retries) {
-        throw new Downloader.BadContentError(localFilePath);
-      }
-      try {
-        await this._fetchAttachment(remoteFileUrl, localFilePath);
-      } catch (e) {
-        if (retried >= retries) {
-          throw e;
-        }
-      }
-      retried++;
-    }
-  }
-
-  /**
-   * Delete the record attachment downloaded locally.
-   * No-op if the related file does not exist.
-   *
-   * @param record A Remote Settings entry with attachment.
-   */
-  async delete(record) {
-    const { attachment: { filename } } = record;
-    const path = OS.Path.join(OS.Constants.Path.localProfileDir, ...this.folders, filename);
-    await OS.File.remove(path, { ignoreAbsent: true });
-    await this._rmDirs();
-  }
-
-  async _baseAttachmentsURL() {
-    if (!this._cdnURL) {
-      const server = Services.prefs.getCharPref("services.settings.server");
-      const serverInfo = await (await fetch(`${server}/`)).json();
-      // Server capabilities expose attachments configuration.
-      const { capabilities: { attachments: { base_url } } } = serverInfo;
-      // Make sure the URL always has a trailing slash.
-      this._cdnURL = base_url + (base_url.endsWith("/") ? "" : "/");
-    }
-    return this._cdnURL;
-  }
-
-  async _fetchAttachment(url, destination) {
-    const headers = new Headers();
-    headers.set("Accept-Encoding", "gzip");
-    const resp = await fetch(url, { headers });
-    if (!resp.ok) {
-      throw new Downloader.DownloadError(url, resp);
-    }
-    const buffer = await resp.arrayBuffer();
-    await OS.File.writeAtomic(destination, buffer, { tmpPath: `${destination}.tmp` });
-  }
-
-  async _makeDirs() {
-    const dirPath = OS.Path.join(OS.Constants.Path.localProfileDir, ...this.folders);
-    await OS.File.makeDir(dirPath, { from: OS.Constants.Path.localProfileDir });
-  }
-
-  async _rmDirs() {
-    for (let i = this.folders.length; i > 0; i--) {
-      const dirPath = OS.Path.join(OS.Constants.Path.localProfileDir, ...this.folders.slice(0, i));
-      await OS.File.removeEmptyDir(dirPath, { ignoreAbsent: true });
-    }
-  }
-}
--- a/services/settings/RemoteSettingsClient.jsm
+++ b/services/settings/RemoteSettingsClient.jsm
@@ -18,18 +18,16 @@ ChromeUtils.defineModuleGetter(this, "Ki
 ChromeUtils.defineModuleGetter(this, "UptakeTelemetry",
                                "resource://services-common/uptake-telemetry.js");
 ChromeUtils.defineModuleGetter(this, "ClientEnvironmentBase",
                                "resource://gre/modules/components-utils/ClientEnvironment.jsm");
 ChromeUtils.defineModuleGetter(this, "RemoteSettingsWorker",
                                "resource://services-settings/RemoteSettingsWorker.jsm");
 ChromeUtils.defineModuleGetter(this, "Utils",
                                "resource://services-settings/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "Downloader",
-                               "resource://services-settings/Attachments.jsm");
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 
 // IndexedDB name.
 const DB_NAME = "remote-settings";
 
 const TELEMETRY_COMPONENT = "remotesettings";
 
@@ -198,18 +196,16 @@ class RemoteSettingsClient extends Event
     this.bucketNamePref = bucketNamePref;
     XPCOMUtils.defineLazyPreferenceGetter(this, "bucketName", this.bucketNamePref);
 
     XPCOMUtils.defineLazyGetter(this, "_kinto", () => new Kinto({
       bucket: this.bucketName,
       adapter: Kinto.adapters.IDB,
       adapterOptions: { dbName: DB_NAME, migrateOldData: false },
     }));
-
-    XPCOMUtils.defineLazyGetter(this, "attachments", () => new Downloader(this.bucketName, collectionName));
   }
 
   get identifier() {
     return `${this.bucketName}/${this.collectionName}`;
   }
 
   get lastCheckTimePref() {
     return this._lastCheckTimePref || `services.settings.${this.bucketName}.${this.collectionName}.last_check`;
--- a/services/settings/RemoteSettingsWorker.js
+++ b/services/settings/RemoteSettingsWorker.js
@@ -63,46 +63,16 @@ const Agent = {
    * @param {String} bucket
    * @param {String} collection
    */
   async importJSONDump(bucket, collection) {
     const { data: records } = await loadJSONDump(bucket, collection);
     await importDumpIDB(bucket, collection, records);
     return records.length;
   },
-
-  /**
-   * Check that the specified file matches the expected size and SHA-256 hash.
-   * @param {String} fileUrl file URL to read from
-   * @param {Number} size expected file size
-   * @param {String} size expected file SHA-256 as hex string
-   * @returns {boolean}
-   */
-  async checkFileHash(fileUrl, size, hash) {
-    let resp;
-    try {
-      resp = await fetch(fileUrl);
-    } catch (e) {
-      // File does not exist.
-      return false;
-    }
-    // Has expected size? (saves computing hash)
-    const fileSize = parseInt(resp.headers.get("Content-Length"), 10);
-    if (fileSize !== size) {
-      return false;
-    }
-    // Has expected content?
-    const buffer = await resp.arrayBuffer();
-    const bytes = new Uint8Array(buffer);
-    const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
-    const hashBytes = new Uint8Array(hashBuffer);
-    const toHex = b => b.toString(16).padStart(2, "0");
-    const hashStr = Array.from(hashBytes, toHex).join("");
-    return hashStr == hash;
-  },
 };
 
 /**
  * Wrap worker invocations in order to return the `callbackId` along
  * the result. This will allow to transform the worker invocations
  * into promises in `RemoteSettingsWorker.jsm`.
  */
 self.onmessage = (event) => {
--- a/services/settings/RemoteSettingsWorker.jsm
+++ b/services/settings/RemoteSettingsWorker.jsm
@@ -42,15 +42,11 @@ class Worker {
 
   async canonicalStringify(localRecords, remoteRecords, timestamp) {
     return this._execute("canonicalStringify", [localRecords, remoteRecords, timestamp]);
   }
 
   async importJSONDump(bucket, collection) {
     return this._execute("importJSONDump", [bucket, collection]);
   }
-
-  async checkFileHash(filepath, size, hash) {
-    return this._execute("checkFileHash", [filepath, size, hash]);
-  }
 }
 
 var RemoteSettingsWorker = new Worker("resource://services-settings/RemoteSettingsWorker.js");
--- a/services/settings/moz.build
+++ b/services/settings/moz.build
@@ -9,17 +9,16 @@ DIRS += [
     'dumps',
 ]
 
 EXTRA_COMPONENTS += [
     'servicesSettings.manifest',
 ]
 
 EXTRA_JS_MODULES['services-settings'] += [
-    'Attachments.jsm',
     'remote-settings.js',
     'RemoteSettingsClient.jsm',
     'RemoteSettingsComponents.jsm',
     'RemoteSettingsWorker.js',
     'RemoteSettingsWorker.jsm',
     'Utils.jsm',
 ]
 
deleted file mode 100644
--- a/services/settings/test/unit/test_attachments_downloader.js
+++ /dev/null
@@ -1,175 +0,0 @@
-/* import-globals-from ../../../common/tests/unit/head_helpers.js */
-
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.js");
-const { Downloader } = ChromeUtils.import("resource://services-settings/Attachments.jsm");
-const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
-
-const RECORD = {
-  attachment: {
-    hash: "f41ed47d0f43325c9f089d03415c972ce1d3f1ecab6e4d6260665baf3db3ccee",
-    size: 1597,
-    filename: "test_file.pem",
-    location: "main-workspace/some-collection/65650a0f-7c22-4c10-9744-2d67e301f5f4.pem",
-    mimetype: "application/x-pem-file",
-  },
-};
-
-let downloader;
-let server;
-
-function pathFromURL(url) {
-  const uri = Services.io.newURI(url);
-  const file = uri.QueryInterface(Ci.nsIFileURL).file;
-  return file.path;
-}
-
-function run_test() {
-  server = new HttpServer();
-  server.start(-1);
-  registerCleanupFunction(() => server.stop(() => {}));
-
-  server.registerDirectory("/cdn/main-workspace/some-collection/", do_get_file("test_attachments_downloader"));
-
-  server.registerPathHandler("/v1/", (request, response) => {
-    response.write(JSON.stringify({
-      capabilities: {
-        attachments: {
-          base_url: `http://localhost:${server.identity.primaryPort}/cdn/`,
-        },
-      },
-    }));
-    response.setHeader("Content-Type", "application/json; charset=UTF-8");
-    response.setStatusLine(null, 200, "OK");
-  });
-
-  Services.prefs.setCharPref("services.settings.server",
-    `http://localhost:${server.identity.primaryPort}/v1`);
-
-  run_next_test();
-}
-
-async function clear_state() {
-  downloader = new Downloader("main", "some-collection");
-  await downloader.delete(RECORD);
-}
-
-add_task(clear_state);
-
-add_task(async function test_download_writes_file_in_profile() {
-  const fileURL = await downloader.download(RECORD);
-  const localFilePath = pathFromURL(fileURL);
-
-  Assert.equal(fileURL, "file://" + OS.Path.join(OS.Constants.Path.localProfileDir,
-                                                 "settings/main/some-collection/test_file.pem"));
-  Assert.ok(await OS.File.exists(localFilePath));
-  const stat = await OS.File.stat(localFilePath);
-  Assert.equal(stat.size, 1597);
-});
-add_task(clear_state);
-
-add_task(async function test_file_is_redownloaded_if_size_does_not_match() {
-  const fileURL = await downloader.download(RECORD);
-  const localFilePath = pathFromURL(fileURL);
-  await OS.File.writeAtomic(localFilePath, "bad-content", { encoding: "utf-8" });
-  let stat = await OS.File.stat(localFilePath);
-  Assert.notEqual(stat.size, 1597);
-
-  await downloader.download(RECORD);
-
-  stat = await OS.File.stat(localFilePath);
-  Assert.equal(stat.size, 1597);
-});
-add_task(clear_state);
-
-add_task(async function test_file_is_redownloaded_if_corrupted() {
-  const fileURL = await downloader.download(RECORD);
-  const localFilePath = pathFromURL(fileURL);
-  const byteArray = await OS.File.read(localFilePath);
-  byteArray[0] = 42;
-  await OS.File.writeAtomic(localFilePath, byteArray);
-  let content = await OS.File.read(localFilePath, { encoding: "utf-8" });
-  Assert.notEqual(content.slice(0, 5), "-----");
-
-  await downloader.download(RECORD);
-
-  content = await OS.File.read(localFilePath, { encoding: "utf-8" });
-  Assert.equal(content.slice(0, 5), "-----");
-});
-add_task(clear_state);
-
-add_task(async function test_download_is_retried_3_times_if_download_fails() {
-  const record = {
-    attachment: {
-      ...RECORD.attachment,
-      location: "404-error.pem",
-    },
-  };
-
-  let called = 0;
-  const _fetchAttachment = downloader._fetchAttachment;
-  downloader._fetchAttachment = (url, destination) => {
-    called++;
-    return _fetchAttachment(url, destination);
-  };
-
-  let error;
-  try {
-    await downloader.download(record);
-  } catch (e) {
-    error = e;
-  }
-
-  Assert.equal(called, 4); // 1 + 3 retries
-  Assert.ok(error instanceof Downloader.DownloadError);
-});
-add_task(clear_state);
-
-add_task(async function test_download_is_retried_3_times_if_content_fails() {
-  const record = {
-    attachment: {
-      ...RECORD.attachment,
-      hash: "always-wrong",
-    },
-  };
-  let called = 0;
-  downloader._fetchAttachment = () => called++;
-
-  let error;
-  try {
-    await downloader.download(record);
-  } catch (e) {
-    error = e;
-  }
-
-  Assert.equal(called, 4); // 1 + 3 retries
-  Assert.ok(error instanceof Downloader.BadContentError);
-});
-add_task(clear_state);
-
-add_task(async function test_delete_removes_local_file() {
-  const fileURL = await downloader.download(RECORD);
-  const localFilePath = pathFromURL(fileURL);
-  Assert.ok(await OS.File.exists(localFilePath));
-
-  downloader.delete(RECORD);
-
-  Assert.ok(!await OS.File.exists(localFilePath));
-  Assert.ok(!await OS.File.exists(downloader.baseFolder));
-});
-add_task(clear_state);
-
-add_task(async function test_downloader_is_accessible_via_client() {
-  const client = RemoteSettings("some-collection");
-
-  const fileURL = await client.attachments.download(RECORD);
-
-  Assert.equal(fileURL, "file://" + OS.Path.join(
-    OS.Constants.Path.localProfileDir,
-    "settings",
-    client.bucketName,
-    client.collectionName,
-    RECORD.attachment.filename
-  ));
-});
-add_task(clear_state);
deleted file mode 100644
--- a/services/settings/test/unit/test_attachments_downloader/65650a0f-7c22-4c10-9744-2d67e301f5f4.pem
+++ /dev/null
@@ -1,26 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEbjCCA1agAwIBAgIQBg3WwdBnkBtUdfz/wp4xNzANBgkqhkiG9w0BAQsFADBa
-MQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJl
-clRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTE1
-MTAxNDEyMDAwMFoXDTIwMTAxNDEyMDAwMFowbzELMAkGA1UEBhMCVVMxCzAJBgNV
-BAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBDbG91ZEZs
-YXJlLCBJbmMuMSAwHgYDVQQDExdDbG91ZEZsYXJlIEluYyBSU0EgQ0EtMTCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJGiNOIE4s0M4wdhDeV9aMfAYY9l
-yG9cfGQqt7a5UgrRA81bi4istCyhzfzRWUW+NAmf6X2HEnA3xLI1M+pH/xEbk9pw
-jc8/1CPy9jUjBwb89zt5PWh2I1KxZVg/Bnx2yYdVcKTUMKt0GLDXfZXN+RYZHJQo
-lDlzjH5xV0IpDMv/FsMEZWcfx1JorBf08bRnRVkl9RY00y2ujVr+492ze+zYQ9s7
-HcidpR+7ret3jzLSvojsaA5+fOaCG0ctVJcLfnkQ5lWR95ByBdO1NapfqZ1+kmCL
-3baVSeUpYQriBwznxfLuGs8POo4QdviYVtSPBWjOEfb+o1c6Mbo8p4noFzUCAwEA
-AaOCARkwggEVMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDQG
-CCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
-Y29tMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9P
-bW5pcm9vdDIwMjUuY3JsMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIB
-FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSRBYrfTCLG
-bYuUTBZFfu5vAvu3wDAfBgNVHSMEGDAWgBTlnVkwgkdYzKz6CFQ2hns6tQRN8DAN
-BgkqhkiG9w0BAQsFAAOCAQEAVJle3ar9NSnTrLAhgfkcpClIY6/kabDIEa8cOnu1
-SOXf4vbtZakSmmIbFbmYDUGIU5XwwVdF/FKNzNBRf9G4EL/S0NXytBKj4A34UGQA
-InaV+DgVLzCifN9cAHi8EFEAfbglUvPvLPFXF0bwffElYm7QBSiHYSZmfOKLCyiv
-3zlQsf7ozNBAxfbmnRMRSUBcIhRwnaFoFgDs7yU6R1Yk4pO7eMgWpdPGhymDTIvv
-RnauKStzKsAli9i5hQ4nTDITUpMAmeJoXodgwRkC3Civw32UR2rxObIyxPpbfODb
-sZKNGO9K5Sjj6turB1zwbd2wI8MhtUCY9tGmSYhe7G6Bkw==
------END CERTIFICATE-----
\ No newline at end of file
--- a/services/settings/test/unit/xpcshell.ini
+++ b/services/settings/test/unit/xpcshell.ini
@@ -1,11 +1,9 @@
 [DEFAULT]
 head = ../../../common/tests/unit/head_global.js ../../../common/tests/unit/head_helpers.js
 firefox-appdir = browser
 tags = remote-settings
 
-[test_attachments_downloader.js]
-support-files = test_attachments_downloader/**
 [test_remote_settings.js]
 [test_remote_settings_poll.js]
 [test_remote_settings_worker.js]
 [test_remote_settings_jexl_filters.js]