UNPKG

coco-the-bear-auth-sessiontoken

Version:

A route handler for CoCo The Bear that enforces session token authentication and authorization.

278 lines (235 loc) 11 kB
const EventEmitter = require('events'); const ObjectID = require('mongodb').ObjectID; const httpErrors = require('@dannybster/coco-the-bear-http-errors'); const systemUnderTest = require('../src/authorization'); const testUtils = require('@dannybster/coco-the-bear-test-utils'); const { notFound, serviceUnavailable, } = httpErrors; describe('authorization.js', function () { let collection; let req; beforeEach(function createCommonPreConditions() { req = { datasource: { db: { collection: (name) => { if (name === 'authorizationDocuments') { return collection; } return null; }, }, }, events: { emitter: new EventEmitter(), }, user: { _id: new ObjectID().toString(), }, }; }); describe('#createAuthorizationDocument', function () { let expectedAuthorizationDocument; beforeEach(function createCommonPreConditionsForCreateAuthorizationDocument() { collection = { insertOne: (doc) => { collection.insertOne.parameters = { doc }; }, }; req.datasource.result = { _id: new ObjectID(), }; expectedAuthorizationDocument = { documentId: req.datasource.result._id, userId: req.user._id, }; }); it('It should ask the mongodb database at req.datasource.db to create an authorization record.', function testCreateAuthorizationDocumentDatabaseApiCall() { systemUnderTest .createAuthorizationDocument(req, null, testUtils.assertCalledOnce(testUtils.noop)); collection.insertOne.parameters.should.eql({ doc: expectedAuthorizationDocument }); }); it('It should emit an app event describing the authorization document.', function testCreateAuthorizationDocumentAppEvent(done) { req.events.emitter.once('app-event', (data) => { data.should.eql({ message: 'Creating an authorization document. See the object for more info.', object: expectedAuthorizationDocument, }); done(); }); systemUnderTest .createAuthorizationDocument(req, null, testUtils.assertCalledOnce(testUtils.noop)); }); describe('If the database driver callsback without an error', function () { beforeEach(function setupDatabaeToCallBackWithSuccess() { collection.insertOne = (doc, callback) => { process.nextTick(callback.bind(null)); }; }); it('It should call next without an error.', function testCreateAuthorizationDocumentSuccessCallsNext(done) { systemUnderTest.createAuthorizationDocument(req, null, done); }); it('It should emit an app-event describing that the authorization record was successfully create.', function testAppEventAuthorizationRecordSuccessfullyCreate(done) { systemUnderTest .createAuthorizationDocument(req, null, testUtils.assertCalledOnce(testUtils.noop)); req.events.emitter.on('app-event', (data) => { data.should.eql({ message: 'Authorization document successfully created. See the object for more info.', object: expectedAuthorizationDocument, }); done(); }); }); }); describe('If the database driver callsback with an error', function () { let expectedErr; beforeEach(function setupDatabaseToCallbackWithError() { const cause = new Error('Database says no!!!'); const message = 'Error creating an authorization document. See the info object for more info.'; const responseBody = { message: 'Error creating authorization document.' }; expectedErr = serviceUnavailable .createError(message, expectedAuthorizationDocument, responseBody, cause); collection.insertOne = (doc, callback) => { process.nextTick(callback.bind(null, cause)); }; req.events.emitter.on('error', testUtils.noop); }); it('It should callback with a service unavailable error.', function testDatabaseErrorCallsNextWithServiceUnavailableError(done) { systemUnderTest.createAuthorizationDocument(req, null, testUtils.assertCalledOnce((err) => { err.should.eql(expectedErr); done(); })); }); it('It should emit a descriptive error event instead of the successful app-event.', function testDatabaseErrorEmitsDescriptiveErrorEvent(done) { systemUnderTest.createAuthorizationDocument(req, null, testUtils.noop); const emitter = req.events.emitter; emitter.on('app-event', () => { throw new Error('This event should not have been called under error conditions.'); }); emitter.on('error', testUtils.assertCalledOnce((err) => { err.should.eql(expectedErr); done(); })); }); }); }); describe('#authorizeRequest', function () { let expectedAuthorizationDocument; beforeEach(function createCommonPreConditionsForAuthorizeRequest() { collection = { findOne: (query) => { collection.findOne.parameters = { query }; }, }; req.params = { identifier: new ObjectID().toString(), }; expectedAuthorizationDocument = { documentId: new ObjectID(req.params.identifier), userId: req.user._id, }; }); it('It should ask the database driver to search for an authorization document matching the identifier parameter and the current user.', function testFindAuthorizationDocumentDatabaseApiCall() { systemUnderTest.authorizeRequest(req, null, testUtils.noop); collection.findOne.parameters.should.eql({ query: expectedAuthorizationDocument, }); }); it('It should emit an app event that it is looking up an authorization document.', function testEmitFindAuthorizationDocumentEvent(done) { req.events.emitter.once('app-event', (data) => { data.should.eql({ message: 'Looking up an authorization document. See the object for more info.', object: expectedAuthorizationDocument, }); done(); }); systemUnderTest.authorizeRequest(req, null, testUtils.noop); }); describe('If the database driver calls back without an error', function () { describe('And a result object', function () { let foundAuthorizationDocument; beforeEach(function createPreConditionsForFoundAuthorizationDocument() { collection.findOne = (query, callback) => { foundAuthorizationDocument = Object.assign({ _id: new ObjectID() }, expectedAuthorizationDocument); process.nextTick(callback.bind(null, null, foundAuthorizationDocument)); }; }); it('It should call next.', function testDatabaseFindsAuthorizationDocumentCallsNext(done) { systemUnderTest.authorizeRequest(req, null, testUtils.assertCalledOnce(done)); }); it('It should emit an app-event that describes it has found an authorization document.', function testDatabaseFindsAuthorizationDocumentEmitsAppEvent(done) { systemUnderTest.authorizeRequest(req, null, testUtils.noop); req.events.emitter.on('app-event', testUtils.assertCalledOnce((data) => { data.should.eql({ message: 'Successfully found an authorization document. See the object for more info.', object: foundAuthorizationDocument, }); done(); })); }); }); describe('And a null result object', function () { let expectedNotFoundError; beforeEach(function createPreConditionsForNotFoundAuthorizationDocument() { const message = 'Authorization document not found. See the info object for more info.'; const responseBody = { message: `A document matching ${expectedAuthorizationDocument.documentId.toString()} could not be found for the user ${req.user._id}` }; expectedNotFoundError = notFound.createError(message, expectedAuthorizationDocument, responseBody); req.events.emitter.on('error', testUtils.noop); collection.findOne = (query, callback) => { process.nextTick(callback.bind(null, null, null)); }; }); it('It should call next with a not found error.', function testAuthorizationDocumentNotFoundCallsNextWithNotFoundError(done) { systemUnderTest.authorizeRequest(req, null, testUtils.assertCalledOnce((err) => { err.should.eql(expectedNotFoundError); done(); })); }); it('It should emit a descriptive not found error event instead of a successful app-event.', function testAuthorizationDocumentNotFoundEmitsNotFoundError(done) { systemUnderTest.authorizeRequest(req, null, testUtils.noop); req.events.emitter.on('app-event', () => { throw new Error('app-event should not have been called.'); }); req.events.emitter.on('error', testUtils.assertCalledOnce((err) => { err.should.eql(expectedNotFoundError); done(); })); }); }); }); describe('If the database driver calls back with an error', function () { const cause = new Error('These are not the databases you are looking for...'); let expectedServiceUnavailablError; beforeEach(function createPreConditionsForNotFoundAuthorizationDocument() { const message = 'Error finding an authorization document. See the info object for more info.'; const responseBody = { message: 'Error finding an authorization document.' }; expectedServiceUnavailablError = notFound.createError(message, expectedAuthorizationDocument, responseBody, cause); req.events.emitter.on('error', testUtils.noop); collection.findOne = (query, callback) => { process.nextTick(callback.bind(null, cause)); }; }); it('It should call next with a descriptive service unavailable error.', function testDatabaseDriverErrorCallsNextWithServiceUnvailableError(done) { systemUnderTest.authorizeRequest(req, null, testUtils.assertCalledOnce((err) => { err.should.eql(expectedServiceUnavailablError); done(); })); }); it('It should emit a descriptive service unavailable error event instead of a successful app-event.', function testAuthorizationDocumentNotFoundEmitsNotFoundError(done) { systemUnderTest.authorizeRequest(req, null, testUtils.noop); req.events.emitter.on('app-event', () => { throw new Error('app-event should not have been called.'); }); req.events.emitter.on('error', testUtils.assertCalledOnce((err) => { err.should.eql(expectedServiceUnavailablError); done(); })); }); }); }); });