Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log errors and exit on authentication issues on Hubot plugin initialization #168

Closed
wants to merge 52 commits into from
Closed
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
8f7c74e
Exit `st2chatop` from `st2client` failures.
jinpingh May 28, 2019
c2770bb
Call 'process.exit(1)' to exit from process.
jinpingh May 28, 2019
6613a3d
Add callback to handle uncaught hubot exceptions.
jinpingh May 31, 2019
fe44e13
Revert change made for `bind(self)`
jinpingh May 31, 2019
c3b451f
Add big comment to Slack monkey patcher
blag Jun 4, 2019
d44be18
Remove Object.assign() polyfill and require node.js >= 8.3.0
blag Jun 4, 2019
3bf6fe2
Break out the dummy Logger into its own module
blag Jun 4, 2019
51b0262
Accept robot_name parameter and save it
blag Jun 4, 2019
969c084
Rename st2client.js variables to api_client and auth_client
blag Jun 4, 2019
98cf5f2
Coalesce multiple var declarations into one
blag Jun 4, 2019
233915c
Combine and rename hubotErrorCallback and exitProcessWithLog into log…
blag Jun 4, 2019
0cf0e26
Refactor loadCommands to not have an options object parameter
blag Jun 4, 2019
28d2e4c
Ensure that we do not authenticate while the robot is stopping and sh…
blag Jun 4, 2019
76e7179
Use array unpacking because we can
blag Jun 4, 2019
f588845
Refactor stream error handle to properly register a callback
blag Jun 4, 2019
8518e92
Remove error handler now that we have an uncaughtException handler
blag Jun 4, 2019
52a4d0f
Rename 'source' to 'stream'
blag Jun 4, 2019
862a331
Close down the previous stream instead of recreating another one
blag Jun 4, 2019
b112859
Add unit tests for invalid auth environment variables
blag Jun 4, 2019
63abae1
Test for exit code
blag Jun 4, 2019
97f2ef9
Simplify Logger methods
blag Jun 4, 2019
b07c28f
Merge pull request #170 from StackStorm/issue-124/simplify-logger
blag Jun 4, 2019
189b2d0
Merge pull request #169 from StackStorm/issue-124/further-tweaks
blag Jun 4, 2019
2ccc85e
Relax assertions on calls to robot.logger.error
blag Jun 5, 2019
e6c5e01
Ignore the actual error string
blag Jun 6, 2019
df5a0db
Merge pull request #175 from StackStorm/issue-124/fix-bad-auth-test
blag Jun 6, 2019
1765c47
Merge branch 'master' into issue-124/exit_on_failures
blag Jun 6, 2019
7605f3c
The express response object is not a hubot response object, so don't …
blag Jun 6, 2019
0dcba8a
Don't exit on ST2 stream (EventSource) error, simply warn about the i…
blag Jun 6, 2019
f81eca6
Merge branch 'master' into issue-124/exit_on_failures
blag Jun 6, 2019
965ffb1
Try to warn the user if posting a message to the chat provider doesn'…
blag Jun 6, 2019
95a4434
Don't exit if loading commands from StackStorm fails
blag Jun 6, 2019
a5311e9
Move changelog line to in development section
blag Jun 6, 2019
c39ba0f
Tweak stop to completely restore previous behavior, and add a shutdow…
blag Jun 6, 2019
6a39dfb
Stop calling 'process.exit(1)` and change some logs
jinpingh Jun 6, 2019
9ac9aa7
Exit `st2chatops` from invalid ST2 configuration.
jinpingh Jun 7, 2019
9903e19
Checking authentication issue from hubot EventSource.
jinpingh Jun 10, 2019
5c86bfa
Move back authentication error check.
jinpingh Jun 10, 2019
6165f1e
Check `err.stack` before log stack information
jinpingh Jun 11, 2019
6d11ae2
Check more error messages for testcases.
jinpingh Jun 11, 2019
de756aa
Log the error message inside logErrorAndExit
jinpingh Jun 11, 2019
59147a6
Change spy function call for error messages.
jinpingh Jun 11, 2019
47639de
Take out two error messages to pass travis testing
jinpingh Jun 11, 2019
954ec13
Remove dead code
blag Jun 11, 2019
1432afd
Rename promise to something useful
blag Jun 11, 2019
e7e9d93
Since we retry anyway, reduce stream errors to warnings
blag Jun 12, 2019
f346581
Move init code to after function definitions
blag Jun 18, 2019
82b8fe6
Remove logErrorAndExit function
blag Jun 18, 2019
27523a0
Allow stopping the stream even when not shutting down hubot
blag Jun 18, 2019
7ee397f
Refactor to use promises properly
blag Jun 18, 2019
0b75e5b
Register globals for linters
blag Jun 18, 2019
68d3b9d
Add and update tests for new behavior
blag Jun 18, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ Changelog

