Files
palemoon27/browser/components/readinglist/test/xpcshell/test_ServerClient.js
T

286 lines
8.8 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource:///modules/readinglist/ServerClient.jsm");
Cu.import("resource://gre/modules/Log.jsm");
let appender = new Log.DumpAppender();
for (let logName of ["FirefoxAccounts", "readinglist.serverclient"]) {
Log.repository.getLogger(logName).addAppender(appender);
}
// Some test servers we use.
let Server = function(handlers) {
this._server = null;
this._handlers = handlers;
}
Server.prototype = {
start() {
this._server = new HttpServer();
for (let [path, handler] in Iterator(this._handlers)) {
// httpd.js seems to swallow exceptions
let thisHandler = handler;
let wrapper = (request, response) => {
try {
thisHandler(request, response);
} catch (ex) {
print("**** Handler for", path, "failed:", ex, ex.stack);
throw ex;
}
}
this._server.registerPathHandler(path, wrapper);
}
this._server.start(-1);
},
stop() {
return new Promise(resolve => {
this._server.stop(resolve);
this._server = null;
});
},
get host() {
return "http://localhost:" + this._server.identity.primaryPort;
},
};
// An OAuth server that hands out tokens.
function OAuthTokenServer() {
let server;
let handlers = {
"/v1/authorization": (request, response) => {
response.setStatusLine("1.1", 200, "OK");
let token = "token" + server.numTokenFetches;
print("Test OAuth server handing out token", token);
server.numTokenFetches += 1;
server.activeTokens.add(token);
response.write(JSON.stringify({access_token: token}));
},
"/v1/destroy": (request, response) => {
// Getting the body seems harder than it should be!
let sis = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
sis.init(request.bodyInputStream);
let body = JSON.parse(sis.read(sis.available()));
sis.close();
let token = body.token;
ok(server.activeTokens.delete(token));
print("after destroy have", server.activeTokens.size, "tokens left.")
response.setStatusLine("1.1", 200, "OK");
response.write('{}');
},
}
server = new Server(handlers);
server.numTokenFetches = 0;
server.activeTokens = new Set();
return server;
}
function promiseObserver(topic) {
return new Promise(resolve => {
function observe(subject, topic, data) {
Services.obs.removeObserver(observe, topic);
resolve(data);
}
Services.obs.addObserver(observe, topic, false);
});
}
// The tests.
function run_test() {
run_next_test();
}
// Arrange for the first token we hand out to be rejected - the client should
// notice the 401 and silently get a new token and retry the request.
add_task(function testAuthRetry() {
let handlers = {
"/v1/batch": (request, response) => {
// We know the first token we will get is "token0", so we simulate that
// "expiring" by only accepting "token1". Then we just echo the response
// back.
let authHeader;
try {
authHeader = request.getHeader("Authorization");
} catch (ex) {}
if (authHeader != "Bearer token1") {
response.setStatusLine("1.1", 401, "Unauthorized");
response.write("wrong token");
return;
}
response.setStatusLine("1.1", 200, "OK");
response.write(JSON.stringify({ok: true}));
}
};
let rlserver = new Server(handlers);
rlserver.start();
let authServer = OAuthTokenServer();
authServer.start();
try {
Services.prefs.setCharPref("readinglist.server", rlserver.host + "/v1");
Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", authServer.host + "/v1");
let fxa = yield createMockFxA();
let sc = new ServerClient(fxa);
let response = yield sc.request({
path: "/batch",
method: "post",
body: {foo: "bar"},
});
equal(response.status, 200, "got the 200 we expected");
equal(authServer.numTokenFetches, 2, "took 2 tokens to get the 200")
deepEqual(response.body, {ok: true});
} finally {
yield authServer.stop();
yield rlserver.stop();
}
});
// Check that specified headers are seen by the server, and that server headers
// in the response are seen by the client.
add_task(function testHeaders() {
let handlers = {
"/v1/batch": (request, response) => {
ok(request.hasHeader("x-foo"), "got our foo header");
equal(request.getHeader("x-foo"), "bar", "foo header has the correct value");
response.setHeader("Server-Sent-Header", "hello");
response.setStatusLine("1.1", 200, "OK");
response.write("{}");
}
};
let rlserver = new Server(handlers);
rlserver.start();
try {
Services.prefs.setCharPref("readinglist.server", rlserver.host + "/v1");
let fxa = yield createMockFxA();
let sc = new ServerClient(fxa);
sc._getToken = () => Promise.resolve();
let response = yield sc.request({
path: "/batch",
method: "post",
headers: {"X-Foo": "bar"},
body: {foo: "bar"}});
equal(response.status, 200, "got the 200 we expected");
equal(response.headers["server-sent-header"], "hello", "got the server header");
} finally {
yield rlserver.stop();
}
});
// Check that a "backoff" header causes the correct notification.
add_task(function testBackoffHeader() {
let handlers = {
"/v1/batch": (request, response) => {
response.setHeader("Backoff", "123");
response.setStatusLine("1.1", 200, "OK");
response.write("{}");
}
};
let rlserver = new Server(handlers);
rlserver.start();
let observerPromise = promiseObserver("readinglist:backoff-requested");
try {
Services.prefs.setCharPref("readinglist.server", rlserver.host + "/v1");
let fxa = yield createMockFxA();
let sc = new ServerClient(fxa);
sc._getToken = () => Promise.resolve();
let response = yield sc.request({
path: "/batch",
method: "post",
headers: {"X-Foo": "bar"},
body: {foo: "bar"}});
equal(response.status, 200, "got the 200 we expected");
let data = yield observerPromise;
equal(data, "123", "got the expected header value.")
} finally {
yield rlserver.stop();
}
});
// Check that a "backoff" header causes the correct notification.
add_task(function testRetryAfterHeader() {
let handlers = {
"/v1/batch": (request, response) => {
response.setHeader("Retry-After", "456");
response.setStatusLine("1.1", 500, "Not OK");
response.write("{}");
}
};
let rlserver = new Server(handlers);
rlserver.start();
let observerPromise = promiseObserver("readinglist:backoff-requested");
try {
Services.prefs.setCharPref("readinglist.server", rlserver.host + "/v1");
let fxa = yield createMockFxA();
let sc = new ServerClient(fxa);
sc._getToken = () => Promise.resolve();
let response = yield sc.request({
path: "/batch",
method: "post",
headers: {"X-Foo": "bar"},
body: {foo: "bar"}});
equal(response.status, 500, "got the 500 we expected");
let data = yield observerPromise;
equal(data, "456", "got the expected header value.")
} finally {
yield rlserver.stop();
}
});
// Check that unicode ends up as utf-8 in requests, and vice-versa in responses.
// (Note the ServerClient assumes all strings in and out are UCS, and thus have
// already been encoded/decoded (ie, it never expects to receive stuff already
// utf-8 encoded, and never returns utf-8 encoded responses.)
add_task(function testUTF8() {
let handlers = {
"/v1/hello": (request, response) => {
// Get the body as bytes.
let sis = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
sis.init(request.bodyInputStream);
let body = sis.read(sis.available());
sis.close();
// The client sent "{"copyright: "\xa9"} where \xa9 is the copyright symbol.
// It should have been encoded as utf-8 which is \xc2\xa9
equal(body, '{"copyright":"\xc2\xa9"}', "server saw utf-8 encoded data");
// and just write it back unchanged.
response.setStatusLine("1.1", 200, "OK");
response.write(body);
}
};
let rlserver = new Server(handlers);
rlserver.start();
try {
Services.prefs.setCharPref("readinglist.server", rlserver.host + "/v1");
let fxa = yield createMockFxA();
let sc = new ServerClient(fxa);
sc._getToken = () => Promise.resolve();
let body = {copyright: "\xa9"}; // see above - \xa9 is the copyright symbol
let response = yield sc.request({
path: "/hello",
method: "post",
body: body
});
equal(response.status, 200, "got the 200 we expected");
deepEqual(response.body, body);
} finally {
yield rlserver.stop();
}
});