/** * Unit testing file for ElevateSession command */ 'use strict'; /* eslint max-nested-callbacks: ["error", 7] import/max-dependencies: ["error", {"max": 13}] */ // eslint-disable-next-line no-unused-vars const testGlobals = require('../../tools/test/testGlobals.js'); const _ = require('lodash'); const Q = require('q'); const chai = require('chai'); const sinon = require('sinon'); const sinonChai = require('sinon-chai'); const chaiAsPromised = require('chai-as-promised'); const rewire = require('rewire'); const JsonRefs = require('json-refs'); const mongodb = require('mongodb'); const {MockRequest} = require('../../utils/test/mock-request'); const {bridgeBodyParser} = require('../api_body_middleware.js'); const utils = require('../../ComServe/utils'); /** * Use rewire to pull in the unit under test, and then get access to the * private variables to stub them */ const apiSecurityDevice = rewire('../api_security_device.js'); const authStub = apiSecurityDevice.__get__('auth'); const flagsStub = apiSecurityDevice.__get__('featureFlags'); const referencesStub = apiSecurityDevice.__get__('references'); const mainDBPStub = apiSecurityDevice.__get__('mainDBP'); /** * Set up chai & sinon to simplify the tests */ const expect = chai.expect; const sandbox = sinon.createSandbox(); chai.use(sinonChai); chai.use(chaiAsPromised); /** * Make a promise-style version of the security handler for easier testing */ const deviceSessionP = (req, def, scopes) => Q.nfcall( apiSecurityDevice.deviceSession, req, def, scopes ); const hmacNoSessionP = (req, def, scopes) => Q.nfcall( apiSecurityDevice.deviceHmacNoSession, req, def, scopes ); const COLLECTION_DEVICES = 'Mock devices collection parameter'; /** * Valid values */ const DEVICE_TOKEN = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop'; const SESSION_TOKEN = 'qrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUV'; const DEVICE_MONGO_ID = (new mongodb.ObjectID()).toHexString(); // New random ObjectID const CLIENT_NAME = 'a@example.com'; const CLIENT_MONGO_ID = (new mongodb.ObjectID()).toHexString(); // New random ObjectID const CLIENT_ID = 'A unique random value generated by us'; const CLIENT_DISPLAY_NAME = 'Display Name'; const SESSION_HEADER = DEVICE_TOKEN + ':' + SESSION_TOKEN; const HMAC_HEADER = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; const TIMESTAMP_HEADER = new Date().toISOString(); const PROTOCOL = 'https'; const PATH = '/api/v0/devices'; const EXPECTED_FULL_URL = 'https://unittest.example.com' + PATH; const METHOD = 'get'; const SESSION_ID = 'A session id as-if generated by express-session middleware'; const DB_FEATURE_FLAGS = ['unit-test']; const DB_DEVICE = { ClientID: CLIENT_ID, DeviceToken: DEVICE_TOKEN, DeviceStatus: utils.DeviceFullyRegistered }; const DB_CLIENT = { _id: CLIENT_MONGO_ID, ClientName: CLIENT_NAME, ClientID: CLIENT_ID, DisplayName: CLIENT_DISPLAY_NAME, FeatureFlags: DB_FEATURE_FLAGS, ClientStatus: utils.ClientEmailVerifiedMask }; const MOCK_SWAGGER_PATHNAME = '/test-api-security-device'; const MOCK_SWAGGER_FEATURE_FLAG = 'unit-test'; const MOCK_SWAGGER_DEFINITION = { post: { summary: 'Just a test', description: 'Just a test', 'x-feature-flag': MOCK_SWAGGER_FEATURE_FLAG, responses: { 200: { description: 'Success' } } } }; /** * Mock request for requests that use the standard security model */ const MOCK_REQUEST_OPTIONS = { headers: { 'x-bridge-device-session': SESSION_HEADER, 'x-bridge-hmac': HMAC_HEADER, 'x-bridge-timestamp': TIMESTAMP_HEADER }, originalUrl: PATH, protocol: PROTOCOL, method: METHOD, sessionID: SESSION_ID, session: {} // Empty session as-if created by express-session }; const MOCK_REQUEST_BODY = '{\n' + ' "test": "value",\n' + ' "other": "value2"\n' + '}'; const MOCK_REQUEST_BODY_OPTIONS = { mockBody: MOCK_REQUEST_BODY }; /** * Mock request for requests that use the "no session" security model */ const SWAGGER_PATH_LOGIN = '/devices/{objectId}/login'; const PATH_LOGIN = '/api/v0/devices/' + DEVICE_MONGO_ID + '/login'; const EXPECTED_FULL_URL_LOGIN = 'https://unittest.example.com' + PATH_LOGIN; const METHOD_LOGIN = 'POST'; const MOCK_LOGIN_REQUEST_OPTIONS = { headers: { 'x-bridge-hmac': HMAC_HEADER, 'x-bridge-timestamp': TIMESTAMP_HEADER }, originalUrl: PATH_LOGIN, protocol: PROTOCOL, method: METHOD_LOGIN, sessionID: SESSION_ID, session: {} // Empty session as-if created by express-session }; const MOCK_LOGIN_REQUEST_BODY = '{\n' + ' "ClientName": "' + CLIENT_NAME + '"\n' + '}'; const MOCK_LOGIN_REQUEST_BODY_OPTIONS = { mockBody: MOCK_LOGIN_REQUEST_BODY }; /** * Function to create a mock `req` objects that mimics the important parts of a * real request object. * * @param {Object} resolvedSwagger - The **resolved** swagger object (i.e. all refs resolved) * @param {Object} reqOptions - The additional fields to add to the request object * @param {Object} bodyOptions - Additional params for creating the MockRequest (provides the body) */ function createMockReq(resolvedSwagger, reqOptions, bodyOptions) { const req = new MockRequest(_.cloneDeep(bodyOptions)); _.merge(req, _.cloneDeep(reqOptions)); return req; } /** * The tests */ describe('Device security validation', () => { let resolvedSwagger; let req; const def = {}; /** * Before we run any tests we need to resolve all the references within * the swagger specification. */ before(() => { /** * Set some values for the collections so we can differentiate them */ mainDBPStub.mainDB._collectionDevice = mainDBPStub.mainDB.collectionDevice; mainDBPStub.mainDB.collectionDevice = COLLECTION_DEVICES; /** * Load the swagger files and merge them back into a single file */ return JsonRefs .resolveRefsAt(require.resolve('../api_swagger_def.json')) .then((swagger) => { /** * Add our test path to the swagger */ resolvedSwagger = swagger.resolved; resolvedSwagger.paths[MOCK_SWAGGER_PATHNAME] = MOCK_SWAGGER_DEFINITION; // // Add them to the default options // _.assign(MOCK_REQUEST_OPTIONS, { swagger: { swaggerObject: resolvedSwagger, operation: resolvedSwagger.paths[MOCK_SWAGGER_PATHNAME].post } }); return resolvedSwagger; }); }); after(() => { /** * Set the collections back */ mainDBPStub.mainDB.collectionDevice = mainDBPStub.mainDB._collectionDevice; delete mainDBPStub.mainDB._collectionDevice; }); describe('device_session security', () => { /** * Before each test, stub the auth functions we will be calling. */ beforeEach((done) => { req = createMockReq(resolvedSwagger, MOCK_REQUEST_OPTIONS, MOCK_REQUEST_BODY_OPTIONS); sandbox.stub(authStub, 'validateCurrentSession').resolves([DB_DEVICE, DB_CLIENT]); sandbox.stub(authStub, 'checkHMAC').resolves(); sandbox.spy(flagsStub, 'isEnabled'); // // Run the mock request through the middleware to get the parsed and raw bodies // bridgeBodyParser()(req, {}, done); }); /** * After each test we reset the sanbox to reset all stubs etc. */ afterEach(() => { sandbox.restore(); }); describe('With validly formatted params that are correct', () => { it('checks the device and session tokens', () => { return deviceSessionP(req, def, SESSION_HEADER).then(() => { return expect(authStub.validateCurrentSession) .to.have.been.calledOnce .calledWith( DEVICE_TOKEN, SESSION_TOKEN ); }); }); it('checks the HMAC', () => { return deviceSessionP(req, def, SESSION_HEADER).then(() => { return expect(authStub.checkHMAC) .to.have.been.calledOnce .calledWith( DB_DEVICE, { address: EXPECTED_FULL_URL, method: METHOD, body: MOCK_REQUEST_BODY + DEVICE_TOKEN + ':' + SESSION_TOKEN, ClientName: CLIENT_NAME, timestamp: TIMESTAMP_HEADER, hmac: HMAC_HEADER } ); }); }); it('checks the FeatureFlags if they are required for the request', () => { return deviceSessionP(req, def, SESSION_HEADER).then(() => { return expect(flagsStub.isEnabled) .to.have.been.calledOnce .calledWith( MOCK_SWAGGER_FEATURE_FLAG, DB_CLIENT ); }); }); it('doesn\'t check the FeatureFlags if they are NOT required for the request', () => { delete req.swagger.operation['x-feature-flag']; return deviceSessionP(req, def, SESSION_HEADER).then(() => { return expect(flagsStub.isEnabled) .to.not.have.been.called; }); }); it('stores web session data + the client (as clientObj) and device (as deviceObj) in req.session.data for controller to use', () => { return deviceSessionP(req, def, SESSION_HEADER).then(() => { return expect(req.session.data) .to.deep.equal({ // // Existing session details for old web console requests // client: CLIENT_MONGO_ID, clientID: CLIENT_ID, displayName: CLIENT_DISPLAY_NAME, email: CLIENT_NAME, isMerchant: false, isVATRegistered: false, FeatureFlags: DB_FEATURE_FLAGS, // // New sessiond details for App APIs copied across // clientObj: DB_CLIENT, deviceObj: DB_DEVICE, isDeviceSession: true }); }); }); it('clears req.sessionID so express-session doesn\'t persist the session', () => { return deviceSessionP(req, def, SESSION_HEADER).then(() => { return expect(req.sessionID) .to.be.null; }); }); it('passes the security tests', () => { return expect(deviceSessionP(req, def, SESSION_HEADER)) .to.eventually.be.fulfilled; }); }); describe('With validly formatted params that are wrong', () => { it('rejects when required feature flag isn\'t enabled', () => { const NO_FEATURE_FLAG_CLIENT = { ClientName: CLIENT_NAME }; authStub.validateCurrentSession.resolves([DB_DEVICE, NO_FEATURE_FLAG_CLIENT]); return expect(deviceSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when validating current session fails', () => { authStub.validateCurrentSession.rejects(); return expect(deviceSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when checking HMAC fails', () => { authStub.checkHMAC.rejects(); return expect(deviceSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('leaves req.sessionID alone when device session verifcation fails', () => { authStub.checkHMAC.rejects(); return deviceSessionP(req, def, SESSION_HEADER).catch(() => { return expect(req.sessionID) .to.equal(SESSION_ID); }); }); }); describe('With invalidly formatted params', () => { describe('Rejects when x-bridge-session-device is the wrong format: ', () => { it('missing entirely', () => { return expect(deviceSessionP(req, def, undefined)).to .eventually.be.rejected; }); it('device token too short', () => { const token = DEVICE_TOKEN.slice(0, -1) + ':' + SESSION_TOKEN; return expect(deviceSessionP(req, def, token)).to .eventually.be.rejected; }); it('device token too long', () => { const token = DEVICE_TOKEN + 'a:' + SESSION_TOKEN; return expect(deviceSessionP(req, def, token)).to .eventually.be.rejected; }); it('device token invalid char', () => { const token = DEVICE_TOKEN.slice(0, -1) + '!:' + SESSION_TOKEN; return expect(deviceSessionP(req, def, token)).to .eventually.be.rejected; }); it('session token too short', () => { const token = DEVICE_TOKEN + ':' + SESSION_TOKEN.slice(0, -1); return expect(deviceSessionP(req, def, token)).to .eventually.be.rejected; }); it('session token too long', () => { const token = DEVICE_TOKEN + ':b' + SESSION_TOKEN; return expect(deviceSessionP(req, def, token)).to .eventually.be.rejected; }); it('session token invalid char', () => { const token = DEVICE_TOKEN + ':?' + SESSION_TOKEN.slice(0, -1); return expect(deviceSessionP(req, def, token)).to .eventually.be.rejected; }); it('wrong character between tokens', () => { const token = DEVICE_TOKEN + ';' + SESSION_TOKEN; return expect(deviceSessionP(req, def, token)).to .eventually.be.rejected; }); }); describe('Rejects when x-bridge-hmac is the wrong format: ', () => { it('missing entirely', () => { delete req.headers['x-bridge-hmac']; return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('too short', () => { req.headers['x-bridge-hmac'] = HMAC_HEADER.slice(0, -1); return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('too long', () => { req.headers['x-bridge-hmac'] = HMAC_HEADER + 'a'; return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('invalid char', () => { req.headers['x-bridge-hmac'] = HMAC_HEADER.slice(0, -1) + '!'; return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); }); describe('Rejects when x-bridge-timestamp is the wrong format: ', () => { it('missing entirely', () => { delete req.headers['x-bridge-timestamp']; return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('not a string', () => { req.headers['x-bridge-timestamp'] = Date.now(); return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('only has date', () => { req.headers['x-bridge-timestamp'] = new Date().toDateString(); return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('only has time', () => { req.headers['x-bridge-timestamp'] = new Date().toTimeString(); return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); }); }); }); describe('device_hmac_nosession security', () => { /** * Before each test, stub the auth functions we will be calling. */ beforeEach((done) => { // // Setup the swagger details as-if parsed from the request by the // swagger middleware // _.assign(MOCK_LOGIN_REQUEST_OPTIONS, { swagger: { swaggerObject: resolvedSwagger, operation: resolvedSwagger.paths[SWAGGER_PATH_LOGIN].post, params: { objectId: { value: DEVICE_MONGO_ID }, body: { value: { ClientName: CLIENT_NAME } } } } }); req = createMockReq( resolvedSwagger, MOCK_LOGIN_REQUEST_OPTIONS, MOCK_LOGIN_REQUEST_BODY_OPTIONS ); sandbox.stub(authStub, 'checkHMAC').resolves(); sandbox.stub(referencesStub, 'getClientByEmail').resolves(DB_CLIENT); sandbox.stub(mainDBPStub, 'findOneObject').resolves(DB_DEVICE); sandbox.spy(flagsStub, 'isEnabled'); sandbox.spy(authStub, 'checkClientStatus'); sandbox.spy(authStub, 'checkDeviceStatus'); // // Run the mock request through the middleware to get the parsed and raw bodies // bridgeBodyParser()(req, {}, done); }); /** * After each test we reset the sanbox to reset all stubs etc. */ afterEach(() => { sandbox.restore(); }); describe('With validly formatted params that are correct', () => { it('gets the client based on the ClientName in the body', () => { return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(referencesStub.getClientByEmail) .to.have.been.calledOnce .calledWith(CLIENT_NAME); }); }); it('gets the device based on the objectID if it is owned by the correct client', () => { return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(mainDBPStub.findOneObject) .to.have.been.calledOnce .calledWith( mainDBPStub.mainDB.collectionDevice, { _id: mongodb.ObjectID(DEVICE_MONGO_ID), ClientID: CLIENT_ID } ); }); }); it('checks the client is in a valid state', () => { return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(authStub.checkClientStatus) .to.have.been.calledOnce .calledWith(utils.ClientEmailVerifiedMask) .returned(null); // Null for no errors }); }); it('checks the device is in a valid state', () => { return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(authStub.checkDeviceStatus) .to.have.been.calledOnce .calledWith(utils.DeviceFullyRegistered) .returned(null); // Null for no errors }); }); it('checks the HMAC, with the function name set to Login1.process', () => { return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(authStub.checkHMAC) .to.have.been.calledOnce .calledWith( DB_DEVICE, { address: EXPECTED_FULL_URL_LOGIN, method: METHOD_LOGIN, body: MOCK_LOGIN_REQUEST_BODY, ClientName: CLIENT_NAME, timestamp: TIMESTAMP_HEADER, hmac: HMAC_HEADER }, 'Login1.process' // Renamed to Login1.process to match expectations ); }); }); it('checks the FeatureFlags if they are required for the request', () => { req.swagger.operation['x-feature-flag'] = MOCK_SWAGGER_FEATURE_FLAG; return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(flagsStub.isEnabled) .to.have.been.calledOnce .calledWith( MOCK_SWAGGER_FEATURE_FLAG, DB_CLIENT ); }); }); it('doesn\'t check the FeatureFlags if they are NOT required for the request', () => { delete req.swagger.operation['x-feature-flag']; return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(flagsStub.isEnabled) .to.not.have.been.called; }); }); it('stores web session data + the client (as clientObj) and device (as deviceObj) in req.session.data for controller to use', () => { return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(req.session.data) .to.deep.equal({ // // Existing session details for old web console requests // client: CLIENT_MONGO_ID, clientID: CLIENT_ID, displayName: CLIENT_DISPLAY_NAME, email: CLIENT_NAME, isMerchant: false, isVATRegistered: false, FeatureFlags: DB_FEATURE_FLAGS, // // New sessiond details for App APIs copied across // clientObj: DB_CLIENT, deviceObj: DB_DEVICE, isDeviceSession: true }); }); }); it('clears req.sessionID so express-session doesn\'t persist the session', () => { return hmacNoSessionP(req, def, SESSION_HEADER).then(() => { return expect(req.sessionID) .to.be.null; }); }); it('passes the security tests', () => { return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .to.eventually.be.fulfilled; }); }); describe('With validly formatted params that are wrong', () => { it('rejects when required feature flag isn\'t enabled', () => { // Fake that a feature flag is required req.swagger.operation['x-feature-flag'] = MOCK_SWAGGER_FEATURE_FLAG; // Return a client that doesn't have that flag const modifiedClient = _.cloneDeep(DB_CLIENT); modifiedClient.FeatureFlags = []; referencesStub.getClientByEmail.resolves(modifiedClient); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when finding the client fails', () => { referencesStub.getClientByEmail.rejects(); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when finding the device fails', () => { mainDBPStub.findOneObject.rejects(); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when the client isn\'t verified', () => { const modifiedClient = _.cloneDeep(DB_CLIENT); modifiedClient.ClientStatus = 0; referencesStub.getClientByEmail.resolves(modifiedClient); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when the client is barred', () => { const modifiedClient = _.cloneDeep(DB_CLIENT); modifiedClient.ClientStatus |= utils.ClientBarredMask; referencesStub.getClientByEmail.resolves(modifiedClient); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when the device isn\'t completely registered', () => { const modifiedDevice = _.cloneDeep(DB_DEVICE); modifiedDevice.DeviceStatus = utils.DeviceRegister2Mask; mainDBPStub.findOneObject.resolves(modifiedDevice); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when the device is suspended', () => { const modifiedDevice = _.cloneDeep(DB_DEVICE); modifiedDevice.DeviceStatus |= utils.DeviceSuspendedMask; mainDBPStub.findOneObject.resolves(modifiedDevice); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when the device is barred', () => { const modifiedDevice = _.cloneDeep(DB_DEVICE); modifiedDevice.DeviceStatus |= utils.DeviceBarredMask; mainDBPStub.findOneObject.resolves(modifiedDevice); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('rejects when checking HMAC fails', () => { authStub.checkHMAC.rejects(); return expect(hmacNoSessionP(req, def, SESSION_HEADER)) .eventually.be.rejected; }); it('leaves req.sessionID alone when device session verifcation fails', () => { authStub.checkHMAC.rejects(); return hmacNoSessionP(req, def, SESSION_HEADER).catch(() => { return expect(req.sessionID) .to.equal(SESSION_ID); }); }); }); describe('With invalidly formatted params', () => { describe('Rejects when x-bridge-hmac is the wrong format: ', () => { it('missing entirely', () => { delete req.headers['x-bridge-hmac']; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('too short', () => { req.headers['x-bridge-hmac'] = HMAC_HEADER.slice(0, -1); return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('too long', () => { req.headers['x-bridge-hmac'] = HMAC_HEADER + 'a'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('invalid char', () => { req.headers['x-bridge-hmac'] = HMAC_HEADER.slice(0, -1) + '!'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); }); describe('Rejects when x-bridge-timestamp is the wrong format: ', () => { it('missing entirely', () => { delete req.headers['x-bridge-timestamp']; return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('not a string', () => { req.headers['x-bridge-timestamp'] = Date.now(); return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('only has date', () => { req.headers['x-bridge-timestamp'] = new Date().toDateString(); return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('only has time', () => { req.headers['x-bridge-timestamp'] = new Date().toTimeString(); return expect(deviceSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); }); describe('Rejects when objectId is the wrong format: ', () => { it('too short', () => { req.swagger.params.objectId.value = DEVICE_MONGO_ID.slice(0, -1); return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('too long', () => { req.swagger.params.objectId.value = DEVICE_MONGO_ID + 'a'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('invalid char', () => { req.swagger.params.objectId.value = DEVICE_MONGO_ID.slice(0, -1) + 'g'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); }); describe('Rejects when ClientName is the wrong format: ', () => { it('too short', () => { req.swagger.params.body.value.ClientName = 'a@b.co'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('too long', () => { req.swagger.params.body.value.ClientName = 'a@' + 'b'.repeat(249) + '.com'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('invalid char', () => { req.swagger.params.body.value.ClientName = 'a@Bücher.example'; // No IDN support return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('missing the @', () => { req.swagger.params.body.value.ClientName = 'example.com'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); it('missing the tld', () => { req.swagger.params.body.value.ClientName = 'aexample'; return expect(hmacNoSessionP(req, def, SESSION_HEADER)).to .eventually.be.rejected; }); }); }); }); });