UNPKG

er_memcached_data_server

Version:

Plugin for event_request that implements a memcached data server

1,289 lines (1,042 loc) 33.8 kB
'use strict'; const { Server, Loggur } = require( 'event_request' ); const { test, assert, runAllTests } = require( 'event_request' ).Testing; const { request } = require( 'http' ); const RateLimitsPlugin = require( 'event_request/server/plugins/available_plugins/rate_limits_plugin' ); const Session = require( 'event_request/server/components/session/session' ); const path = require( 'path' ); const MemcachedDataServer = require( '../src/memcached_data_server' ); const DataServerPlugin = require( 'event_request/server/plugins/available_plugins/data_server_plugin' ); const app = new Server(); const dataServer = new MemcachedDataServer(); Loggur.disableDefault(); Loggur.loggers = {}; app.apply( new DataServerPlugin( 'er_data_server', { dataServer } ) ); /** * @brief Sends a request to the server and returns a Promise * * @param String path * @param String method * @param Number statusCode * @param mixed data * @param Number port * @param String expectedBody * * @return Promise */ function sendServerRequest( path, method = 'GET', statusCode = 200, data = '', headers = {}, port = 3333, expectedBody = null ) { return new Promise(( resolve,reject )=>{ const predefinedHeaders = { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength( data ) }; headers = { ...predefinedHeaders, ...headers }; const options = { hostname : 'localhost', port, path, method, headers }; const req = request( options, ( res ) =>{ const bodyParts = []; res.on( 'data',( chunk )=>{ bodyParts.push( chunk ); } ); res.on( 'end',()=>{ res.body = Buffer.concat( bodyParts ); if ( res.statusCode !== statusCode ) { return reject( `Expected StatusCode: ${statusCode} but got ${res.statusCode} with body: ${res.body}`) } if ( expectedBody !== null ) { assert.equal( res.body.toString(), expectedBody ); } return resolve( res ); }); }); req.on('error', ( e ) => { reject( e ); }); req.write( data ); req.end(); }); } /** * @brief Removes the cache file */ function removeCache( dataServer ) { if ( dataServer ) { dataServer.server.flush(()=>{}); } } test({ message : 'MemcachedDataServer.set sets data', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer(); const key = `key${Math.random()}`; const value = 'value'; const ttl = 100; const persist = true; await dataServer.set( key, value, ttl, { persist } ) const dataSet = await dataServer.get( key ); assert.equal( dataSet !== null, true ); assert.equal( dataSet, value ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.set.sets.data.twice', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer(); const key = `key${Math.random()}`; const value = 'value'; const ttl = 100; const persist = true; await dataServer.set( key, value, ttl, { persist } ) await dataServer.set( key, value, ttl, { persist } ) const dataSet = await dataServer.get( key ); assert.equal( dataSet !== null, true ); assert.equal( dataSet, value ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.set sets data without options', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async()=>{ const dataServer = new MemcachedDataServer(); const key = `key${Math.random()}` const value = 'value'; const ttl = 100; await dataServer.set( key, value, ttl ); const dataSet = await dataServer.get( key ); assert.equal( dataSet !== null, true ); assert.equal( dataSet, value ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.set with ttl === -1', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const key = `key${Math.random()}` const value = 'value'; const ttl = -1; await dataServer.set( key, value, ttl ); const dataSet = await dataServer.get( key ); assert.equal( dataSet !== null, true ); assert.equal( dataSet, value ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.set fails on handleError', dataProvider : [ ['key', 'value', 10, 123], ['key', 'value', 10, 'str'], ['key', 'value', 10, false], ['key', 'value', null, { persist: false }], ['key', 'value', [], { persist: false }], ['key', 'value', 'str', { persist: false }], ['key', 'value', false, { persist: false }], ['key', 'value', {}, { persist: false }], ], test : ( done, key, value, ttl, options )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); assert.equal( await dataServer.set( key, value, ttl, options ), null ); assert.equal( await dataServer.get( key ) === null, true ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.get gets data', test : ( done )=>{ removeCache(); // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const key = `key${Math.random()}`; const value = 'value'; const ttl = 100; await dataServer.set( key, value, ttl ); const dataSet = await dataServer.get( key ); assert.equal( dataSet, value ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.get.when.data.does.not.exist', test : ( done )=>{ removeCache(); // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const dataSet = await dataServer.get( 'test' ); assert.equal( dataSet, null ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.get with invalid data', dataProvider : [ ['key', 123], ['key', false], [undefined, {}], [null, {}], [false, {}], [[], {}], [{}, {}], ], test : ( done, key, options )=>{ removeCache(); // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); assert.equal( await dataServer.get( key, options ), null ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.get prunes ( when expired it will be null )', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const key = `key${Math.random()}`; const value = 'value'; const ttl = 1; const persist = true; await dataServer.set( key, value, ttl, { persist } ); setTimeout( async ()=>{ assert.equal( await dataServer.get( key ), null ); removeCache( dataServer ); done(); }, 1100 ); }, 10 ); } }); test({ message : 'MemcachedDataServer.touch.updates.expirationDate', test : ( done )=>{ setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const key = `key${Math.random()}`; const value = 'value'; const ttl = 1; const persist = true; await dataServer.set( key, value, ttl, { persist } ); await dataServer.touch( key, 5 ); setTimeout( async()=>{ const dataSet = await dataServer.get( key ); assert.equal( dataSet, value ); removeCache( dataServer ); done(); }, 1100 ); }, 10 ); } }); test({ message : 'MemcachedDataServer.touch.if.data.does.not.exist', test : ( done )=>{ setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const key = `key${Math.random()}`; assert.deepStrictEqual( await dataServer.touch( key, 5 ), false ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.testWithServerAttachesSuccessfully', test : ( done )=>{ const app = new Server(); const name = '/testWithServerAttachesSuccessfully'; const key = `${name}${Math.random()}`; const value = 'test'; app.apply( new DataServerPlugin( 'er_data_server', { dataServer } ) ); app.get( name, async ( event )=>{ assert.equal( event.dataServer instanceof MemcachedDataServer, true ); await event.dataServer.set( key, value ); event.send( name ); }); app.get( `${name}GET`, async ( event )=>{ assert.equal( event.dataServer instanceof MemcachedDataServer, true ); assert.equal( await event.dataServer.get( key ), value ); event.send( `${name}GET` ); }); app.listen( 3334, ()=>{ sendServerRequest( name, 'GET', 200, '', {}, 3334 ).then(( response )=>{ assert.equal( response.body.toString(), name ); return sendServerRequest( `${name}GET`, 'GET', 200, '', {}, 3334 ); }).then(( response )=>{ assert.equal( response.body.toString(), `${name}GET` ); done(); }).catch( done ); }); } }); test({ message : 'MemcachedDataServer.touch with invalid data', dataProvider : [ ['key', '123', {}], [false, '123', {}], [[], '123', {}], [{}, '123', {}], [null, '123', {}], [undefined, '123', {}], ['key', [], {}], ['key', {}, {}], ['key', false, {}], ['key', null, {}], ['key', null, 123], ['key', null, 'string'], ['key', null, false] ], test : ( done, key, ttl, options )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); await dataServer.set( key, '123' ); assert.equal( await dataServer.touch( key, ttl, options ), false ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.delete removes key and returns true but returns false if it does not exist or not string', test : ( done )=>{ removeCache(); // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer({ persist: false }); const key = `key${Math.random()}`; const value = { test: 'value' }; await dataServer.set( key, value ); assert.equal( await dataServer.delete( 123 ), false ); assert.equal( await dataServer.delete( key ), true ); assert.equal( await dataServer.delete( key ), true ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.increment increments data', dataProvider : [ [100, 100, 200], [0, 100, 100], [-1, 100, null], ['string', 100, null], [[], 100, null], [{}, 100, null], [100, null, null], [100, 'string', null], [100, {}, null], [100, [], null], ], test : async ( done, value, increment, expectedValue )=>{ removeCache(); const dataServer = new MemcachedDataServer({ persist: false }); const key = `key${Math.random()}`; await dataServer.set( key, value ).catch( done ); const result = await dataServer.increment( key, increment ).catch( done ); if ( expectedValue === null ) { removeCache( dataServer ); return done( ! ( null === result ) ); } if ( result === null ) { return done( `Result was null but expected: ${expectedValue}` ); } assert.equal( result, expectedValue ); removeCache( dataServer ); done(); } }); test({ message : 'MemcachedDataServer.decrement decrement data', dataProvider : [ [100, 100, 0], [0, 100, 0], [1, 100, 0], [100, 99, 1], [100, 50, 50], ['string', true, null], [[], 100, null], [{}, 100, null], [100, null, null], [100, 'string', null], [100, {}, null], [100, [], null], ], test : async ( done, value, decrement, expectedValue )=>{ removeCache(); const dataServer = new MemcachedDataServer({ persist: false }); const key = `key${Math.random()}`; await dataServer.set( key, value ).catch( done ); const result = await dataServer.decrement( key, decrement ).catch( done ); if ( expectedValue === null ) { removeCache( dataServer ); return done( ! ( null === result ) ); } if ( result === null ) { return done( `Result was null but expected: ${expectedValue}` ); } assert.equal( result, expectedValue ); removeCache( dataServer ); done(); } }); test({ message : 'MemcachedDataServer.set does not set if invalid data', dataProvider : [ [null, 'value', 100, true], ['key', null, 100, true], ['key', 'value', null, true], [123, 'value', 100, true], ['key', 'value', '100', true], ['key', 'value', 100, 'true'], [null, 'value', 100, 'true'], [undefined, 'value', 100, 'true'], [[], 'value', 100, 'true'], [{}, 'value', 100, 'true'], [false, 'value', 100, 'true'], ], test : ( done, key, value, ttl, persist )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); assert.equal( await dataServer.set( key, value, ttl, persist ), null ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.lock locks data correctly', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); await dataServer.unlock( 'key' ); assert.equal( await dataServer.lock( 'key' ), true ); assert.equal( await dataServer.lock( 'key' ), false ); assert.equal( await dataServer.unlock( 'key' ), true ); assert.equal( await dataServer.lock( 'key' ), true ); assert.equal( await dataServer.lock( 'key' ), false ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.lock locks data correctly with double unlock', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); await dataServer.unlock( 'key' ); assert.equal( await dataServer.lock( 'key' ), true ); assert.equal( await dataServer.lock( 'key' ), false ); assert.equal( await dataServer.unlock( 'key' ), true ); assert.equal( await dataServer.unlock( 'key' ), true ); assert.equal( await dataServer.lock( 'key' ), true ); assert.equal( await dataServer.lock( 'key' ), false ); await dataServer.unlock( 'key' ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.unlock always returns true', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); assert.equal( await dataServer.unlock( 'key' ), true ); assert.equal( await dataServer.unlock( 'key' ), true ); assert.equal( await dataServer.lock( 'key' ), true ); assert.equal( await dataServer.unlock( 'key' ), true ); await dataServer.unlock( 'key' ); removeCache( dataServer ); done(); }, 10 ); } }); test({ message : 'MemcachedDataServer.lock acquires only one lock', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const promises = []; for ( let i = 0; i < 10000; i ++ ) promises.push( dataServer.lock( 'key' ) ); Promise.all( promises ).then( async( locks )=>{ let acquiredLocks = 0; for ( const lock of locks ) { if ( lock ) acquiredLocks ++; } assert.equal( acquiredLocks, 1 ); await dataServer.unlock( 'key' ); removeCache( dataServer ); done(); }).catch( done ); }, 10 ); } }); test({ message : 'MemcachedDataServer.lockBurst acquires another lock with burst of locks', test : ( done )=>{ // Wait in case the file has not been deleted from the FS setTimeout( async ()=>{ const dataServer = new MemcachedDataServer( { persist: false } ); const promises = []; const key = `key${Math.random()}lockBurst`; for ( let i = 0; i < 200; i ++ ) { if ( i % 20 === 0 ) dataServer.unlock( key ); promises.push( dataServer.lock( key ) ); } Promise.all( promises ).then( async( locks )=>{ let acquiredLocks = 0; for ( const lock of locks ) { if ( lock ) acquiredLocks ++; } // Variable since there is a slight change of a race condition that is due to memcached assert.equal( acquiredLocks === 10 || acquiredLocks === 11, true ); removeCache( dataServer ); done(); }).catch( done ); }, 10 ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimits', test : ( done )=>{ const dataStore = new MemcachedDataServer(); const appOne = new Server(); const appTwo = new Server(); const name = 'testErRateLimitsBucketWorksCrossApps'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); appOne.apply( new RateLimitsPlugin( 'rate_limits' ), { fileLocation, dataStore, useFile: true } ); appTwo.apply( new RateLimitsPlugin( 'rate_limits' ), { fileLocation, dataStore, useFile: true } ); appOne.get( `/${name}`, async ( event )=>{ event.send( name ); } ); appTwo.get( `/${name}`, ( event )=>{ event.send( name ); } ); appOne.listen( 3360 ); appTwo.listen( 3361 ); setTimeout(()=>{ sendServerRequest( `/${name}`, 'GET', 200, '', {}, 3360 ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429, '', {}, 3361 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); }, 100 ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsPermissive', test : ( done )=>{ const name = 'testErRateLimitsWithPermissiveLimiting'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); let called = 0; if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ called ++; if ( called > 1 ) { assert.equal( event.rateLimited, true ); } else { assert.equal( event.rateLimited, false ); } event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}` ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsPermissiveRefills', test : ( done )=>{ const name = 'testErRateLimitsWithPermissiveLimitingRefills'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ assert.equal( event.rateLimited, false ); event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ setTimeout(()=>{ sendServerRequest( `/${name}` ).then(( response )=>{ assert.equal( response.body.toString(), name ); done(); }).catch( done ) }, 1000 ); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsConnectionDelay', test : ( done )=>{ const name = 'testErRateLimitsWithConnectionDelayPolicy'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); const now = Math.floor( new Date().getTime() / 1000 ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}` ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); assert.equal( ( Math.floor( new Date().getTime() / 1000 ) - now ) >= 2, true ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsStrict', test : ( done )=>{ const name = 'testErRateLimitsWithStrictPolicy'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsStrictSTRESS', test : ( done )=>{ // This test runs locally easily, but does not work well in the travis env const name = 'testErRateLimitsWithStrictPolicyStress'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); const promises = []; for ( let i = 0; i < 100; i ++ ) { promises.push( sendServerRequest( `/${name}` ) ); } setTimeout(()=>{ for ( let i = 0; i < 50; i ++ ) { promises.push( sendServerRequest( `/${name}` ) ); } Promise.all( promises).then(()=>{ done(); }).catch( done ); }, 2600 ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsStrictSpecifiedMethodMatches', test : ( done )=>{ const name = 'testErRateLimitsWithStrictPolicyWithSpecifiedMethods'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsStrictSpecifiedMultipleMethodsMatch', test : ( done )=>{ const name = 'testErRateLimitsWithStrictPolicyWithMultipleSpecifiedMethods'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsStrictSpecifiedMethodDoesNotMatch', test : ( done )=>{ const name = 'testErRateLimitsWithStrictPolicyWithSpecifiedMethodsThatDoNotMatch'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}` ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsStopPropagation', test : ( done )=>{ const name = 'testErRateLimitsWithPropagation'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); let called = 0; if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ called ++; if ( called > 1 ) { assert.equal( event.rateLimited, true ); } else { assert.equal( event.rateLimited, false ); } event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 200 ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsMultipleRules', test : ( done )=>{ const name = 'testErRateLimitsWithMultipleRules'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsStrictOverridesConenctionDelay', test : ( done )=>{ const name = 'testErRateLimitsStrictOverridesConnectionDelayPolicy'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsConnectionOverridesPermissive', test : ( done )=>{ const name = 'testErRateLimitsConnectionDelayOverridesPermissivePolicy'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}` ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsConnectionReturns429IfNoMoreRetries', test : ( done )=>{ const name = 'testErRateLimitsConnectionDelayReturns429IfNoMoreRetries'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerRateLimitsIpLimit', test : ( done )=>{ const name = 'testErRateLimitsWithStrictPolicyWithIpLimit'; const fileLocation = path.join( __dirname, './fixture/rate_limits.json' ); if ( ! app.hasPlugin( app.er_rate_limits ) ) app.apply( app.er_rate_limits, { fileLocation, useFile: true } ); app.get( `/${name}`, ( event )=>{ event.send( name ); } ); setTimeout(()=>{ sendServerRequest( `/${name}` ).then(( response )=>{ return sendServerRequest( `/${name}`, 'GET', 429 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'Too many requests' } ) ); done(); }).catch( done ); }, 50 ); } }); test({ message : 'MemcachedDataServer.testWithServerResponseCache', test : ( done )=>{ const name = 'testErResponseCacheCaches'; let i = 0; if ( ! app.hasPlugin( app.er_response_cache ) ) { app.apply( app.er_data_server, { dataServer } ); app.apply( app.er_response_cache ); } app.get( `/${name}`, 'cache.request', ( event )=>{ if ( i === 0 ) { i ++; return event.send( name ); } event.sendError( 'ERROR', 501 ); }); sendServerRequest( `/${name}` ).then(( response )=>{ assert.equal( response.body.toString(), name ); return sendServerRequest( `/${name}` ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerCacheDoesNotCacheIfNotNeeded', test : ( done )=>{ const name = 'testErResponseCacheDoesNotCacheEverything'; let i = 0; if ( ! app.hasPlugin( app.er_response_cache ) ) { app.apply( app.er_data_server, { dataServer } ); app.apply( app.er_response_cache ); } app.get( `/${name}`, ( event )=>{ if ( i === 0 ) { i ++; return event.send( name ); } event.sendError( 'ERROR', 501 ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ assert.equal( response.body.toString(), name ); return sendServerRequest( `/${name}`, 'GET', 501 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'ERROR' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerCacheDoesNotCacheRaw', test : ( done )=>{ const name = 'testErResponseCacheDoesNotCacheRaw'; let i = 0; if ( ! app.hasPlugin( app.er_response_cache ) ) { app.apply( app.er_data_server, { dataServer } ); app.apply( app.er_response_cache ); } app.get( `/${name}`, ( event )=>{ if ( i === 0 ) { i ++; return event.send( name, 200, true ); } event.sendError( 'ERROR', 501 ); } ); sendServerRequest( `/${name}` ).then(( response )=>{ assert.equal( response.body.toString(), name ); return sendServerRequest( `/${name}`, 'GET', 501 ); }).then(( response )=>{ assert.equal( response.body.toString(), JSON.stringify( { error: 'ERROR' } ) ); done(); }).catch( done ); } }); test({ message : 'MemcachedDataServer.testWithServerSession', test : ( done )=>{ const name = 'testErSession'; const appTwo = new Server(); assert.throws(()=>{ const appOne = new Server(); appOne.apply( appOne.er_session ); }); appTwo.apply( appTwo.er_data_server, { dataServer } ); appTwo.apply( appTwo.er_session ); appTwo.get( `/${name}`, ( event )=>{ event.initSession( event.next ).catch( event.next ); } ); appTwo.get( `/${name}`, async ( event )=>{ assert.equal( event.session instanceof Session, true ); const session = event.session; if ( session.has( 'authenticated' ) === false ) { assert.throws(()=>{ session.get( 'authenticated' ); }); session.add( 'authenticated', true ); } else { assert.equal( session.get( 'authenticated' ), true ); event.setResponseHeader( 'authenticated', 1 ); } event.send( name ); } ); appTwo.listen( 3390, ()=>{ sendServerRequest( `/${name}`, 'GET', 200, '', {}, 3390 ).then(( response )=>{ assert.equal( response.body.toString(), name ); assert.equal( typeof response.headers['set-cookie'] !== 'undefined', true ); const cookies = {}, rc = response.headers['set-cookie'][0]; rc && rc.split( ';' ).forEach( function( cookie ) { const parts = cookie.split( '=' ); cookies[parts.shift().trim()] = decodeURI( parts.join( '=' ) ); }); assert.equal( typeof cookies.sid === 'string', true ); const headers = { cookie: `sid=${cookies.sid}`}; return sendServerRequest( `/${name}`, 'GET', 200, '', headers, 3390 ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); assert.equal( typeof response.headers.authenticated !== 'undefined', true ); assert.equal( response.headers.authenticated, 1 ); const headers = { cookie: `sid=wrong`}; return sendServerRequest( `/${name}`, 'GET', 200, '', headers, 3390 ); }).then(( response )=>{ assert.equal( response.body.toString(), name ); assert.equal( typeof response.headers.authenticated === 'undefined', true ); done(); }).catch( done ); }); } }); test({ message : 'MemcachedDataServer.with.custom.options', test : ( done )=>{ const dataServer = new MemcachedDataServer( { serverLocations: '', serverOptions: { poolSize: 100 }, ttl: 300 } ); dataServer.stop(); setTimeout(()=>{ done(); }, 50 ); } }); test({ message : 'MemcachedDataServer.with.ttl.-1', test : ( done )=>{ const dataServer = new MemcachedDataServer( { serverLocations: '', serverOptions: { poolSize: 100 }, ttl: -1 } ); dataServer.stop(); setTimeout(()=>{ done(); }, 50 ); } }); test({ message : 'MemcachedDataServer._getTtl', test : ( done )=>{ const dataServer = new MemcachedDataServer( { serverLocations: '', serverOptions: { poolSize: 100 }, ttl: -1 } ); dataServer.stop(); setTimeout(()=>{ assert.deepStrictEqual( dataServer._getTtl(), 2592000 ); done(); }, 50 ); } }); app.listen( 3333, async()=>{ runAllTests(); });