summaryrefslogtreecommitdiffstats
path: root/toolkit/components/normandy/test/unit/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/normandy/test/unit/utils.js')
-rw-r--r--toolkit/components/normandy/test/unit/utils.js181
1 files changed, 181 insertions, 0 deletions
diff --git a/toolkit/components/normandy/test/unit/utils.js b/toolkit/components/normandy/test/unit/utils.js
new file mode 100644
index 0000000000..3af9d1f86f
--- /dev/null
+++ b/toolkit/components/normandy/test/unit/utils.js
@@ -0,0 +1,181 @@
+"use strict";
+/* eslint-disable no-unused-vars */
+
+// Loaded into the same scope as head_xpc.js
+/* import-globals-from head_xpc.js */
+
+const { Preferences } = ChromeUtils.import(
+ "resource://gre/modules/Preferences.jsm"
+);
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
+ChromeUtils.import("resource://normandy/lib/NormandyApi.jsm", this);
+
+const CryptoHash = Components.Constructor(
+ "@mozilla.org/security/hash;1",
+ "nsICryptoHash",
+ "initWithString"
+);
+const FileInputStream = Components.Constructor(
+ "@mozilla.org/network/file-input-stream;1",
+ "nsIFileInputStream",
+ "init"
+);
+
+const preferenceBranches = {
+ user: Preferences,
+ default: new Preferences({ defaultBranch: true }),
+};
+
+// duplicated from test/browser/head.js until we move everything over to mochitests.
+function withMockPreferences(testFunction) {
+ return async function inner(...args) {
+ const prefManager = new MockPreferences();
+ try {
+ await testFunction(...args, prefManager);
+ } finally {
+ prefManager.cleanup();
+ }
+ };
+}
+
+class MockPreferences {
+ constructor() {
+ this.oldValues = { user: {}, default: {} };
+ }
+
+ set(name, value, branch = "user") {
+ this.preserve(name, branch);
+ preferenceBranches[branch].set(name, value);
+ }
+
+ preserve(name, branch) {
+ if (!(name in this.oldValues[branch])) {
+ this.oldValues[branch][name] = preferenceBranches[branch].get(
+ name,
+ undefined
+ );
+ }
+ }
+
+ cleanup() {
+ for (const [branchName, values] of Object.entries(this.oldValues)) {
+ const preferenceBranch = preferenceBranches[branchName];
+ for (const [name, value] of Object.entries(values)) {
+ if (value !== undefined) {
+ preferenceBranch.set(name, value);
+ } else {
+ preferenceBranch.reset(name);
+ }
+ }
+ }
+ }
+}
+
+class MockResponse {
+ constructor(content) {
+ this.content = content;
+ }
+
+ async text() {
+ return this.content;
+ }
+
+ async json() {
+ return JSON.parse(this.content);
+ }
+}
+
+function withServer(server, task) {
+ return withMockPreferences(async function inner(preferences) {
+ const serverUrl = `http://localhost:${server.identity.primaryPort}`;
+ preferences.set("app.normandy.api_url", `${serverUrl}/api/v1`);
+ preferences.set(
+ "security.content.signature.root_hash",
+ // Hash of the key that signs the normandy dev certificates
+ "4C:35:B1:C3:E3:12:D9:55:E7:78:ED:D0:A7:E7:8A:38:83:04:EF:01:BF:FA:03:29:B2:46:9F:3C:C5:EC:36:04"
+ );
+ NormandyApi.clearIndexCache();
+
+ try {
+ await task(serverUrl, preferences, server);
+ } finally {
+ await new Promise(resolve => server.stop(resolve));
+ }
+ });
+}
+
+function makeScriptServer(scriptPath) {
+ const server = new HttpServer();
+ server.registerContentType("sjs", "sjs");
+ server.registerFile("/", do_get_file(scriptPath));
+ server.start(-1);
+ return server;
+}
+
+function withScriptServer(scriptPath, task) {
+ return withServer(makeScriptServer(scriptPath), task);
+}
+
+function makeMockApiServer(directory) {
+ const server = new HttpServer();
+ server.registerDirectory("/", directory);
+
+ server.setIndexHandler(async function(request, response) {
+ response.processAsync();
+ const dir = request.getProperty("directory");
+ const index = dir.clone();
+ index.append("index.json");
+
+ if (!index.exists()) {
+ response.setStatusLine("1.1", 404, "Not Found");
+ response.write(`Cannot find path ${index.path}`);
+ response.finish();
+ return;
+ }
+
+ try {
+ const contents = await OS.File.read(index.path, { encoding: "utf-8" });
+ response.write(contents);
+ } catch (e) {
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write(e.toString());
+ } finally {
+ response.finish();
+ }
+ });
+
+ server.start(-1);
+ return server;
+}
+
+function withMockApiServer(task) {
+ return withServer(makeMockApiServer(do_get_file("mock_api")), task);
+}
+
+const CryptoUtils = {
+ _getHashStringForCrypto(aCrypto) {
+ // return the two-digit hexadecimal code for a byte
+ let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);
+
+ // convert the binary hash data to a hex string.
+ let binary = aCrypto.finish(false);
+ let hash = Array.from(binary, c => toHexString(c.charCodeAt(0)));
+ return hash.join("").toLowerCase();
+ },
+
+ /**
+ * Get the computed hash for a given file
+ * @param {nsIFile} file The file to be hashed
+ * @param {string} [algorithm] The hashing algorithm to use
+ */
+ getFileHash(file, algorithm = "sha256") {
+ const crypto = CryptoHash(algorithm);
+ const fis = new FileInputStream(file, -1, -1, false);
+ crypto.updateFromStream(fis, file.fileSize);
+ const hash = this._getHashStringForCrypto(crypto);
+ fis.close();
+ return hash;
+ },
+};