forked from mirrors/gecko-dev
213 lines
8 KiB
JavaScript
213 lines
8 KiB
JavaScript
/* globals sinon */
|
|
"use strict";
|
|
|
|
ChromeUtils.import("resource://gre/modules/CanonicalJSON.jsm", this);
|
|
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
|
|
ChromeUtils.import("resource://normandy/lib/NormandyApi.jsm", this);
|
|
|
|
load("utils.js"); /* globals withMockApiServer, MockResponse, withScriptServer, withServer, makeMockApiServer */
|
|
|
|
add_task(withMockApiServer(async function test_get(serverUrl) {
|
|
// Test that NormandyApi can fetch from the test server.
|
|
const response = await NormandyApi.get(`${serverUrl}/api/v1/`);
|
|
const data = await response.json();
|
|
equal(data["recipe-list"], "/api/v1/recipe/", "Expected data in response");
|
|
}));
|
|
|
|
add_task(withMockApiServer(async function test_getApiUrl(serverUrl) {
|
|
const apiBase = `${serverUrl}/api/v1`;
|
|
// Test that NormandyApi can use the self-describing API's index
|
|
const recipeListUrl = await NormandyApi.getApiUrl("action-list");
|
|
equal(recipeListUrl, `${apiBase}/action/`, "Can retrieve action-list URL from API");
|
|
}));
|
|
|
|
add_task(withMockApiServer(async function test_getApiUrlSlashes(serverUrl, preferences) {
|
|
const fakeResponse = new MockResponse(JSON.stringify({"test-endpoint": `${serverUrl}/test/`}));
|
|
const mockGet = sinon.stub(NormandyApi, "get", async () => fakeResponse);
|
|
|
|
// without slash
|
|
{
|
|
NormandyApi.clearIndexCache();
|
|
preferences.set("app.normandy.api_url", `${serverUrl}/api/v1`);
|
|
const endpoint = await NormandyApi.getApiUrl("test-endpoint");
|
|
equal(endpoint, `${serverUrl}/test/`);
|
|
ok(mockGet.calledWithExactly(`${serverUrl}/api/v1/`), "trailing slash was added");
|
|
mockGet.reset();
|
|
}
|
|
|
|
// with slash
|
|
{
|
|
NormandyApi.clearIndexCache();
|
|
preferences.set("app.normandy.api_url", `${serverUrl}/api/v1/`);
|
|
const endpoint = await NormandyApi.getApiUrl("test-endpoint");
|
|
equal(endpoint, `${serverUrl}/test/`);
|
|
ok(mockGet.calledWithExactly(`${serverUrl}/api/v1/`), "existing trailing slash was preserved");
|
|
mockGet.reset();
|
|
}
|
|
|
|
NormandyApi.clearIndexCache();
|
|
mockGet.restore();
|
|
}));
|
|
|
|
add_task(withMockApiServer(async function test_fetchRecipes() {
|
|
const recipes = await NormandyApi.fetchRecipes();
|
|
equal(recipes.length, 1);
|
|
equal(recipes[0].name, "system-addon-test");
|
|
}));
|
|
|
|
add_task(async function test_fetchSignedObjects_canonical_mismatch() {
|
|
const getApiUrl = sinon.stub(NormandyApi, "getApiUrl");
|
|
|
|
// The object is non-canonical (it has whitespace, properties are out of order)
|
|
const response = new MockResponse(`[
|
|
{
|
|
"object": {"b": 1, "a": 2},
|
|
"signature": {"signature": "", "x5u": ""}
|
|
}
|
|
]`);
|
|
const get = sinon.stub(NormandyApi, "get").resolves(response);
|
|
|
|
try {
|
|
await NormandyApi.fetchSignedObjects("object");
|
|
ok(false, "fetchSignedObjects did not throw for canonical JSON mismatch");
|
|
} catch (err) {
|
|
ok(err instanceof NormandyApi.InvalidSignatureError, "Error is an InvalidSignatureError");
|
|
ok(/Canonical/.test(err), "Error is due to canonical JSON mismatch");
|
|
}
|
|
|
|
getApiUrl.restore();
|
|
get.restore();
|
|
});
|
|
|
|
// Test validation errors due to validation throwing an exception (e.g. when
|
|
// parameters passed to validation are malformed).
|
|
add_task(withMockApiServer(async function test_fetchSignedObjects_validation_error() {
|
|
const getApiUrl = sinon.stub(NormandyApi, "getApiUrl").resolves("http://localhost/object/");
|
|
|
|
// Mock two URLs: object and the x5u
|
|
const get = sinon.stub(NormandyApi, "get").callsFake(async url => {
|
|
if (url.endsWith("object/")) {
|
|
return new MockResponse(CanonicalJSON.stringify([
|
|
{
|
|
object: {a: 1, b: 2},
|
|
signature: {signature: "invalidsignature", x5u: "http://localhost/x5u/"},
|
|
},
|
|
]));
|
|
} else if (url.endsWith("x5u/")) {
|
|
return new MockResponse("certchain");
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
// Validation should fail due to a malformed x5u and signature.
|
|
try {
|
|
await NormandyApi.fetchSignedObjects("object");
|
|
ok(false, "fetchSignedObjects did not throw for a validation error");
|
|
} catch (err) {
|
|
ok(err instanceof NormandyApi.InvalidSignatureError, "Error is an InvalidSignatureError");
|
|
ok(/signature/.test(err), "Error is due to a validation error");
|
|
}
|
|
|
|
getApiUrl.restore();
|
|
get.restore();
|
|
}));
|
|
|
|
// Test validation errors due to validation returning false (e.g. when parameters
|
|
// passed to validation are correctly formed, but not valid for the data).
|
|
const invalidSignatureServer = makeMockApiServer(do_get_file("invalid_recipe_signature_api"));
|
|
add_task(withServer(invalidSignatureServer, async function test_fetchSignedObjects_invalid_signature() {
|
|
try {
|
|
await NormandyApi.fetchSignedObjects("recipe");
|
|
ok(false, "fetchSignedObjects did not throw for an invalid signature");
|
|
} catch (err) {
|
|
ok(err instanceof NormandyApi.InvalidSignatureError, "Error is an InvalidSignatureError");
|
|
ok(/signature/.test(err), "Error is due to an invalid signature");
|
|
}
|
|
}));
|
|
|
|
add_task(withMockApiServer(async function test_classifyClient() {
|
|
const classification = await NormandyApi.classifyClient();
|
|
Assert.deepEqual(classification, {
|
|
country: "US",
|
|
request_time: new Date("2017-02-22T17:43:24.657841Z"),
|
|
});
|
|
}));
|
|
|
|
add_task(withMockApiServer(async function test_fetchActions() {
|
|
const actions = await NormandyApi.fetchActions();
|
|
equal(actions.length, 4);
|
|
const actionNames = actions.map(a => a.name);
|
|
ok(actionNames.includes("console-log"));
|
|
ok(actionNames.includes("opt-out-study"));
|
|
ok(actionNames.includes("show-heartbeat"));
|
|
ok(actionNames.includes("preference-experiment"));
|
|
}));
|
|
|
|
add_task(withMockApiServer(async function test_fetchExtensionDetails() {
|
|
const extensionDetails = await NormandyApi.fetchExtensionDetails(1);
|
|
deepEqual(extensionDetails, {
|
|
"id": 1,
|
|
"name": "Normandy Fixture",
|
|
"xpi": "http://example.com/browser/toolkit/components/normandy/test/browser/fixtures/normandy.xpi",
|
|
"extension_id": "normandydriver@example.com",
|
|
"version": "1.0",
|
|
"hash": "ade1c14196ec4fe0aa0a6ba40ac433d7c8d1ec985581a8a94d43dc58991b5171",
|
|
"hash_algorithm": "sha256",
|
|
});
|
|
}));
|
|
|
|
add_task(withScriptServer("query_server.sjs", async function test_getTestServer(serverUrl) {
|
|
// Test that NormandyApi can fetch from the test server.
|
|
const response = await NormandyApi.get(serverUrl);
|
|
const data = await response.json();
|
|
Assert.deepEqual(data, {queryString: {}, body: {}}, "NormandyApi returned incorrect server data.");
|
|
}));
|
|
|
|
add_task(withScriptServer("query_server.sjs", async function test_getQueryString(serverUrl) {
|
|
// Test that NormandyApi can send query string parameters to the test server.
|
|
const response = await NormandyApi.get(serverUrl, {foo: "bar", baz: "biff"});
|
|
const data = await response.json();
|
|
Assert.deepEqual(
|
|
data, {queryString: {foo: "bar", baz: "biff"}, body: {}},
|
|
"NormandyApi sent an incorrect query string."
|
|
);
|
|
}));
|
|
|
|
add_task(withScriptServer("query_server.sjs", async function test_postData(serverUrl) {
|
|
// Test that NormandyApi can POST JSON-formatted data to the test server.
|
|
const response = await NormandyApi.post(serverUrl, {foo: "bar", baz: "biff"});
|
|
const data = await response.json();
|
|
Assert.deepEqual(
|
|
data, {queryString: {}, body: {foo: "bar", baz: "biff"}},
|
|
"NormandyApi sent an incorrect query string."
|
|
);
|
|
}));
|
|
|
|
add_task(withMockApiServer(async function test_fetchImplementation_itWorksWithRealData() {
|
|
const [action] = await NormandyApi.fetchActions();
|
|
const implementation = await NormandyApi.fetchImplementation(action);
|
|
|
|
const decoder = new TextDecoder();
|
|
const relativePath = `mock_api${action.implementation_url}`;
|
|
const file = do_get_file(relativePath);
|
|
const expected = decoder.decode(await OS.File.read(file.path));
|
|
|
|
equal(implementation, expected);
|
|
}));
|
|
|
|
add_task(withScriptServer(
|
|
"echo_server.sjs",
|
|
async function test_fetchImplementationFail(serverUrl) {
|
|
const action = {
|
|
implementation_url: `${serverUrl}?status=500&body=servererror`,
|
|
};
|
|
|
|
try {
|
|
await NormandyApi.fetchImplementation(action);
|
|
ok(false, "fetchImplementation throws for non-200 response status codes");
|
|
} catch (err) {
|
|
// pass
|
|
}
|
|
},
|
|
));
|