From b7bdf43e24a8de3fea2dfd81531ecfa94200ea4b Mon Sep 17 00:00:00 2001 From: Timothy Guan-tin Chien Date: Thu, 18 Oct 2012 18:45:17 +0800 Subject: [PATCH] Test framework, testcases, and update README on testing --- .gitmodules | 6 + README.md | 15 ++ test/index.html | 28 +++ test/integration/immediate.js | 47 ++++ test/integration/interactive.js | 46 ++++ test/qunit | 1 + test/sinon | 1 + test/testinit.js | 38 +++ test/unit/basics.js | 23 ++ test/unit/initiation.js | 409 ++++++++++++++++++++++++++++++++ 10 files changed, 614 insertions(+) create mode 100644 .gitmodules create mode 100644 test/index.html create mode 100644 test/integration/immediate.js create mode 100644 test/integration/interactive.js create mode 160000 test/qunit create mode 160000 test/sinon create mode 100644 test/testinit.js create mode 100644 test/unit/basics.js create mode 100644 test/unit/initiation.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..de2fb76 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "test/qunit"] + path = test/qunit + url = git://github.com/jquery/qunit.git +[submodule "test/sinon"] + path = test/sinon + url = git://github.com/cjohansen/Sinon.JS.git diff --git a/README.md b/README.md index e84ebd1..6ff09ff 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,18 @@ You can - Use the token to request data from Google's server directly from the client-side web app in the browser (with JSON-P or CORS), for example, [this is how HTML Word Cloud does it](https://github.com/timdream/wordcloud/blob/master/jquery.getcontent.js#L124). - Send the token to your own server, verify it with Google to associate a Google account with a user session on your site. [Documentation here](https://developers.google.com/accounts/docs/OAuth2Login#validatingtoken). + +## Testing + +To run tests, first you would need to pull the required QUnit and Sinon.JS library by running + + git submodule init + git submodule update + +Then, start a localhost HTTP server, for example, + + python -m SimpleHTTPServer + +Point your browser to [http://127-0-0-1.org.uk:8009/test/](http://127-0-0-1.org.uk:8009/test/) to start testing. You will need to disable popup blocker to properly run the interactive testcases. + +You will find all the information you need to write testcases on the [QUnit](http://qunitjs.com) and [Sinon.JS](http://sinonjs.org) website. All code submission are expected to accompany with testcases. \ No newline at end of file diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..724ce46 --- /dev/null +++ b/test/index.html @@ -0,0 +1,28 @@ + + + + + google-oauth2-web-client tests + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/test/integration/immediate.js b/test/integration/immediate.js new file mode 100644 index 0000000..965724b --- /dev/null +++ b/test/integration/immediate.js @@ -0,0 +1,47 @@ +'use strict'; + +module('Immediate login integration'); + +test('successful login should trigger onlogin callback', function () { + var client_id = localhost_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + scope: localhost_scope, + redirect_uri: localhost_redirect_uri + }); + equal(returnValue, true, 'init() returns true.'); + + window.GO2.login(false, true); + + stop(); + + window.GO2.onlogin = function (token) { + window.GO2.onlogin = null; + ok(!!token, 'Got token from onlogin: ' + token); + + start(); + }; +}); + +test('when logged in, GO2.getAccessToken() should return the token', function () { + var token = window.GO2.getAccessToken(); + ok(!!token, 'Pass with token:' + token); +}); + +test('GO2.logout() should trigger onlogout callback', function () { + stop(); + + window.GO2.onlogout = function () { + window.GO2.onlogout = null; + ok(true, 'Passed!'); + + start(); + }; + window.GO2.logout(); +}); + +test('when logged out, GO2.getAccessToken() should not return the token', function () { + var token = window.GO2.getAccessToken(); + ok(!token, 'Not getting token.'); +}); diff --git a/test/integration/interactive.js b/test/integration/interactive.js new file mode 100644 index 0000000..df99b70 --- /dev/null +++ b/test/integration/interactive.js @@ -0,0 +1,46 @@ +'use strict'; + +module('Interactive login integration'); + +test('successful login should trigger onlogin callback', function () { + var client_id = localhost_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + scope: localhost_scope, + redirect_uri: localhost_redirect_uri + }); + equal(returnValue, true, 'init() returns true.'); + + window.GO2.login(); + + stop(); + window.GO2.onlogin = function (token) { + window.GO2.onlogin = null; + ok(!!token, 'Got token from onlogin: ' + token); + + start(); + }; +}); + +test('when logged in, GO2.getAccessToken() should return the token', function () { + var token = window.GO2.getAccessToken(); + ok(!!token, 'Pass with token:' + token); +}); + +test('GO2.logout() should trigger onlogout callback', function () { + stop(); + + window.GO2.onlogout = function () { + window.GO2.onlogout = null; + ok(true, 'Passed!'); + + start(); + }; + window.GO2.logout(); +}); + +test('when logged out, GO2.getAccessToken() should not return the token', function () { + var token = window.GO2.getAccessToken(); + ok(!token, 'Not getting token.'); +}); diff --git a/test/qunit b/test/qunit new file mode 160000 index 0000000..900f720 --- /dev/null +++ b/test/qunit @@ -0,0 +1 @@ +Subproject commit 900f72051b0112342feda3d700a7a049d886b9ce diff --git a/test/sinon b/test/sinon new file mode 160000 index 0000000..4fa7a76 --- /dev/null +++ b/test/sinon @@ -0,0 +1 @@ +Subproject commit 4fa7a76ed0abe06491fc1cf836bb7f8ae649f6cb diff --git a/test/testinit.js b/test/testinit.js new file mode 100644 index 0000000..78e68af --- /dev/null +++ b/test/testinit.js @@ -0,0 +1,38 @@ +'use strict'; + +var localhost_client_id = '519733320959.apps.googleusercontent.com'; +var localhost_scope = ['https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/userinfo.email']; +var localhost_redirect_uri = 'http://127-0-0-1.org.uk:8009/test/'; + +// The fake one allows Google to show an error page without log us in automatically +var fake_client_id = 'not-valid.apps.googleusercontent.com'; + +var apiUrl = 'https://accounts.google.com/o/oauth2/auth'; +var defaultScope = 'https://www.googleapis.com/auth/plus.me'; + +function splitUrlArgs(urlArgs) { + var urlArgKeys = []; + var urlArgValues = []; + + urlArgs.split('&').forEach(function (keyValue) { + var kv = keyValue.split('='); + urlArgKeys.push(kv.shift()); + urlArgValues.push(decodeURIComponent(kv.join(''))); + }); + + return { + keys: urlArgKeys, + values: urlArgValues + }; +} + +test('Testing environment test', function () { + var win = window.open(''); + equal(typeof win, 'object', 'Popup blocker is not in-effect'); + if (win && win.close) + win.close(); + + equal(window.location.href.substr(0, localhost_redirect_uri.length), localhost_redirect_uri, + 'Integration will work; test is running on presumed URL.'); +}); diff --git a/test/unit/basics.js b/test/unit/basics.js new file mode 100644 index 0000000..b751c00 --- /dev/null +++ b/test/unit/basics.js @@ -0,0 +1,23 @@ +'use strict'; + +module('Basics'); + +test('GO2 object exists', function () { + equal(typeof window.GO2, 'object', 'Passed!'); +}); + +test('GO2.init function exists', function () { + equal(typeof window.GO2.init, 'function', 'Passed!'); +}); + +test('GO2.login function exists', function () { + equal(typeof window.GO2.login, 'function', 'Passed!'); +}); + +test('GO2.logout function exists', function () { + equal(typeof window.GO2.logout, 'function', 'Passed!'); +}); + +test('GO2.getAccessToken function exists', function () { + equal(typeof window.GO2.getAccessToken, 'function', 'Passed!'); +}); diff --git a/test/unit/initiation.js b/test/unit/initiation.js new file mode 100644 index 0000000..d431f7e --- /dev/null +++ b/test/unit/initiation.js @@ -0,0 +1,409 @@ +'use strict'; + +module('GO2.init()'); + +test('GO2.init() without options object should return false', function () { + var returnValue = window.GO2.init(); + equal(returnValue, false, 'Passed!'); +}); + +test('GO2.init() without client_id should return false', function () { + var returnValue = window.GO2.init({}); + equal(returnValue, false, 'Passed!'); +}); + +test('GO2.init() with client_id should return true', function () { + var returnValue = window.GO2.init( + { client_id: fake_client_id }); + equal(returnValue, true, 'Passed!'); +}); + +module('GO2.login() immediate login initiation'); + +if (!window.location.hash) + window.location.hash = '#url-with-hash'; + +test('GO2.login(false, true) should launch an iframe with correct url', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init( + { client_id: client_id }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the document.createElement method so we can inspect the calls. + var spy = sinon.spy(document, 'createElement'); + + window.GO2.login(false, true); + + ok(spy.called, 'createElement is called.'); + equal(spy.callCount, 1, 'createElement is called once.'); + equal(spy.firstCall.args[0], 'iframe', 'createElement is asked to create iframe element.'); + + var frame = spy.returnValues[0]; + equal(frame.constructor, HTMLIFrameElement, 'return value is an iframe.'); + equal(frame.parentNode, document.body, 'iframe is appended to body.'); + + var url = frame.src; + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + + ok(urlArgs.keys.indexOf('client_id') !== -1, 'client_id exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('client_id')], client_id, + 'client id is the specified client id.'); + + ok(urlArgs.keys.indexOf('scope') !== -1, 'scope exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('scope')], defaultScope, + 'scope is the default scope.'); + + ok(urlArgs.keys.indexOf('redirect_uri') !== -1, 'redirect_uri exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('redirect_uri')].indexOf('#'), -1, + 'redirect_uri does not contain hash.'); + + if (window.location.hash === '#url-with-hash') + window.location.hash = ''; + + spy.restore(); +}); + +test('The second GO2.login(false, true) should remove the first frame', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init( + { client_id: client_id }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the document.createElement method so we can inspect the calls. + var spy = sinon.spy(document, 'createElement'); + + window.GO2.login(false, true); + window.GO2.login(false, true); + + equal(spy.callCount, 2, 'createElement is called twice'); + + var frame0 = spy.returnValues[0]; + var frame1 = spy.returnValues[1]; + + notEqual(frame0.parentNode, document.body, 'first iframe is not appended to body.'); + equal(frame1.parentNode, document.body, 'second iframe is appended to body.'); + + spy.restore(); +}); + +test('scope should be able to assigned to iframe', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + scope: 'fake-scope' + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the document.createElement method so we can inspect the calls. + var spy = sinon.spy(document, 'createElement'); + + window.GO2.login(false, true); + + var frame = spy.returnValues[0]; + var url = frame.src; + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + + ok(urlArgs.keys.indexOf('scope') !== -1, 'scope exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('scope')], 'fake-scope', + 'scope is the specified scope.'); + + spy.restore(); +}); + +test('scope as an array should be able to assigned to iframe', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + scope: ['fake-scope', 'fake-scope2'] + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the document.createElement method so we can inspect the calls. + var spy = sinon.spy(document, 'createElement'); + + window.GO2.login(false, true); + + var frame = spy.returnValues[0]; + var url = frame.src; + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + + ok(urlArgs.keys.indexOf('scope') !== -1, 'scope exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('scope')], 'fake-scope fake-scope2', + 'scope is the specified scope.'); + + spy.restore(); +}); + +test('redirect_uri should be able to assigned to iframe', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + redirect_uri: 'fake-redirect_uri' + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the document.createElement method so we can inspect the calls. + var spy = sinon.spy(document, 'createElement'); + + window.GO2.login(false, true); + + var frame = spy.returnValues[0]; + var url = frame.src; + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + + ok(urlArgs.keys.indexOf('redirect_uri') !== -1, 'redirect_uri exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('redirect_uri')], 'fake-redirect_uri', + 'redirect_uri is the specified redirect_uri.'); + + spy.restore(); +}); + +test('approval_prompt should be able to set to auto on iframe', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the document.createElement method so we can inspect the calls. + var spy = sinon.spy(document, 'createElement'); + + window.GO2.login(true, true); + + var frame = spy.returnValues[0]; + var url = frame.src; + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + ok(urlArgs.keys.indexOf('approval_prompt') !== -1, 'approval_prompt exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('approval_prompt')], 'auto', + 'approval_prompt is the specified to auto.'); + + spy.restore(); +}); + +module('GO2.login() interactive login initiation'); + +test('GO2.login() should launch an popup with correct url', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + scope: 'fake-scope' + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the window.open method so we can inspect the calls. + var spy = sinon.spy(window, 'open'); + + window.GO2.login(); + + equal(spy.callCount, 1, 'window.open is called once.'); + + var url = spy.firstCall.args[0]; + var popupWindow = spy.returnValues[0]; + + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + equal(typeof popupWindow, 'object', 'window reference is returned.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + + ok(urlArgs.keys.indexOf('client_id') !== -1, 'client_id exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('client_id')], client_id, + 'client id is the specified client id.'); + + ok(urlArgs.keys.indexOf('scope') !== -1, 'scope exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('scope')], 'fake-scope', + 'scope is the specified scope.'); + + ok(urlArgs.keys.indexOf('redirect_uri') !== -1, 'redirect_uri exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('redirect_uri')].indexOf('#'), -1, + 'redirect_uri does not contain hash.'); + + if (popupWindow && popupWindow.close) + popupWindow.close(); + + spy.restore(); +}); + +test('scope should be able to assigned to login popup', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + scope: 'fake-scope' + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the window.open method so we can inspect the calls. + var spy = sinon.spy(window, 'open'); + + window.GO2.login(); + + equal(spy.callCount, 1, 'window.open is called once.'); + + var url = spy.firstCall.args[0]; + var popupWindow = spy.returnValues[0]; + + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + ok(urlArgs.keys.indexOf('scope') !== -1, 'scope exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('scope')], 'fake-scope', + 'scope is the specified scope.'); + + if (popupWindow && popupWindow.close) + popupWindow.close(); + + spy.restore(); +}); + +test('scope as an array should be able to assigned to login popup', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + scope: ['fake-scope', 'fake-scope2'] + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the window.open method so we can inspect the calls. + var spy = sinon.spy(window, 'open'); + + window.GO2.login(); + + equal(spy.callCount, 1, 'window.open is called once.'); + + var url = spy.firstCall.args[0]; + var popupWindow = spy.returnValues[0]; + + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + ok(urlArgs.keys.indexOf('scope') !== -1, 'scope exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('scope')], 'fake-scope fake-scope2', + 'scope is the specified scope.'); + + if (popupWindow && popupWindow.close) + popupWindow.close(); + + spy.restore(); +}); + +test('redirect_uri should be able to assigned to login popup', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id, + redirect_uri: 'fake-redirect_uri' + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the window.open method so we can inspect the calls. + var spy = sinon.spy(window, 'open'); + + window.GO2.login(); + + equal(spy.callCount, 1, 'window.open is called once.'); + + var url = spy.firstCall.args[0]; + var popupWindow = spy.returnValues[0]; + + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + ok(urlArgs.keys.indexOf('redirect_uri') !== -1, 'redirect_uri exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('redirect_uri')], 'fake-redirect_uri', + 'redirect_uri is the specified redirect_uri.'); + + if (popupWindow && popupWindow.close) + popupWindow.close(); + + spy.restore(); +}); + +test('approval_prompt should be set non-exist to login popup by default', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the window.open method so we can inspect the calls. + var spy = sinon.spy(window, 'open'); + + window.GO2.login(); + + equal(spy.callCount, 1, 'window.open is called once.'); + + var url = spy.firstCall.args[0]; + var popupWindow = spy.returnValues[0]; + + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + ok(urlArgs.keys.indexOf('approval_prompt') === -1, 'approval_prompt does not exists'); + + if (popupWindow && popupWindow.close) + popupWindow.close(); + + spy.restore(); +}); + +test('approval_prompt should be able to set to force to login popup', function () { + var client_id = fake_client_id; + + var returnValue = window.GO2.init({ + client_id: client_id + }); + equal(returnValue, true, 'init() returns true.'); + + // Wrap the window.open method so we can inspect the calls. + var spy = sinon.spy(window, 'open'); + + window.GO2.login(true); + + equal(spy.callCount, 1, 'window.open is called once.'); + + var url = spy.firstCall.args[0]; + var popupWindow = spy.returnValues[0]; + + equal(url.substr(0, apiUrl.length + 1), apiUrl + '?', + 'src is pointing to the correct api endpoint.'); + + var urlArgs = splitUrlArgs(url.substr(apiUrl.length + 1)); + ok(urlArgs.keys.indexOf('approval_prompt') !== -1, 'approval_prompt exists'); + equal(urlArgs.values[urlArgs.keys.indexOf('approval_prompt')], 'force', + 'approval_prompt is the specified to force.'); + + if (popupWindow && popupWindow.close) + popupWindow.close(); + + spy.restore(); +});