diff options
Diffstat (limited to 'toolkit/components/normandy/test/unit/utils.js')
-rw-r--r-- | toolkit/components/normandy/test/unit/utils.js | 181 |
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; + }, +}; |