├── .gitignore ├── Readme.md ├── lib └── index.js ├── package.json └── test ├── codeStyle.js ├── facebook.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | hapi-passport 2 | ============= 3 | 4 | ```hapi-passport``` is supposed to be a connector between passport.js strategies and the [hapi](https://github.com/spumko/hapi) request api. Right now its in a early phase so its only tested with facebook. 5 | 6 | usage 7 | ===== 8 | 9 | You need to install ```hapi-passport``` together with the connector strategies like ```passport-facebook```. 10 | 11 | ```bash 12 | $ npm install hapi-passport passport-facebook 13 | ``` 14 | 15 | With this you can make a request handler like this: 16 | 17 | ```javascript 18 | var FacebookStrategy = require("passport-facebook"), 19 | facebookLogin = require("hapi-passport")(new FacebookStrategy(...)); 20 | ``` 21 | 22 | and connect it hapi using 23 | 24 | ```javascript 25 | server.routes({method: "GET", path: "/login/facebook", facebookLogin() }); 26 | ``` 27 | 28 | By default it would just show loose error or success messages. You can work around that by passing redirect urls for the different cases: 29 | 30 | ```javascript 31 | server.routes({ 32 | method: "GET", 33 | path: "/login/facebook", 34 | handler: facebookLogin({ 35 | successRedirect: "http://mydomain/login/success", 36 | errorRedirect: "http://mydomain/login/error", 37 | failRedirect: "http://mydomain/login/failed" 38 | }) 39 | }); 40 | ``` 41 | 42 | ... or you can use handlers for the various cases: 43 | 44 | ```javascript 45 | server.routes({ 46 | method: "GET", path: "/login/facebook", handler: facebookLogin({ 47 | onSuccess: function (info, request, reply) { 48 | // maybe do a redirect? 49 | }, 50 | onFailed: function (warning, request, reply) { 51 | // maybe show an error? 52 | }, 53 | onError: function (error, request, reply) { 54 | // tell the world that you are angry. 55 | } 56 | }) 57 | }); 58 | ``` 59 | 60 | Resulting into something like: 61 | 62 | ```javascript 63 | var FacebookStrategy = require("passport-facebook"), 64 | facebookLogin = require("hapi-passport")(new FacebookStrategy({ 65 | clientID: "FACEBOOK_APP_ID", // Facebook app id 66 | clientSecret: "FACEBOOK_APP_SECRET", // Facebook secret 67 | callbackURL: "http://localhost:3000/login/facebook" // needs to be added in the facebook admin interface 68 | }, function verify(accessToken, refreshToken, profile, verified) { 69 | verified(error, info); 70 | })); 71 | 72 | server.routes({ 73 | method: "GET", path: "/login/facebook", handler: facebookLogin({ 74 | onSuccess: function (info, request, reply) { 75 | // maybe do a redirect? 76 | }, 77 | onFailed: function (warning, request, reply) { 78 | // maybe show an error? 79 | }, 80 | onError: function (error, request, reply) { 81 | // tell the world that you are angry. 82 | } 83 | }) 84 | }); 85 | ``` 86 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function onFailedDefault(warning, request, reply) { 4 | reply(warning); 5 | return request; 6 | } 7 | 8 | function onErrorDefault(error, request, reply) { 9 | reply(error); 10 | return request; 11 | } 12 | 13 | function onSuccessDefault(info, request, reply) { 14 | reply(info); 15 | return request; 16 | } 17 | 18 | function def(options, prop, fallback) { 19 | if (!options[prop]) { 20 | options[prop] = fallback; 21 | } 22 | } 23 | 24 | module.exports = function hapiPassport(prototype) { 25 | var strategy = Object.create(prototype); 26 | return function createPassportLogin(options) { 27 | 28 | if (!options) { 29 | options = {}; 30 | } 31 | 32 | function onFailedRedirect(warning, request, reply) { 33 | reply("Login failed, redirecting...", warning).redirect(options.failRedirect); 34 | return request; 35 | } 36 | 37 | function onErrorRedirect(error, request, reply) { 38 | reply("Error while trying to login...", error).redirect(options.errorRedirect); 39 | return request; 40 | } 41 | 42 | function onSuccessRedirect(info, request, reply) { 43 | reply("Login successful, redirecting...", info).redirect(options.successRedirect); 44 | return request; 45 | } 46 | 47 | def(options, "onFailed", options.failRedirect ? onFailedRedirect : onFailedDefault); 48 | def(options, "onError", options.errorRedirect ? onErrorRedirect : onErrorDefault); 49 | def(options, "onSuccess", options.successRedirect ? onSuccessRedirect : onSuccessDefault); 50 | 51 | return function processLogin(request, reply, requestOptions) { 52 | requestOptions = requestOptions || {}; 53 | 54 | strategy.redirect = function (url) { 55 | reply().redirect(url); 56 | }; 57 | strategy.fail = function (warning) { 58 | options.onFailed(warning, request, reply); 59 | }; 60 | strategy.error = function (error) { 61 | options.onError(error, request, reply); 62 | }; 63 | strategy.success = function (info) { 64 | options.onSuccess(info, request, reply); 65 | }; 66 | strategy.authenticate({ 67 | query: request.query, 68 | body: request.body || request.payload, 69 | session: request.session 70 | }, requestOptions); 71 | }; 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-passport", 3 | "version": "0.0.6", 4 | "description": "Passport Strategy connector to hapi", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "lab -r console -t -m 5000" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/ikkyotech/hapi-passport.git" 12 | }, 13 | "keywords": [ 14 | "hapi", 15 | "passport", 16 | "authentication" 17 | ], 18 | "author": "Martin Heidegger ", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/ikkyotech/hapi-passport/issues" 22 | }, 23 | "homepage": "https://github.com/ikkyotech/hapi-passport", 24 | "devDependencies": { 25 | "lab": "~3.2.1", 26 | "lab-lint": "0.0.3", 27 | "nodemock": "~0.3.4", 28 | "passport-facebook": "~1.0.3", 29 | "passport-oauth2": "^1.1.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/codeStyle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /*jslint nomen: true*/ 4 | var dirname = __dirname; 5 | /*jslint nomen: false */ 6 | 7 | require("lab-lint")(require("lab"), dirname); -------------------------------------------------------------------------------- /test/facebook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Facebook = require("passport-facebook"), 4 | Lab = require("lab"), 5 | test = Lab.test, 6 | nodemock = require("nodemock"), 7 | expect = Lab.expect; 8 | 9 | function dontCall() { 10 | throw new Error("This shouldn't be called."); 11 | } 12 | 13 | Lab.experiment("Making sure that the passport-facebook works as expected", function () { 14 | 15 | test("simplest request", function (done) { 16 | var redirectMock = nodemock.mock("redirect").takes("https://www.facebook.com/dialog/oauth?response_type=code&redirect_uri=&client_id=myClientId"), 17 | facebookImpl = new Facebook({ 18 | clientID: "myClientId", 19 | clientSecret: "myClientSecret" 20 | }, dontCall); 21 | 22 | facebookImpl.redirect = redirectMock.redirect; 23 | facebookImpl.authenticate({}, {}); 24 | redirectMock.assert(); 25 | done(); 26 | }); 27 | 28 | test("Calling request", function (done) { 29 | var callbackURL = "http://google.com?hello&world", 30 | redirectMock = nodemock.mock("redirect").takes("https://www.facebook.com/dialog/oauth?response_type=code&redirect_uri=" + encodeURIComponent(callbackURL) + "&client_id=myClientId"), 31 | facebookImpl = new Facebook({ 32 | clientID: "myClientId", 33 | clientSecret: "myClientSecret", 34 | callbackURL: callbackURL 35 | }, dontCall); 36 | 37 | facebookImpl.redirect = redirectMock.redirect; 38 | facebookImpl.authenticate({}, {}); 39 | redirectMock.assert(); 40 | done(); 41 | }); 42 | 43 | test("With a access denied error", function (done) { 44 | var callbackURL = "http://google.com?hello&world", 45 | errorDescription = "Just some error message", 46 | failMock = nodemock.mock("failF").takesF(function (error) { 47 | return error.message === errorDescription; 48 | }), 49 | facebookImpl = new Facebook({ 50 | clientID: "myClientId", 51 | clientSecret: "myClientSecret", 52 | callbackURL: callbackURL 53 | }, dontCall); 54 | 55 | facebookImpl.fail = failMock.failF; 56 | facebookImpl.authenticate({ 57 | query: { 58 | error: "access_denied", 59 | error_description: errorDescription 60 | } 61 | }, {}); 62 | failMock.assert(); 63 | done(); 64 | }); 65 | 66 | test("With a other error", function (done) { 67 | // Please see also http://tools.ietf.org/html/draft-ietf-oauth-v2-30#page-26 68 | var AuthorizationError = require('passport-oauth2/lib/errors/authorizationerror'), 69 | errorToken = Math.random().toString(), 70 | errorUri = 'http://error.uri' + errorToken, 71 | errorDescription = 'error desu' + errorToken, 72 | errorMock = nodemock.mock('error') 73 | .takesF(function (error) { 74 | // console.log(error); 75 | if (!(error instanceof AuthorizationError)) { 76 | return false; 77 | } 78 | if (error.uri !== errorUri) { 79 | return false; 80 | } 81 | // if(error.code === 'access_denied') return error.status === 403; 82 | if (error.code === 'server_error') { 83 | return error.status === 502; 84 | } 85 | if (error.code === 'temporarily_unavailable') { 86 | return error.status === 503; 87 | } 88 | // return error instanceof AuthorizationError; 89 | return error.status === 500; 90 | }) 91 | .times(7), 92 | facebookImpl = new Facebook({ 93 | clientID: "myClientId", 94 | clientSecret: "myClientSecret" 95 | }, dontCall); 96 | 97 | facebookImpl.error = errorMock.error; 98 | // facebookImpl.authenticate({ query: { error: 'access_denied' } }, {}); 99 | facebookImpl.authenticate({ query: { error: 'invalid_scope', error_description: errorDescription, error_uri: errorUri } }, {}); 100 | facebookImpl.authenticate({ query: { error: 'invalid_request', error_description: errorDescription, error_uri: errorUri } }, {}); 101 | facebookImpl.authenticate({ query: { error: 'invalid_client', error_description: errorDescription, error_uri: errorUri } }, {}); 102 | facebookImpl.authenticate({ query: { error: 'unauthorized_client', error_description: errorDescription, error_uri: errorUri } }, {}); 103 | facebookImpl.authenticate({ query: { error: 'unsupported_response_type', error_description: errorDescription, error_uri: errorUri } }, {}); 104 | facebookImpl.authenticate({ query: { error: 'temporarily_unavailable', error_description: errorDescription, error_uri: errorUri } }, {}); 105 | facebookImpl.authenticate({ query: { error: 'server_error', error_description: errorDescription, error_uri: errorUri } }, {}); 106 | 107 | errorMock.assert(); 108 | done(); 109 | }); 110 | 111 | test("attempting a successful response", function (done) { 112 | var code = "abcd", 113 | accessToken = "accessToken1", 114 | refreshToken = "refreshToken1", 115 | infoInput = "some Input", 116 | profile = { id: "id" }, 117 | oauth2 = '_oauth2', 118 | oauthMock = nodemock.mock("getOAuthAccessToken").takesF(function (inCode, params, callback) { 119 | expect(inCode).to.equal(code); 120 | expect(params).to.eql({ 121 | grant_type: 'authorization_code', 122 | redirect_uri: undefined 123 | }); 124 | expect(typeof callback).to.equal("function"); 125 | callback(null, accessToken, refreshToken, params); 126 | return true; 127 | }).mock("get") 128 | .takes("https://graph.facebook.com/me", accessToken, function () { return undefined; }) 129 | .calls(2, [null, JSON.stringify(profile)]), 130 | facebookImpl = new Facebook({ 131 | clientID: "myClientId", 132 | clientSecret: "myClientSecret" 133 | }, function (passAccessToken, passRefreshToken, profile, verified) { 134 | expect(passAccessToken).to.equal(accessToken); 135 | expect(passRefreshToken).to.equal(refreshToken); 136 | expect(profile).to.equal(profile); 137 | verified(null, infoInput); 138 | }); 139 | facebookImpl[oauth2] = oauthMock; 140 | facebookImpl.success = function (info) { 141 | expect(info).to.equal(infoInput); 142 | done(); 143 | }; 144 | facebookImpl.authenticate({ session: {}, query: { code: code } }); 145 | }); 146 | }); 147 | 148 | Lab.experiment("authorizationParams", function () { 149 | test("normal patterns", function (done) { 150 | // Please see also https://developers.facebook.com/docs/reference/dialogs/oauth/ 151 | var options, 152 | displayList = ['page', 'popup', 'iframe', 'async', 'touch'], 153 | authTypeList = ['https', 'reauthenticate'], 154 | params, 155 | facebookImpl = new Facebook({ 156 | clientID: "myClientId", 157 | clientSecret: "myClientSecret" 158 | }, dontCall); 159 | displayList.forEach(function (display) { 160 | authTypeList.forEach(function (authType) { 161 | options = { 162 | display: display, 163 | authType: authType, 164 | authNonce: Math.random().toString() 165 | }; 166 | params = facebookImpl.authorizationParams(options); 167 | expect(params.display).to.equal(options.display); 168 | expect(params.auth_type).to.equal(options.authType); 169 | expect(params.auth_nonce).to.equal(options.authNonce); 170 | }); 171 | }); 172 | done(); 173 | }); 174 | }); 175 | 176 | Lab.experiment("userProfile", function () { 177 | test("normal patterns", function (done) { 178 | var InternalOAuthError = require('passport-oauth2').InternalOAuthError, 179 | FacebookGraphAPIError = require('passport-facebook/lib/errors/facebookgraphapierror'), 180 | profileURL = 'http://www.facebook.com/AtuyL', 181 | accessToken = 'dummy-access-token', 182 | errorObjectList = [ 183 | "error: string", 184 | { 185 | message: 'error: json', 186 | type: 'type', 187 | code: 'code', 188 | error_subcode: 'error_subcode' 189 | }, 190 | null 191 | ], 192 | errorPatterns = [ 193 | errorObjectList[0], // InternalOAuthError 194 | { data: JSON.stringify({ error: errorObjectList[1] }) }, // FacebookGraphAPIError 195 | errorObjectList[2] // not error 196 | ], 197 | bodyObjectList = [ 198 | "{ unparsable json }", 199 | { 200 | id: "id", 201 | username: "username", 202 | name: "name", 203 | last_name: "last_name", 204 | first_name: "first_name", 205 | middle_name: "middle_name", 206 | gender: "gender", 207 | link: "link", 208 | email: "email", 209 | picture: "picture" 210 | }, 211 | { 212 | id: "id", 213 | username: "username", 214 | name: "name", 215 | last_name: "last_name", 216 | first_name: "first_name", 217 | middle_name: "middle_name", 218 | gender: "gender", 219 | link: "link", 220 | email: "email", 221 | picture: { 222 | data: { 223 | url: "picture_data_url" 224 | } 225 | } 226 | } 227 | ], 228 | bodyPatterns = [ 229 | bodyObjectList[0], 230 | JSON.stringify(bodyObjectList[1]), 231 | JSON.stringify(bodyObjectList[2]) 232 | ], 233 | facebookImpl = new Facebook({ 234 | clientID: "800464516633425", 235 | clientSecret: "b78c7b021c42acb72242f1d7e354264a", 236 | callbackURL: "http://localhost:8080/callback", 237 | profileURL: profileURL 238 | }, dontCall); 239 | 240 | facebookImpl.redirect = function (authpath) { 241 | console.log(authpath); 242 | }; 243 | 244 | errorPatterns.forEach(function (error) { 245 | bodyPatterns.forEach(function (body, bIndex) { 246 | 247 | var mockOauth2 = nodemock.mock('get') 248 | .takes(profileURL, accessToken, function () { return undefined; }) 249 | .calls(2, [error, body, {}]), 250 | oauth2 = '_oauth2'; 251 | facebookImpl[oauth2] = mockOauth2; 252 | facebookImpl.userProfile(accessToken, function (e, p) { 253 | if (e) { 254 | if (e instanceof InternalOAuthError) { 255 | expect(p).to.equal(undefined); 256 | expect(e.name).to.equal('InternalOAuthError'); 257 | expect(e.message).to.equal('Failed to fetch user profile'); 258 | expect(e.oauthError).to.equal(errorObjectList[0]); 259 | } else if (e instanceof FacebookGraphAPIError) { 260 | expect(p).to.equal(undefined); 261 | expect(e.name).to.equal('FacebookGraphAPIError'); 262 | expect(e.message).to.equal(errorObjectList[1].message); 263 | expect(e.type).to.equal(errorObjectList[1].type); 264 | expect(e.code).to.equal(errorObjectList[1].code); 265 | expect(e.subcode).to.equal(errorObjectList[1].error_subcode); 266 | expect(e.status).to.equal(500); 267 | } else if (e instanceof Error) { 268 | expect(e.message).to.equal('Failed to parse user profile'); 269 | } else { 270 | done(new Error('Unexpected Error Type.')); 271 | } 272 | } else { 273 | expect(e).to.equal(null); 274 | var ref = bodyObjectList[bIndex]; 275 | expect(p.id).to.equal(ref.id); 276 | expect(p.username).to.equal(ref.username); 277 | expect(p.displayName).to.equal(ref.name); 278 | expect(p.name.familyName).to.equal(ref.last_name); 279 | expect(p.name.givenName).to.equal(ref.first_name); 280 | expect(p.name.middleName).to.equal(ref.middle_name); 281 | expect(p.gender).to.equal(ref.gender); 282 | expect(p.profileUrl).to.equal(ref.link); 283 | expect(p.emails[0].value).to.equal(ref.email); 284 | if (typeof ref.picture === 'string') { 285 | expect(p.photos[0].value).to.equal(ref.picture); 286 | } else { 287 | expect(p.photos[0].value).to.equal(ref.picture.data.url); 288 | } 289 | expect(p.provider).to.equal('facebook'); 290 | } 291 | }); 292 | }); 293 | }); 294 | done(); 295 | }); 296 | }); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var hp = require("../lib"), 4 | Lab = require("lab"), 5 | test = Lab.test, 6 | nodemock = require("nodemock"), 7 | expect = Lab.expect, 8 | requestStub = {}; 9 | 10 | Lab.experiment("Make sure that the facebook login", function () { 11 | 12 | test("should just work without any option", function (done) { 13 | hp({ 14 | authenticate: function () { return undefined; } 15 | })()(requestStub, {}); 16 | done(); 17 | }); 18 | 19 | test("should pass down the authentication", function (done) { 20 | var called = false; 21 | hp({ 22 | authenticate: function () { 23 | called = true; 24 | } 25 | })({})(requestStub, {}); 26 | expect(called).to.equal(true); 27 | done(); 28 | }); 29 | 30 | test("should put redirect on the strategies object", function (done) { 31 | var called = false, 32 | url = "hello world", 33 | responseMethod = function () { 34 | return { 35 | redirect: function (resultUrl) { 36 | expect(resultUrl).to.equal(url); 37 | called = true; 38 | } 39 | }; 40 | }; 41 | hp({ 42 | authenticate: function () { 43 | this.redirect(url); 44 | expect(called).to.equal(true); 45 | } 46 | })({})(requestStub, responseMethod); 47 | done(); 48 | }); 49 | 50 | test("should pass failure's to the failure option", function (done) { 51 | var called = false, 52 | mockFail = function (error) { 53 | expect(error.message).to.equal("foo"); 54 | called = true; 55 | }; 56 | 57 | hp({ 58 | authenticate: function () { 59 | this.fail({message: "foo"}); 60 | } 61 | })({ onFailed: mockFail })(requestStub, {}); 62 | expect(called).to.equal(true); 63 | done(); 64 | }); 65 | 66 | test("should pass error's to the error option", function (done) { 67 | var called = false, 68 | mockError = function mockError(error) { 69 | expect(error.message).to.equal("foo"); 70 | called = true; 71 | }; 72 | 73 | hp({ 74 | authenticate: function () { 75 | this.error({message: "foo"}); 76 | } 77 | })({ onError: mockError })(requestStub, {}); 78 | expect(called).to.equal(true); 79 | done(); 80 | }); 81 | 82 | test("should pass error's to the error option", function (done) { 83 | var called = false, 84 | mockError = function mockError(error) { 85 | expect(error.message).to.equal("foo"); 86 | called = true; 87 | }; 88 | 89 | hp({ 90 | authenticate: function () { 91 | this.error({message: "foo"}); 92 | } 93 | })({ onError: mockError })(requestStub, {}); 94 | expect(called).to.equal(true); 95 | done(); 96 | }); 97 | 98 | test("should use a error redirect if given", function (done) { 99 | var redirect = "http://test", 100 | originalError = {message: "foo"}, 101 | called = "", 102 | replyMethod = function (message, error) { 103 | expect(message).to.equal("Error while trying to login..."); 104 | expect(error).to.equal(originalError); 105 | called += "a"; 106 | return { 107 | redirect: function (uri) { 108 | expect(uri).to.equal(redirect); 109 | called += "b"; 110 | } 111 | }; 112 | }; 113 | 114 | hp({ 115 | authenticate: function () { 116 | this.error(originalError); 117 | } 118 | })({ errorRedirect: redirect })(requestStub, replyMethod); 119 | expect(called).to.equal("ab"); 120 | done(); 121 | }); 122 | 123 | test("should use a fail redirect if given", function (done) { 124 | var redirect = "http://test", 125 | originalError = {message: "foo"}, 126 | called = "", 127 | replyMethod = function (message, error) { 128 | expect(message).to.equal("Login failed, redirecting..."); 129 | expect(error).to.equal(originalError); 130 | called += "a"; 131 | return replyMethod; 132 | }; 133 | 134 | replyMethod.redirect = function (uri) { 135 | expect(uri).to.equal(redirect); 136 | called += "b"; 137 | }; 138 | 139 | hp({ 140 | authenticate: function () { 141 | this.fail(originalError); 142 | } 143 | })({ failRedirect: redirect })(requestStub, replyMethod); 144 | expect(called).to.equal("ab"); 145 | done(); 146 | }); 147 | 148 | test("should use a success redirect if given", function (done) { 149 | var redirect = "http://test", 150 | originalError = {message: "foo"}, 151 | called = "", 152 | replyMethod = function (message, error) { 153 | expect(message).to.equal("Login successful, redirecting..."); 154 | expect(error).to.equal(originalError); 155 | called += "a"; 156 | return replyMethod; 157 | }; 158 | 159 | replyMethod.redirect = function (uri) { 160 | expect(uri).to.equal(redirect); 161 | called += "b"; 162 | }; 163 | 164 | hp({ 165 | authenticate: function () { 166 | this.success(originalError); 167 | } 168 | })({ successRedirect: redirect })(requestStub, replyMethod); 169 | expect(called).to.equal("ab"); 170 | done(); 171 | }); 172 | 173 | test("should just reply with a error when a error occurs", function (done) { 174 | var originalError = {message: "foo"}, 175 | called = "", 176 | replyMethod = function (error) { 177 | expect(error).to.equal(originalError); 178 | called += "a"; 179 | return replyMethod; 180 | }; 181 | 182 | hp({ 183 | authenticate: function () { 184 | this.error(originalError); 185 | } 186 | })()(requestStub, replyMethod); 187 | expect(called).to.equal("a"); 188 | done(); 189 | }); 190 | 191 | test("should just reply with the fail message when a failure occurs", function (done) { 192 | var originalError = {message: "foo"}, 193 | called = "", 194 | replyMethod = function (error) { 195 | expect(error).to.equal(originalError); 196 | called += "a"; 197 | return replyMethod; 198 | }; 199 | 200 | hp({ 201 | authenticate: function () { 202 | this.fail(originalError); 203 | } 204 | })()(requestStub, replyMethod); 205 | expect(called).to.equal("a"); 206 | done(); 207 | }); 208 | 209 | test("should just reply with the info message when the request was successful", function (done) { 210 | var originalInfo = {message: "foo"}, 211 | called = "", 212 | replyMethod = function (error) { 213 | expect(error).to.equal(originalInfo); 214 | called += "a"; 215 | return replyMethod; 216 | }; 217 | 218 | hp({ 219 | authenticate: function () { 220 | this.success(originalInfo); 221 | } 222 | })()(requestStub, replyMethod); 223 | expect(called).to.equal("a"); 224 | done(); 225 | }); 226 | }); --------------------------------------------------------------------------------