зеркало из https://github.com/mozilla/gecko-dev.git
364 строки
13 KiB
JavaScript
364 строки
13 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
_("Test that node reassignment happens correctly using the FxA identity mgr.");
|
|
// The node-reassignment logic is quite different for FxA than for the legacy
|
|
// provider. In particular, there's no special request necessary for
|
|
// reassignment - it comes from the token server - so we need to ensure the
|
|
// Fxa cluster manager grabs a new token.
|
|
|
|
const {RESTRequest} = ChromeUtils.import("resource://services-common/rest.js");
|
|
const {Service} = ChromeUtils.import("resource://services-sync/service.js");
|
|
const {Status} = ChromeUtils.import("resource://services-sync/status.js");
|
|
const {BrowserIDManager} = ChromeUtils.import("resource://services-sync/browserid_identity.js");
|
|
const {PromiseUtils} = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
|
|
|
|
add_task(async function setup() {
|
|
// Disables all built-in engines. Important for avoiding errors thrown by the
|
|
// add-ons engine.
|
|
await Service.engineManager.clear();
|
|
|
|
// Setup the FxA identity manager and cluster manager.
|
|
Status.__authManager = Service.identity = new BrowserIDManager();
|
|
});
|
|
|
|
|
|
// API-compatible with SyncServer handler. Bind `handler` to something to use
|
|
// as a ServerCollection handler.
|
|
function handleReassign(handler, req, resp) {
|
|
resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
|
|
resp.setHeader("Content-Type", "application/json");
|
|
let reassignBody = JSON.stringify({error: "401inator in place"});
|
|
resp.bodyOutputStream.write(reassignBody, reassignBody.length);
|
|
}
|
|
|
|
var numTokenRequests = 0;
|
|
|
|
function prepareServer(cbAfterTokenFetch) {
|
|
syncTestLogging();
|
|
let config = makeIdentityConfig({username: "johndoe"});
|
|
// A server callback to ensure we don't accidentally hit the wrong endpoint
|
|
// after a node reassignment.
|
|
let callback = {
|
|
__proto__: SyncServerCallback,
|
|
onRequest(req, resp) {
|
|
let full = `${req.scheme}://${req.host}:${req.port}${req.path}`;
|
|
let expected = config.fxaccount.token.endpoint;
|
|
Assert.ok(full.startsWith(expected),
|
|
`request made to ${full}, expected ${expected}`);
|
|
},
|
|
};
|
|
let server = new SyncServer(callback);
|
|
server.registerUser("johndoe");
|
|
server.start();
|
|
|
|
// Set the token endpoint for the initial token request that's done implicitly
|
|
// via configureIdentity.
|
|
config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe/";
|
|
// And future token fetches will do magic around numReassigns.
|
|
let numReassigns = 0;
|
|
return configureIdentity(config).then(() => {
|
|
Service.identity._tokenServerClient = {
|
|
getTokenFromBrowserIDAssertion(uri, assertion) {
|
|
return new Promise(res => {
|
|
// Build a new URL with trailing zeros for the SYNC_VERSION part - this
|
|
// will still be seen as equivalent by the test server, but different
|
|
// by sync itself.
|
|
numReassigns += 1;
|
|
let trailingZeros = new Array(numReassigns + 1).join("0");
|
|
let token = config.fxaccount.token;
|
|
token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe";
|
|
token.uid = config.username;
|
|
_(`test server saw token fetch - endpoint now ${token.endpoint}`);
|
|
numTokenRequests += 1;
|
|
res(token);
|
|
if (cbAfterTokenFetch) {
|
|
cbAfterTokenFetch();
|
|
}
|
|
});
|
|
},
|
|
};
|
|
return server;
|
|
});
|
|
}
|
|
|
|
function getReassigned() {
|
|
try {
|
|
return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
|
|
} catch (ex) {
|
|
if (ex.result != Cr.NS_ERROR_UNEXPECTED) {
|
|
do_throw("Got exception retrieving lastSyncReassigned: " +
|
|
Log.exceptionStr(ex));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Make a test request to `url`, then watch the result of two syncs
|
|
* to ensure that a node request was made.
|
|
* Runs `between` between the two. This can be used to undo deliberate failure
|
|
* setup, detach observers, etc.
|
|
*/
|
|
async function syncAndExpectNodeReassignment(server, firstNotification, between,
|
|
secondNotification, url) {
|
|
_("Starting syncAndExpectNodeReassignment\n");
|
|
let deferred = PromiseUtils.defer();
|
|
async function onwards() {
|
|
let numTokenRequestsBefore;
|
|
function onFirstSync() {
|
|
_("First sync completed.");
|
|
Svc.Obs.remove(firstNotification, onFirstSync);
|
|
Svc.Obs.add(secondNotification, onSecondSync);
|
|
|
|
Assert.equal(Service.clusterURL, "");
|
|
|
|
// Track whether we fetched a new token.
|
|
numTokenRequestsBefore = numTokenRequests;
|
|
|
|
// Allow for tests to clean up error conditions.
|
|
between();
|
|
}
|
|
function onSecondSync() {
|
|
_("Second sync completed.");
|
|
Svc.Obs.remove(secondNotification, onSecondSync);
|
|
Service.scheduler.clearSyncTriggers();
|
|
|
|
// Make absolutely sure that any event listeners are done with their work
|
|
// before we proceed.
|
|
waitForZeroTimer(function() {
|
|
_("Second sync nextTick.");
|
|
Assert.equal(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token");
|
|
Service.startOver().then(() => {
|
|
server.stop(deferred.resolve);
|
|
});
|
|
});
|
|
}
|
|
|
|
Svc.Obs.add(firstNotification, onFirstSync);
|
|
await Service.sync();
|
|
}
|
|
|
|
// Make sure that we really do get a 401 (but we can only do that if we are
|
|
// already logged in, as the login process is what sets up the URLs)
|
|
if (Service.isLoggedIn) {
|
|
_("Making request to " + url + " which should 401");
|
|
let request = new RESTRequest(url);
|
|
await request.get();
|
|
Assert.equal(request.response.status, 401);
|
|
CommonUtils.nextTick(onwards);
|
|
} else {
|
|
_("Skipping preliminary validation check for a 401 as we aren't logged in");
|
|
CommonUtils.nextTick(onwards);
|
|
}
|
|
await deferred.promise;
|
|
}
|
|
|
|
// Check that when we sync we don't request a new token by default - our
|
|
// test setup has configured the client with a valid token, and that token
|
|
// should be used to form the cluster URL.
|
|
add_task(async function test_single_token_fetch() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a normal sync only fetches 1 token");
|
|
|
|
let numTokenFetches = 0;
|
|
|
|
function afterTokenFetch() {
|
|
numTokenFetches++;
|
|
}
|
|
|
|
// Set the cluster URL to an "old" version - this is to ensure we don't
|
|
// use that old cached version for the first sync but prefer the value
|
|
// we got from the token (and as above, we are also checking we don't grab
|
|
// a new token). If the test actually attempts to connect to this URL
|
|
// it will crash.
|
|
Service.clusterURL = "http://example.com/";
|
|
|
|
let server = await prepareServer(afterTokenFetch);
|
|
|
|
Assert.ok(!Service.isLoggedIn, "not already logged in");
|
|
await Service.sync();
|
|
Assert.equal(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
|
|
Assert.equal(numTokenFetches, 0, "didn't fetch a new token");
|
|
// A bit hacky, but given we know how prepareServer works we can deduce
|
|
// that clusterURL we expect.
|
|
let expectedClusterURL = server.baseURI + "1.1/johndoe/";
|
|
Assert.equal(Service.clusterURL, expectedClusterURL);
|
|
await Service.startOver();
|
|
await promiseStopServer(server);
|
|
});
|
|
|
|
add_task(async function test_momentary_401_engine() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a failure for engine URLs that's resolved by reassignment.");
|
|
let server = await prepareServer();
|
|
let john = server.user("johndoe");
|
|
|
|
_("Enabling the Rotary engine.");
|
|
let { engine, syncID, tracker } = await registerRotaryEngine();
|
|
|
|
// We need the server to be correctly set up prior to experimenting. Do this
|
|
// through a sync.
|
|
let global = {syncID: Service.syncID,
|
|
storageVersion: STORAGE_VERSION,
|
|
rotary: {version: engine.version, syncID}};
|
|
john.createCollection("meta").insert("global", global);
|
|
|
|
_("First sync to prepare server contents.");
|
|
await Service.sync();
|
|
|
|
_("Setting up Rotary collection to 401.");
|
|
let rotary = john.createCollection("rotary");
|
|
let oldHandler = rotary.collectionHandler;
|
|
rotary.collectionHandler = handleReassign.bind(this, undefined);
|
|
|
|
// We want to verify that the clusterURL pref has been cleared after a 401
|
|
// inside a sync. Flag the Rotary engine to need syncing.
|
|
john.collection("rotary").timestamp += 1000;
|
|
|
|
function between() {
|
|
_("Undoing test changes.");
|
|
rotary.collectionHandler = oldHandler;
|
|
|
|
function onLoginStart() {
|
|
// lastSyncReassigned shouldn't be cleared until a sync has succeeded.
|
|
_("Ensuring that lastSyncReassigned is still set at next sync start.");
|
|
Svc.Obs.remove("weave:service:login:start", onLoginStart);
|
|
Assert.ok(getReassigned());
|
|
}
|
|
|
|
_("Adding observer that lastSyncReassigned is still set on login.");
|
|
Svc.Obs.add("weave:service:login:start", onLoginStart);
|
|
}
|
|
|
|
await syncAndExpectNodeReassignment(server,
|
|
"weave:service:sync:finish",
|
|
between,
|
|
"weave:service:sync:finish",
|
|
Service.storageURL + "rotary");
|
|
|
|
await tracker.clearChangedIDs();
|
|
await Service.engineManager.unregister(engine);
|
|
});
|
|
|
|
// This test ends up being a failing info fetch *after we're already logged in*.
|
|
add_task(async function test_momentary_401_info_collections_loggedin() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a failure for info/collections after login that's resolved by reassignment.");
|
|
let server = await prepareServer();
|
|
|
|
_("First sync to prepare server contents.");
|
|
await Service.sync();
|
|
|
|
_("Arrange for info/collections to return a 401.");
|
|
let oldHandler = server.toplevelHandlers.info;
|
|
server.toplevelHandlers.info = handleReassign;
|
|
|
|
function undo() {
|
|
_("Undoing test changes.");
|
|
server.toplevelHandlers.info = oldHandler;
|
|
}
|
|
|
|
Assert.ok(Service.isLoggedIn, "already logged in");
|
|
|
|
await syncAndExpectNodeReassignment(server,
|
|
"weave:service:sync:error",
|
|
undo,
|
|
"weave:service:sync:finish",
|
|
Service.infoURL);
|
|
});
|
|
|
|
// This test ends up being a failing info fetch *before we're logged in*.
|
|
// In this case we expect to recover during the login phase - so the first
|
|
// sync succeeds.
|
|
add_task(async function test_momentary_401_info_collections_loggedout() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a failure for info/collections before login that's resolved by reassignment.");
|
|
|
|
let oldHandler;
|
|
let sawTokenFetch = false;
|
|
|
|
function afterTokenFetch() {
|
|
// After a single token fetch, we undo our evil handleReassign hack, so
|
|
// the next /info request returns the collection instead of a 401
|
|
server.toplevelHandlers.info = oldHandler;
|
|
sawTokenFetch = true;
|
|
}
|
|
|
|
let server = await prepareServer(afterTokenFetch);
|
|
|
|
// Return a 401 for the next /info request - it will be reset immediately
|
|
// after a new token is fetched.
|
|
oldHandler = server.toplevelHandlers.info;
|
|
server.toplevelHandlers.info = handleReassign;
|
|
|
|
Assert.ok(!Service.isLoggedIn, "not already logged in");
|
|
|
|
await Service.sync();
|
|
Assert.equal(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
|
|
// sync was successful - check we grabbed a new token.
|
|
Assert.ok(sawTokenFetch, "a new token was fetched by this test.");
|
|
// and we are done.
|
|
await Service.startOver();
|
|
await promiseStopServer(server);
|
|
});
|
|
|
|
// This test ends up being a failing meta/global fetch *after we're already logged in*.
|
|
add_task(async function test_momentary_401_storage_loggedin() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a failure for any storage URL after login that's resolved by" +
|
|
"reassignment.");
|
|
let server = await prepareServer();
|
|
|
|
_("First sync to prepare server contents.");
|
|
await Service.sync();
|
|
|
|
_("Arrange for meta/global to return a 401.");
|
|
let oldHandler = server.toplevelHandlers.storage;
|
|
server.toplevelHandlers.storage = handleReassign;
|
|
|
|
function undo() {
|
|
_("Undoing test changes.");
|
|
server.toplevelHandlers.storage = oldHandler;
|
|
}
|
|
|
|
Assert.ok(Service.isLoggedIn, "already logged in");
|
|
|
|
await syncAndExpectNodeReassignment(server,
|
|
"weave:service:sync:error",
|
|
undo,
|
|
"weave:service:sync:finish",
|
|
Service.storageURL + "meta/global");
|
|
});
|
|
|
|
// This test ends up being a failing meta/global fetch *before we've logged in*.
|
|
add_task(async function test_momentary_401_storage_loggedout() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a failure for any storage URL before login, not just engine parts. " +
|
|
"Resolved by reassignment.");
|
|
let server = await prepareServer();
|
|
|
|
// Return a 401 for all storage requests.
|
|
let oldHandler = server.toplevelHandlers.storage;
|
|
server.toplevelHandlers.storage = handleReassign;
|
|
|
|
function undo() {
|
|
_("Undoing test changes.");
|
|
server.toplevelHandlers.storage = oldHandler;
|
|
}
|
|
|
|
Assert.ok(!Service.isLoggedIn, "already logged in");
|
|
|
|
await syncAndExpectNodeReassignment(server,
|
|
"weave:service:login:error",
|
|
undo,
|
|
"weave:service:sync:finish",
|
|
Service.storageURL + "meta/global");
|
|
});
|