supe
Version:
Flexible Framework for Fault-Tolerant Node.js Apps
1,030 lines (731 loc) • 35 kB
JavaScript
var assert = require('assert'),
supe = require('../index');
describe('Supe Test Suite', function(){
describe('About Supe', function(){
it('is a function', function(){
assert.equal(typeof supe, 'function', 'is not a function');
});
});
describe('Supervisor (Instantiated Supe) Properties', function(){
var supervisor = supe(),
expected_properties = [
{ key: 'is_registered', type: 'function' },
{ key: 'deregister', type: 'function' },
{ key: 'register', type: 'function' },
{ key: 'start', type: 'function' },
{ key: 'stop', type: 'function' },
{ key: 'get', type: 'function' },
{ key: 'use', type: 'function' },
{ key: 'noticeboard', type: 'object' },
{ key: 'hook', type: 'object' }
],
expected_properties_index = [];
expected_properties.forEach( function( prop ){
it( 'has its own "' + prop.key + '" ' + prop.type, function(){
assert.equal( supervisor.hasOwnProperty( prop.key ), true, 'has no "' + prop.key + '" property' );
switch( prop.type ){
case 'function':
assert.equal( typeof supervisor[ prop.key ], 'function', '"' + prop.key + '" is not a function' );
break;
case 'object':
assert.equal( Object.prototype.toString.call( supervisor[ prop.key ] ), '[object Object]', '"' + prop.key + '" is not a function' );
break;
default:
throw new Error( 'no tests defined for '+ prop.type +' data-type' );
break;
}
expected_properties_index.push( prop.key );
});
});
it('has no unexpected properties', function(){
for( var prop in supervisor ){
assert.equal( expected_properties_index.indexOf( prop ) > -1, true, 'has unexpected property "' + prop + '"' );
}
});
it('"noticeboard" is an instance of cjs-noticeboard', function(){
assert.equal( supervisor.noticeboard instanceof require('cjs-noticeboard'), true, 'not an instance of cjs-noticeboard');
});
});
describe('Supervisor Functions Behavior', function(){
describe('Supervisor.is_registered', function(){
var citizen_name = 'logger',
supervisor;
before( function(){
supervisor = supe();
supervisor.register( citizen_name, './test/citizen/interval-logger' );
});
it('returns true if citizen is registered', function(){
assert.equal( supervisor.is_registered( citizen_name ), true, 'did not return true for registered citizen name' );
});
it('returns false is citizen is not registered', function(){
assert.equal( supervisor.is_registered( 'unregistered-citizen' ), false, 'did not return false for unregistered citizen name' );
});
it('returns false if given citizen name is not a string', function(){
var nonstrings = [ 1, [], {}, function(){} ];
nonstrings.forEach( function( nonstring ){
var registered = supervisor.is_registered( nonstring );
assert.equal( registered, false, 'citizen was registered with a non-string name parameter' );
});
});
});
describe('Supervisor.register', function(){
var supervisor,
new_process;
beforeEach( function(){
supervisor = supe();
new_process = supervisor.register( 'logger', './test/citizen/interval-logger' );
});
it('returns an object (citizen)', function( done ){
assert.equal( Object.prototype.toString.call( new_process ) === '[object Object]', true, 'did not return an object' );
setTimeout( done, 0 );
});
it('will not create a new citizen without name parameter', function(){
var crashed = false;
try{
supervisor.register( null, './test/citizen/interval-logger' );
}
catch( e ){ crashed = true; }
assert.equal( crashed === true , true, 'citizen was created without name parameter' );
});
it('will not create a new citizen without file parameter', function(){
var failed = false;
try{
supervisor.register( 'fileless-citizen' );
}
catch( e ){ failed = true }
assert.equal( failed === true, true, 'citizen was created without file parameter' );
});
it('will not create a new citizen if file parameter is not a string', function(){
var nonstrings = [ 1, [], {}, function(){} ];
nonstrings.forEach( function( nonstring ){
var failed = false;
try{
supervisor.register( 'nonstring-file-citizen' );
}
catch( e ){ failed = true; }
assert.equal( failed === true, true, 'citizen was created with a non-string file parameter' );
});
});
it('will not create a new citizen if name is associated with a different citizen', function(){
var overwrite_error = false;
supervisor.register( 'common-citizen', './test/citizen/one-time-logger' );
try{
common_citizen_2 = supervisor.register( 'common-citizen', './test/citizen/interval-logger' );
}
catch( e ){ overwrite_error = true; }
assert.equal( overwrite_error === true, true, 'new citizen was registered under the name of an existing citizen' );
});
it('will pass parameters to citizen\'s config', function(){
var params = { retries: 5, duration: 5, happy: true },
citizen = supervisor.register( 'custom-logger', './test/citizen/one-time-logger', params );
for( var prop in params ){
if( !params.hasOwnProperty( prop ) ) continue;
assert.equal( citizen.config.hasOwnProperty( prop ), true, 'citizen config does not have expected property "' + prop + '"' );
assert.equal( citizen.config[ prop ] === params[ prop ], true, 'parameter property "' + prop + '" does not match citizen config\'s "' + prop + '"' );
}
});
it('will override default citizen\'s config', function(){
var supervisor = supe(),
default_logger = supervisor.register( 'default-logger', './test/citizen/one-time-logger' ),
custom_logger = supervisor.register( 'custom-logger', './test/citizen/one-time-logger', { retries: 5, duration: 5, happy: true });
assert.equal( default_logger.config !== custom_logger.config, true, 'config for default and custom loggers are identical' );
});
});
describe('Supervisor.deregister', function(){
var supervisor = supe(),
citizen_name = 'citizen-to-deregister';
afterEach( function(){
if( supervisor.get( citizen_name ) ) supervisor.deregister( citizen_name );
supervisor = supe();
});
it('will remove a citizen from supe\'s registry', function(){
supervisor.register( citizen_name, './test/citizen/interval-logger' );
supervisor.deregister( citizen_name );
assert.equal( supervisor.get( citizen_name ), false, 'citizen still exists' );
});
it('will stop a running citizen before deregistering it', function( done ){
this.timeout( 10000 );
var stopped = false;
supervisor.start( citizen_name, './test/citizen/interval-logger' );
supervisor.hook.add( citizen_name + '-stopped', 'mark-stopped', function(){
stopped = true;
});
supervisor.hook.add( citizen_name + '-deregistered', 'do-assertions', function(){
var citizen = supervisor.get( citizen_name ),
citizen_exists = citizen && citizen != false && citizen != null;
assert.equal( citizen_exists, false, 'citizen was not deregistered' );
assert.equal( stopped, true, 'citizen was not stopped' );
done();
});
supervisor.deregister( citizen_name );
});
});
describe('Supervisor.start', function(){
var supervisor,
new_process;
beforeEach( function(){
supervisor = supe();
});
it('citizen has a process reference ("ref" property) that is a node child_process', function( done ){
this.timeout( 10000 );
var new_process = supervisor.start( 'logger', './test/citizen/interval-logger' );
assert.equal( new_process.hasOwnProperty( 'ref' ), true, 'citizen does not have ref property' );
assert.equal( new_process.ref instanceof require('child_process').ChildProcess, true, 'citizen "ref" property is not a child process' );
// cleanup
supervisor.hook.add( 'logger-stopped', 'done', function(){
done();
});
supervisor.stop( 'logger' );
});
it('will restart a previously-started citizen', function( done ){
this.timeout( 10000 );
var citizen_name = 'one-time-logger',
citizen = supervisor.register( citizen_name, './test/citizen/one-time-logger' ),
first_start_ref,
second_start_ref,
started = 0;
supervisor.hook.add( citizen_name + '-started', 'do-assertions', function(){
started += 1;
switch( started ){
case 1:
first_start_ref = citizen.ref;
supervisor.hook.add( citizen_name + '-shutdown', 'restart-citizen', function(){
supervisor.hook.del( citizen_name + '-shutdown', 'restart-citizen' );
supervisor.start( citizen_name );
});
break;
case 2:
supervisor.hook.del( citizen_name + '-started', 'do-assertions' );
second_start_ref = citizen.ref;
assert.equal( first_start_ref !== second_start_ref, true, 'first and second start refer to the same process instance' );
done();
// cleanup
citizen.ref.kill();
break;
default:
throw new Error( 'citizen "' + citizen_name + '" started more times than expected' );
break;
}
});
supervisor.start( citizen_name );
});
it('will not restart a currently-running citizen', function( done ){
var new_citizen_started = false,
test_completed = false;
supervisor.hook.add( 'logger-started', 'fail-test', function(){
new_citizen_started = true;
complete_test();
});
setTimeout( complete_test, 888 );
try {
supervisor.start( 'logger' );
}
catch(e){
complete_test();
}
function complete_test(){
if( test_completed ) return;
test_completed = true;
assert.equal( new_citizen_started, false, 'restarted currently-running process' );
done();
}
});
});
describe('Supervisor.stop', function(){
var supervisor = supe(),
citizen_name, citizen;
afterEach( function(){
if( ! citizen || ! citizen.ref ) return;
citizen.ref.kill();
citizen_name = citizen = null;
});
it('will stop a running citizen', function( done ){
this.timeout( 20000 );
citizen_name = 'nodejs-app-no-supe';
citizen = supervisor.start( citizen_name, './test/citizen/notice-once-receiver' );
var stopped = false;
supervisor.hook.add( citizen_name + '-stopped', 'do-assertions', function( msg ){
stopped = true;
do_assertions();
});
supervisor.stop( citizen_name );
function do_assertions(){
done();
assert.equal( stopped, true, '"' + citizen_name + '" was not stopped' );
}
});
it('will eventually stop a citizen that refuses to shutdown', function( done ){
this.timeout( 10000 );
var citizen_stopped = false;
citizen_name = 'shutdown-ignorer';
citizen = supervisor.register( citizen_name, './test/citizen/shutdown-ignorer' );
supervisor.hook.add( citizen_name + '-started', 'stop-it', function(){
supervisor.stop( citizen_name );
});
supervisor.hook.add( citizen_name + '-stopped', 'do-assertions', function(){
citizen_stopped = true;
assert.equal( citizen_stopped, true, 'citizen was not stopped' );
done();
});
supervisor.start( citizen_name );
});
it('throws error if given name is not a string', function(){
var non_strings = [ 1, {}, [], null, false, NaN ],
errors_thrown = 0;
non_strings.forEach( function( non_string ){
try {
supervisor.stop( non_string );
}
catch(e){
errors_thrown += 1;
}
});
assert.equal( errors_thrown == non_strings.length, true, 'errors thrown does not match number of non strings tested' );
});
it('throws error if citizen with given name does not exist', function(){
var error_thrown = false;
try{
supervisor.stop( 'non-existent-citizen' );
}
catch(e) {
error_thrown = true;
}
assert.equal( error_thrown, true, 'no error thrown when stopping non-existent citizen' );
});
});
describe('Supervisor.get', function(){
var supervisor = supe();
it('returns citizen with given name if it exists', function( done ){
var citizen = supervisor.start( 'logger', './test/citizen/interval-logger' ),
get_val = supervisor.get( 'logger' );
assert.equal( citizen === get_val, true, 'get return value does not match created citizen' );
// cleanup
supervisor.hook.add( 'logger-stopped', 'end-test', function(){
done();
});
supervisor.stop( 'logger' );
});
it('returns false if citizen with given name does not exist', function(){
var get_val = supervisor.get( 'non-existent-logger' );
assert.equal( Object.prototype.toString.call( get_val ) !== ['object Object'], true, 'returned an object, not false' );
});
it('returns false if given name is not a string', function(){
var non_strings = [ 1, {}, [], null, false, NaN ];
non_strings.forEach( function( non_string ){
var get_val = supervisor.get( non_string );
assert.equal( Object.prototype.toString.call( get_val ) !== ['object Object'], true, 'returned an object, not false' );
});
});
});
describe('Supervisor.use', function(){
var supervisor = supe();
it('executes given function with current Supervisor as first argument', function(){
var unique_key = Date.now();
supervisor.use( set_unique_key_module );
assert.equal( supervisor.hasOwnProperty( 'is_same_instance' ) === true, true, 'supervisor instance does not have prop "is_same_instance"' );
assert.equal( supervisor.is_same_instance === unique_key, true, 'supervisor prop "is_same_instance" does not match expected value' );
function set_unique_key_module( supe ){
supe.is_same_instance = unique_key;
}
});
it('does nothing and returns false if first argument is false-y', function(){
var falsey_values = [ false, 0, '', null, undefined ];
falsey_values.forEach( function( falsey ){
assert.equal( supervisor.use( falsey ) === false, true, 'expected false, got something else' );
});
});
it('throws an error if first argument is not a function', function(){
var non_functions = [
{ type: 'number', val: 1 },
{ type: 'string', val: '1' },
{ type: 'array', val: ['1'] },
{ type: 'object', val: { a: '1' } }
]
non_functions.forEach( function( non_func ){
var error_thrown = false;
try {
supervisor.use( non_func.val );
}
catch( e ){
error_thrown = true;
}
assert.equal( error_thrown === true, true, 'error not thrown when given ' + non_func.type + ' argument' );
});
});
});
});
describe('Supervisor Noticeboard Integration', function(){
var supervisor;
beforeEach( function(){
supervisor = supe({ retries: 0 });
});
it('sends notice "(name)-started" when citizen is started', function( done ){
this.timeout( 10000 );
var started = false;
supervisor.noticeboard.once( 'one-time-crasher-started', 'do-assertions', function( msg ){
started = true;
assert.equal( started, true, 'did not detect specific citizen start-up' );
done();
// cleanup
supervisor.get( 'one-time-crasher' ).ref.kill();
});
supervisor.start( 'one-time-crasher', './test/citizen/one-time-crasher', { retries: 0 } );
});
it('sends notice "citizen-started" when any citizen is started', function( done ){
this.timeout( 10000 );
var first_citizen_name = 'first-crasher',
second_citizen_name = 'second-crasher',
startups_detected = 0;
supervisor.noticeboard.watch( 'citizen-started', 'do-assertions', function( msg ){
startups_detected += 1;
var details = msg.notice;
if( details.name !== second_citizen_name ) return;
assert.equal( startups_detected === 2, true, 'did not detect all citizen start' );
assert.equal( details.name === second_citizen_name, true, 'did not detect expected citizen start' );
done();
});
supervisor.start( first_citizen_name, './test/citizen/one-time-crasher' );
supervisor.start( second_citizen_name, './test/citizen/one-time-crasher' );
});
it('sends notice "(name)-shutdown" when a citizen shuts down', function( done ){
this.timeout( 15000 );
var detected_shutdown = false;
supervisor.noticeboard.watch( 'one-time-logger-shutdown', 'do-assertions', function( msg ){
detected_shutdown = true;
assert.equal( detected_shutdown === true, true, 'did not detect specific citizen shutdown' );
done();
});
supervisor.start( 'one-time-logger', './test/citizen/one-time-logger' );
});
it('sends notice "citizen-shutdown" when any citizen shuts down', function( done ){
this.timeout( 10000 );
var first_citizen_name = 'first-logger',
second_citizen_name = 'second-logger',
shutdowns_detected = 0,
shutdowns = [];
supervisor.noticeboard.watch( 'citizen-shutdown', 'do-assertions', function( msg ){
shutdowns_detected += 1;
var citizen_name = msg.notice.name;
shutdowns.push( citizen_name );
if( shutdowns_detected < 2 ) return;
var detected_shutdown_from_both_citizens = shutdowns.indexOf( first_citizen_name ) > -1 && shutdowns.indexOf( second_citizen_name ) > -1;
assert.equal( detected_shutdown_from_both_citizens, true, 'did not detect expected citizen shutdown' );
done();
});
supervisor.start( first_citizen_name, './test/citizen/one-time-logger' );
supervisor.start( second_citizen_name, './test/citizen/one-time-logger' );
});
it('sends notice "(name)-crashed" on crash', function( done ){
this.timeout( 10000 );
var detected_crash = false;
supervisor.noticeboard.once( 'one-time-crasher-crashed', 'do-assertions', function( msg ){
detected_crash = true;
assert.equal( detected_crash, true, 'did not detect specific citizen crash' );
done();
});
supervisor.start( 'one-time-crasher', './test/citizen/one-time-crasher', { retries: 0 } );
});
it('sends notice "citizen-crashed" when any citizen crashes', function( done ){
this.timeout( 15000 );
var first_citizen_name = 'first-crasher',
second_citizen_name = 'second-crasher',
crashes_detected = 0;
supervisor.noticeboard.watch( 'citizen-crashed', 'do-assertions', function(){
crashes_detected += 1;
if( crashes_detected < 2 ) return;
assert.equal( crashes_detected === 2, true, 'did not detect all citizen crashes' );
done();
});
supervisor.start( first_citizen_name, './test/citizen/one-time-crasher' );
supervisor.start( second_citizen_name, './test/citizen/one-time-crasher' );
});
});
describe('Citizen Properties', function(){
var supervisor,
key_analyzer;
beforeEach( function(){
supervisor = supe();
key_analyzer = supervisor.start( 'key-analyzer', './test/citizen/key-analyzer' );
});
it( 'has its own "get_name" function', function( done ){
this.timeout( 5000 );
var key = 'get_name',
expected_typeof = 'function';
supervisor.hook.add( 'supervisor-mail', 'process-analysis', function( envelope ){
var message = envelope.msg;
if( ! message.type || message.type !== 'key-analysis' ) return;
var analysis = message;
if( analysis.key != key ) return;
if( analysis.success != true ) throw new Error( 'citizen analysis of supe key "' + analysis.key + '" failed' );
assert.equal( analysis.exists, true, '"' + key + '" does not exist on citizen supe instance' );
assert.equal( analysis.typeof, expected_typeof, '"' + key + '" is not a ' + expected_typeof );
done();
});
key_analyzer.mail.send( key );
});
});
describe('Supervisor Behavior', function(){
this.timeout( 10000 );
var supervisor;
beforeEach( function(){
supervisor = supe();
});
it('will automatically restart a crashed citizen', function( done ){
this.timeout( 10000 );
var citizen_name = 'crasher';
supervisor.hook.add( citizen_name + '-auto-restarted', 'do-assertions', function( msg ){
var details = msg.notice,
citizen = supervisor.get( citizen_name );
supervisor.hook.del( citizen_name + '-auto-restarted', 'do-assertions' );
assert.equal( citizen.ref && citizen.ref.stdout && citizen.ref instanceof require('events'), true, 'restarted citizen does not have reference to valid child process' );
// cleanup
supervisor.hook.add( citizen_name + '-stopped', 'end test', function(){
done();
});
supervisor.stop( citizen_name );
});
supervisor.start( citizen_name, './test/citizen/one-time-crasher', { retries: 1 });
});
it('will not automatically restart citizen that crashed excessively', function( done ){
this.timeout( 10000 );
var citizen_name = 'crasher',
max_restarts = 2,
restarts = 0;
supervisor.hook.add( citizen_name + '-auto-restarted', 'count-auto-restarts', function(){
restarts += 1;
if( restarts > max_restarts ) done( new Error( 'restarted citizen more than permitted amount of times' ) );
});
supervisor.hook.add( citizen_name + '-excessive-crash', 'do-assertions', function(){
// wait a second before doing assertions, just in case
setTimeout( function(){
assert.equal( restarts === max_restarts, true, 'current restarts does not match max allowed restarts' );
done();
}, 1000 );
});
supervisor.start( citizen_name, './test/citizen/one-time-crasher', { retries: max_restarts });
});
it('will route mail sent by a citizen to addressed citizen', function( done ){
this.timeout( 10000 );
supervisor.hook.add( 'supervisor-mail', 'do-assertions', function( envelope ){
if( envelope.type !== 'mail' ) return;
if( envelope.from !== 'routed-mail-receiver' ) return;
if( envelope.msg.received_mail_from !== 'routed-mail-sender' ) return;
done();
});
supervisor.start( 'routed-mail-receiver', './test/citizen/routed-mail-receiver' );
supervisor.start( 'routed-mail-sender', './test/citizen/routed-mail-sender' );
});
it('will requeue unacked mail if a citizen crashes', function( done ){
this.timeout( 10000 );
var name = 'unacker',
message = 'CRASH',
citizen;
supervisor.hook.add( name + '-crashed', 'do-assertions', function(){
supervisor.hook.del( name + '-crashed', 'do-assertions' );
assert.equal( citizen.state.current_mail === null, true, 'citizen should not have current mail in its state' );
assert.equal( citizen.mail.inbox.length === 1, true, 'inbox does not contain expected amount of mail' );
assert.equal( citizen.mail.inbox[0].msg === message, true, 'content of message on queue does not match sent message' );
done();
});
citizen = supervisor.start( name, './test/citizen/unacked-mail', { retries: 0 });
citizen.mail.send( message );
});
it('will requeue unacked mail if a citizen shuts down', function( done ){
this.timeout( 10000 );
var name = 'unacker',
message = 'SHUTDOWN',
citizen;
supervisor.hook.add( name + '-shutdown', 'do-assertions', function( msg ){
supervisor.hook.del( name + '-shutdown', 'do-assertions' );
assert.equal( citizen.state.current_mail === null, true, 'citizen should not have current mail in its state' );
assert.equal( citizen.mail.inbox.length === 1, true, 'inbox does not contain expected amount of mail' );
assert.equal( citizen.mail.inbox[0].msg === message, true, 'content of message on queue does not match sent message' );
done();
});
citizen = supervisor.start( name, './test/citizen/unacked-mail', { retries: 0 });
citizen.mail.send( message );
});
it('will cache citizen notices', function( done ){
this.timeout( 8888 );
supervisor.start( 'notice-sender', './test/citizen/notice-sender' );
var cache_checker = setInterval( check_cache_for_citizen_notice, 888 );
function check_cache_for_citizen_notice(){
var notice_cache = supervisor.noticeboard.cache[ 'sample-notice-from-citizen' ];
if( ! notice_cache ) return;
if( notice_cache !== 'hello supervisor' ) return;
clearInterval( cache_checker );
done();
}
});
});
describe('Citizen Behavior', function(){
var supervisor;
beforeEach( function(){
supervisor = supe();
});
it('can pause flow of inbound mail', function( done ){
this.timeout( 10000 );
var name = 'pauser',
citizen,
paused_at,
pause_duration_ms;
supervisor.hook.add( 'supervisor-mail', 'handle-mail', function( envelope ){
if( ! envelope.msg ) return;
var content = envelope.msg;
if( content.pause_for ){
paused_at = content.paused_at;
pause_duration_ms = content.pause_for;
}
if( content.received === 'do assertions' ){
var received_at = Date.now(),
processed = received_at - paused_at;
assert.equal( processed >= pause_duration_ms, true, 'mail was processed in ' + processed + 'ms but pause duration is ' + pause_duration_ms + 'ms' );
// cleanup
supervisor.hook.add( name + '-stopped', 'end', function(){
done();
});
supervisor.stop( name );
}
});
citizen = supervisor.start( name, './test/citizen/paused-mail', { retries: 0 });
citizen.mail.send( 'PAUSE' );
citizen.mail.send( 'do assertions' );
});
it('can process mail asynchronously', function( done ){
this.timeout( 10000 );
var name = 'async-mail-handler',
async_mail_handled;
supervisor.hook.add( 'citizen-signal', 'detect-unhandled-mail', function( envelope ){
if( typeof async_mail_handled != 'undefined' ) return;
var citizen_name = envelope.from;
if( citizen_name != name ) return;
if( envelope.signal != 'UNHANDLED-MAIL' ) return;
async_mail_handled = false;
do_assertions();
});
supervisor.hook.add( 'citizen-signal', 'detect-acked-mail', function( envelope ){
if( typeof async_mail_handled != 'undefined' ) return;
var citizen_name = envelope.from;
if( citizen_name != name ) return;
if( envelope.signal != 'ACK-CURRENT-MAIL' ) return;
async_mail_handled = true;
do_assertions();
});
var citizen = supervisor.start( name, './test/citizen/async-mail-handler', { retries: 0 });
citizen.mail.send( 'async mail' );
function do_assertions(){
assert.equal( async_mail_handled, true, 'async mail was not handled' );
supervisor.hook.add( name + '-stopped', 'continue-tests', function(){
done();
});
supervisor.stop( name );
}
});
});
describe('Citizen Noticeboard Integration', function(){
var supervisor;
beforeEach( function(){
supervisor = supe({ retries: 0 });
});
it('can watch a notice on supervisor\'s noticeboard', function( done ){
this.timeout( 10000 );
var citizen_name = 'notice-receiver',
sample_notice = 'sample-notice',
sample_message = 'hello citizen',
citizen;
supervisor.noticeboard.watch( 'ready-to-receive-notices', 'send-sample-notice', function( msg ){
var ready_citizen = msg.notice.citizen;
if( ready_citizen !== citizen_name ) return;
supervisor.noticeboard.notify( sample_notice, sample_message );
supervisor.noticeboard.ignore( 'ready-to-receive-notices', 'send-sample-notice' );
});
supervisor.hook.add( 'supervisor-mail', 'do-assertions', function( envelope ){
if( envelope.type !== 'mail' ) return;
var content = envelope.msg;
if( !content.received || !content.notice ) return;
assert.equal( content.notice, sample_notice, 'received notice is not what was sent' );
assert.equal( content.received, sample_message, 'received message is not what was sent' );
done();
// cleanup
citizen.ref.kill();
});
citizen = supervisor.start( citizen_name, './test/citizen/notice-receiver', { retries: 0 });
});
it('can "watch once" a notice on supervisor\'s noticeboard', function( done ){
this.timeout( 10000 );
var citizen_name = 'notice-receiver',
sample_notice = 'sample-notice',
sample_message = 'hello citizen',
citizen_responses = 0,
citizen;
supervisor.noticeboard.watch( 'ready-to-receive-notices', 'send-sample-notices', function( msg ){
var ready_citizen = msg.notice.citizen;
if( ready_citizen != citizen_name ) return;
supervisor.noticeboard.notify( sample_notice, sample_message );
supervisor.noticeboard.notify( sample_notice, sample_message );
supervisor.noticeboard.notify( sample_notice, sample_message );
supervisor.noticeboard.ignore( 'ready-to-receive-notices', 'send-sample-notices' );
});
supervisor.hook.add( 'supervisor-mail', 'do-assertions', function( envelope ){
if( envelope.type !== 'mail' ) return;
var content = envelope.msg;
if( !content.received || !content.notice ) return;
citizen_responses += 1;
});
citizen = supervisor.start( citizen_name, './test/citizen/notice-once-receiver', { retries: 0 });
setTimeout( function(){
assert.equal( citizen_responses, 1 );
done();
// cleanup
citizen.ref.kill();
}, 1500 );
});
it('will ignore duplicate notice pipe requests to supervisor\'s noticeboard', function( done ){
this.timeout(10000);
var sample_notice = 'sample-notice',
failed = false,
citizen;
supervisor.noticeboard.notify( sample_notice, 42 );
supervisor.hook.add( 'double-piper-crashed', 'fail-test', function(){
supervisor.hook.del( 'double-piper-crashed', 'fail-test' );
failed = true;
});
citizen = supervisor.start( 'double-piper', './test/citizen/duplicate-notice-sender', { retries: 0 });
setTimeout( function(){
assert.equal( failed, false );
done();
// cleanup
citizen.ref.kill();
}, 3000 );
});
it('can post a notice on supervisor\'s noticeboard', function( done ){
this.timeout( 10000 );
supervisor.noticeboard.watch( 'sample-notice-from-citizen', 'do-assertions', function( msg ){
done();
});
supervisor.start( 'notice-sender', './test/citizen/notice-sender', { retries: 0 });
});
it('can use supervisor\'s noticeboard cache', function( done ){
this.timeout( 5000 );
var payload_to_cache = { success: true };
// handle citizen's response to cache test
supervisor.hook.add( 'supervisor-mail', 'assess-cache-access', access_cache_access );
// cache payload
supervisor.noticeboard.notify( 'sample-notice', payload_to_cache );
supervisor.start( 'cache-accesser', './test/citizen/cache-accesser' );
function access_cache_access( envelope ){
if( envelope.type !== 'mail' ) return;
if( envelope.from !== 'cache-accesser' ) return;
var response = envelope.msg.received,
payload_matches_cache = true;
for( var key in payload_to_cache ){
if( ! payload_to_cache.hasOwnProperty( key ) ) continue;
if( ! response.hasOwnProperty( key ) ) payload_matches_cache = false;
if( payload_to_cache[ key ] != response[ key ] ) payload_matches_cache = false;
break;
}
if( payload_matches_cache ) done();
}
});
});
});