alnoda-workspaces/workspaces/base-workspace/Cronicle-0.8.61/lib/test.js
2021-07-30 12:18:29 +00:00

1559 lines
52 KiB
JavaScript
Executable file

// Unit tests for Cronicle (run using `npm test`)
// Copyright (c) 2016 - 2017 Joseph Huckaby
// Released under the MIT License
var cp = require('child_process');
var fs = require('fs');
var async = require('async');
var glob = require('glob');
var moment = require('moment-timezone');
var Tools = require('pixl-tools');
var PixlServer = require("pixl-server");
// we need a few config files
var config = require('../sample_conf/config.json');
var setup = require('../sample_conf/setup.json');
// override things for the unit tests
config.debug = true;
config.echo = false;
config.color = false;
config.master = false;
config.WebServer.http_port = 4012;
config.base_app_url = "http://localhost:4012";
config.udp_broadcast_port = 4014;
config.email_from = "test@localhost";
config.smtp_hostname = "localhost";
config.secret_key = "UNIT_TEST";
config.log_filename = "unit.log";
config.pid_file = "logs/unit.pid";
config.debug_level = 10;
config.scheduler_startup_grace = 0;
config.job_startup_grace = 1;
config.Storage.Filesystem.base_dir = "data/unittest";
config.web_hook_config_keys = ["base_app_url", "something_custom"];
config.something_custom = "nonstandard property";
config.track_manual_jobs = true;
// chdir to the proper server root dir
process.chdir( require('path').dirname( __dirname ) );
// global refs
var server = null;
var storage = null;
var cronicle = null;
var request = null;
var api_url = '';
var session_id = '';
module.exports = {
logDebug: function(level, msg, data) {
// proxy request to system logger with correct component
if (cronicle && cronicle.logger) {
cronicle.logger.set( 'component', 'UnitTest' );
cronicle.logger.debug( level, msg, data );
}
},
setUp: function (callback) {
// always called before tests start
var self = this;
// make sure another unit test isn't running
var pid = false;
try { pid = fs.readFileSync('logs/unit.pid', { encoding: 'utf8' }); }
catch (e) {;}
if (pid) {
var alive = true;
try { process.kill(parseInt(pid), 0); }
catch (e) { alive = false; }
if (alive) {
console.warn("Another unit test is already running (PID " + pid + "). Exiting.");
process.exit(1);
}
}
// clean out data from last time
try { cp.execSync('rm -rf logs/unit.pid data/unittest'); }
catch (e) {;}
// construct server object
server = new PixlServer({
__name: 'Cronicle',
__version: require('../package.json').version,
config: config,
components: [
require('pixl-server-storage'),
require('pixl-server-web'),
require('pixl-server-api'),
require('pixl-server-user'),
require('./engine.js')
]
});
server.startup( function() {
// server startup complete
storage = server.Storage;
cronicle = server.Cronicle;
// prepare to make api calls
request = cronicle.request;
api_url = server.config.get('base_app_url') + server.API.config.get('base_uri');
// cancel auto ticks, so we can send our own later
clearTimeout( server.tickTimer );
delete server.tickTimer;
// bootstrap storage with initial records
async.eachSeries( setup.storage,
function(params, callback) {
var func = params.shift();
params.push( callback );
// massage a few params
if (typeof(params[1]) == 'object') {
var obj = params[1];
if (obj.created) obj.created = Tools.timeNow(true);
if (obj.modified) obj.modified = Tools.timeNow(true);
if (obj.regexp && (obj.regexp == '_HOSTNAME_')) obj.regexp = '^(' + Tools.escapeRegExp( server.hostname ) + ')$';
if (obj.hostname && (obj.hostname == '_HOSTNAME_')) obj.hostname = server.hostname;
if (obj.ip && (obj.ip == '_IP_')) obj.ip = server.ip;
}
// call storage directly
storage[func].apply( storage, params );
},
function(err) {
if (err) throw err;
// begin unit tests
callback();
}
); // async.eachSeries
} ); // server.startup
}, // setUp
beforeEach: function(test) {
// called just before each test
this.logDebug(10, "Starting unit test: " + test.name );
},
afterEach: function(test) {
// called after each test completes
this.logDebug(10, "Unit test complete: " + test.name );
},
//
// Tests Array:
//
tests: [
function testServerStarted(test) {
test.ok( server.started > 0, 'Cronicle started up successfully');
test.done();
},
function testStorage(test) {
storage.get( 'users/admin', function(err, user) {
test.ok( !err, "No error fetching admin user" );
test.ok( !!user, "User record is non-null" );
test.ok( user.username == "admin", "Username is correct" );
test.ok( user.created > 0, "User creation date is non-zero" );
test.done();
} );
},
function testcheckMasterEligibility(test) {
cronicle.checkMasterEligibility( function() {
test.ok( cronicle.multi.cluster == true, "Server found in cluster" );
test.ok( cronicle.multi.eligible == true, "Server is eligible for master" );
test.ok( cronicle.multi.master == false, "Server is not yet master" );
test.ok( cronicle.multi.slave == false, "Server is not a slave" );
test.done();
} );
},
function testGoMaster(test) {
cronicle.goMaster();
test.ok( cronicle.multi.master == true, "Server became master" );
test.ok( cronicle.multi.slave == false, "Server is not a slave" );
test.ok( cronicle.multi.cluster == true, "Server is still found in cluster" );
test.ok( cronicle.multi.masterHostname == server.hostname, "Server masterHostname is self" );
test.ok( !!cronicle.multi.lastPingSent, "Server lastPingSent is non-zero" );
test.ok( !!cronicle.tz, "Server has a timezone set" );
// need a rest here, so async sub-components can start up
setTimeout( function() { test.done(); }, 500 );
},
function testAPIPing(test) {
// make basic REST API call, check response
request.json( api_url + '/app/ping', {}, function(err, resp, data) {
test.ok( !err, "No error requesting ping API" );
test.ok( resp.statusCode == 200, "HTTP 200 from ping API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.done();
} );
},
function testAPILoginBadUsername(test) {
// login with unknown username
var params = {
username: "nobody",
password: "foo"
};
request.json( api_url + '/user/login', params, function(err, resp, data) {
test.ok( !err, "No error requesting user/login API" );
test.ok( resp.statusCode == 200, "HTTP 200 from user/login API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code != 0, "Code is non-zero (we expect an error)" );
test.ok( !data.session_id, "No session_id in response" );
test.done();
} );
},
function testAPILoginBadPassword(test) {
// login with good user but bad password
var params = {
username: "admin",
password: "adminnnnnnn"
};
request.json( api_url + '/user/login', params, function(err, resp, data) {
test.ok( !err, "No error requesting user/login API" );
test.ok( resp.statusCode == 200, "HTTP 200 from user/login API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code != 0, "Code is non-zero (we expect an error)" );
test.ok( !data.session_id, "No session_id in response" );
test.done();
} );
},
function testAPIUserLogin(test) {
// login as admin (successfully), save session id for downstream tests
var params = {
username: "admin",
password: "admin"
};
request.json( api_url + '/user/login', params, function(err, resp, data) {
test.ok( !err, "No error requesting user/login API" );
test.ok( resp.statusCode == 200, "HTTP 200 from user/login API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.session_id, "Found session_id in response" );
// save session_id for later
session_id = data.session_id;
test.done();
} );
},
function testAPIConfig(test) {
// test app/config api
var params = {};
request.json( api_url + '/app/config', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.config, "Found config in response data" );
test.done();
} );
},
// app/create_plugin
function testAPICreatePlugin(test) {
// test app/create_plugin api
var self = this;
var params = {"params":[{"type":"textarea","id":"script","title":"Script Source","rows":10,"value":"#!/bin/sh\n\n# Enter your shell script code here"}],"title":"Copy of Shell Script","command":"bin/shell-plugin.js","enabled":1,"session_id":session_id};
request.json( api_url + '/app/create_plugin', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.id, "Found new id in data" );
// save plugin id for later
self.plugin_id = data.id;
// check to see that plugin actually got saved to storage
storage.listFind( 'global/plugins', { id: data.id }, function(err, plugin) {
test.ok( !err, "No error fetching data" );
test.ok( !!plugin, "Data record record is non-null" );
test.ok( plugin.username == "admin", "Username is correct" );
test.ok( plugin.created > 0, "Record creation date is non-zero" );
test.done();
} );
} );
},
// app/update_plugin
function testAPIUpdatePlugin(test) {
// test app/update_plugin api
var self = this;
var params = {"id":this.plugin_id, "title":"Updated Plugin Title","session_id":session_id};
request.json( api_url + '/app/update_plugin', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that plugin actually got saved to storage
storage.listFind( 'global/plugins', { id: self.plugin_id }, function(err, plugin) {
test.ok( !err, "No error fetching data" );
test.ok( !!plugin, "Data record is non-null" );
test.ok( plugin.username == "admin", "Username is correct" );
test.ok( plugin.created > 0, "Record creation date is non-zero" );
test.ok( plugin.title == "Updated Plugin Title", "Title was updated correctly" );
test.done();
} );
} );
},
// app/delete_plugin
function testAPIDeletePlugin(test) {
// test app/delete_plugin api
var self = this;
var params = {"id":this.plugin_id, "session_id":session_id};
request.json( api_url + '/app/delete_plugin', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that plugin actually got deleted from storage
storage.listFind( 'global/plugins', { id: self.plugin_id }, function(err, plugin) {
test.ok( !err, "No error expected for missing data" );
test.ok( !plugin, "Data record should be null (deleted)" );
delete self.plugin_id;
test.done();
} );
} );
},
// app/create_category
function testAPICreateCategory(test) {
// test app/create_category api
var self = this;
var params = {"title":"test will del cat","description":"yo","max_children":0,"enabled":1,"notify_success":"","notify_fail":"","web_hook":"","cpu_limit":0,"cpu_sustain":0,"memory_limit":0,"memory_sustain":0,"log_max_size":0,"session_id":session_id};
request.json( api_url + '/app/create_category', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.id, "Found new id in data" );
// save cat id for later
self.cat_id = data.id;
// check to see that cat actually got saved to storage
storage.listFind( 'global/categories', { id: data.id }, function(err, cat) {
test.ok( !err, "No error fetching data" );
test.ok( !!cat, "Data record record is non-null" );
test.ok( cat.username == "admin", "Username is correct" );
test.ok( cat.created > 0, "Record creation date is non-zero" );
test.done();
} );
} );
},
// app/update_category
function testAPIUpdateCategory(test) {
// test app/update_category api
var self = this;
var params = {"id":this.cat_id, "title":"Updated Category Title","session_id":session_id};
request.json( api_url + '/app/update_category', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that cat actually got saved to storage
storage.listFind( 'global/categories', { id: self.cat_id }, function(err, cat) {
test.ok( !err, "No error fetching data" );
test.ok( !!cat, "Data record is non-null" );
test.ok( cat.username == "admin", "Username is correct" );
test.ok( cat.created > 0, "Record creation date is non-zero" );
test.ok( cat.title == "Updated Category Title", "Title was updated correctly" );
test.done();
} );
} );
},
// app/delete_category
function testAPIDeleteCategory(test) {
// test app/delete_category api
var self = this;
var params = {"id":this.cat_id, "session_id":session_id};
request.json( api_url + '/app/delete_category', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that cat actually got deleted from storage
storage.listFind( 'global/categories', { id: self.cat_id }, function(err, cat) {
test.ok( !err, "No error expected for missing data" );
test.ok( !cat, "Data record should be null (deleted)" );
delete self.cat_id;
test.done();
} );
} );
},
// app/create_server_group
function testAPICreateServerGroup(test) {
// test app/create_server_group api
var self = this;
var params = {"title":"del gap","regexp":"dasds","master":0,"session_id":session_id};
request.json( api_url + '/app/create_server_group', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.id, "Found new id in data" );
// save group id for later
self.group_id = data.id;
// check to see that group actually got saved to storage
storage.listFind( 'global/server_groups', { id: data.id }, function(err, group) {
test.ok( !err, "No error fetching data" );
test.ok( !!group, "Data record record is non-null" );
test.ok( group.title == "del gap", "Title is correct" );
test.ok( group.regexp == "dasds", "Regexp is correct" );
test.done();
} );
} );
},
// app/update_server_group
function testAPIUpdateServerGroup(test) {
// test app/update_server_group api
var self = this;
var params = {"id":this.group_id, "title":"Updated Group Title","session_id":session_id};
request.json( api_url + '/app/update_server_group', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that group actually got saved to storage
storage.listFind( 'global/server_groups', { id: self.group_id }, function(err, group) {
test.ok( !err, "No error fetching data" );
test.ok( !!group, "Data record is non-null" );
test.ok( group.title == "Updated Group Title", "Title was updated correctly" );
test.done();
} );
} );
},
// app/delete_server_group
function testAPIDeleteServerGroup(test) {
// test app/delete_server_group api
var self = this;
var params = {"id":this.group_id, "session_id":session_id};
request.json( api_url + '/app/delete_server_group', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that group actually got deleted from storage
storage.listFind( 'global/server_groups', { id: self.group_id }, function(err, group) {
test.ok( !err, "No error expected for missing data" );
test.ok( !group, "Data record should be null (deleted)" );
delete self.group_id;
test.done();
} );
} );
},
// app/create_api_key
function testAPICreateAPIKey(test) {
// test app/create_api_key api
var self = this;
var params = {"key":"35b60c12892dd4503cf3a8dbf22d3354","privileges":{"admin":0,"create_events":0,"edit_events":0,"delete_events":1,"run_events":0,"abort_events":0,"state_update":0},"active":"1","title":"test will delete","description":"dshfdwsfs","session_id":session_id};
request.json( api_url + '/app/create_api_key', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.id, "Found new id in data" );
test.ok( !!data.key, "Found new api key in data" );
// save api key id for later
self.apikey_id = data.id;
self.apikey_key = data.key;
// check to see that api key actually got saved to storage
storage.listFind( 'global/api_keys', { id: data.id }, function(err, api_key) {
test.ok( !err, "No error fetching data" );
test.ok( !!api_key, "Data record is non-null" );
test.ok( api_key.username == "admin", "Username is correct" );
test.ok( api_key.created > 0, "Record creation date is non-zero" );
test.ok( !!api_key.key, "API Key record has key" );
test.ok( api_key.key == "35b60c12892dd4503cf3a8dbf22d3354", "API Key is correct" );
test.done();
} );
} );
},
function testAPIKeyUsage(test) {
// try to hit an API using the API Key as auth (not a user session id)
var self = this;
var params = { "api_key": this.apikey_key, offset: 0, limit: 100 };
request.json( api_url + '/app/get_schedule', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.done();
} );
},
function testAPIKeyUnauthorized(test) {
// try to access an API that is unauthorized for an API Key
// an error is expected here
var self = this;
var params = {"title":"this should fail","description":"yo key","max_children":0,"enabled":1,"notify_success":"","notify_fail":"","web_hook":"","cpu_limit":0,"cpu_sustain":0,"memory_limit":0,"memory_sustain":0,"api_key":this.apikey_key};
request.json( api_url + '/app/create_category', params, function(err, resp, data) {
test.ok( !err, "No error requesting API", err );
test.ok( resp.statusCode == 200, "HTTP 200 from API", resp.statusCode );
test.ok( "code" in data, "Found code prop in JSON response", data );
test.ok( data.code != 0, "Code is non-zero (error is expected)", data );
test.done();
} );
},
// app/update_api_key
function testAPIUpdateAPIKey(test) {
// test app/update_api_key api
var self = this;
var params = {"id":this.apikey_id, "title":"Updated API Key Title","session_id":session_id};
request.json( api_url + '/app/update_api_key', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that api key actually got saved to storage
storage.listFind( 'global/api_keys', { id: self.apikey_id }, function(err, api_key) {
test.ok( !err, "No error fetching data" );
test.ok( !!api_key, "Data record is non-null" );
test.ok( api_key.username == "admin", "Username is correct" );
test.ok( api_key.created > 0, "Record creation date is non-zero" );
test.ok( api_key.title == "Updated API Key Title", "Title was updated correctly" );
test.done();
} );
} );
},
// app/get_api_keys
function testAPIGetAPIKeys(test) {
// test app/get_api_keys api
var self = this;
var params = { "session_id": session_id, offset: 0, limit: 100 };
request.json( api_url + '/app/get_api_keys', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.rows, "Found rows in response" );
test.ok( !!data.rows.length, "Rows has length" );
var api_key = Tools.findObject( data.rows, { id: self.apikey_id } );
test.ok( !!api_key, "Found our API Key in rows" );
test.ok( api_key.id == self.apikey_id, "API Key ID matches our query" );
test.ok( api_key.username == "admin", "Username is correct" );
test.ok( api_key.created > 0, "Record creation date is non-zero" );
test.ok( !!api_key.key, "API Key record has key" );
test.done();
} );
},
// app/get_api_key
function testAPIGetAPIKey(test) {
// test app/get_api_key api
var self = this;
var params = { "session_id": session_id, id: this.apikey_id };
request.json( api_url + '/app/get_api_key', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
var api_key = data.api_key;
test.ok( !!api_key, "Found our API Key in data" );
test.ok( api_key.id == self.apikey_id, "API Key ID matches our query" );
test.ok( api_key.username == "admin", "Username is correct" );
test.ok( api_key.created > 0, "Record creation date is non-zero" );
test.ok( !!api_key.key, "API Key record has key" );
test.done();
} );
},
// app/delete_api_key
function testAPIDeleteAPIKey(test) {
// test app/delete_api_key api
var self = this;
var params = {"id":this.apikey_id, "session_id":session_id};
request.json( api_url + '/app/delete_api_key', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that api key actually got deleted from storage
storage.listFind( 'global/api_keys', { id: self.apikey_id }, function(err, api_key) {
test.ok( !err, "No error expected for missing data" );
test.ok( !api_key, "Data record should be null (deleted)" );
delete self.apikey_id;
test.done();
} );
} );
},
// app/create_event
function testAPICreateEvent(test) {
// test app/create_event api
var self = this;
var params = {
"enabled": 1,
"params": {
"duration": "10",
"progress": 1,
"action": "Success",
"secret": "foo"
},
"timing": {
"years": [2001], // we'll run it manually first
"minutes": [0]
},
"max_children": 1,
"timeout": 300,
"catch_up": 0,
"timezone": cronicle.tz,
"plugin": "testplug",
"title": "Well Test!",
"category": "general",
"target": "maingrp",
"multiplex": 0,
"retries": 0,
"retry_delay": 0,
"detached": 0,
"notify_success": "",
"notify_fail": "",
"web_hook": "",
"cpu_limit": 0,
"cpu_sustain": 0,
"memory_limit": 0,
"memory_sustain": 0,
"notes": "",
"session_id": session_id
};
request.json( api_url + '/app/create_event', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.id, "Found new id in data" );
// save event id for later
self.event_id = data.id;
// check to see that event actually got saved to storage
storage.listFind( 'global/schedule', { id: data.id }, function(err, event) {
test.ok( !err, "No error fetching data" );
test.ok( !!event, "Data record record is non-null" );
test.ok( event.username == "admin", "Username is correct" );
test.ok( event.created > 0, "Record creation date is non-zero" );
test.done();
} );
} );
},
// app/update_event
function testAPIUpdateEvent(test) {
// test app/update_event api
var self = this;
var params = {
"id": this.event_id,
"title": "Updated Event Title",
"session_id": session_id
};
request.json( api_url + '/app/update_event', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that event actually got saved to storage
storage.listFind( 'global/schedule', { id: self.event_id }, function(err, event) {
test.ok( !err, "No error fetching data" );
test.ok( !!event, "Data record record is non-null" );
test.ok( event.username == "admin", "Username is correct" );
test.ok( event.created > 0, "Record creation date is non-zero" );
test.ok( event.title == "Updated Event Title", "New title is correct" );
test.done();
} );
} );
},
// app/get_schedule
function testAPIGetSchedule(test) {
// test app/get_schedule api
var self = this;
var params = { "session_id": session_id, offset: 0, limit: 100 };
request.json( api_url + '/app/get_schedule', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.rows, "Found rows in response" );
test.ok( !!data.rows.length, "Rows has length" );
var event = Tools.findObject( data.rows, { id: self.event_id } );
test.ok( !!event, "Found our event in rows" );
test.ok( event.id == self.event_id, "Event ID matches our query" );
test.ok( event.username == "admin", "Username is correct" );
test.ok( event.created > 0, "Record creation date is non-zero" );
test.done();
} );
},
// app/get_event
function testAPIGetEvent(test) {
// test app/get_event api
var self = this;
var params = { "session_id": session_id, id: this.event_id };
request.json( api_url + '/app/get_event', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
var event = data.event;
test.ok( !!event, "Found our event in data" );
test.ok( event.id == self.event_id, "Event ID matches our query" );
test.ok( event.username == "admin", "Username is correct" );
test.ok( event.created > 0, "Record creation date is non-zero" );
test.done();
} );
},
// app/run_event
function testAPIRunEvent(test) {
// test app/run_event api
// run event manually, specify an override
var self = this;
var params = {
"session_id": session_id,
id: this.event_id,
notify_fail: 'test@test.com'
};
request.json( api_url + '/app/run_event', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.ids, "Found ids in response" );
test.ok( data.ids.length == 1, "Data ids has length of 1" );
test.ok( !!data.ids[0], "Found Job ID in response data" );
var job_id = data.ids[0];
self.job_id = job_id;
// wait a few seconds here for job to start and get to around 50%
setTimeout( function() {
test.done();
}, 1000 * 5 );
} );
},
function testJobInProgress(test) {
// make sure job is in progress
var self = this;
var all_jobs = cronicle.getAllActiveJobs();
var job = all_jobs[ this.job_id ];
test.ok( !!job, "Found our job in active list" );
test.ok( job.event == this.event_id, "Job has correct Event ID" );
test.ok( job.progress > 0, "Job has positive progress" );
test.ok( job.notify_fail == "test@test.com", "Our notify_fail override made it in" );
test.ok( !!job.pid, "Job has a PID" );
// try to ping pid
var ping = false;
try { ping = process.kill( job.pid, 0 ); }
catch (e) {;}
test.ok( !!ping, "Job PID was successfully pinged" );
// force cronicle to measure mem/cpu
cronicle.monitorServerResources( function(err) {
test.ok( !err, "No error calling monitorServerResources", err );
test.done();
} );
},
// app/get_live_job_log
function testAPIGetLiveJobLog(test) {
// test get_live_job_log API (raw HTTP get, not a JSON API)
var self = this;
request.get( api_url + '/app/get_live_job_log?id=' + this.job_id, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( !!data, "Got data buffer" );
test.ok( data.length > 0, "Data buffer has length" );
test.done();
} );
},
// app/get_job_status
function testAPIGetJobStatus(test) {
// test app/get_job_status api
var self = this;
var params = {
"session_id": session_id,
id: this.job_id
};
request.json( api_url + '/app/get_job_status', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
test.ok( !!data.job, "Found job in data" );
var job = data.job;
test.ok( job.id == self.job_id, "Job ID matches" );
test.ok( job.progress > 0, "Job progress is still non-zero" );
test.ok( !!job.cpu, "Job has CPU metrics" );
test.ok( job.cpu.count > 0, "Job CPU count is non-zero" );
// test.ok( job.cpu.current > 0, "Job CPU current is non-zero" );
test.ok( !!job.mem, "Job has memory metrics" );
test.ok( job.mem.count > 0, "Job memory count is non-zero" );
// test.ok( job.mem.current > 0, "Job memory current is non-zero" );
test.done();
} );
},
// app/update_job
function testAPIUpdateJob(test) {
// test app/update_job api
var self = this;
var params = {
"session_id": session_id,
id: this.job_id,
notify_fail: 'test2@test.com'
};
request.json( api_url + '/app/update_job', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
var all_jobs = cronicle.getAllActiveJobs();
var job = all_jobs[ self.job_id ];
test.ok( !!job, "Found our job in active list" );
test.ok( job.event == self.event_id, "Job has correct Event ID" );
test.ok( job.notify_fail == "test2@test.com", "Our notify_fail update was applied" );
test.done();
} );
},
// wait for job to complete
function testWaitJobComplete(test) {
// go into wait loop while job is still in progress
var self = this;
var params = {
"session_id": session_id,
id: this.job_id,
need_log: 1
};
var details = { code: 1 };
var count = 0;
async.doWhilst(
function (callback) {
// poll get_job_details API
request.json( api_url + '/app/get_job_details', params, function(err, resp, data) {
if (err) return callback(err);
if (resp.statusCode != 200) return callback(new Error("HTTP " + resp.statusCode + " " + resp.statusMessage));
// e-brake to prevent infinite loop
if (count++ > 100) return callback(new Error("Too many loop iterations polling get_job_details API"));
details = data;
setTimeout( callback, 500 );
} );
},
function () { return (details.code != 0); },
function (err) {
// job is complete
var job = details.job;
test.ok( !!job, "Got job details in response" );
test.ok( job.id == self.job_id, "Job ID matches" );
test.ok( !!job.complete, "Job is marked as complete" );
test.ok( job.code == 0, "Job is not marked as an error" );
test.ok( !!job.perf, "Job has perf metrics" );
test.ok( !!job.pid, "Job record still has a pid" );
// job pid should be dead at this point
var ping = false;
try { ping = process.kill( job.pid, 0 ); }
catch (e) {;}
test.ok( !ping, "Job PID is dead" );
test.done();
}
);
},
// app/get_job_log
function testAPIGetJobLog(test) {
// test get_job_log API (raw HTTP get, not a JSON API)
var self = this;
request.get( api_url + '/app/get_job_log?id=' + this.job_id, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( !!data, "Got data buffer" );
test.ok( data.length > 0, "Data buffer has length" );
test.ok( data.toString().match(/success/i), "Log buffer contains expected string" );
test.done();
} );
},
// app/get_event_history
function testAPIGetEventHistory(test) {
// go into wait loop while event history is being written
var self = this;
var params = {
"session_id": session_id,
id: this.event_id,
offset: 0,
limit: 100
};
var details = { rows: [] };
var count = 0;
async.doWhilst(
function (callback) {
// poll get_event_history API
request.json( api_url + '/app/get_event_history', params, function(err, resp, data) {
if (err) return callback(err);
if (resp.statusCode != 200) return callback(new Error("HTTP " + resp.statusCode + " " + resp.statusMessage));
// e-brake to prevent infinite loop
if (count++ > 10) return callback(new Error("Too many loop iterations polling get_event_history API"));
details = data;
setTimeout( callback, 500 );
} );
},
function () { return ( !details.rows || !details.rows.length ); },
function (err) {
// history is written
var stub = details.rows[0];
test.ok( !!stub, "Got event history in response" );
test.ok( stub.id == self.job_id, "History ID matches Job ID" );
test.ok( stub.code == 0, "Correct code in history item" );
test.ok( stub.event == self.event_id, "History item Event ID matching Event ID" );
test.ok( stub.elapsed > 0, "History item has non-zero elapsed time" );
test.ok( stub.action == "job_complete", "History item has correct action" );
test.done();
}
);
},
// app/get_history
function testAPIGetHistory(test) {
// go into wait loop while history is being written
var self = this;
var params = {
"session_id": session_id,
offset: 0,
limit: 100
};
var details = { rows: [] };
var count = 0;
async.doWhilst(
function (callback) {
// poll get_history API
request.json( api_url + '/app/get_history', params, function(err, resp, data) {
if (err) return callback(err);
if (resp.statusCode != 200) return callback(new Error("HTTP " + resp.statusCode + " " + resp.statusMessage));
// e-brake to prevent infinite loop
if (count++ > 10) return callback(new Error("Too many loop iterations polling get_history API"));
details = data;
setTimeout( callback, 500 );
} );
},
function () { return ( !details.rows || !details.rows.length ); },
function (err) {
// history is written
var stub = details.rows[0];
test.ok( !!stub, "Got event history in response" );
test.ok( stub.id == self.job_id, "History ID matches Job ID" );
test.ok( stub.code == 0, "Correct code in history item" );
test.ok( stub.event == self.event_id, "History item Event ID matching Event ID" );
test.ok( stub.elapsed > 0, "History item has non-zero elapsed time" );
test.ok( stub.action == "job_complete", "History item has correct action" );
test.done();
}
);
},
// app/get_activity
function testAPIGetActivity(test) {
// go into wait loop while activity log is being written
var self = this;
var params = {
"session_id": session_id,
offset: 0,
limit: 100
};
var details = { rows: [] };
var count = 0;
async.doWhilst(
function (callback) {
// poll get_activity API
request.json( api_url + '/app/get_activity', params, function(err, resp, data) {
if (err) return callback(err);
if (resp.statusCode != 200) return callback(new Error("HTTP " + resp.statusCode + " " + resp.statusMessage));
// e-brake to prevent infinite loop
if (count++ > 10) return callback(new Error("Too many loop iterations polling get_activity API"));
details = data;
setTimeout( callback, 500 );
} );
},
function () { return ( !details.rows || !details.rows.length || (details.rows[0].id != self.job_id) ); },
function (err) {
// activity is written
// test.debug("Activity response:", details);
var stub = details.rows[0];
test.debug("Activity first item:", stub);
test.ok( !!stub, "Got activity in response" );
test.ok( stub.id == self.job_id, "Activity ID matches Job ID" );
test.ok( stub.event == self.event_id, "Activity item Event ID matches Event ID" );
test.ok( stub.action == "job_run", "Activity item has correct action" );
test.done();
}
);
},
function testSchedulerEventTiming(test) {
// test various formats of event timing
// timestamp for testing: Epoch 1454797620
// Sat Feb 6 14:27:00 2016 (PST)
var cursor = 1454797620;
var tz = "America/Los_Angeles";
test.ok( !!cronicle.checkEventTiming( {}, cursor, tz ), "Every minute should run" );
test.ok( !!cronicle.checkEventTiming( { minutes: [27] }, cursor, tz ), "Hourly should run" );
test.ok( !cronicle.checkEventTiming( { minutes: [28] }, cursor, tz ), "Hourly should not run" );
test.ok( !!cronicle.checkEventTiming( { hours: [14], minutes: [27] }, cursor, tz ), "Daily should run" );
test.ok( !!cronicle.checkEventTiming( { hours: [14] }, cursor, tz ), "Daily every minute should run" );
test.ok( !cronicle.checkEventTiming( { hours: [17], minutes: [27] }, cursor, tz ), "Daily should not run" );
test.ok( !!cronicle.checkEventTiming( { weekdays: [6], hours: [14], minutes: [27] }, cursor, tz ), "Weekly should run" );
test.ok( !!cronicle.checkEventTiming( { weekdays: [6], minutes: [27] }, cursor, tz ), "Weekly hourly should run" );
test.ok( !cronicle.checkEventTiming( { weekdays: [0], hours: [14], minutes: [27] }, cursor, tz ), "Weekly should not run" );
test.ok( !!cronicle.checkEventTiming( { days: [6], hours: [14], minutes: [27] }, cursor, tz ), "Monthly should run" );
test.ok( !!cronicle.checkEventTiming( { days: [6], minutes: [27] }, cursor, tz ), "Monthly hourly should run" );
test.ok( !cronicle.checkEventTiming( { days: [5], hours: [14], minutes: [27] }, cursor, tz ), "Monthly should not run" );
test.ok( !!cronicle.checkEventTiming( { months: [2], days: [6], hours: [14], minutes: [27] }, cursor, tz ), "Yearly should run" );
test.ok( !!cronicle.checkEventTiming( { months: [2], minutes: [27] }, cursor, tz ), "Yearly hourly should run" );
test.ok( !cronicle.checkEventTiming( { months: [12], days: [6], hours: [14], minutes: [27] }, cursor, tz ), "Yearly should not run" );
test.ok( !!cronicle.checkEventTiming( { years: [2016], months: [2], days: [6], hours: [14], minutes: [27] }, cursor, tz ), "Single should run" );
test.ok( !cronicle.checkEventTiming( { years: [2015], months: [2], days: [6], hours: [14], minutes: [27] }, cursor, tz ), "Single should not run" );
// now test same timestamp in a different timezone
tz = "America/New_York";
test.ok( !!cronicle.checkEventTiming( { hours: [17], minutes: [27] }, cursor, tz ), "New York should run" );
test.ok( !cronicle.checkEventTiming( { hours: [14], minutes: [27] }, cursor, tz ), "New York should not run" );
test.done();
},
function testUpdateEventForSchedule(test) {
// update event with hourly timing and a simple shell command
var self = this;
var params = {
"params": {
"script": "#!/bin/sh\n\necho \"UNIT TEST STRING\""
},
"timing": {
"minutes": [25] // hourly on the 25th minute
},
"plugin": "shellplug",
"web_hook": api_url + '/app/unit_test_web_hook'
};
storage.listFindUpdate( 'global/schedule', { id: this.event_id }, params, function(err) {
test.ok( !err, "Failed to update event: " + err );
test.done();
} );
},
function testSchedulerTick(test) {
// tick scheduler with false time, which should start our job
var self = this;
test.ok( !!cronicle.state.enabled, "Scheduler state is currently enabled" );
// add API handler for testing web hooks
cronicle.api_unit_test_web_hook = function(args, callback) {
// hello
var params = args.params || {};
if (self.expect_web_hook && self.current_test && (params.action == 'job_complete')) {
var test = self.current_test;
delete self.current_test;
self.web_hook_data = params;
test.ok( !!params, "Got web hook data" );
test.done();
}
callback({ code: 0 });
}; // web hook handler
// set props for api callback to detect
self.expect_web_hook = true;
self.web_hook_data = null;
self.current_test = test;
// setup our fake timestamp to match event timing settings
var dargs = Tools.getDateArgs( Tools.timeNow(true) );
dargs.min = 25; // match our event timing
// tick the scheduler
cronicle.schedulerMinuteTick( dargs );
},
function testWebHookData(test) {
// web hook should have got us here, so let's examine the data
var job = this.web_hook_data;
test.debug("Web hook data:", job);
delete this.web_hook_data;
delete this.expect_web_hook;
test.ok( !!job, "Got web hook data" );
test.ok( !!job.id, "Job has an ID", job );
test.ok( job.id != this.job_id, "Job ID does not match previous job", job );
test.ok( job.code == 0, "Job is not marked as an error", job );
test.ok( job.event == this.event_id, "Job Event ID matches", job );
test.ok( job.category == "general", "Job has correct category", job );
test.ok( job.plugin == "shellplug", "Job has correct Plugin", job );
test.ok( !!job.base_app_url, "Job has correct key pulled from config via web hook", job );
test.ok( job.something_custom == "nonstandard property", "Job has correct custom web hook property", job );
test.ok( !job.smtp_hostname, "Job does not have config key not in the web hook key list", job );
test.done();
},
function testRunFailedEvent(test) {
// run an event that fails
var self = this;
// set props for api callback to detect
this.expect_web_hook = true;
this.web_hook_data = null;
this.current_test = test;
storage.listFind( 'global/schedule', { id: this.event_id }, function(err, event) {
test.ok( !err, "No error locating event in schedule" );
test.ok( !!event, "Found event in schedule" );
var job = Tools.copyHash( event, true );
job.params.script = "#!/bin/sh\n\necho \"UNIT TEST DELIBERATE FAILURE\"\nexit 1\n";
cronicle.launchJob( job, function(err, jobs) {
// not doing anything here, as web hook should fire automatically and finish the test
} );
} );
},
function testRunFailedResults(test) {
// make sure failed event really failed
var job = this.web_hook_data;
test.debug( "Web hook data: ", job );
delete this.web_hook_data;
delete this.expect_web_hook;
test.ok( !!job, "Got web hook data" );
test.ok( !!job.id, "Job has an ID" );
test.ok( job.code != 0, "Job is marked as an error" );
test.ok( !!job.description, "Job has an error description" );
test.ok( job.event == this.event_id, "Job Event ID matches" );
test.ok( job.category == "general", "Job has correct category" );
test.ok( job.plugin == "shellplug", "Job has correct Plugin" );
// need rest here, for async logs to finish inserting
setTimeout( function() {
test.done();
}, 500 );
},
function testRunDetachedEvent(test) {
// run event in detached mode
var self = this;
storage.listFind( 'global/schedule', { id: this.event_id }, function(err, event) {
test.ok( !err, "No error locating event in schedule" );
test.ok( !!event, "Found event in schedule" );
var job = Tools.copyHash( event, true );
job.detached = 1;
cronicle.launchJob( job, function(err, jobs) {
test.ok( !err, "No error launching job" );
test.ok( !!jobs, "Got array of launched jobs" );
test.ok( jobs.length == 1, "Launched exactly one job" );
test.ok( jobs[0].id, "Got Job ID" );
// save new job id
self.detached_job_id = jobs[0].id;
test.done();
} );
} );
},
function testWaitForDetachedQueue(test) {
// monitor queue directory until finished file shows up
var self = this;
var file_spec = server.config.get('queue_dir') + '/*.json';
var files_found = false;
async.doWhilst(
function (callback) {
// poll queue dir
glob(file_spec, {}, function (err, files) {
// got task files
if (files && files.length) {
files_found = true;
}
setTimeout( callback, 250 );
} );
},
function () { return (!files_found); },
function (err) {
// got files, we're done
test.done();
}
);
},
function testFinishDetachedEvent(test) {
// force external queue to run to process finished event
// set props for api callback to detect
this.expect_web_hook = true;
this.web_hook_data = null;
this.current_test = test;
cronicle.monitorExternalQueue();
// not calling test.done() as it should fire via web hook
},
function testDetachedWebHookData(test) {
// web hook should have got us here, so let's examine the data
var job = this.web_hook_data;
test.debug( "Detached web hook data: ", job );
delete this.web_hook_data;
delete this.expect_web_hook;
test.ok( !!job, "Got web hook data" );
test.ok( !!job.id, "Job has an ID" );
test.ok( job.id == this.detached_job_id, "Job ID matches our detached job" );
test.ok( job.code == 0, "Job is not marked as an error" );
test.ok( job.event == this.event_id, "Job Event ID matches" );
test.ok( job.category == "general", "Job has correct category" );
test.ok( job.plugin == "shellplug", "Job has correct Plugin" );
// need rest here, for async logs to finish inserting,
// before we delete the associated event (which also deletes logs!)
setTimeout( function() {
test.done();
}, 500 );
},
// app/delete_event
function testAPIDeleteEvent(test) {
// test app/delete_event api
var self = this;
var params = {
"id": this.event_id,
"session_id": session_id
};
request.json( api_url + '/app/delete_event', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that event actually got deleted from storage
storage.listFind( 'global/schedule', { id: self.event_id }, function(err, event) {
test.ok( !err, "No error expected for missing data" );
test.ok( !event, "Data record should be null (deleted)" );
delete self.event_id;
test.done();
} );
} );
},
// TODO: app/abort_job
// TODO: app/abort_jobs
// TODO: catch-up event
// app/update_master_state
function testAPIUpdateMasterState(test) {
// test app/update_master_state api
var self = this;
var params = {
"session_id": session_id,
"enabled": 0
};
// pre-check that state is currently enabled
test.ok( !!cronicle.state.enabled, "Scheduler state is currently enabled" );
// disable it via API
request.json( api_url + '/app/update_master_state', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that change took effect
test.ok( !cronicle.state.enabled, "State is actually disabled" );
test.done();
} );
},
// user/logout
function testAPIUserLogout(test) {
// test user/logout api
var self = this;
var params = {
"session_id": session_id
};
request.json( api_url + '/user/logout', params, function(err, resp, data) {
test.ok( !err, "No error requesting API" );
test.ok( resp.statusCode == 200, "HTTP 200 from API" );
test.ok( "code" in data, "Found code prop in JSON response" );
test.ok( data.code == 0, "Code is zero (no error)" );
// check to see that session actually got deleted from storage
storage.get('sessions/' + session_id, function(err, data) {
test.ok( !!err, "Error expected for missing session" );
test.ok( !data, "Data record should be null (deleted)" );
test.done();
} );
} );
}
], // tests array
tearDown: function (callback) {
// always called right before shutdown
this.logDebug(1, "Running tearDown");
// add some delays here so async storage ops can complete
setTimeout( function() {
server.shutdown( function() {
// delete our mess after a short rest (just so no errors are logged)
setTimeout( function() {
try { cp.execSync('rm -rf data/unittest'); }
catch (e) {;}
callback();
}, 500 );
} );
}, 500 );
}
};