aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorJames Lal <james@lightsofapollo.com>2013-04-26 08:54:43 -0700
committerJames Lal <james@lightsofapollo.com>2013-05-02 14:01:06 -0700
commit8857b80ae0dd7be54d0d731000c9f8edb0434336 (patch)
tree5e7e4ee8dcc3cb01d48b4b88ab14039bc50cf034 /test
parent9b6e2c616154f2c20fe6272dca083868c02f98f4 (diff)
downloadjsCalDAV-8857b80ae0dd7be54d0d731000c9f8edb0434336.tar.gz
Bug 867747 - OAuth2 authentication support (particularly for google) r=kgrandon,gaye
Diffstat (limited to 'test')
-rw-r--r--test/caldav/connection_test.js64
-rw-r--r--test/caldav/http/basic_auth_test.js57
-rw-r--r--test/caldav/http/oauth2_test.js194
-rw-r--r--test/caldav/index_test.js2
-rw-r--r--test/caldav/oauth2_test.js258
-rw-r--r--test/caldav/querystring_test.js20
-rw-r--r--test/caldav/xhr_test.js2
-rw-r--r--test/helper.js36
-rw-r--r--test/support/fake_xhr.js15
9 files changed, 617 insertions, 31 deletions
diff --git a/test/caldav/connection_test.js b/test/caldav/connection_test.js
index fb32e8e..b035072 100644
--- a/test/caldav/connection_test.js
+++ b/test/caldav/connection_test.js
@@ -1,20 +1,25 @@
testSupport.lib('xhr');
testSupport.lib('connection');
+testSupport.lib('http/basic_auth');
+testSupport.lib('http/oauth2');
suite('caldav/connection', function() {
- var subject;
var Connection;
var XHR;
- var user = 'foo';
- var password = 'bar';
- var domain = 'http://foo.com';
+ var BasicAuth;
suiteSetup(function() {
Connection = Caldav.require('connection');
XHR = Caldav.require('xhr');
+ BasicAuth = Caldav.require('http/basic_auth');
});
+ var subject;
+ var user = 'foo';
+ var password = 'bar';
+ var domain = 'http://foo.com';
+
setup(function() {
subject = new Connection({
user: user,
@@ -38,32 +43,53 @@ suite('caldav/connection', function() {
assert.equal(subject.domain, domain, 'should remove trailing slash');
});
-
});
+ suite('#request', function() {
- suite('request', function() {
+ function commonCases() {
+ test('url without domain', function() {
+ var request = subject.request({
+ url: 'bar.json'
+ });
- test('credentails', function() {
- var result = subject.request({
- url: domain
+ // we add slash
+ assert.equal(request.url, domain + '/bar.json');
});
+ }
- assert.instanceOf(result, XHR);
- assert.equal(result.url, domain);
- assert.equal(result.password, password);
- assert.equal(result.user, user);
- });
+ suite('basic auth (default)', function() {
+
+ test('credentails', function() {
+ var result = subject.request({
+ url: domain
+ });
- test('url without domain', function() {
- var request = subject.request({
- url: 'bar.json'
+ assert.instanceOf(result, BasicAuth);
+ assert.equal(result.url, domain);
+ assert.equal(result.password, password);
+ assert.equal(result.user, user);
});
- // we add slash
- assert.equal(request.url, domain + '/bar.json');
+ commonCases();
});
});
+ suite('#update', function() {
+ test('without .onupdate handler', function() {
+ subject.update({ x: true });
+ assert.equal(subject.x, true);
+ });
+
+ test('with handler', function(done) {
+ subject.onupdate = function() {
+ assert.equal(subject.oauth, 'foo');
+ done();
+ };
+
+ subject.update({ oauth: 'foo' });
+ });
+ });
+
});
diff --git a/test/caldav/http/basic_auth_test.js b/test/caldav/http/basic_auth_test.js
new file mode 100644
index 0000000..566de08
--- /dev/null
+++ b/test/caldav/http/basic_auth_test.js
@@ -0,0 +1,57 @@
+testSupport.lib('xhr');
+testSupport.lib('connection');
+testSupport.lib('http/basic_auth');
+testSupport.helper('fake_xhr');
+
+suite('http/basic_auth', function() {
+
+ var XHR;
+ var FakeXhr;
+ var Connection;
+ var BasicAuth;
+
+ suiteSetup(function() {
+ FakeXhr = Caldav.require('support/fake_xhr');
+ XHR = Caldav.require('xhr');
+ Connection = Caldav.require('connection');
+ BasicAuth = Caldav.require('http/basic_auth');
+ });
+
+ var subject;
+ var connection;
+ var url = 'http://foo.com/bar';
+
+ setup(function() {
+ connection = new Connection({
+ user: 'jlal',
+ password: 'foo',
+ domain: 'google.com'
+ });
+
+ subject = new BasicAuth(connection, {
+ url: url,
+ xhrClass: FakeXhr
+ });
+ });
+
+ test('initialization', function() {
+ assert.instanceOf(subject, XHR);
+ assert.equal(subject.url, url);
+ });
+
+ test('#send', function() {
+ var xhr = subject.send();
+
+ assert.deepEqual(
+ xhr.openArgs,
+ [
+ 'GET',
+ url,
+ subject.async,
+ connection.user,
+ connection.password
+ ]
+ );
+ });
+
+});
diff --git a/test/caldav/http/oauth2_test.js b/test/caldav/http/oauth2_test.js
new file mode 100644
index 0000000..809dfc0
--- /dev/null
+++ b/test/caldav/http/oauth2_test.js
@@ -0,0 +1,194 @@
+testSupport.lib('xhr');
+testSupport.lib('connection');
+testSupport.lib('querystring');
+testSupport.lib('oauth2');
+testSupport.lib('http/oauth2');
+testSupport.helper('fake_xhr');
+
+suite('http/oauth2', function() {
+ var XHR;
+ var FakeXhr;
+ var Connection;
+ var GoogleOauth;
+ var OAuth;
+ var QueryString;
+
+ suiteSetup(function() {
+ FakeXhr = Caldav.require('support/fake_xhr');
+ XHR = Caldav.require('xhr');
+ Connection = Caldav.require('connection');
+ GoogleOauth = Caldav.require('http/oauth2');
+ QueryString = Caldav.require('querystring');
+ OAuth = Caldav.require('oauth2');
+ });
+
+ var subject;
+ var connection;
+
+ setup(function() {
+ connection = new Connection({
+ domain: 'google.com',
+ oauth: { code: 'xxx' },
+ apiCredentials: {
+ url: 'http://foobar.com/',
+ client_id: 'client_id',
+ client_secret: 'client_secret',
+ redirect_uri: 'redirect_uri'
+ }
+ });
+
+ subject = new GoogleOauth(connection, {
+ xhrClass: FakeXhr,
+ url: 'http://bar.com'
+ });
+ });
+
+ test('initialization', function() {
+ assert.instanceOf(subject, XHR);
+ assert.deepEqual(subject.oauth.apiCredentials, connection.apiCredentials);
+ });
+
+ test('without oauth code/refresh_token', function() {
+ connection.oauth = {};
+ assert.throws(function() {
+ new GoogleOauth(connection, {});
+ }, /oauth/);
+ });
+
+ suite('.send', function() {
+ testSupport.mock.useFakeXHR();
+
+ var updatesConnection;
+ setup(function() {
+ updatesConnection = false;
+ connection.onupdate = function() {
+ updatesConnection = true;
+ };
+ });
+
+ function buildRequest() {
+ return new GoogleOauth(connection, {
+ url: 'foo/bar',
+ xhrClass: FakeXhr
+ });
+ }
+
+ function buildResponse(data) {
+ var result = {
+ issued_at: Date.now(),
+ expires_in: 3600,
+ access_token: 'access_token',
+ token_type: 'Bearer'
+ };
+
+ var key;
+ for (key in data) {
+ result[key] = data[key];
+ }
+
+ return result;
+ }
+
+ var request;
+ var response;
+
+ suite('with code', function() {
+ setup(function() {
+ response = buildResponse({
+ refresh_token: 'refresh',
+ user: 'gotuser'
+ });
+ request = buildRequest();
+ });
+
+ test('fetches access_token then sends', function(done) {
+ var isComplete = false;
+ var xhr;
+
+ subject.oauth.authenticateCode = function(code, callback) {
+ assert.equal(code, connection.oauth.code, 'sends correct code');
+ setTimeout(function() {
+ assert.ok(!xhr.sendArgs, 'has not sent request yet');
+ callback(null, response);
+ assert.ok(updatesConnection, 'sends connection update event');
+ assert.deepEqual(connection.oauth, response, 'updates connection');
+ assert.equal(connection.user, 'gotuser', 'updates user');
+
+ assert.ok(xhr.sendArgs, 'sent request');
+ isComplete = true;
+ xhr.respond();
+ });
+ };
+
+ xhr = subject.send(function() {
+ assert.ok(isComplete, 'is complete');
+ done();
+ });
+ });
+ });
+
+ suite('with expired access_token', function() {
+
+ var expectedOauth;
+ setup(function() {
+ // no refresh_token intentionally
+ response = buildResponse();
+
+ connection.oauth = buildResponse({
+ issued_at: Date.now() - 10000,
+ expires_in: 3600,
+ refresh_token: 'refresh_me'
+ });
+
+ request = buildRequest();
+
+ expectedOauth = {};
+ for (var key in response) {
+ expectedOauth[key] = response[key];
+ }
+
+ expectedOauth.refresh_token = connection.oauth.refresh_token;
+ });
+
+ test('refreshes access_token', function(done) {
+ var isComplete = false;
+ var xhr;
+
+ subject.oauth.refreshToken = function(refreshToken, callback) {
+ assert.equal(
+ refreshToken,
+ connection.oauth.refresh_token,
+ 'sends correct refresh token'
+ );
+
+
+ setTimeout(function() {
+ assert.ok(!xhr.sendArgs, 'has not sent request yet');
+ callback(null, response);
+ assert.ok(
+ updatesConnection, 'sends connection update event'
+ );
+
+ assert.deepEqual(
+ connection.oauth, expectedOauth, 'updates connection'
+ );
+
+ assert.ok(xhr.sendArgs, 'sent request');
+ isComplete = true;
+ xhr.respond();
+ });
+ };
+
+ xhr = subject.send(function() {
+ assert.ok(isComplete, 'is complete');
+ done();
+ });
+
+ });
+ });
+
+ suite('with 401 response', function() {
+ });
+ });
+
+});
diff --git a/test/caldav/index_test.js b/test/caldav/index_test.js
index 59012a4..baad822 100644
--- a/test/caldav/index_test.js
+++ b/test/caldav/index_test.js
@@ -31,6 +31,8 @@ suite('caldav', function() {
assert.ok(root.Connection, 'Caldav.Connection');
assert.ok(root.Resources, 'Caldav.Resources');
assert.ok(root.Resources.Calendar, 'Calendar.Resources.Calendar');
+ assert.ok(root.OAuth2, 'OAuth2');
+ assert.ok(root.Http, 'Http');
});
});
diff --git a/test/caldav/oauth2_test.js b/test/caldav/oauth2_test.js
new file mode 100644
index 0000000..9c3024c
--- /dev/null
+++ b/test/caldav/oauth2_test.js
@@ -0,0 +1,258 @@
+testSupport.lib('querystring');
+testSupport.lib('xhr');
+testSupport.lib('oauth2');
+testSupport.helper('fake_xhr');
+
+suite('oauth', function() {
+ var XHR;
+ var FakeXhr;
+ var OAuth;
+ var QueryString;
+
+ suiteSetup(function() {
+ FakeXhr = Caldav.require('support/fake_xhr');
+ XHR = Caldav.require('xhr');
+ QueryString = Caldav.require('querystring');
+ OAuth = Caldav.require('oauth2');
+ });
+
+ function mockTime() {
+ setup(function() {
+ this.clock = this.sinon.useFakeTimers();
+ });
+
+ teardown(function() {
+ this.clock.restore();
+ });
+ }
+
+ var subject;
+ var apiCredentials = {
+ url: 'http://foobar.com/',
+ client_id: 'client_id',
+ client_secret: 'client_secret',
+ redirect_uri: 'redirect_uri'
+ };
+
+ setup(function() {
+ subject = new OAuth(apiCredentials);
+ });
+
+ test('initialization', function() {
+ assert.deepEqual(subject.apiCredentials, apiCredentials);
+ });
+
+ test('invalid credentials', function() {
+ assert.throws(function() {
+ new OAuth({ foo: 'bar' });
+ }, /apiCredentials/);
+ });
+
+ suite('#authoriztionCode', function() {
+ testSupport.mock.useFakeXHR();
+
+ var code = 'codexxx';
+
+ test('without code', function(done) {
+ subject.authenticateCode(null, function(err) {
+ assert.instanceOf(err, Error, 'sends an error');
+ done();
+ });
+ });
+
+ suite('success', function() {
+ mockTime();
+
+ var response = {
+ access_token: 'token',
+ refresh_token: 'refresh',
+ expires_in: 3600,
+ token_type: 'Bearer'
+ };
+
+ var expectedResponse;
+ var expectedRequest;
+
+ setup(function() {
+ // copy expected properties over
+ expectedResponse = {
+ issued_at: Date.now()
+ };
+
+ for (var key in response) {
+ expectedResponse[key] = response[key];
+ }
+
+ // expected post data
+ expectedRequest = QueryString.stringify({
+ code: code,
+ client_id: subject.apiCredentials.client_id,
+ client_secret: subject.apiCredentials.client_secret,
+ redirect_uri: subject.apiCredentials.redirect_uri,
+ grant_type: 'authorization_code'
+ });
+ });
+
+ test('sending request', function(done) {
+ var isComplete = false;
+ var xhr = subject.authenticateCode(code, function(err, result) {
+ assert.isNull(err);
+
+ assert.isTrue(isComplete, 'completed assertions');
+ assert.deepEqual(result, expectedResponse);
+ done();
+ });
+
+
+ // verify xhr does the right thing
+ assert.equal(xhr.sendArgs[0], expectedRequest, 'sends correct params');
+ assert.equal(xhr.openArgs[0], 'POST', 'is HTTP post verb');
+ assert.equal(xhr.openArgs[1], apiCredentials.url, 'opened with url');
+ isComplete = true;
+
+ xhr.respond(
+ JSON.stringify(response),
+ 200,
+ { 'Content-Type': 'application/json' }
+ );
+ });
+
+ test('with username_info', function(done) {
+ var userInfoData = { email: 'myfooba.com' };
+ var userInfo = subject.apiCredentials.user_info = {
+ url: 'http://google.com/',
+ field: 'email'
+ };
+
+ var isComplete = false;
+ var xhr = subject.authenticateCode(code, function(err, data) {
+ assert.isNull(err, 'no error');
+ assert.ok(isComplete, 'completed testing');
+ expectedResponse.user = userInfoData.email;
+ assert.deepEqual(data, expectedResponse);
+ done();
+ });
+
+ xhr.respond(
+ JSON.stringify(response),
+ 200,
+ { 'Content-Type': 'application/json' }
+ );
+
+ var userInfoXhr =
+ FakeXhr.instances[FakeXhr.instances.length - 1];
+
+ assert.notEqual(userInfoXhr, xhr, 'issued userinfo');
+
+ assert.equal(
+ userInfoXhr.openArgs[1],
+ userInfo.url
+ );
+
+ assert.equal(
+ userInfoXhr.headers['Authorization'],
+ response.token_type + ' ' + response.access_token,
+ 'sets access token'
+ );
+
+ isComplete = true;
+
+ userInfoXhr.respond(
+ JSON.stringify(userInfoData),
+ 200,
+ { 'Content-Type': 'application/json' }
+ );
+ });
+
+ });
+ });
+
+ suite('refreshToken', function() {
+ test('without .refresh_token', function() {
+ assert.throws(function() {
+ subject.refreshToken(null, function() {});
+ }, /token/);
+ });
+
+ suite('success', function() {
+ testSupport.mock.useFakeXHR();
+ mockTime();
+
+ var refreshToken = 'mytokenfoo';
+ var response = {
+ access_token: 'newcode',
+ expires_in: 3600,
+ token_type: 'Bearer'
+ };
+
+ var expectedResponse;
+ var expectedRequest;
+ setup(function() {
+ expectedResponse = {
+ access_token: response.access_token,
+ expires_in: response.expires_in,
+ token_type: response.token_type,
+ issued_at: Date.now()
+ };
+
+ expectedRequest = QueryString.stringify({
+ refresh_token: refreshToken,
+ client_id: subject.apiCredentials.client_id,
+ client_secret: subject.apiCredentials.client_secret,
+ grant_type: 'refresh_token'
+ });
+ });
+
+
+ test('send request', function(done) {
+ var isComplete = false;
+ var xhr = subject.refreshToken(refreshToken, function(err, result) {
+ assert.isNull(err);
+ assert.isTrue(isComplete, 'assertions complete');
+ assert.deepEqual(result, expectedResponse);
+ done();
+ });
+
+ assert.deepEqual(xhr.sendArgs[0], expectedRequest, 'sent formdata');
+ assert.equal(xhr.openArgs[1], apiCredentials.url, 'opened with url');
+ isComplete = true;
+
+ xhr.respond(
+ JSON.stringify(response),
+ 200,
+ { 'Content-Type': 'application/json' }
+ );
+ });
+ });
+ });
+
+ suite('#accessTokenValid', function() {
+ test('no access_token', function() {
+ assert.isFalse(subject.accessTokenValid({ code: 'xxx' }));
+ });
+
+ test('access_token present but time invalid', function() {
+ var oauth = {
+ access_token: 'xxx',
+ expires_in: 3600,
+ issued_at: Date.now() - 3700
+ };
+
+ assert.isFalse(subject.accessTokenValid(oauth));
+ });
+
+
+ test('access_token present and not expired', function() {
+ var oauth = {
+ access_token: 'xxx',
+ expires_in: 3600,
+ issued_at: Date.now()
+ };
+
+ assert.isTrue(subject.accessTokenValid(oauth));
+ });
+ });
+
+
+});
+
diff --git a/test/caldav/querystring_test.js b/test/caldav/querystring_test.js
new file mode 100644
index 0000000..e614b6f
--- /dev/null
+++ b/test/caldav/querystring_test.js
@@ -0,0 +1,20 @@
+testSupport.lib('querystring');
+
+suite('caldav/querystring', function() {
+ var QueryString;
+
+ suiteSetup(function() {
+ QueryString = Caldav.require('querystring');
+ });
+
+ /*
+ * quick sanity check we just copied node's version so we expect it to work.
+ */
+ test('stringify', function() {
+ var input = { foo: 'bar', baz: 'qux' };
+ var expected = 'foo=bar&baz=qux';
+
+ assert.equal(QueryString.stringify(input), expected);
+ });
+
+});
diff --git a/test/caldav/xhr_test.js b/test/caldav/xhr_test.js
index a3e6b66..6e93337 100644
--- a/test/caldav/xhr_test.js
+++ b/test/caldav/xhr_test.js
@@ -1,7 +1,7 @@
testSupport.lib('xhr');
testSupport.helper('fake_xhr');
-suite('webacls/xhr', function() {
+suite('xhr', function() {
var subject,
Xhr,
FakeXhr;
diff --git a/test/helper.js b/test/helper.js
index 2a8c772..5cf8d5f 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -23,7 +23,7 @@
}
requireBak.apply(this, arguments);
- }
+ };
}
/* cross require */
@@ -45,7 +45,7 @@
} else {
window.require(file, callback);
}
- }
+ };
/* sinon */
if (testSupport.isNode) {
@@ -103,7 +103,7 @@
cb(null, xhr.responseText);
}
}
- }
+ };
xhr.send(null);
}
};
@@ -121,6 +121,26 @@
};
testSupport.mock = {
+ useFakeXHR: function() {
+ testSupport.helper('fake_xhr');
+ testSupport.lib('xhr');
+
+ var realXHR;
+ var XHR;
+ var FakeXhr;
+
+ suiteSetup(function() {
+ XHR = Caldav.require('xhr');
+ FakeXhr = Caldav.require('support/fake_xhr');
+
+ realXHR = XHR.prototype.xhrClass;
+ XHR.prototype.xhrClass = FakeXhr;
+ });
+
+ suiteTeardown(function() {
+ XHR.prototype.xhrClass = realXHR;
+ });
+ },
/**
* Mocks out a method
@@ -165,7 +185,7 @@
testSupport.helper = function(lib) {
testSupport.require('/test/support/' + lib);
- }
+ };
Caldav = require('../lib/caldav/caldav.js');
@@ -179,12 +199,15 @@
return require(path);
}
return oldRequire(path);
- }
+ };
}
+ /* since we have global mocks easier to just include these globally */
requireRequest = function(callback) {
testSupport.lib('responder');
- testSupport.lib('xhr');
+ testSupport.lib('oauth2');
+ testSupport.lib('http/basic_auth');
+ testSupport.lib('http/oauth2');
testSupport.lib('connection');
testSupport.lib('sax');
testSupport.lib('sax/base');
@@ -192,7 +215,6 @@
testSupport.lib('request/errors');
testSupport.lib('request/abstract');
testSupport.lib('template');
- testSupport.helper('fake_xhr');
testSupport.lib('request/propfind');
//in the future we need a callback for browser support.
diff --git a/test/support/fake_xhr.js b/test/support/fake_xhr.js
index 0ffcff8..3f6cea0 100644
--- a/test/support/fake_xhr.js
+++ b/test/support/fake_xhr.js
@@ -1,4 +1,5 @@
(function(module) {
+ console.log('I HAZ LOADED');
function FakeXhr() {
this.openArgs = null;
@@ -14,7 +15,7 @@
FakeXhr.prototype = {
open: function() {
- this.openArgs = arguments;
+ this.openArgs = Array.prototype.slice.call(arguments);
},
getResponseHeader: function(key) {
@@ -26,18 +27,24 @@
},
send: function() {
- this.sendArgs = arguments;
+ this.sendArgs = Array.prototype.slice.call(arguments);
},
- respond: function(data, code) {
+ respond: function(data, code, headers) {
+ if (headers) {
+ this.responseHeaders = headers;
+ } else {
+ this.responseHeaders['content-type'] = 'text/xml';
+ }
+
this.readyState = 4;
- this.responseHeaders['content-type'] = 'text/xml';
this.responseText = data;
this.status = code || 200;
this.onreadystatechange();
}
};
+ console.log('EXPORTS ME', FakeXhr);
module.exports = FakeXhr;
}.apply(