in development
--------------
* st2chatops now gracefully exits on authentication failures and uncaught exceptions (improvement)
* code cleanup (improvement)

0.9.3
-----
31 changes: 0 additions & 31 deletions lib/slack-messages.js
Original file line number Diff line number Diff line change
@@ -16,37 +16,6 @@

var utils = require('./utils.js');

// Polyfill from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
if (target === null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}

var to = Object(target);

for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];

if (nextSource !== null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}

var buildMessagesWithChunkedFieldValue = function (msg) {
var msgs;

12 changes: 12 additions & 0 deletions lib/slack_monkey_patch.js
Original file line number Diff line number Diff line change
@@ -24,6 +24,18 @@ function patchSendMessage(robot) {
// formatted and parsed on the server side.
// NOTE / TODO: We can get rid of this nasty patch once our node-slack-client and hubot-slack pull
// requests are merged.
// This code was refactored in https://github.com/StackStorm/hubot-stackstorm/pull/6
// which was opened and merged by Kami on 2015-06-04.
// As of 2019-05-22, these were the PRs I could find for the node-slack-client
// hubot-slack repositories:
// * https://github.com/slackapi/node-slack-sdk/pull/42
// - which was closed during a refactor and converted into an issue:
// https://github.com/slackapi/node-slack-sdk/issues/138
// * https://github.com/slackapi/hubot-slack/pull/544
// - which was opened on 2018-11-14, which seems to be too late to actually
// apply to this code
// So...I'm not entirely sure this monkey patch is still necessary.
// End-to-end testing is required to figure out for sure.
if (robot.adapter && robot.adapter.constructor && robot.adapter.constructor.name === 'SlackBot') {
for (var channel in robot.adapter.client.channels) {
robot.adapter.client.channels[channel].sendMessage = sendMessageRaw.bind(robot.adapter.client.channels[channel]);
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
"devDependencies": {
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-string": "^1.5.0",
"eslint": "^5.16.0",
"eslint-plugin-notice": "0.7.8",
"gulp": "^3.9.1",
@@ -48,6 +49,7 @@
"hubot-help": "0.2.2",
"hubot-mock-adapter": "^1.1.1",
"log": "1.4.0",
"mocked-env": "^1.2.4",
"nock": "^10.0.0",
"nyc": "^13.0.1",
"sinon": "^6.3.5",
203 changes: 119 additions & 84 deletions scripts/stackstorm.js
Original file line number Diff line number Diff line change
@@ -99,22 +99,20 @@ var TWOFACTOR_MESSAGE = "This action requires two-factor auth! Waiting for your
module.exports = function(robot) {
slack_monkey_patch.patchSendMessage(robot);

var self = this;

var promise = Promise.resolve();

if (env.ST2_API) {
robot.logger.warning("ST2_API is deprecated and will be removed in a future releases. Instead, please use the ST2_API_URL environment variable.");
}
var url = utils.parseUrl(env.ST2_API_URL);

var opts = {
protocol: url.protocol,
host: url.hostname,
port: url.port,
prefix: url.path,
rejectUnauthorized: false
};
var _stream = null,
self = this,
authenticated = Promise.resolve(),
url = utils.parseUrl(env.ST2_API_URL),
opts = {
protocol: url.protocol,
host: url.hostname,
port: url.port,
prefix: url.path,
rejectUnauthorized: false
};

if (env.ST2_STREAM_URL) {
var stream_url = utils.parseUrl(env.ST2_STREAM_URL);
@@ -126,16 +124,10 @@ module.exports = function(robot) {
};
}

var api = st2client(opts);

if (env.ST2_API_KEY) {
api.setKey({ key: env.ST2_API_KEY });
} else if (env.ST2_AUTH_TOKEN) {
api.setToken({ token: env.ST2_AUTH_TOKEN });
}
var api_client = st2client(opts);

function authenticate() {
api.removeListener('expiry', authenticate);
api_client.removeListener('expiry', authenticate);

// API key gets precedence 1
if (env.ST2_API_KEY) {
@@ -152,7 +144,7 @@ module.exports = function(robot) {

var url = utils.parseUrl(env.ST2_AUTH_URL);

var client = st2client({
var auth_client = st2client({
auth: {
protocol: url.protocol,
host: url.hostname,
@@ -161,34 +153,19 @@ module.exports = function(robot) {
}
});

return client.authenticate(env.ST2_AUTH_USERNAME, env.ST2_AUTH_PASSWORD)
return auth_client.authenticate(env.ST2_AUTH_USERNAME, env.ST2_AUTH_PASSWORD)
.then(function (token) {
robot.logger.info('Token received. Expiring ' + token.expiry);
api.setToken(token);
client.on('expiry', authenticate);
api_client.setToken(token);
auth_client.on('expiry', authenticate);
})
.catch(function (err) {
robot.logger.error('Failed to authenticate: ' + err.message);

// Exit from invalid ST2_AUTH_USERNAME or ST2_AUTH_PASSWORD.
robot.logger.error('Failed to authenticate with st2 username and password.');
throw err;
});
}

if (env.ST2_API_KEY || env.ST2_AUTH_TOKEN || env.ST2_AUTH_USERNAME || env.ST2_AUTH_PASSWORD) {
// If using username and password then all are required.
if ((env.ST2_AUTH_USERNAME || env.ST2_AUTH_PASSWORD) &&
!(env.ST2_AUTH_USERNAME && env.ST2_AUTH_PASSWORD && env.ST2_AUTH_URL)) {
throw new Error('Env variables ST2_AUTH_USERNAME, ST2_AUTH_PASSWORD and ST2_AUTH_URL should only be used together.');
}
promise = authenticate();
}

// Pending 2-factor auth commands
if (env.HUBOT_2FA) {
var twofactor = {};
robot.logger.info('Two-factor auth is enabled');
}

// factory to manage commands
var command_factory = new CommandFactory(robot);

@@ -199,9 +176,9 @@ module.exports = function(robot) {
var postDataHandler = postData.getDataPostHandler(robot.adapterName, robot, formatter);

var loadCommands = function() {
robot.logger.info('Loading commands....');
robot.logger.info('Loading commands...');

api.actionAlias.list()
return api_client.actionAlias.list()
.then(function (aliases) {
// Remove all the existing commands
command_factory.removeCommands();
@@ -235,8 +212,10 @@ module.exports = function(robot) {
robot.logger.info(command_factory.st2_hubot_commands.length + ' commands are loaded');
})
.catch(function (err) {
var error_msg = 'Failed to retrieve commands from "%s": %s';
robot.logger.error(util.format(error_msg, env.ST2_API_URL, err.message));
var error_msg = 'Failed to retrieve commands from "%s":\n%s';
err.stack = util.format(error_msg, env.ST2_API_URL, err.stack);
robot.logger.error(err.stack);
throw err;
});
};

@@ -263,7 +242,7 @@ module.exports = function(robot) {
var sendAliasExecutionRequest = function (msg, payload) {
robot.logger.debug('Sending command payload:', JSON.stringify(payload));

api.aliasExecution.create(payload)
api_client.aliasExecution.create(payload)
.then(function (res) { sendAck(msg, res); })
.catch(function (err) {
// Compatibility with older StackStorm versions
@@ -307,7 +286,7 @@ module.exports = function(robot) {
var twofactor_id = uuid.v4();
robot.logger.debug('Requested an action that requires 2FA. Guid: ' + twofactor_id);
msg.send(TWOFACTOR_MESSAGE);
api.executions.create({
api_client.executions.create({
'action': env.HUBOT_2FA,
'parameters': {
'uuid': twofactor_id,
@@ -326,7 +305,7 @@ module.exports = function(robot) {
};

robot.respond(/([\s\S]+?)$/i, function(msg) {
var command, result, command_name, format_string, action_alias;
var command, result;

// Normalize the command and remove special handling provided by the chat service.
// e.g. slack replace quote marks with left double quote which would break behavior.
@@ -339,9 +318,7 @@ module.exports = function(robot) {
return;
}

command_name = result[0];
format_string = result[1];
action_alias = result[2];
var [command_name, format_string, action_alias] = result;

executeCommand(msg, command_name, format_string, command, action_alias);
});
@@ -357,25 +334,29 @@ module.exports = function(robot) {
}
postDataHandler.postData(data);

res.send('{"status": "completed", "msg": "Message posted successfully"}');
} catch (e) {
robot.logger.error("Unable to decode JSON: " + e);
robot.logger.error(e.stack);
res.send('{"status": "failed", "msg": "An error occurred trying to post the message: ' + e + '"}');
res.send(JSON.stringify({
"status": "completed",
"msg": "Message posted successfully"
}));
} catch (err) {
robot.logger.error('Encountered an error when attempting to post ' +
'message to chat provider, execution results for ' +
'chatops.post_message may not be accurate.');
robot.logger.error(err);
}
});

var commands_load_interval;

function start() {
api.stream.listen().catch(function (err) {
robot.logger.error('Unable to connect to stream:', err);
}).then(function (source) {
source.onerror = function (err) {
// TODO: squeeze a little bit more info out of evensource.js
robot.logger.error('Stream error:', err);
};
source.addEventListener('st2.announcement__chatops', function (e) {
return api_client.stream.listen().then(function (stream) {
_stream = stream; // save stream for use in stop()
stream.on('error', function (error_event) {
robot.logger.warning('StackStorm event stream error:');
robot.logger.warning(JSON.stringify(error_event));
robot.logger.warning('Attempting to reconnect to StackStorm event stream.');
});
stream.addEventListener('st2.announcement__chatops', function (e) {
var data;

robot.logger.debug('Chatops message received:', e.data);
@@ -390,7 +371,7 @@ module.exports = function(robot) {
});

if (env.HUBOT_2FA) {
source.addEventListener('st2.announcement__2fa', function (e) {
stream.addEventListener('st2.announcement__2fa', function (e) {
var data;

robot.logger.debug('Successfull two-factor auth:', e.data);
@@ -406,24 +387,37 @@ module.exports = function(robot) {
delete twofactor[data.uuid];
});
}
}).then(function () {
// Add an interval which tries to re-load the commands
commands_load_interval = setInterval(
// Lambda function that swallows errors so they don't propagate up the stack
// (errors are still logged within loadCommands itself)
function () {
loadCommands().catch(function (err) {});
},
(env.ST2_COMMANDS_RELOAD_INTERVAL * 1000));

// Install SIGUSR2 handler which reloads the command
install_sigusr2_handler();
}).then(function () {
// Initial command loading
return loadCommands();
});

// Add an interval which tries to re-load the commands
commands_load_interval = setInterval(loadCommands.bind(self), (env.ST2_COMMANDS_RELOAD_INTERVAL * 1000));

// Initial command loading
loadCommands();

// Install SIGUSR2 handler which reloads the command
install_sigusr2_handler();
}

function stop() {
function stop(opts) {
var default_opts = {shutdown: false},
opts = Object.assign(default_opts, (opts || {}));
clearInterval(commands_load_interval);
api.stream.listen().then(function (source) {
source.removeAllListeners();
source.close();
});

if (_stream) {
_stream.removeAllListeners();
_stream.close();
}

if (opts.shutdown) {
robot.shutdown();
}
}

function install_sigusr2_handler() {
@@ -432,10 +426,51 @@ module.exports = function(robot) {
});
}

if ((env.ST2_AUTH_USERNAME || env.ST2_AUTH_PASSWORD || env.ST2_AUTH_URL) &&
!(env.ST2_AUTH_USERNAME && env.ST2_AUTH_PASSWORD && env.ST2_AUTH_URL)) {
robot.logger.error('Environment variables ST2_AUTH_USERNAME, ST2_AUTH_PASSWORD, and '+
'ST2_AUTH_URL must all be specified together.');
if (!env.ST2_AUTH_USERNAME) {
robot.logger.error('Missing: ST2_AUTH_USERNAME');
}
if (!env.ST2_AUTH_PASSWORD) {
robot.logger.error('Missing: ST2_AUTH_PASSWORD');
}
if (!env.ST2_AUTH_URL) {
robot.logger.error('Missing: ST2_AUTH_URL');
}

stop({shutdown: true});
// Easier than calling process.exit(-1) here
return authenticated;
}

if (env.ST2_API_KEY || env.ST2_AUTH_TOKEN ||
(env.ST2_AUTH_USERNAME && env.ST2_AUTH_PASSWORD && env.ST2_AUTH_URL)) {
authenticated = authenticate();
}

if (env.ST2_API_KEY) {
api_client.setKey({ key: env.ST2_API_KEY });
} else if (env.ST2_AUTH_TOKEN) {
api_client.setToken({ token: env.ST2_AUTH_TOKEN });
}

// Pending 2-factor auth commands
if (env.HUBOT_2FA) {
var twofactor = {};
robot.logger.info('Two-factor auth is enabled');
}

// Authenticate with StackStorm backend and then call start.
// On a failure to authenticate log the error but do not quit.
return promise.then(function () {
start();
return stop;
return authenticated.then(function () {
return start().then(function () {
return stop;
});
}).catch(function (err) {
// Since start is only called once when the plugin is loaded, bail
// loudly and harshly if loadCommands() isn't successful
stop({shutdown: true});
throw err;
});
};
56 changes: 56 additions & 0 deletions tests/dummy-logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Licensed to the StackStorm, Inc ('StackStorm') under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

"use strict";



function Logger(enabled) {
this.enabled = enabled;
this.logs = {
error: [],
warning: [],
info: [],
debug: []
};

this.error = function(msg) {
if (this.enabled) {
this.logs.error.push(msg);
}
};

this.warning = function(msg) {
if (this.enabled) {
this.logs.warning.push(msg);
}
};

this.info = function(msg) {
if (this.enabled) {
this.logs.info.push(msg);
}
};

this.debug = function(msg) {
if (this.enabled) {
this.logs.debug.push(msg);
}
};
}

module.exports = Logger;
38 changes: 3 additions & 35 deletions tests/dummy-robot.js
Original file line number Diff line number Diff line change
@@ -14,46 +14,14 @@

"use strict";

var Log = require('log');
var Logger = require('./dummy-logger.js');

function Logger(enabled) {
this.enabled = enabled;
this.log = new Log('debug');

this.error = function(msg) {
if (!this.enabled) {
return;
}
this.log.error(msg);
};

this.warning = function(msg) {
if (!this.enabled) {
return;
}
this.log.warning(msg);
};

this.info = function(msg) {
if (!this.enabled) {
return;
}
this.log.info(msg);
};

this.debug = function(msg) {
if (!this.enabled) {
return;
}
this.log.debug(msg);
};
}

function Robot(name, adapter, enable_logging) {
function Robot(name, adapter, enable_logging, robot_name) {
this.logger = new Logger(enable_logging);
this.name = name;
this.commands = [];
this.adapter = adapter;
this.robot_name = robot_name;

this.messageRoom = function(recipient, data) {
return;
177 changes: 177 additions & 0 deletions tests/test-st2-invalid-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
Licensed to the StackStorm, Inc ('StackStorm') under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*jshint quotmark:false*/
/*jshint -W030*/
/*global describe, it, before, after, beforeEach, afterEach*/
'use strict';

var chai = require("chai"),
expect = chai.expect,
chaiString = require("chai-string"),
sinon = require('sinon'),
sinonChai = require('sinon-chai'),
chaiAsPromised = require('chai-as-promised'),
mockedEnv = require('mocked-env'),
Robot = require("hubot/src/robot"),
Logger = require('./dummy-logger.js');

chai.use(sinonChai);
chai.use(chaiString);
chai.use(chaiAsPromised);

global.process.exit = sinon.spy();

describe("invalid st2 credential configuration", function() {
var robot = new Robot(null, "mock-adapter", false, "Hubot");
robot.logger = new Logger(true);
var restore_env = null,
info_spy = sinon.spy(robot.logger, 'info'),
error_spy = sinon.spy(robot.logger, 'error');

beforeEach(function () {
process.exit.resetHistory();
});

afterEach(function() {
restore_env && restore_env();
error_spy.resetHistory();
info_spy.resetHistory();
// Remove stackstorm.js from the require cache
// https://medium.com/@gattermeier/invalidate-node-js-require-cache-c2989af8f8b0
delete require.cache[require.resolve("../scripts/stackstorm.js")];
});

it("should error out with missing auth URL", function(done) {
// Mock process.env for all modules
// https://glebbahmutov.com/blog/mocking-process-env/
restore_env = mockedEnv({
ST2_AUTH_USERNAME: 'nonexistent-st2-auth-username',
ST2_AUTH_PASSWORD: 'nonexistent-st2-auth-password'
});

// Load script under test
var stackstorm = require("../scripts/stackstorm.js");
stackstorm(robot).then(function () {
expect(error_spy).to.have.been.calledTwice;
expect(error_spy.firstCall.args[0]).to.be.equal(
'Environment variables ST2_AUTH_USERNAME, ST2_AUTH_PASSWORD, and ST2_AUTH_URL '+
'must all be specified together.');
expect(error_spy.secondCall.args[0]).to.be.equal('Missing: ST2_AUTH_URL');

done();
}).catch(function (err) {
console.log(err);
done(err);
});
});

it("should error out with missing auth username", function(done) {
// Mock process.env for all modules
// https://glebbahmutov.com/blog/mocking-process-env/
restore_env = mockedEnv({
ST2_AUTH_URL: 'https://nonexistent-st2-auth-url',
ST2_AUTH_PASSWORD: 'nonexistent-st2-auth-password'
});

// Load script under test
var stackstorm = require("../scripts/stackstorm.js");
stackstorm(robot).then(function () {
expect(error_spy).to.have.been.calledTwice;
expect(error_spy.firstCall.args[0]).to.be.equal(
'Environment variables ST2_AUTH_USERNAME, ST2_AUTH_PASSWORD, and ST2_AUTH_URL '+
'must all be specified together.');
expect(error_spy.secondCall.args[0]).to.be.equal('Missing: ST2_AUTH_USERNAME');

done();
}).catch(function (err) {
console.log(err);
done(err);
});
});


it("should error out with missing auth password", function(done) {
// Mock process.env for all modules
// https://glebbahmutov.com/blog/mocking-process-env/
restore_env = mockedEnv({
ST2_AUTH_URL: 'https://nonexistent-st2-auth-url',
ST2_AUTH_USERNAME: 'nonexistent-st2-auth-username'
});

// Load script under test
var stackstorm = require("../scripts/stackstorm.js");
stackstorm(robot).then(function () {
expect(error_spy).to.have.been.calledTwice;
expect(error_spy.firstCall.args[0]).to.be.equal(
'Environment variables ST2_AUTH_USERNAME, ST2_AUTH_PASSWORD, and ST2_AUTH_URL '+
'must all be specified together.');
expect(error_spy.secondCall.args[0]).to.be.equal('Missing: ST2_AUTH_PASSWORD');

done();
}).catch(function (err) {
console.log(err);
done(err);
});
});

it("should throw exception with bad auth URL", function(done) {
restore_env = mockedEnv({
ST2_AUTH_URL: 'https://nonexistent-st2-auth-url:9101',
ST2_AUTH_USERNAME: 'nonexistent-st2-auth-username',
ST2_AUTH_PASSWORD: 'nonexistent-st2-auth-password'
});

// Load script under test
var i, stackstorm = require("../scripts/stackstorm.js");
stackstorm(robot).catch(function (err) {
expect(error_spy.args).to.have.lengthOf(1);
expect(error_spy.args[0][0]).to.be.a('string');
expect(error_spy.args[0][0]).to.equal('Failed to authenticate with st2 username and password.');

expect(err.message).to.match(/getaddrinfo [A-Z]+ nonexistent-st2-auth-url nonexistent-st2-auth-url:9101/);

done();
});
});

it("should raise exception with bad API key", function(done) {
restore_env = mockedEnv({
ST2_API_KEY: 'aaa'
});

// Load script under test
var i, stackstorm = require("../scripts/stackstorm.js");

expect(stackstorm(robot)).to.be.rejectedWith(Error, /connect [A-Z]+ 127\.0\.0\.1:9101/).notify(done);

expect(info_spy).to.have.been.calledOnceWith('Using ST2_API_KEY as authentication. Expiry will lead to bot exit.');
});

it("should raise exception with bad auth token", function(done) {
restore_env = mockedEnv({
ST2_AUTH_TOKEN: 'aaa'
});

// Load script under test
var i, stackstorm = require("../scripts/stackstorm.js");

expect(stackstorm(robot)).to.be.rejectedWith(Error, /connect [A-Z]+ 127\.0\.0\.1:9101/).notify(done);

expect(info_spy).to.have.been.calledOnceWith('Using ST2_AUTH_TOKEN as authentication. Expiry will lead to bot exit.');
});
});
10 changes: 9 additions & 1 deletion tests/test-st2bot-setup.js
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ require('coffee-register');

var expect = require("chai").expect,
path = require("path"),
express = require('express'),
Robot = require("hubot/src/robot"),
TextMessage = require("hubot/src/message").TextMessage;

@@ -36,12 +37,18 @@ var disableAuth = function() {
};

describe("stanley the StackStorm bot", function() {
var robot, user, adapter, st2bot, stop;
var robot, user, adapter, st2bot, stop, server;

before(function(done) {
robot = new Robot(null, "mock-adapter", true, "Hubot");
server = express()
.get('/v1/actionalias', function (req, res) {
res.status(200).send({});
})
.listen(9101);

// Hack. Need a better solution than stubbing out methods.
// TODO: Replace these methods with sinon method spies
if (disableLogger) {
robot.logger.error = controlledLogger;
robot.logger.warning = controlledLogger;
@@ -78,6 +85,7 @@ describe("stanley the StackStorm bot", function() {
});

after(function() {
server.close();
stop && stop();
robot.server.close();
robot.shutdown();
9 changes: 8 additions & 1 deletion tests/test-twofactor.js
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ require('coffee-register');
var fs = require('fs'),
expect = require("chai").expect,
path = require("path"),
express = require('express'),
Robot = require("hubot/src/robot"),
TextMessage = require("hubot/src/message").TextMessage,
CommandFactory = require('../lib/command_factory.js'),
@@ -41,10 +42,15 @@ var enableTwofactor = function() {
};

describe("two-factor auth module", function() {
var robot, user, adapter, st2bot, stop, command_factory;
var robot, user, adapter, st2bot, stop, command_factory, server;

before(function(done) {
robot = new Robot(null, "mock-adapter", true, "Hubot");
server = express()
.get('/v1/actionalias', function (req, res) {
res.status(200).send({});
})
.listen(9101);

// Hack. Need a better solution than stubbing out methods.
if (disableLogger) {
@@ -92,6 +98,7 @@ describe("two-factor auth module", function() {
});

after(function() {
server.close();
stop && stop();
robot.server.close();
robot.shutdown();