From 1181a67b7cff9b924fb6997a310176102cc1aaa9 Mon Sep 17 00:00:00 2001 From: sgtcoolguy Date: Wed, 23 Mar 2016 11:37:47 -0400 Subject: [PATCH] Add more unit tests --- test/test-oauth.js | 162 ++++++++++++++++++++++++++++++--------------- test/ti-mock.js | 131 ++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 54 deletions(-) create mode 100644 test/ti-mock.js diff --git a/test/test-oauth.js b/test/test-oauth.js index 4eb4998..62a433a 100644 --- a/test/test-oauth.js +++ b/test/test-oauth.js @@ -1,54 +1,5 @@ - -// TODO Need some way to mock out the webview portion, basically just verify the URL we passed and be able to fake a response URL - -let handler = function (httpclient) { - httpclient.responseText = ''; - httpclient.onload(); -}; - -/* Generate a mock Ti.Network.HTTPClient for testing */ -function HTTPClient(obj) { - this.timeout = obj.timeout; - this.onload = obj.onload; - this.onerror = obj.onerror; - this.headers = {}; -} - -HTTPClient.prototype.send = function(data) { - this.data = data; - handler(this); -}; - -HTTPClient.prototype.setRequestHeader = function(name, value) { - this.headers[name] = value; -}; - -HTTPClient.prototype.open = function(method, url) { - this.method = method; - this.url = url; -}; - -global.Ti = { - Network: { - createHTTPClient: function(obj) { - return new HTTPClient(obj); - } - }, - App: { - Properties: { - _props: {}, - setString: function (key, value) { - global.Ti.App.Properties._props[key] = value; - }, - getString: function (key, def) { - if (global.Ti.App.Properties._props.hasOwnProperty(key)) { - return global.Ti.App.Properties._props[key]; - } - return def; - } - } - } -}; +import Titanium from './ti-mock'; +const Ti = global.Ti = global.Titanium = Titanium; import OAuth from '../src/index'; @@ -69,16 +20,85 @@ const implicit = { username: 'demouser', password: 'testpass' }; - +// TODO Add tests cases for calling onerror of httpclient for timeouts/etc describe('OAuth 2.0', () => { describe('Authorization Code Grant', () => { }); describe('Implicit Grant', () => { + it('should authorize', (done) => { + Ti.UI.Webview.handler = (webview) => { + // Validate the url set on the webview, etc + expect(webview.url).to.include(`http://brentertainment.com/oauth2/lockdin/authorize?approval_prompt=force&redirect_uri=http%3A%2F%2Flocalhost%2FCallback&response_type=token&client_id=${implicit.clientId}&btmpl=mobile&state=`); + let state = webview.url.slice(webview.url.indexOf('state=') + 6); + // fire error event + let error = webview.eventListeners['error'] || []; + error.forEach( callback => { + callback({ url: `http://localhost/Callback?access_token=997f871ee3194eb39b4f2a19d5871cb8608e9c0a&state=${state}&token_type=Bearer` }); + }); + }; + OAuth.authorizeImplicitly(implicit.url, implicit.clientId, function (err, oauth) { + if (err) { + return done(err); + } + + expect(oauth).to.exist; + expect(oauth).to.be.an('object'); + expect(oauth.clientId).to.equal(password.clientId); + expect(oauth.accessToken).to.equal('997f871ee3194eb39b4f2a19d5871cb8608e9c0a'); + expect(oauth.tokenType).to.equal('Bearer'); + + done(); + }); + }); + + it('should return error for possible CSRF from un-matched state value', (done) => { + Ti.UI.Webview.handler = (webview) => { + // Validate the url set on the webview, etc + expect(webview.url).to.include(`http://brentertainment.com/oauth2/lockdin/authorize?approval_prompt=force&redirect_uri=http%3A%2F%2Flocalhost%2FCallback&response_type=token&client_id=${implicit.clientId}&btmpl=mobile&state=`); + // fire error event + let error = webview.eventListeners['error'] || []; + error.forEach(callback => { + callback({ url: 'http://localhost/Callback?access_token=997f871ee3194eb39b4f2a19d5871cb8608e9c0a&state=madeupstate&token_type=Bearer' }); + }); + }; + OAuth.authorizeImplicitly(implicit.url, implicit.clientId, function (err, oauth) { + expect(err).to.exist; + + expect(oauth).not.to.exist; + + done(); + }); + }); + + it('should not allow refresh of token', (done) => { + Ti.UI.Webview.handler = (webview) => { + // Validate the url set on the webview, etc + expect(webview.url).to.include(`http://brentertainment.com/oauth2/lockdin/authorize?approval_prompt=force&redirect_uri=http%3A%2F%2Flocalhost%2FCallback&response_type=token&client_id=${implicit.clientId}&btmpl=mobile&state=`); + let state = webview.url.slice(webview.url.indexOf('state=') + 6); + // fire error event + let error = webview.eventListeners['error'] || []; + error.forEach( callback => { + callback({ url: `http://localhost/Callback?access_token=997f871ee3194eb39b4f2a19d5871cb8608e9c0a&state=${state}&token_type=Bearer` }); + }); + }; + OAuth.authorizeImplicitly(implicit.url, implicit.clientId, function (err, oauth) { + if (err) { + return done(err); + } + + expect(oauth).to.exist; + + oauth.refresh('http://example.com/refreshUrl', function (err, refresh_oauth) { + expect(err).to.exist; + expect(refresh_oauth).not.to.exist; + done(); + }); + }); + }); }); describe('Resource Owner Password Credentials Grant', () => { - // TODO Add tests to handle failure cases! it('should authorize with username and password', (done) => { - handler = function (httpclient) { + Ti.Network.HTTPClient.handler = (httpclient) => { // Verify headers/data/method/url expect(httpclient.headers).to.have.property('Content-Type', 'application/x-www-form-urlencoded'); expect(httpclient.method).to.equal('POST'); @@ -110,6 +130,40 @@ describe('OAuth 2.0', () => { done(); }); }); + + it('should allow refresh of token', (done) => { + Ti.Network.HTTPClient.handler = (httpclient) => { + httpclient.responseText = '{"access_token":"997f871ee3194eb39b4f2a19d5871cb8608e9c0a","expires_in":3600,"token_type":"Bearer","scope":null,"refresh_token":"2a6c5b92369fb8c8f56a82e7a1b81dbf6d756a69"}'; + httpclient.status = 200; + + httpclient.onload(); + }; + OAuth.authorizeWithPassword(password.url, password.clientId, password.clientSecret, password.username, password.password, function (err, oauth) { + if (err) { + return done(err); + } + + expect(oauth).to.exist; + expect(oauth.refreshToken).to.equal('2a6c5b92369fb8c8f56a82e7a1b81dbf6d756a69'); + + // Set up next response + Ti.Network.HTTPClient.handler = (httpclient) => { + httpclient.responseText = '{"access_token":"2a6c5b92369fb8c8f56a82e7a1b81dbf6d756a60","expires_in":3600,"token_type":"Bearer","scope":null,"refresh_token":"997f871ee3194eb39b4f2a19d5871cb8608e9c0b"}'; + httpclient.status = 200; + + httpclient.onload(); + }; + + oauth.refresh('http://example.com/refreshUrl', function (err, refresh_oauth) { + expect(err).not.to.exist; + expect(refresh_oauth).to.exist; + expect(oauth.accessToken).to.equal('2a6c5b92369fb8c8f56a82e7a1b81dbf6d756a60'); + expect(oauth.refreshToken).to.equal('997f871ee3194eb39b4f2a19d5871cb8608e9c0b'); + + done(); + }); + }); + }); }); describe('Client Credentials Grant', () => { }); diff --git a/test/ti-mock.js b/test/ti-mock.js new file mode 100644 index 0000000..d5af18c --- /dev/null +++ b/test/ti-mock.js @@ -0,0 +1,131 @@ + +/* Generate a mock Ti.Network.HTTPClient for testing */ +function TiNetworkHTTPClient(obj) { + this.timeout = obj.timeout; + this.onload = obj.onload; + this.onerror = obj.onerror; + this.headers = {}; +} + +TiNetworkHTTPClient.handler = function (httpclient) { + httpclient.responseText = ''; + httpclient.status = 200; + httpclient.statusText = 'OK'; + httpclient.onload(); +}; + +TiNetworkHTTPClient.prototype.send = function(data) { + this.data = data; + TiNetworkHTTPClient.handler(this); +}; + +TiNetworkHTTPClient.prototype.setRequestHeader = function(name, value) { + this.headers[name] = value; +}; + +TiNetworkHTTPClient.prototype.open = function(method, url) { + this.method = method; + this.url = url; +}; + +function TiUIWindow(obj) { + this.children = []; +} + +TiUIWindow.prototype.add = function (view) { + this.children.push(view); +}; + +TiUIWindow.prototype.open = function () { + this.children.forEach(c => c.show()); // FIXME Should we call show on each child? or some method noting it's been opened? +}; + +TiUIWindow.prototype.close = function () { + // no-op +}; + +function TiUIWebview(obj) { + this.url = obj.url; + this.width = obj.width; + this.height = obj.height; + this.eventListeners = {}; +} + +TiUIWebview.handler = function (webview) { + // fire beforeload + let beforeLoad = webview.eventListeners['beforeload'] || []; + beforeLoad.forEach(callback => callback({ url: webview.url })); + // fire load + let load = webview.eventListeners['load'] || []; + load.forEach(callback => callback({ url: webview.url })); +}; + +TiUIWebview.prototype.addEventListener = function (name, callback) { + let listeners = this.eventListeners[name] || []; + listeners.push(callback); + this.eventListeners[name] = listeners; +}; + +TiUIWebview.prototype.show = function () { + // Load the URL + TiUIWebview.handler(this); +}; + +TiUIWebview.prototype.hide = function () { + // no-op +}; + +function TiUIActivityIndicator(obj) { + +} + +TiUIActivityIndicator.prototype.show = function () { + // no-op +}; + +TiUIActivityIndicator.prototype.hide = function () { + // no-op +}; + + +let Ti = { + Network: { + createHTTPClient: function(obj) { + return new TiNetworkHTTPClient(obj); + }, + HTTPClient: TiNetworkHTTPClient + }, + App: { + Properties: { + _props: {}, + setString: function (key, value) { + global.Ti.App.Properties._props[key] = value; + }, + getString: function (key, def) { + if (global.Ti.App.Properties._props.hasOwnProperty(key)) { + return global.Ti.App.Properties._props[key]; + } + return def; + } + } + }, + UI: { + ActivityIndicator: TiUIActivityIndicator, + Window: TiUIWindow, + Webview: TiUIWebview, + ActivityIndicatorStyle: { + DARK: 1 + }, + createActivityIndicator: function (obj) { + return new TiUIActivityIndicator(obj); + }, + createWindow: function(obj) { + return new TiUIWindow(obj); + }, + createWebView: function(obj) { + return new TiUIWebview(obj); + } + } +}; + +export default Ti;