Skip to content

Commit

Permalink
Added null client
Browse files Browse the repository at this point in the history
  • Loading branch information
dafortune committed Mar 2, 2017
1 parent a805bc8 commit 588d883
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 2 deletions.
11 changes: 10 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ var auth0Ticket = require('./utils/auth0_ticket');
var httpClient = require('./utils/http_client');
var transactionFactory = require('./transaction/factory');
var clientFactory = require('./utils/client_factory');

var apiTransport = {
polling: 'polling',
manual: 'polling',
socket: 'socket'
};

/**
* @public
*
Expand Down Expand Up @@ -81,7 +88,9 @@ auth0GuardianJS.prototype.start = function start(callback) {

self.httpClient.post('/api/start-flow',
self.credentials,
{ state_transport: self.transport },
// TODO: polling is not a good name for api state checking since
// it could be polling or manual checking
{ state_transport: apiTransport[self.transport] },
function startTransaction(err, txLegacyData) {
if (err) {
callback(err);
Expand Down
3 changes: 3 additions & 0 deletions lib/utils/client_factory.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var polling = require('./polling_client');
var socketio = require('./socket_client');
var nullClient = require('./null_client');

exports.create = function create(options) {
var serviceUrl = options.serviceUrl;
Expand All @@ -13,6 +14,8 @@ exports.create = function create(options) {

if (transport === 'polling') {
return polling(serviceUrl, { httpClient: httpClient });
} else if (transport === 'manual') {
return nullClient();
}

// default socket
Expand Down
23 changes: 23 additions & 0 deletions lib/utils/null_client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

// Null client used when no event client listener is needed (transport=manual)

var EventEmitter = require('events').EventEmitter;
var object = require('./object');
var asyncHelpers = require('./async');

function nullClient() {
var self = object.create(nullClient.prototype);

EventEmitter.call(self);

return self;
}

nullClient.prototype = new EventEmitter();

nullClient.prototype.connect = function connect(token, callback) {
asyncHelpers.setImmediate(callback);
};

module.exports = nullClient;
190 changes: 189 additions & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const expect = require('chai').expect;
const guardianjsb = require('../lib');
const sinon = require('sinon');
// const EventEmitter = require('events').EventEmitter;
const nullClient = require('../lib/utils/null_client');

describe('guardian.js', function () {
let httpClient;
Expand Down Expand Up @@ -308,6 +308,193 @@ describe('guardian.js', function () {
});
});
});

describe('when transport is manual', function () {
describe('when everything works ok', function () {
let response;

beforeEach(function () {
response = {
deviceAccount: {
methods: ['otp'],
availableMethods: ['otp'],
name: 'test',
phoneNumber: '+1234'
},
availableEnrollmentMethods: ['otp'],
availableAuthenticationMethods: ['push'],
transactionToken
};

socketClient.connect.yields();
httpClient.post.yields(null, response);

guardianjs = guardianjsb({
serviceUrl: 'https://tenant.guardian.auth0.com',
requestToken,
issuer: {
label: 'label',
name: 'name'
},
accountLabel: 'accountLabel',
globalTrackingId: 'globalTrackingId',
dependencies: {
httpClient
},
transport: 'manual'
});
});

it('calls start flow as expected', function (done) {
guardianjs.start((err) => {
expect(err).not.to.exist;
expect(httpClient.post.calledOnce).to.be.true;

const call = httpClient.post.getCall(0);
expect(call.args[0]).to.equal('/api/start-flow');
expect(call.args[1].getAuthHeader()).to.equal(`Bearer ${requestToken}`);
expect(call.args[2]).to.eql({ state_transport: 'polling' });

done();
});
});

it('uses nullClient', function () {
// This is an implementation detail, but worth checking IMO
expect(guardianjs.socketClient).to.be.an.instanceOf(nullClient);
});

describe('for an user already enrolled', function () {
beforeEach(function () {
response = {
deviceAccount: {
methods: ['otp'],
availableMethods: ['otp'],
name: 'test',
phoneNumber: '+1234'
},
availableEnrollmentMethods: ['otp'],
availableAuthenticationMethods: ['push'],
transactionToken,
transport: 'manual'
};

socketClient.connect.yields();
httpClient.post.yields(null, response);

guardianjs = guardianjsb({
serviceUrl: 'https://tenant.guardian.auth0.com',
requestToken,
issuer: {
label: 'label',
name: 'name'
},
accountLabel: 'accountLabel',
globalTrackingId: 'globalTrackingId',
dependencies: {
httpClient,
socketClient
},
transport: 'manual'
});
});

it('callbacks with an enrolled-transaction', function (done) {
guardianjs.start((err, tx) => {
expect(err).not.to.exist;

const enrollment = tx.getEnrollments()[0];
expect(enrollment).to.exist;

expect(tx.isEnrolled()).to.be.true;

expect(enrollment.getAvailableMethods())
.to.eql(response.deviceAccount.availableMethods);
expect(enrollment.getMethods())
.to.eql(response.deviceAccount.methods);
expect(enrollment.getName())
.to.eql(response.deviceAccount.name);
expect(enrollment.getPhoneNumber())
.to.eql(response.deviceAccount.phoneNumber);

expect(tx.transactionToken.getAuthHeader())
.to.equal(`Bearer ${transactionToken}`);

done();
});
});
});

describe('for an user not enrolled', function () {
beforeEach(function () {
response = {
deviceAccount: {
id: '1234',
otpSecret: 'abcd1234',
recoveryCode: '12asddasdasdasd'
},
availableEnrollmentMethods: ['otp'],
availableAuthenticationMethods: ['push'],
enrollmentTxId: '1234678',
transactionToken,
transport: 'manual'
};

socketClient.connect.yields();
httpClient.post.yields(null, response);

guardianjs = guardianjsb({
serviceUrl: 'https://tenant.guardian.auth0.com',
requestToken,
issuer: {
label: 'label',
name: 'name'
},
accountLabel: 'accountLabel',
globalTrackingId: 'globalTrackingId',
dependencies: {
httpClient,
socketClient
}
});
});

it('callbacks with a non enrolled transaction', function (done) {
guardianjs.start((err, tx) => {
expect(err).not.to.exist;

const enrollment = tx.getEnrollments()[0];
expect(enrollment).not.to.exist;

expect(tx.getAvailableEnrollmentMethods()).to.eql(['otp']);
expect(tx.getAvailableAuthenticationMethods()).to.eql(['push']);

expect(tx.enrollmentAttempt.getEnrollmentTransactionId())
.to.eql(response.enrollmentTxId);
expect(tx.enrollmentAttempt.getOtpSecret())
.to.eql(response.deviceAccount.otpSecret);
expect(tx.enrollmentAttempt.getIssuerName())
.to.eql('name');
expect(tx.enrollmentAttempt.getIssuerLabel())
.to.eql('label');
expect(tx.enrollmentAttempt.getAccountLabel())
.to.eql('accountLabel');
expect(tx.enrollmentAttempt.getRecoveryCode())
.to.eql(response.deviceAccount.recoveryCode);
expect(tx.enrollmentAttempt.getEnrollmentId())
.to.eql(response.deviceAccount.id);
expect(tx.enrollmentAttempt.getBaseUri())
.to.equal('https://tenant.guardian.auth0.com');

expect(tx.transactionToken.getAuthHeader())
.to.equal(`Bearer ${transactionToken}`);

done();
});
});
});
});
});
});

describe('#resume', function () {
Expand Down Expand Up @@ -359,3 +546,4 @@ describe('guardian.js', function () {
});
});
});

37 changes: 37 additions & 0 deletions test/utils/client_factory.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

const expect = require('chai').expect;
const clientFactory = require('../../lib/utils/client_factory');
const pollingClient = require('../../lib/utils/polling_client');
const socketClient = require('../../lib/utils/socket_client');
const nullClient = require('../../lib/utils/null_client');

describe('client factory', function () {
describe('#create', function () {
describe('if options.dependency is defined', function () {
it('returns the dependency', function () {
const dependency = { hola: 'hello' };

expect(clientFactory.create({ dependency })).to.equal(dependency);
});
});

describe('if options.transport is polling', function () {
it('returns an instance of polling client', function () {
expect(clientFactory.create({ transport: 'polling', serviceUrl: 'http://localhost', httpClient: {} })).to.be.an.instanceOf(pollingClient);
});
});

describe('if options.transport is manual', function () {
it('returns an instance of null client', function () {
expect(clientFactory.create({ transport: 'manual' })).to.be.an.instanceOf(nullClient);
});
});

describe('if options.transport is not defined', function () {
it('returns an instance of socket client', function () {
expect(clientFactory.create({ serviceUrl: 'http://localhost' })).to.be.an.instanceOf(socketClient);
});
});
});
});
30 changes: 30 additions & 0 deletions test/utils/null_client.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

const expect = require('chai').expect;
const nullClientBuilder = require('../../lib/utils/null_client');

describe('utils/null_client', function () {
let nullClient;

beforeEach(function () {
nullClient = nullClientBuilder();
});

it('has method #on', function () {
expect(nullClient).to.have.respondsTo('on');
});

it('has method #once', function () {
expect(nullClient).to.have.respondsTo('once');
});

it('has method #removeAllListeners', function () {
expect(nullClient).to.have.respondsTo('removeAllListeners');
});

describe('#connect', function () {
it('callbacks immediatelly', function (done) {
nullClient.connect('abc', done);
});
});
});

0 comments on commit 588d883

Please sign in to comment.