Bug 1069816: add unit tests for the GoogleImporter class. r=abr

This commit is contained in:
Mike de Boer 2014-10-02 12:37:41 +02:00
Родитель 9aa1b91cc1
Коммит 0b4d324d7e
9 изменённых файлов: 381 добавлений и 40 удалений

Просмотреть файл

@ -1,11 +1,16 @@
[DEFAULT]
support-files =
fixtures/google_auth.txt
fixtures/google_contacts.txt
fixtures/google_token.txt
google_service.sjs
head.js
loop_fxa.sjs
../../../../base/content/test/general/browser_fxa_oauth.html
[browser_CardDavImporter.js]
[browser_fxa_login.js]
[browser_GoogleImporter.js]
skip-if = e10s
[browser_loop_fxa_server.js]
[browser_LoopContacts.js]

Просмотреть файл

@ -3,46 +3,6 @@
const {CardDavImporter} = Cu.import("resource:///modules/loop/CardDavImporter.jsm", {});
const mockDb = {
_store: { },
_next_guid: 1,
add: function(details, callback) {
if (!("id" in details)) {
callback(new Error("No 'id' field present"));
return;
}
details._guid = this._next_guid++;
this._store[details._guid] = details;
callback(null, details);
},
remove: function(guid, callback) {
if (!guid in this._store) {
callback(new Error("Could not find _guid '" + guid + "' in database"));
return;
}
delete this._store[guid];
callback(null);
},
get: function(guid, callback) {
callback(null, this._store[guid]);
},
getByServiceId: function(serviceId, callback) {
for (let guid in this._store) {
if (serviceId === this._store[guid].id) {
callback(null, this._store[guid]);
return;
}
}
callback(null, null);
},
removeAll: function(callback) {
this._store = {};
this._next_guid = 1;
callback(null);
}
};
const kAuth = {
"method": "basic",
"user": "username",

Просмотреть файл

@ -0,0 +1,77 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const {GoogleImporter} = Cu.import("resource:///modules/loop/GoogleImporter.jsm", {});
let importer = new GoogleImporter();
function promiseImport() {
return new Promise(function(resolve, reject) {
importer.startImport({}, function(err, stats) {
if (err) {
reject(err);
} else {
resolve(stats);
}
}, mockDb, window);
});
}
add_task(function* test_GoogleImport() {
let stats;
// An error may throw and the test will fail when that happens.
stats = yield promiseImport();
// Assert the world.
Assert.equal(stats.total, 5, "Five contacts should get processed");
Assert.equal(stats.success, 5, "Five contacts should be imported");
yield promiseImport();
Assert.equal(Object.keys(mockDb._store).length, 5, "Database should contain only five contact after reimport");
let c = mockDb._store[mockDb._next_guid - 5];
Assert.equal(c.name[0], "John Smith", "Full name should match");
Assert.equal(c.givenName[0], "John", "Given name should match");
Assert.equal(c.familyName[0], "Smith", "Family name should match");
Assert.equal(c.email[0].type, "other", "Email type should match");
Assert.equal(c.email[0].value, "john.smith@example.com", "Email should match");
Assert.equal(c.email[0].pref, true, "Pref should match");
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/0", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 4];
Assert.equal(c.name[0], "Jane Smith", "Full name should match");
Assert.equal(c.givenName[0], "Jane", "Given name should match");
Assert.equal(c.familyName[0], "Smith", "Family name should match");
Assert.equal(c.email[0].type, "other", "Email type should match");
Assert.equal(c.email[0].value, "jane.smith@example.com", "Email should match");
Assert.equal(c.email[0].pref, true, "Pref should match");
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/1", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 3];
Assert.equal(c.name[0], "Davy Randall Jones", "Full name should match");
Assert.equal(c.givenName[0], "Davy Randall", "Given name should match");
Assert.equal(c.familyName[0], "Jones", "Family name should match");
Assert.equal(c.email[0].type, "other", "Email type should match");
Assert.equal(c.email[0].value, "davy.jones@example.com", "Email should match");
Assert.equal(c.email[0].pref, true, "Pref should match");
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/2", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 2];
Assert.equal(c.name[0], "noname@example.com", "Full name should match");
Assert.equal(c.email[0].type, "other", "Email type should match");
Assert.equal(c.email[0].value, "noname@example.com", "Email should match");
Assert.equal(c.email[0].pref, true, "Pref should match");
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/3", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 1];
Assert.equal(c.name[0], "lycnix", "Full name should match");
Assert.equal(c.email[0].type, "other", "Email type should match");
Assert.equal(c.email[0].value, "lycnix", "Email should match");
Assert.equal(c.email[0].pref, true, "Pref should match");
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/7", "UID should match and be scoped to provider");
});

Просмотреть файл

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html>
<head><title>Success code=test-code</title></head>
<body>Le Code.</body>
</html>

Просмотреть файл

@ -0,0 +1,94 @@
<?xml version='1.0' encoding='UTF-8'?>
<feed gd:etag="W/&quot;DUQNRHc8cCt7I2A9XRdSF04.&quot;" xmlns="http://www.w3.org/2005/Atom" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:gd="http://schemas.google.com/g/2005" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/">
<id>tester@mochi.com</id>
<updated>2014-09-26T13:16:35.978Z</updated>
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
<title>Mochi Tester's Contacts</title>
<link href="http://www.google.com/" rel="alternate" type="text/html"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full" rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full" rel="http://schemas.google.com/g/2005#post" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/batch" rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full?max-results=25" rel="self" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full?start-index=26&amp;max-results=25" rel="next" type="application/atom+xml"/>
<author>
<name>Mochi Tester</name>
<email>tester@mochi.com</email>
</author>
<generator uri="http://www.google.com/m8/feeds" version="1.0">Contacts</generator>
<openSearch:totalResults>25</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>10000000</openSearch:itemsPerPage>
<entry gd:etag="&quot;R3YyejRVLit7I2A9WhJWEkkNQwc.&quot;">
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/0</id>
<updated>2012-08-17T23:50:36.892Z</updated>
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-17T23:50:36.892Z</app:edited>
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
<title>John Smith</title>
<link gd:etag="&quot;Ug92D34SfCt7I2BmLHJTRgVzTlgrJXEAU08.&quot;" href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/0" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/0" rel="self" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/0" rel="edit" type="application/atom+xml"/>
<gd:name>
<gd:fullName>John Smith</gd:fullName>
<gd:givenName>John</gd:givenName>
<gd:familyName>Smith</gd:familyName>
</gd:name>
<gd:email address="john.smith@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
<gContact:website href="http://www.google.com/profiles/109576547678240773721" rel="profile"/>
</entry>
<entry gd:etag="&quot;R3YyejRVLit7I2A9WhJWEkkNQwc.&quot;">
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/1</id>
<updated>2012-08-17T23:50:36.892Z</updated>
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-17T23:50:36.892Z</app:edited>
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
<title>Jane Smith</title>
<link gd:etag="&quot;WA9BY1xFWit7I2BhLEkieCxLHEYTGCYuNxo.&quot;" href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/1" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/1" rel="self" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/1" rel="edit" type="application/atom+xml"/>
<gd:name>
<gd:fullName>Jane Smith</gd:fullName>
<gd:givenName>Jane</gd:givenName>
<gd:familyName>Smith</gd:familyName>
</gd:name>
<gd:email address="jane.smith@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
<gContact:website href="http://www.google.com/profiles/112886528199784431028" rel="profile"/>
</entry>
<entry gd:etag="&quot;R3YyejRVLit7I2A9WhJWEkkNQwc.&quot;">
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/2</id>
<updated>2012-08-17T23:50:36.892Z</updated>
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-17T23:50:36.892Z</app:edited>
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
<title>Davy Randall Jones</title>
<link gd:etag="&quot;KiV2PkYRfCt7I2BuD1AzEBFxD1VcGjwBUyA.&quot;" href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/2" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/2" rel="self" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/2" rel="edit" type="application/atom+xml"/>
<gd:name>
<gd:fullName>Davy Randall Jones</gd:fullName>
<gd:givenName>Davy Randall</gd:givenName>
<gd:familyName>Jones</gd:familyName>
</gd:name>
<gd:email address="davy.jones@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
<gContact:website href="http://www.google.com/profiles/109710625881478599011" rel="profile"/>
</entry>
<entry gd:etag="&quot;Q3w7ezVSLit7I2A9WB5WGUkNRgE.&quot;">
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/3</id>
<updated>2007-08-01T05:45:52.203Z</updated>
<app:edited xmlns:app="http://www.w3.org/2007/app">2007-08-01T05:45:52.203Z</app:edited>
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
<title/>
<link href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/3" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/3" rel="self" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/3" rel="edit" type="application/atom+xml"/>
<gd:email address="noname@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
</entry>
<entry gd:etag="&quot;Q3w7ezVSLit7I2A9WB5WGUkNRgE.&quot;">
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/7</id>
<updated>2007-08-01T05:45:52.203Z</updated>
<app:edited xmlns:app="http://www.w3.org/2007/app">2007-08-01T05:45:52.203Z</app:edited>
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
<title/>
<link href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/7" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/7" rel="self" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/7" rel="edit" type="application/atom+xml"/>
<gd:email address="lycnix" primary="true" rel="http://schemas.google.com/g/2005#other"/>
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,3 @@
{
"access_token": "test-token"
}

Просмотреть файл

@ -0,0 +1,147 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {classes: Cc, interfaces: Ci, Constructor: CC} = Components;
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream");
function handleRequest(req, res) {
try {
reallyHandleRequest(req, res);
} catch (ex) {
res.setStatusLine("1.0", 200, "AlmostOK");
let msg = "Error handling request: " + ex + "\n" + ex.stack;
log(msg);
res.write(msg);
}
}
function log(msg) {
// dump("GOOGLE-SERVER-MOCK: " + msg + "\n");
}
const kBasePath = "browser/browser/components/loop/test/mochitest/fixtures/";
const kStatusCodes = {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
500: "Internal Server Error",
501: "Not Implemented",
503: "Service Unavailable"
};
function HTTPError(code = 500, message) {
this.code = code;
this.name = kStatusCodes[code] || "HTTPError";
this.message = message || this.name;
}
HTTPError.prototype = new Error();
HTTPError.prototype.constructor = HTTPError;
function sendError(res, err) {
if (!(err instanceof HTTPError)) {
err = new HTTPError(typeof err == "number" ? err : 500,
err.message || typeof err == "string" ? err : "");
}
res.setStatusLine("1.1", err.code, err.name);
res.write(err.message);
}
function parseQuery(query, params = {}) {
for (let param of query.replace(/^[?&]/, "").split(/(?:&|\?)/)) {
param = param.split("=");
if (!param[0])
continue;
params[unescape(param[0])] = unescape(param[1]);
}
return params;
}
function getRequestBody(req) {
let avail;
let bytes = [];
let body = new BinaryInputStream(req.bodyInputStream);
while ((avail = body.available()) > 0)
Array.prototype.push.apply(bytes, body.readByteArray(avail));
return String.fromCharCode.apply(null, bytes);
}
function getInputStream(path) {
let file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("CurWorkD", Ci.nsILocalFile);
for (let part of path.split("/"))
file.append(part);
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
fileStream.init(file, 1, 0, false);
return fileStream;
}
function checkAuth(req) {
if (!req.hasHeader("Authorization"))
throw new HTTPError(401, "No Authorization header provided.");
let auth = req.getHeader("Authorization");
if (auth != "Bearer test-token")
throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
}
function reallyHandleRequest(req, res) {
log("method: " + req.method);
let body = getRequestBody(req);
log("body: " + body);
let contentType = req.hasHeader("Content-Type") ? req.getHeader("Content-Type") : null;
log("contentType: " + contentType);
let params = parseQuery(req.queryString);
parseQuery(body, params);
log("params: " + JSON.stringify(params));
// Delegate an authentication request to the correct handler.
if ("action" in params) {
methodHandlers[params.action](req, res, params);
} else {
sendError(res, 501);
}
}
function respondWithFile(res, fileName, mimeType) {
res.setStatusLine("1.1", 200, "OK");
res.setHeader("Content-Type", mimeType);
let inputStream = getInputStream(kBasePath + fileName);
res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
inputStream.close();
}
const methodHandlers = {
auth: function(req, res, params) {
respondWithFile(res, "google_auth.txt", "text/html");
},
token: function(req, res, params) {
respondWithFile(res, "google_token.txt", "application/json");
},
contacts: function(req, res, params) {
try {
checkAuth(req);
} catch (ex) {
sendError(res, ex, ex.code);
return;
}
respondWithFile(res, "google_contacts.txt", "text/xml");
}
};

Просмотреть файл

@ -198,3 +198,51 @@ let mockPushHandler = {
this._notificationCallback(version);
}
};
const mockDb = {
_store: { },
_next_guid: 1,
add: function(details, callback) {
if (!("id" in details)) {
callback(new Error("No 'id' field present"));
return;
}
details._guid = this._next_guid++;
this._store[details._guid] = details;
callback(null, details);
},
remove: function(guid, callback) {
if (!guid in this._store) {
callback(new Error("Could not find _guid '" + guid + "' in database"));
return;
}
delete this._store[guid];
callback(null);
},
getAll: function(callback) {
callback(null, this._store);
},
get: function(guid, callback) {
callback(null, this._store[guid]);
},
getByServiceId: function(serviceId, callback) {
for (let guid in this._store) {
if (serviceId === this._store[guid].id) {
callback(null, this._store[guid]);
return;
}
}
callback(null, null);
},
removeAll: function(callback) {
this._store = {};
this._next_guid = 1;
callback(null);
},
promise: function(method, ...params) {
return new Promise(resolve => {
this[method](...params, (err, res) => err ? reject(err) : resolve(res));
});
}
};

Просмотреть файл

@ -249,6 +249,8 @@ user_pref("browser.newtabpage.directory.ping", "");
// Enable Loop
user_pref("loop.enabled", true);
user_pref("loop.throttled", false);
user_pref("loop.oauth.google.URL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=");
user_pref("loop.oauth.google.getContactsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=contacts");
// Ensure UITour won't hit the network
user_pref("browser.uitour.pinnedTabUrl", "http://%(server)s/uitour-dummy/pinnedTab");