diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cb35e4a..590faca 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,9 @@ in development -------------- * Move babel-eslint dependency to devDependencies (bug fix) -* Small refactor and more tests (for `scripts/stackstorm.js`) (improvement) +* Small refactor and more tests (for ``scripts/stackstorm.js``) (improvement) +* Refactor chat providers into their own modules (improvement) +* Modernize directory structure to be more consistent with other hubot plugins (improvement) 0.9.6 ----- @@ -13,7 +15,7 @@ in development 0.9.5 ----- -* Exit hubot on invalid, expired `ST2_API_KEY` / `ST2_AUTH_TOKEN` or any other Unauthorized response from st2 server (bug fix) +* Exit hubot on invalid, expired ``ST2_API_KEY`` / ``ST2_AUTH_TOKEN`` or any other Unauthorized response from st2 server (bug fix) * When st2 username/password is used, re-generate st2 auth token in advance, giving enough time for request to complete, st2client.js (bug fix) 0.9.4 @@ -28,7 +30,7 @@ in development 0.9.2 ----- -* Rename ENV variable `ST2_API` -> `ST2_API_URL` for consistency, keep `ST2_API` for backwards compatibility (improvement) +* Rename ENV variable ``ST2_API`` -> ``ST2_API_URL`` for consistency, keep ``ST2_API`` for backwards compatibility (improvement) 0.9.1 ----- diff --git a/gulpfile.js b/gulpfile.js index e8473e7..c644c6f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -24,7 +24,7 @@ gulp.task('lint', () => gulp.src(settings.lint, { cwd: settings.dev }) .pipe(plugins.eslint.format()) ); -gulp.task('test', () => gulp.src('tests/**/*.js', { read: false }) +gulp.task('test', () => gulp.src('test/**/*.js', { read: false }) .pipe(plugins.mocha({ reporter: 'spec' })) ); diff --git a/index.js b/index.js index d41008a..b1c8970 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ var fs = require('fs'); var path = require('path'); -var SCRIPTS_PATH = path.resolve(__dirname, 'scripts'); +var SCRIPTS_PATH = path.resolve(__dirname, 'src'); module.exports = function(robot, scripts) { diff --git a/lib/format_data.js b/lib/format_data.js deleted file mode 100644 index 1090ead..0000000 --- a/lib/format_data.js +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2019 Extreme Networks, Inc. -// -// Licensed 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"; - -var _ = require('lodash'), - truncate = require('truncate'), - util = require('util'), - utils = require('./utils.js'); - -var env = process.env; - -/* - SlackFormatter. -*/ -function SlackFormatter(robot) { - this.robot = robot; -} - -SlackFormatter.prototype.formatData = function(data) { - if (utils.isNull(data)) { - return ""; - } - // For slack we do not truncate or format the result. This is because - // data is posted to slack as a message attachment. - return data; -}; - -SlackFormatter.prototype.formatRecepient = function(recepient) { - return recepient; -}; - -SlackFormatter.prototype.normalizeCommand = function(command) { - // replace left double quote with regular quote - command = command.replace(/\u201c/g, '\u0022'); - // replace right double quote with regular quote - command = command.replace(/\u201d/g, '\u0022'); - // replace left single quote with regular apostrophe - command = command.replace(/\u2018/g, '\u0027'); - // replace right single quote with regular apostrophe - command = command.replace(/\u2019/g, '\u0027'); - return command; -}; - -SlackFormatter.prototype.normalizeAddressee = function(msg) { - return { - name: msg.message.user.name, - room: msg.message.room - }; -}; - -/* - MattermostFormatter. -*/ -function MattermostFormatter(robot) { - this.robot = robot; -} - -MattermostFormatter.prototype.formatData = function(data) { - if (utils.isNull(data)) { - return ""; - } - // For slack we do not truncate or format the result. This is because - // data is posted to slack as a message attachment. - return data; -}; - -MattermostFormatter.prototype.formatRecepient = function(recepient) { - return recepient; -}; - -MattermostFormatter.prototype.normalizeCommand = function(command) { - // replace left double quote with regular quote - command = command.replace(/\u201c/g, '\u0022'); - // replace right double quote with regular quote - command = command.replace(/\u201d/g, '\u0022'); - // replace left single quote with regular apostrophe - command = command.replace(/\u2018/g, '\u0027'); - // replace right single quote with regular apostrophe - command = command.replace(/\u2019/g, '\u0027'); - return command; -}; - -MattermostFormatter.prototype.normalizeAddressee = function(msg) { - return { - name: msg.message.user.name, - room: msg.message.room - }; -}; - -/* - MSTeamsFormatter -*/ -function MSTeamsFormatter(robot) { - this.robot = robot; -} - -MSTeamsFormatter.prototype.formatData = function(data) { - this.robot.logger.debug("Got data in formatData: " + JSON.stringify(data)); - - // Remove starting newlines - data = data.replace(/^\n/g, ''); - // Replace single newlines with double newlines - data = data.replace(/([^\n])\n([^\n])/g, "$1\n\n$2"); - - return data; -}; - -MSTeamsFormatter.prototype.formatRecepient = function(recepient) { - this.robot.logger.debug("Got recipient in formatRecepient: " + JSON.stringify(recepient)); - return recepient; -}; - -MSTeamsFormatter.prototype.normalizeCommand = function (command) { - this.robot.logger.debug("Got command in normalizeCommand: " + JSON.stringify(command)); - return command; -} - -MSTeamsFormatter.prototype.normalizeAddressee = function(msg) { - return { - name: msg.message.user.name, - room: msg.message.room - }; -}; - -/* - HipChatFormatter. -*/ -function HipChatFormatter(robot) { - this.robot = robot; -} - -HipChatFormatter.prototype.formatData = function(data) { - if (utils.isNull(data)) { - return ""; - } - // HipChat has "show more" capability in messages so no truncation. - return '/code ' + data; -}; - -HipChatFormatter.prototype.formatRecepient = function(recepient) { - var robot_name = env.HUBOT_HIPCHAT_JID.split("_")[0]; - var hipchat_domain = (env.HUBOT_HIPCHAT_XMPP_DOMAIN === 'btf.hipchat.com') ? - 'conf.btf.hipchat.com' : 'conf.hipchat.com'; - return util.format('%s_%s@%s', robot_name, recepient, hipchat_domain); -}; - -HipChatFormatter.prototype.normalizeCommand = function(command) { - return command; -}; - -HipChatFormatter.prototype.normalizeAddressee = function(msg) { - return { - name: msg.message.user.mention_name, - room: msg.message.user.jid - }; -}; - -/* - RocketChatFormatter. -*/ -function RocketChatFormatter(robot) { - this.robot = robot; -} - -RocketChatFormatter.prototype.formatData = function(data) { - if (utils.isNull(data)) { - return ""; - } - // For Rocket we do not truncate or format the result. This is because - // data is posted to RocketChat as a message attachment. - return data; -}; - -RocketChatFormatter.prototype.formatRecepient = function(recepient) { - return recepient; -}; - -RocketChatFormatter.prototype.normalizeCommand = function(command) { - return command; -}; - -RocketChatFormatter.prototype.normalizeAddressee = function(msg) { - return { - name: msg.message.user.name, - room: msg.message.room - }; -}; - -/* - SparkFormatter. -*/ -function SparkFormatter(robot) { - this.robot = robot; - - // Limit the size of a message. - this.truncate_length = env.ST2_MAX_MESSAGE_LENGTH; -} - -SparkFormatter.prototype.formatData = function(data) { - if (utils.isNull(data)) { - return ""; - } - if (this.truncate_length > 0) { - // The ellipsis argument is only to preserve backwards compatibility, as the - // truncate function switched from using '...' (three period characters - // forming and ellipsis) in truncate 1.x to '…' (a single Unicode ellipsis - // character) in truncate 2+. - // Switching to using the new default ellipsis ('…') probably won't break - // anything. - data = truncate(data, this.truncate_length, {ellipsis: '...'}); - } - return data; -}; - -SparkFormatter.prototype.formatRecepient = function(recepient) { - return recepient; -}; - -SparkFormatter.prototype.normalizeCommand = function(command) { - return command; -}; - -SparkFormatter.prototype.normalizeAddressee = function(msg) { - return { - name: msg.message.user.name, - room: msg.message.user.room - }; -}; - -/* - DefaultFormatter. -*/ -function DefaultFormatter(robot) { - this.robot = robot; - - // Limit the size of a message. - this.truncate_length = env.ST2_MAX_MESSAGE_LENGTH; -} - -DefaultFormatter.prototype.formatData = function(data) { - if (utils.isNull(data)) { - return ""; - } - if (this.truncate_length > 0) { - // The ellipsis argument is only to preserve backwards compatibility, as the - // truncate function switched from using '...' (three period characters - // forming and ellipsis) in truncate 1.x to '…' (a single Unicode ellipsis - // character) in truncate 2+. - // Switching to using the new default ellipsis ('…') probably won't break - // anything. - data = truncate(data, this.truncate_length, {ellipsis: '...'}); - } - return data; -}; - -DefaultFormatter.prototype.formatRecepient = function(recepient) { - return recepient; -}; - -DefaultFormatter.prototype.normalizeCommand = function(command) { - return command; -}; - -DefaultFormatter.prototype.normalizeAddressee = function(msg) { - return { - name: msg.message.user.name, - room: msg.message.room - }; -}; - -var formatters = { - 'slack': SlackFormatter, - 'matteruser': MattermostFormatter, - 'mattermost': MattermostFormatter, - 'botframework': MSTeamsFormatter, - 'hipchat': HipChatFormatter, - 'spark': SparkFormatter, - 'rocketchat': RocketChatFormatter, - 'default': DefaultFormatter -}; - -module.exports.getFormatter = function(adapterName, robot) { - if (!(adapterName in formatters)) { - robot.logger.warning( - util.format('No supported formatter found for %s. Using DefaultFormatter.', adapterName)); - adapterName = 'default'; - } - return new formatters[adapterName](robot); -}; diff --git a/lib/post_data.js b/lib/post_data.js deleted file mode 100644 index 7b6e4ea..0000000 --- a/lib/post_data.js +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright 2019 Extreme Networks, Inc. -// -// Licensed 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"; - -var env = process.env, - messages = require('./slack-messages.js'), - util = require('util'), - utils = require('./utils.js'); - -/* - SlackDataPostHandler. -*/ -function SlackDataPostHandler(robot, formatter) { - this.robot = robot; - this.formatter = formatter; -} - -SlackDataPostHandler.prototype.postData = function(data) { - var recipient, attachment_color, split_message, - attachment, pretext = ""; - var envelope, - // We capture robot here so the `sendMessage` closure captures the - // correct `this` - robot = this.robot; - - if (data.whisper && data.user) { - recipient = data.user; - envelope = { - "user": data.user - }; - } else { - recipient = data.channel; - pretext = (data.user && !data.whisper) ? util.format('@%s: ', data.user) : ""; - envelope = { - "room": data.channel, - "id": data.channel, - "user": data.user, - }; - } - - // Allow packs to specify arbitrary keys - if (data.extra && data.extra.slack && data.extra.slack.attachments) { - // Action: - // - // result: - // format: ... - // extra: - // slack: - // icon_emoji: ":jira:" - // username: Jira Bot - // attachments: - // - - // fallback: "Info about Jira ticket {{ execution.result.result.key }}" - // color: "#042A60" - // title: "{{ execution.result.result.key }}" - // title_link: "{{ execution.result.result.url }}" - // fields: - // - - // title: Summary - // value: "{{ execution.result.result.summary }}" - // short: false - // - // becomes: - // - // { - // "icon_emoji": ":jira:", - // "username": "Jira Bot", - // "attachments": [ - // { - // "fallback": "Info about Jira ticket {{ execution.result.result.key }}", - // "color": "#042A60", - // "title": "{{ execution.result.result.key }}", - // "title_link": "{{ execution.result.result.url }}", - // "fields": [ - // { - // "title": "Summary", - // "value": "{{ execution.result.result.summary }}", - // "short": false - // } - // ], - // } - // ] - // } - - var messages_to_send = messages.buildMessages(data.extra.slack); - - var sendMessage = function (i) { - robot.adapter.client.send(envelope, messages_to_send[i]); - - if (messages_to_send.length > ++i) { - setTimeout(function(){sendMessage(i);}, 300); - } - }; - - sendMessage(0); - - return; - } - - if (data.extra && data.extra.color) { - attachment_color = data.extra.color; - } else { - attachment_color = env.ST2_SLACK_SUCCESS_COLOR; - if (data.message.indexOf("status : failed") > -1) { - attachment_color = env.ST2_SLACK_FAIL_COLOR; - } - } - - split_message = utils.splitMessage(this.formatter.formatData(data.message)); - - if (split_message.text) { - var content = { - color: attachment_color, - "mrkdwn_in": ["text", "pretext"], - }; - if (data.extra && data.extra.slack) { - // Backwards compatibility - - // Action: - // - // result: - // format: ... - // extra: - // slack: - // author_name: Jira_Bot - // author_link: "https://stackstorm.com" - // author_icon: "https://stackstorm.com/favicon.ico" - // color: "#042A60" - // fallback: "Info about Jira ticket {{ execution.result.result.key }}" - // title: "{{ execution.result.result.key }}" - // title_link: "{{ execution.result.result.url }}" - // fields: - // - - // title: Summary - // value: "{{ execution.result.result.summary }}" - // short: false - // - // becomes: - // - // { - // "attachments": [ - // { - // "author_name": "Jira Bot", - // "author_link": "https://stackstorm.com", - // "author_icon": "https://stackstorm.com/favicon.ico", - // "color": "#042A60", - // "fallback": "Info about Jira ticket {{ execution.result.result.key }}", - // "title": "{{ execution.result.result.key }}", - // "title_link": "{{ execution.result.result.url }}", - // "fields": [ - // { - // "title": "Summary", - // "value": "{{ execution.result.result.summary }}", - // "short": false - // } - // ] - // } - // ] - // } - - for (var attrname in data.extra.slack) { content[attrname] = data.extra.slack[attrname]; } - } - - var chunks = split_message.text.match(/[\s\S]{1,7900}/g); - var sendChunk = function (i) { - content.pretext = i === 0 ? pretext + split_message.pretext : null; - content.text = chunks[i]; - content.fallback = chunks[i]; - robot.adapter.client.send(envelope, {'attachments': [content]}); - - if (chunks.length > ++i) { - setTimeout(function(){ sendChunk(i); }, 300); - } - }; - sendChunk(0); - } else { - this.robot.adapter.client.send(envelope, pretext + split_message.pretext); - } -}; - -/* - MSTeamsPostHandler. -*/ -function MSTeamsPostHandler(robot, formatter) { - this.robot = robot; - this.formatter = formatter; -} - -MSTeamsPostHandler.prototype.postData = function(data) { - var messages_to_send, - // We capture robot here so the `sendMessage` closure captures the - // correct `this` - robot = this.robot, - split_message = utils.splitMessage(data.message); - - if (data.extra && data.extra.botframework) { - robot.logger.warning(util.format('The extra.botframework attribute of aliases is not used yet.')); - } - - if (split_message.pretext) { - var text = this.formatter.formatData(split_message.text); - messages_to_send = [split_message.pretext, text]; - } else { - messages_to_send = [this.formatter.formatData(data.message)]; - } - - // We define a recursive closure that calls itself with the next data to send - // after a timeout. This approximates sending synchronous (sequential) HTTP - // requests. - var sendMessage = function (i) { - robot.adapter.send(data.context, messages_to_send[i]); - - if (messages_to_send.length > ++i) { - setTimeout(function () { sendMessage(i); }, 300); - } - }; - - sendMessage(0); - - return; -}; - -/* - MattermostDataPostHandler. -*/ -function MattermostDataPostHandler(robot, formatter) { - // We capture robot here so the `sendMessage` closure captures the correct - // `this` - this.robot = robot; - this.formatter = formatter; -} - -MattermostDataPostHandler.prototype.postData = function(data) { - var recipient, attachment_color, split_message, - attachment, pretext = ""; - - // If we are supposed to whisper to a single user, use a direct message - if (data.whisper && data.user) { - recipient = data.user; - } else { // Otherwise, message the channel - recipient = data.channel; - // If we aren't supposed to whisper, then we at least at-mention the user - pretext = (data.user && !data.whisper) ? util.format('@%s: ', data.user) : ""; - } - - // Use the color specified in the `extra` block - if (data.extra && data.extra.color) { - attachment_color = data.extra.color; - } else { - // Assume success, and use the success color - attachment_color = env.ST2_MATTERMOST_SUCCESS_COLOR; - - // Try to detect execution failure and use the failure color instead - if (data.message.indexOf("status : failed") > -1) { - attachment_color = env.ST2_MATTERMOST_FAIL_COLOR; - } - } - - split_message = utils.splitMessage(this.formatter.formatData(data.message)); - - if (split_message.text) { - // Default values - var content = { - color: attachment_color, - "mrkdwn_in": ["text", "pretext"], - }; - // Override the default values with values from `data.extra.mattermost` - if (data.extra && data.extra.mattermost) { - for (var attrname in data.extra.mattermost) { content[attrname] = data.extra.mattermost[attrname]; } - } - - // We capture robot here so the `sendMessage` closure captures the correct - // `this` - var robot = this.robot; - var chunks = split_message.text.match(/[\s\S]{1,3800}/g); - - // We define a recursive closure that calls itself with the next data to - // send after a timeout. This approximates sending synchronous (sequential) - // HTTP requests. - var sendChunk = function (i) { - content.text = chunks[i]; - content.fallback = chunks[i]; - - /* - Based on the issue Support for "Button attachments" #151 - Inorder to accept the matteruser adapter message attachments changed the attachement json - */ - - attachment = { - room: recipient, - attachments: content.attachments ? content.attachments : content, - // There is likely a bug here - `split_message.text` being a true-y - // value does not imply that `split_message.pretext` is also non-empty, - // but we unconditionally set `text` to - // `pretext + split_message.pretext` on the first message - text: i === 0 ? pretext + split_message.pretext : null - }; - robot.emit('slack-attachment', attachment); - if (chunks.length > ++i) { - setTimeout(function(){ sendChunk(i); }, 300); - } - }; - sendChunk(0); - } else { - this.robot.messageRoom.call(this.robot, recipient, pretext + split_message.pretext); - } -}; - -/* - HipchatDataPostHandler. -*/ -function HipchatDataPostHandler(robot, formatter) { - this.robot = robot; - this.formatter = formatter; -} - -HipchatDataPostHandler.prototype.postData = function(data) { - var recipient, split_message, formatted_message, - // Special handler to try and figure out when a hipchat message - // is a whisper: - whisper = data.whisper && (data.channel.indexOf('@') > -1), - pretext = ""; - - recipient = data.channel; - if (data.user && !data.whisper) { - pretext = util.format('@%s: ', data.user); - } - - if (recipient.indexOf('@') === -1 ) { - recipient = this.formatter.formatRecepient(recipient); - } - split_message = utils.splitMessage(data.message); - if (pretext) { - split_message.pretext = pretext + split_message.pretext; - } - - /* Hipchat is unable to render text and code in the - same message, so split them */ - if (split_message.pretext) { - if (data.whisper) { - this.robot.send.call(this.robot, data.channel, split_message.pretext); - } else { - this.robot.messageRoom.call(this.robot, recipient, split_message.pretext); - } - } - if (split_message.text) { - if (data.whisper) { - this.robot.send.call(this.robot, data.channel, this.formatter.formatData(split_message.text)); - } else { - this.robot.messageRoom.call(this.robot, recipient, this.formatter.formatData(split_message.text)); - } - } -}; - -/* - Spark Handler. -*/ -function SparkDataPostHandler(robot, formatter) { - this.robot = robot; - this.formatter = formatter; -} - -SparkDataPostHandler.prototype.postData = function(data) { - var recipient, split_message, formatted_message, - text = ""; - - if (data.whisper && data.user) { - recipient = { user: data.user }; - } else { - recipient = { channel: data.channel }; - text = (data.user && !data.whisper) ? util.format('%s: ', data.user) : ""; - } - - recipient = this.formatter.formatRecepient(recipient); - // TODO: Pull attributes from data.extra.spark before pulling them from data.extra - recipient.extra = data.extra; - text += this.formatter.formatData(data.message); - - // Ignore the delimiter in the default formatter and just concat parts. - split_message = utils.splitMessage(text); - if (split_message.pretext && split_message.text) { - formatted_message = util.format("%s\n%s", split_message.pretext, split_message.text); - } else { - formatted_message = split_message.pretext || split_message.text; - } - - this.robot.messageRoom.call(this.robot, recipient, formatted_message); -}; - -/* - RocketChatDataPostHandler. -*/ -function RocketChatDataPostHandler(robot, formatter) { - this.robot = robot; - this.formatter = formatter; -} - -RocketChatDataPostHandler.prototype.postData = function(data) { - var recipient, attachment_color, split_message, - attachment, pretext = ""; - - if (data.whisper && data.user) { - recipient = data.user; - } else { - recipient = data.channel; - pretext = (data.user && !data.whisper) ? util.format('@%s: ', data.user) : ""; - } - - if (data.extra && data.extra.color) { - attachment_color = data.extra.color; - } else { - attachment_color = env.ST2_ROCKETCHAT_SUCCESS_COLOR; - if (data.message.indexOf("status : failed") > -1) { - attachment_color = env.ST2_ROCKETCHAT_FAIL_COLOR; - } - } - - split_message = utils.splitMessage(this.formatter.formatData(data.message)); - - if (split_message.text) { - var m = {}; - var content = { - color: attachment_color, - }; - if (data.extra && data.extra.rocketchat) { - for (var attrname in data.extra.rocketchat) { content[attrname] = data.extra.rocketchat[attrname]; } - } - - var chunks = split_message.text.match(/[\s\S]{1,7900}/g); - var robot = this.robot; - var sendChunk = function(i) { - if (i === 0) { - m.msg = pretext + split_message.pretext; - } - m.attachments = [{ - text: chunks[i], - }]; - for (var attrname in content) { m.attachments[0][attrname] = content[attrname]; } - robot.messageRoom.call(robot, recipient, m); - if (chunks.length > ++i) { - setTimeout(function(){ sendChunk(i); }, 300); - } - }; - sendChunk(0); - } else { - this.robot.messageRoom.call(this.robot, recipient, pretext + split_message.pretext); - } -}; - -/* - DefaultDataPostHandler. -*/ -function DefaultDataPostHandler(robot, formatter) { - this.robot = robot; - this.formatter = formatter; -} - -DefaultDataPostHandler.prototype.postData = function(data) { - var recipient, split_message, formatted_message, - text = ""; - - if (data.whisper && data.user) { - recipient = data.user; - } else { - recipient = data.channel; - text = (data.user && !data.whisper) ? util.format('%s: ', data.user) : ""; - } - - recipient = this.formatter.formatRecepient(recipient); - text += this.formatter.formatData(data.message); - - // Ignore the delimiter in the default formatter and just concat parts. - split_message = utils.splitMessage(text); - if (split_message.pretext && split_message.text) { - formatted_message = util.format("%s\n%s", split_message.pretext, split_message.text); - } else { - formatted_message = split_message.pretext || split_message.text; - } - - this.robot.messageRoom.call(this.robot, recipient, formatted_message); -}; - -var dataPostHandlers = { - 'slack': SlackDataPostHandler, - 'botframework': MSTeamsPostHandler, - 'matteruser': MattermostDataPostHandler, - 'mattermost': MattermostDataPostHandler, - 'hipchat': HipchatDataPostHandler, - 'spark': SparkDataPostHandler, - 'rocketchat': RocketChatDataPostHandler, - 'default': DefaultDataPostHandler -}; - -module.exports.getDataPostHandler = function(adapterName, robot, formatter) { - if (!(adapterName in dataPostHandlers)) { - robot.logger.warning( - util.format('No post handler found for %s. Using DefaultDataPostHandler.', adapterName)); - adapterName = 'default'; - } - robot.logger.debug( - util.format('Using %s post data handler.', adapterName)); - return new dataPostHandlers[adapterName](robot, formatter); -}; diff --git a/lib/slack_monkey_patch.js b/lib/slack_monkey_patch.js deleted file mode 100644 index 03559d5..0000000 --- a/lib/slack_monkey_patch.js +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019 Extreme Networks, Inc. -// -// Licensed 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. - -function sendMessageRaw(message) { - /*jshint validthis:true */ - message['channel'] = this.id; - message['parse'] = 'none'; - this._client._send(message); -} - -function patchSendMessage(robot) { - // We monkey patch sendMessage function to send "parse" argument with the message so the text is not - // 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]); - } - } -} - - -exports.patchSendMessage = patchSendMessage; diff --git a/package.json b/package.json index 8ddeee2..c372a6e 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "hubot-mock-adapter": "^1.1.1", "log": "1.4.0", "mocked-env": "^1.3.1", + "natives": "^1.1.6", "nock": "^10.0.0", "nyc": "^13.0.1", "sinon": "^6.3.5", @@ -58,16 +59,15 @@ "main": "index.js", "scripts": { "eslint": "eslint .", - "test": "node_modules/.bin/nyc mocha \"tests/**/*.js\"" + "test": "nyc mocha" }, "nyc": { "temp-directory": "./coverage/.nyc_output", "include": [ - "lib", - "scripts" + "src" ], "exclude": [ - "tests" + "test" ], "reporter": [ "html", @@ -79,7 +79,6 @@ }, "files": [ "/index.js", - "/lib", - "/scripts" + "/src" ] } diff --git a/settings.json b/settings.json index daeb98a..0a232ef 100644 --- a/settings.json +++ b/settings.json @@ -2,8 +2,7 @@ "dev": ".", "lint": [ "*.js", - "scripts/**/*.js", - "lib/**/*.js", - "tests/**/*.js" + "src/**/*.js", + "test/**/*.js" ] } diff --git a/src/lib/adapters/botframework.js b/src/lib/adapters/botframework.js new file mode 100644 index 0000000..c906455 --- /dev/null +++ b/src/lib/adapters/botframework.js @@ -0,0 +1,17 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +module.exports = require('./msteams'); diff --git a/src/lib/adapters/default.js b/src/lib/adapters/default.js new file mode 100644 index 0000000..c2f2d15 --- /dev/null +++ b/src/lib/adapters/default.js @@ -0,0 +1,89 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var utils = require('./../utils'); +var truncate = require('truncate'); + + +function DefaultAdapter(robot) { + var self = this; + self.robot = robot; + // Limit the size of a message. + self.truncate_length = env.ST2_MAX_MESSAGE_LENGTH; +}; + +DefaultAdapter.prototype.postData = function(data) { + var self = this; + var recipient, split_message, formatted_message, + text = ""; + + if (data.whisper && data.user) { + recipient = data.user; + } else { + recipient = data.channel; + text = (data.user && !data.whisper) ? util.format('%s: ', data.user) : ""; + } + + recipient = self.formatRecipient(recipient); + text += self.formatData(data.message); + + // Ignore the delimiter in the default formatter and just concat parts. + split_message = utils.splitMessage(text); + if (split_message.pretext && split_message.text) { + formatted_message = util.format("%s\n%s", split_message.pretext, split_message.text); + } else { + formatted_message = split_message.pretext || split_message.text; + } + + self.robot.messageRoom.call(self.robot, recipient, formatted_message); +}; + +DefaultAdapter.prototype.formatData = function(data) { + var self = this; + + if (utils.isNull(data)) { + return ""; + } + if (self.truncate_length > 0) { + // The ellipsis argument is only to preserve backwards compatibility, as the + // truncate function switched from using '...' (three period characters + // forming and ellipsis) in truncate 1.x to '…' (a single Unicode ellipsis + // character) in truncate 2+. + // Switching to using the new default ellipsis ('…') probably won't break + // anything. + data = truncate(data, self.truncate_length, {ellipsis: '...'}); + } + return data; +}; + +DefaultAdapter.prototype.formatRecipient = function(recipient) { + return recipient; +}; + +DefaultAdapter.prototype.normalizeCommand = function(command) { + return command; +}; + +DefaultAdapter.prototype.normalizeAddressee = function(msg) { + return { + name: msg.message.user.name, + room: msg.message.room + }; +}; + +module.exports = DefaultAdapter; diff --git a/src/lib/adapters/hipchat.js b/src/lib/adapters/hipchat.js new file mode 100644 index 0000000..5fbb9e0 --- /dev/null +++ b/src/lib/adapters/hipchat.js @@ -0,0 +1,92 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var utils = require('./../utils'); +var DefaultAdapter = require('./default'); + + +function HipChatAdapter(robot) { + var self = this; + DefaultAdapter.call(self, robot); +}; + +util.inherits(HipChatAdapter, DefaultAdapter); + +HipChatAdapter.prototype.postData = function(data) { + var self = this; + + var recipient, split_message, formatted_message, + // Special handler to try and figure out when a hipchat message + // is a whisper: + whisper = data.whisper && (data.channel.indexOf('@') > -1), + pretext = ""; + + recipient = data.channel; + if (data.user && !data.whisper) { + pretext = util.format('@%s: ', data.user); + } + + if (recipient.indexOf('@') === -1 ) { + recipient = self.formatRecipient(recipient); + } + split_message = utils.splitMessage(data.message); + if (pretext) { + split_message.pretext = pretext + split_message.pretext; + } + + /* HipChat is unable to render text and code in the + same message, so split them */ + if (split_message.pretext) { + if (data.whisper) { + self.robot.send.call(self.robot, data.channel, split_message.pretext); + } else { + self.robot.messageRoom.call(self.robot, recipient, split_message.pretext); + } + } + if (split_message.text) { + if (data.whisper) { + self.robot.send.call(self.robot, data.channel, self.formatData(split_message.text)); + } else { + self.robot.messageRoom.call(self.robot, recipient, self.formatData(split_message.text)); + } + } +}; + +HipChatAdapter.prototype.formatData = function(data) { + if (utils.isNull(data)) { + return ""; + } + // HipChat has "show more" capability in messages so no truncation. + return '/code ' + data; +}; + +HipChatAdapter.prototype.formatRecipient = function(recipient) { + var robot_name = env.HUBOT_HIPCHAT_JID.split("_")[0]; + var hipchat_domain = (env.HUBOT_HIPCHAT_XMPP_DOMAIN === 'btf.hipchat.com') ? + 'conf.btf.hipchat.com' : 'conf.hipchat.com'; + return util.format('%s_%s@%s', robot_name, recipient, hipchat_domain); +}; + +HipChatAdapter.prototype.normalizeAddressee = function(msg) { + return { + name: msg.message.user.mention_name, + room: msg.message.user.jid + }; +}; + +module.exports = HipChatAdapter; diff --git a/src/lib/adapters/index.js b/src/lib/adapters/index.js new file mode 100644 index 0000000..7319730 --- /dev/null +++ b/src/lib/adapters/index.js @@ -0,0 +1,44 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var fs = require('fs'); +var path = require('path'); + +var filenames = fs.readdirSync(__dirname); + +var adapters = {}; + +filenames.forEach(function(filename) { + if (filename === 'index.js') { + return; + } + + var adapterName = filename.replace(/\.[^\.]+$/, ""); + adapters[adapterName] = require(path.join(__dirname, filename)); +}); + +module.exports.getAdapter = function(adapterName, robot) { + if (!(adapterName in adapters)) { + robot.logger.warning( + util.format('No adapter found for %s. Using DefaultAdapter.', adapterName)); + adapterName = 'default'; + } + robot.logger.debug( + util.format('Using %s adapter', adapterName)); + return new adapters[adapterName](robot); +}; diff --git a/src/lib/adapters/mattermost.js b/src/lib/adapters/mattermost.js new file mode 100644 index 0000000..a724b56 --- /dev/null +++ b/src/lib/adapters/mattermost.js @@ -0,0 +1,108 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var utils = require('./../utils'); +var SlackAdapter = require('./slack'); + + +function MattermostAdapter(robot) { + var self = this; + SlackAdapter.call(self, robot); +} + +util.inherits(MattermostAdapter, SlackAdapter); + +MattermostAdapter.prototype.postData = function(data) { + var self = this; + + var recipient, attachment_color, split_message, + attachment, pretext = ""; + + // If we are supposed to whisper to a single user, use a direct message + if (data.whisper && data.user) { + recipient = data.user; + } else { // Otherwise, message the channel + recipient = data.channel; + // If we aren't supposed to whisper, then we at least at-mention the user + pretext = (data.user && !data.whisper) ? util.format('@%s: ', data.user) : ""; + } + + // Use the color specified in the `extra` block + if (data.extra && data.extra.color) { + attachment_color = data.extra.color; + } else { + // Assume success, and use the success color + attachment_color = env.ST2_MATTERMOST_SUCCESS_COLOR; + + // Try to detect execution failure and use the failure color instead + if (data.message.indexOf("status : failed") > -1) { + attachment_color = env.ST2_MATTERMOST_FAIL_COLOR; + } + } + + split_message = utils.splitMessage(self.formatData(data.message)); + + if (split_message.text) { + // Default values + var content = { + color: attachment_color, + "mrkdwn_in": ["text", "pretext"], + }; + // Override the default values with values from `data.extra.mattermost` + if (data.extra && data.extra.mattermost) { + for (var attrname in data.extra.mattermost) { content[attrname] = data.extra.mattermost[attrname]; } + } + + // We capture robot here so the `sendMessage` closure captures the correct + // `this` + var robot = self.robot; + var chunks = split_message.text.match(/[\s\S]{1,3800}/g); + + // We define a recursive closure that calls itself with the next data to + // send after a timeout. This approximates sending synchronous (sequential) + // HTTP requests. + var sendChunk = function (i) { + content.text = chunks[i]; + content.fallback = chunks[i]; + + /* + Based on the issue Support for "Button attachments" #151 + Inorder to accept the matteruser adapter message attachments changed the attachement json + */ + + attachment = { + room: recipient, + attachments: content.attachments ? content.attachments : content, + // There is likely a bug here - `split_message.text` being a true-y + // value does not imply that `split_message.pretext` is also non-empty, + // but we unconditionally set `text` to + // `pretext + split_message.pretext` on the first message + text: i === 0 ? pretext + split_message.pretext : null + }; + robot.emit('slack-attachment', attachment); + if (chunks.length > ++i) { + setTimeout(function(){ sendChunk(i); }, 300); + } + }; + sendChunk(0); + } else { + self.robot.messageRoom.call(self.robot, recipient, pretext + split_message.pretext); + } +}; + +module.exports = MattermostAdapter; diff --git a/src/lib/adapters/matteruser.js b/src/lib/adapters/matteruser.js new file mode 100644 index 0000000..95b2d6d --- /dev/null +++ b/src/lib/adapters/matteruser.js @@ -0,0 +1,17 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +module.exports = require('./mattermost'); diff --git a/src/lib/adapters/msteams.js b/src/lib/adapters/msteams.js new file mode 100644 index 0000000..d4b8b28 --- /dev/null +++ b/src/lib/adapters/msteams.js @@ -0,0 +1,99 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var utils = require('./../utils'); +var DefaultAdapter = require('./default'); + + +function MSTeamsAdapter(robot) { + var self = this; + DefaultAdapter.call(self, robot); +} + +util.inherits(MSTeamsAdapter, DefaultAdapter); + +MSTeamsAdapter.prototype.postData = function(data) { + var self = this; + var messages_to_send, + // We capture robot here so the `sendMessage` closure captures the + // correct `this` + robot = self.robot, + split_message = utils.splitMessage(data.message); + + if (data.extra && data.extra.botframework) { + robot.logger.warning(util.format('The extra.botframework attribute of aliases is not used yet.')); + } + + if (split_message.pretext) { + var text = self.formatData(split_message.text); + messages_to_send = [split_message.pretext, text]; + } else { + messages_to_send = [self.formatData(data.message)]; + } + + // We define a recursive closure that calls itself with the next data to send + // after a timeout. This approximates sending synchronous (sequential) HTTP + // requests. + var sendMessage = function (i) { + robot.adapter.send(data.context, messages_to_send[i]); + + if (messages_to_send.length > ++i) { + setTimeout(function () { sendMessage(i); }, 300); + } + }; + + sendMessage(0); + + return; +} + +MSTeamsAdapter.prototype.formatData = function(data) { + var self = this; + + self.robot.logger.debug("Got data in formatData: " + JSON.stringify(data)); + + // Remove starting newlines + data = data.replace(/^\n/g, ''); + // Replace single newlines with double newlines + data = data.replace(/([^\n])\n([^\n])/g, "$1\n\n$2"); + + return data; +}; + +MSTeamsAdapter.prototype.formatRecipient = function(recipient) { + var self = this; + + self.robot.logger.debug("Got recipient in formatRecipient: " + JSON.stringify(recipient)); + return recipient; +}; + +MSTeamsAdapter.prototype.normalizeCommand = function (command) { + var self = this; + + self.robot.logger.debug("Got command in normalizeCommand: " + JSON.stringify(command)); + return command; +} + +MSTeamsAdapter.prototype.normalizeAddressee = function(msg) { + return { + name: msg.message.user.name, + room: msg.message.room + }; +}; + +module.exports = MSTeamsAdapter; diff --git a/src/lib/adapters/rocketchat.js b/src/lib/adapters/rocketchat.js new file mode 100644 index 0000000..2ddec74 --- /dev/null +++ b/src/lib/adapters/rocketchat.js @@ -0,0 +1,84 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var utils = require('./../utils'); +var SlackAdapter = require('./slack'); + + +function RocketChatAdapter(robot) { + var self = this; + SlackAdapter.call(self, robot); +} + +util.inherits(RocketChatAdapter, SlackAdapter); + +RocketChatAdapter.prototype.postData = function(data) { + var self = this; + + var recipient, attachment_color, split_message, + attachment, pretext = ""; + + if (data.whisper && data.user) { + recipient = data.user; + } else { + recipient = data.channel; + pretext = (data.user && !data.whisper) ? util.format('@%s: ', data.user) : ""; + } + + if (data.extra && data.extra.color) { + attachment_color = data.extra.color; + } else { + attachment_color = env.ST2_ROCKETCHAT_SUCCESS_COLOR; + if (data.message.indexOf("status : failed") > -1) { + attachment_color = env.ST2_ROCKETCHAT_FAIL_COLOR; + } + } + + split_message = utils.splitMessage(self.formatData(data.message)); + + if (split_message.text) { + var m = {}; + var content = { + color: attachment_color, + }; + if (data.extra && data.extra.rocketchat) { + for (var attrname in data.extra.rocketchat) { content[attrname] = data.extra.rocketchat[attrname]; } + } + + var chunks = split_message.text.match(/[\s\S]{1,7900}/g); + var robot = self.robot; + var sendChunk = function(i) { + if (i === 0) { + m.msg = pretext + split_message.pretext; + } + m.attachments = [{ + text: chunks[i], + }]; + for (var attrname in content) { m.attachments[0][attrname] = content[attrname]; } + robot.messageRoom.call(robot, recipient, m); + if (chunks.length > ++i) { + setTimeout(function(){ sendChunk(i); }, 300); + } + }; + sendChunk(0); + } else { + self.robot.messageRoom.call(self.robot, recipient, pretext + split_message.pretext); + } +}; + +module.exports = RocketChatAdapter; diff --git a/src/lib/adapters/slack.js b/src/lib/adapters/slack.js new file mode 100644 index 0000000..b36fbec --- /dev/null +++ b/src/lib/adapters/slack.js @@ -0,0 +1,254 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var utils = require('./../utils'); +var messages = require('./../slack-messages'); +var DefaultAdapter = require('./default'); + + +// NOTE: Be careful about making changes to this adapter, because the adapters +// for Mattermost, Cisco Spark, and Rocketchat all inherit from this one +function SlackAdapter(robot) { + var self = this; + DefaultAdapter.call(self, robot); + + // We monkey patch sendMessage function to send "parse" argument with the message so the text is not + // 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 the original code in lib/slack_monkey_patch.js (see the Git history) + // So...I'm not entirely sure this monkey patch is still necessary. + // End-to-end testing is required to figure out for sure. + var sendMessageRaw = function(message) { + /*jshint validthis:true */ + message['channel'] = this.id; + message['parse'] = 'none'; + this._client._send(message); + } + + 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]); + } + } +}; + +util.inherits(SlackAdapter, DefaultAdapter); + +SlackAdapter.prototype.postData = function(data) { + var self = this; + + var recipient, attachment_color, split_message, + attachment, pretext = ""; + var envelope, + // We capture robot here so the `sendMessage` closure captures the + // correct `this` + robot = self.robot; + + if (data.whisper && data.user) { + recipient = data.user; + envelope = { + "user": data.user + }; + } else { + recipient = data.channel; + pretext = (data.user && !data.whisper) ? util.format('@%s: ', data.user) : ""; + envelope = { + "room": data.channel, + "id": data.channel, + "user": data.user, + }; + } + + // Allow packs to specify arbitrary keys + if (data.extra && data.extra.slack && data.extra.slack.attachments) { + // Action: + // + // result: + // format: ... + // extra: + // slack: + // icon_emoji: ":jira:" + // username: Jira Bot + // attachments: + // - + // fallback: "Info about Jira ticket {{ execution.result.result.key }}" + // color: "#042A60" + // title: "{{ execution.result.result.key }}" + // title_link: "{{ execution.result.result.url }}" + // fields: + // - + // title: Summary + // value: "{{ execution.result.result.summary }}" + // short: false + // + // becomes: + // + // { + // "icon_emoji": ":jira:", + // "username": "Jira Bot", + // "attachments": [ + // { + // "fallback": "Info about Jira ticket {{ execution.result.result.key }}", + // "color": "#042A60", + // "title": "{{ execution.result.result.key }}", + // "title_link": "{{ execution.result.result.url }}", + // "fields": [ + // { + // "title": "Summary", + // "value": "{{ execution.result.result.summary }}", + // "short": false + // } + // ], + // } + // ] + // } + + var messages_to_send = messages.buildMessages(data.extra.slack); + + var sendMessage = function (i) { + robot.adapter.client.send(envelope, messages_to_send[i]); + + if (messages_to_send.length > ++i) { + setTimeout(function(){sendMessage(i);}, 300); + } + }; + + sendMessage(0); + + return; + } + + if (data.extra && data.extra.color) { + attachment_color = data.extra.color; + } else { + attachment_color = env.ST2_SLACK_SUCCESS_COLOR; + if (data.message.indexOf("status : failed") > -1) { + attachment_color = env.ST2_SLACK_FAIL_COLOR; + } + } + + split_message = utils.splitMessage(self.formatData(data.message)); + + if (split_message.text) { + var content = { + color: attachment_color, + "mrkdwn_in": ["text", "pretext"], + }; + if (data.extra && data.extra.slack) { + // Backwards compatibility + + // Action: + // + // result: + // format: ... + // extra: + // slack: + // author_name: Jira_Bot + // author_link: "https://stackstorm.com" + // author_icon: "https://stackstorm.com/favicon.ico" + // color: "#042A60" + // fallback: "Info about Jira ticket {{ execution.result.result.key }}" + // title: "{{ execution.result.result.key }}" + // title_link: "{{ execution.result.result.url }}" + // fields: + // - + // title: Summary + // value: "{{ execution.result.result.summary }}" + // short: false + // + // becomes: + // + // { + // "attachments": [ + // { + // "author_name": "Jira Bot", + // "author_link": "https://stackstorm.com", + // "author_icon": "https://stackstorm.com/favicon.ico", + // "color": "#042A60", + // "fallback": "Info about Jira ticket {{ execution.result.result.key }}", + // "title": "{{ execution.result.result.key }}", + // "title_link": "{{ execution.result.result.url }}", + // "fields": [ + // { + // "title": "Summary", + // "value": "{{ execution.result.result.summary }}", + // "short": false + // } + // ] + // } + // ] + // } + + for (var attrname in data.extra.slack) { content[attrname] = data.extra.slack[attrname]; } + } + + var chunks = split_message.text.match(/[\s\S]{1,7900}/g); + var sendChunk = function (i) { + content.pretext = i === 0 ? pretext + split_message.pretext : null; + content.text = chunks[i]; + content.fallback = chunks[i]; + robot.adapter.client.send(envelope, {'attachments': [content]}); + + if (chunks.length > ++i) { + setTimeout(function(){ sendChunk(i); }, 300); + } + }; + sendChunk(0); + } else { + self.robot.adapter.client.send(envelope, pretext + split_message.pretext); + } +}; + +SlackAdapter.prototype.formatData = function(data) { + if (utils.isNull(data)) { + return ""; + } + // For slack we do not truncate or format the result. This is because + // data is posted to slack as a message attachment. + return data; +}; + +SlackAdapter.prototype.formatRecipient = function(recipient) { + return recipient; +}; + +SlackAdapter.prototype.normalizeCommand = function(command) { + var self = this; + command = SlackAdapter.super_.prototype.normalizeCommand.call(self, command); + // replace left double quote with regular quote + command = command.replace(/\u201c/g, '\u0022'); + // replace right double quote with regular quote + command = command.replace(/\u201d/g, '\u0022'); + // replace left single quote with regular apostrophe + command = command.replace(/\u2018/g, '\u0027'); + // replace right single quote with regular apostrophe + command = command.replace(/\u2019/g, '\u0027'); + return command; +}; + +module.exports = SlackAdapter; diff --git a/src/lib/adapters/spark.js b/src/lib/adapters/spark.js new file mode 100644 index 0000000..e19da7d --- /dev/null +++ b/src/lib/adapters/spark.js @@ -0,0 +1,74 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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"; + +var env = process.env; +var util = require('util'); +var utils = require('./../utils'); +var DefaultAdapter = require('./default'); +var SlackAdapter = require('./slack'); + + +function SparkAdapter(robot) { + var self = this; + SlackAdapter.call(self, robot); +} + +util.inherits(SparkAdapter, SlackAdapter); + +SparkAdapter.prototype.postData = function(data) { + var self = this; + + var recipient, split_message, formatted_message, + text = ""; + + if (data.whisper && data.user) { + recipient = { user: data.user }; + } else { + recipient = { channel: data.channel }; + text = (data.user && !data.whisper) ? util.format('%s: ', data.user) : ""; + } + + recipient = self.formatRecipient(recipient); + // TODO: Pull attributes from data.extra.spark before pulling them from data.extra + recipient.extra = data.extra; + text += self.formatData(data.message); + + // Ignore the delimiter in the default formatter and just concat parts. + split_message = utils.splitMessage(text); + if (split_message.pretext && split_message.text) { + formatted_message = util.format("%s\n%s", split_message.pretext, split_message.text); + } else { + formatted_message = split_message.pretext || split_message.text; + } + + self.robot.messageRoom.call(self.robot, recipient, formatted_message); +}; + +// Override this with the original function from DefaultAdapter +// We do want this one to truncate +SparkAdapter.prototype.formatData = function(data) { + var self = this; + return DefaultAdapter.prototype.formatData.call(self, data); +}; + +SparkAdapter.prototype.normalizeAddressee = function(msg) { + return { + name: msg.message.user.name, + room: msg.message.user.room + }; +}; + +module.exports = SparkAdapter; diff --git a/lib/command_factory.js b/src/lib/command_factory.js similarity index 100% rename from lib/command_factory.js rename to src/lib/command_factory.js diff --git a/lib/format_command.js b/src/lib/format_command.js similarity index 100% rename from lib/format_command.js rename to src/lib/format_command.js diff --git a/lib/slack-messages.js b/src/lib/slack-messages.js similarity index 100% rename from lib/slack-messages.js rename to src/lib/slack-messages.js diff --git a/lib/utils.js b/src/lib/utils.js similarity index 100% rename from lib/utils.js rename to src/lib/utils.js diff --git a/scripts/stackstorm.js b/src/stackstorm.js similarity index 94% rename from scripts/stackstorm.js rename to src/stackstorm.js index 90ce832..380e7db 100644 --- a/scripts/stackstorm.js +++ b/src/stackstorm.js @@ -33,12 +33,10 @@ var _ = require('lodash'), util = require('util'), env = _.clone(process.env), Promise = require('rsvp').Promise, - utils = require('../lib/utils.js'), - slack_monkey_patch = require('../lib/slack_monkey_patch.js'), - formatCommand = require('../lib/format_command.js'), - formatData = require('../lib/format_data.js'), - postData = require('../lib/post_data.js'), - CommandFactory = require('../lib/command_factory.js'), + utils = require('./lib/utils.js'), + formatCommand = require('./lib/format_command.js'), + CommandFactory = require('./lib/command_factory.js'), + adapters = require('./lib/adapters'), st2client = require('st2client'), uuid = require('uuid') ; @@ -97,8 +95,6 @@ var TWOFACTOR_MESSAGE = "This action requires two-factor auth! Waiting for your module.exports = function(robot) { - slack_monkey_patch.patchSendMessage(robot); - // Makes the script crash on unhandled rejections instead of ignoring them and keep running. // Usually happens when trying to connect to a nonexistent instances or similar unrecoverable issues. // In the future Node.js versions, promise rejections that are not handled will terminate the process with a non-zero exit code. @@ -195,11 +191,8 @@ module.exports = function(robot) { // factory to manage commands var command_factory = new CommandFactory(robot); - // formatter to manage per adapter message formatting. - var formatter = formatData.getFormatter(robot.adapterName, robot); - - // handler to manage per adapter message post-ing. - var postDataHandler = postData.getDataPostHandler(robot.adapterName, robot, formatter); + // adapter - specific to each chat provider + var adapter = adapters.getAdapter(robot.adapterName, robot); var loadCommands = function() { robot.logger.info('Loading commands....'); @@ -276,14 +269,14 @@ module.exports = function(robot) { return sendAck(msg, { execution: { id: err.message } }); } robot.logger.error('Failed to create an alias execution:', err); - var addressee = formatter.normalizeAddressee(msg); + var addressee = adapter.normalizeAddressee(msg); var message = util.format(_.sample(ERROR_MESSAGES), err.message); if (err.requestId) { message = util.format( message, util.format('; Use request ID %s to grep st2 api logs.', err.requestId)); } - postDataHandler.postData({ + adapter.postData({ whisper: false, user: addressee.name, channel: addressee.room, @@ -296,7 +289,7 @@ module.exports = function(robot) { }; var executeCommand = function(msg, command_name, format_string, command, action_alias) { - var addressee = formatter.normalizeAddressee(msg); + var addressee = adapter.normalizeAddressee(msg); var payload = { 'name': command_name, 'format': format_string, @@ -334,7 +327,7 @@ module.exports = function(robot) { // 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. - command = formatter.normalizeCommand(msg.match[1]); + command = adapter.normalizeCommand(msg.match[1]); result = command_factory.getMatchingCommand(command); @@ -357,7 +350,7 @@ module.exports = function(robot) { } else { data = req.body; } - postDataHandler.postData(data); + adapter.postData(data); res.send('{"status": "completed", "msg": "Message posted successfully"}'); } catch (e) { @@ -391,7 +384,7 @@ module.exports = function(robot) { data = e.data; } - postDataHandler.postData(data); + adapter.postData(data); }); if (env.HUBOT_2FA) { diff --git a/tests/dummy-adapters.js b/test/dummy-adapters.js similarity index 93% rename from tests/dummy-adapters.js rename to test/dummy-adapters.js index d6d1ea5..a9d5ddd 100644 --- a/tests/dummy-adapters.js +++ b/test/dummy-adapters.js @@ -22,7 +22,7 @@ MockSlackClient.prototype.send = function(envelope, message) { this.logger.info('Sending ' + JSON.stringify(message) + ' to ' + JSON.stringify(envelope)); }; -function MockSlackAdapter(logger) { +function SlackBot(logger) { this.logger = logger; this.client = new MockSlackClient(logger); } @@ -35,5 +35,5 @@ MockBotFrameworkAdapter.prototype.send = function(envelope, message) { this.logger.info('Sending ' + JSON.stringify(message) + ' to ' + JSON.stringify(envelope)); }; -module.exports.MockSlackAdapter = MockSlackAdapter; +module.exports.MockSlackAdapter = SlackBot; module.exports.MockBotFrameworkAdapter = MockBotFrameworkAdapter; diff --git a/test/dummy-logger.js b/test/dummy-logger.js new file mode 100644 index 0000000..ae2aff7 --- /dev/null +++ b/test/dummy-logger.js @@ -0,0 +1,53 @@ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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; diff --git a/tests/dummy-robot.js b/test/dummy-robot.js similarity index 100% rename from tests/dummy-robot.js rename to test/dummy-robot.js diff --git a/tests/fixtures/aliases.json b/test/fixtures/aliases.json similarity index 100% rename from tests/fixtures/aliases.json rename to test/fixtures/aliases.json diff --git a/tests/test-command-factory.js b/test/test-command-factory.js similarity index 97% rename from tests/test-command-factory.js rename to test/test-command-factory.js index ea3b629..ce4ea14 100644 --- a/tests/test-command-factory.js +++ b/test/test-command-factory.js @@ -20,12 +20,12 @@ var fs = require('fs'), chai = require('chai'), assert = chai.assert, expect = chai.expect, - CommandFactory = require('../lib/command_factory.js'), + CommandFactory = require('../src/lib/command_factory.js'), Robot = require('./dummy-robot.js'), - formatCommand = require('../lib/format_command.js'), - utils = require('../lib/utils.js'); + formatCommand = require('../src/lib/format_command.js'), + utils = require('../src/lib/utils.js'); -var ALIAS_FIXTURES = fs.readFileSync('tests/fixtures/aliases.json'); +var ALIAS_FIXTURES = fs.readFileSync('test/fixtures/aliases.json'); ALIAS_FIXTURES = JSON.parse(ALIAS_FIXTURES); diff --git a/tests/test-formatcommand.js b/test/test-formatcommand.js similarity index 96% rename from tests/test-formatcommand.js rename to test/test-formatcommand.js index be10213..0aa1a14 100644 --- a/tests/test-formatcommand.js +++ b/test/test-formatcommand.js @@ -19,7 +19,7 @@ var chai = require('chai'), assert = chai.assert, expect = chai.expect, - formatcommand = require('../lib/format_command.js'); + formatcommand = require('../src/lib/format_command.js'); describe('format_command', function() { it('should create the right format with format and description', diff --git a/tests/test-formatdata.js b/test/test-formatdata.js similarity index 63% rename from tests/test-formatdata.js rename to test/test-formatdata.js index d373c76..1096171 100644 --- a/tests/test-formatdata.js +++ b/test/test-formatdata.js @@ -19,17 +19,24 @@ var chai = require('chai'), expect = chai.expect, env = process.env, - formatData = require('../lib/format_data.js'), + adapters = require('../src/lib/adapters'), + dummyAdapters = require('./dummy-adapters.js'), + Log = require('log'), DummyRobot = require('./dummy-robot.js'); env.ST2_MAX_MESSAGE_LENGTH = 500; +var MockSlackAdapter = dummyAdapters.MockSlackAdapter; +var MockBotFrameworkAdapter = dummyAdapters.MockBotFrameworkAdapter; + describe('SlackFormatter', function() { var adapterName = 'slack'; + var logger = new Log('info'); + var robot = new DummyRobot(false, new MockSlackAdapter(logger)); it('should create a snippet for non-empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('DATA', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('DATA'); expect(o).to.be.an('string'); expect(o).to.equal('DATA'); }); @@ -37,8 +44,8 @@ describe('SlackFormatter', function() { it('should truncate text more than a certain length', function() { var org_max_length = env.ST2_MAX_MESSAGE_LENGTH; env.ST2_MAX_MESSAGE_LENGTH = 10; - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('abcd efgh ijklm', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('abcd efgh ijklm'); env.ST2_MAX_MESSAGE_LENGTH = org_max_length; expect(o).to.be.an('string'); @@ -47,42 +54,42 @@ describe('SlackFormatter', function() { }); it('should be an empty string for empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData(''); expect(o).to.be.an('string'); expect(o).to.equal(''); }); - it('should echo back recepient', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatRecepient('Estee'); + it('should echo back recipient', function() { + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('Estee'); }); it('should normalize command', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run local \u201cuname -a"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run local \u201cuname -a"'); expect(o).to.be.an('string'); expect(o).to.equal('run local "uname -a"'); }); it('should normalize command with special double quotes', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"'); expect(o).to.be.an('string'); expect(o).to.equal('run remote "uname -a" "localhost, 127.0.0.1"'); }); it('should normalize command with special single quotes', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run remote \u2018uname -a\' \u2019localhost, 127.0.0.1\''); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run remote \u2018uname -a\' \u2019localhost, 127.0.0.1\''); expect(o).to.be.an('string'); expect(o).to.equal('run remote \'uname -a\' \'localhost, 127.0.0.1\''); }); it('should normalize the addressee', function() { - var formatter = formatData.getFormatter(adapterName, null); + var adapter = adapters.getAdapter(adapterName, robot); var msg = { message: { room: "SlackRoomName", @@ -91,7 +98,7 @@ describe('SlackFormatter', function() { } } }; - var o = formatter.normalizeAddressee(msg); + var o = adapter.normalizeAddressee(msg); expect(o.name).to.be.an('string'); expect(o.name).to.equal('SlackUserName'); expect(o.room).to.be.an('string'); @@ -101,44 +108,45 @@ describe('SlackFormatter', function() { describe('MattermostFormatter', function() { var adapterName = 'mattermost'; + var robot = new DummyRobot('dummy', null, false); it('should echo back for non-empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('DATA', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('DATA'); expect(o).to.be.an('string'); expect(o).to.equal('DATA'); }); it('should be an empty string for empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData(''); expect(o).to.be.an('string'); expect(o).to.equal(''); }); - it('should correctly format recepient', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatRecepient('Estee'); + it('should correctly format recipient', function() { + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('Estee'); }); it('should normalize command with special double quotes', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"'); expect(o).to.be.an('string'); expect(o).to.equal('run remote "uname -a" "localhost, 127.0.0.1"'); }); it('should normalize command with special single quotes', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run remote \u2018uname -a\' \u2019localhost, 127.0.0.1\''); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run remote \u2018uname -a\' \u2019localhost, 127.0.0.1\''); expect(o).to.be.an('string'); expect(o).to.equal('run remote \'uname -a\' \'localhost, 127.0.0.1\''); }); it('should normalize the addressee', function() { - var formatter = formatData.getFormatter(adapterName, null); + var adapter = adapters.getAdapter(adapterName, robot); var msg = { message: { room: "MattermostRoomName", @@ -147,7 +155,7 @@ describe('MattermostFormatter', function() { } } }; - var o = formatter.normalizeAddressee(msg); + var o = adapter.normalizeAddressee(msg); expect(o.name).to.be.an('string'); expect(o.name).to.equal('MattermostUserName'); expect(o.room).to.be.an('string'); @@ -160,49 +168,49 @@ describe('MSTeamsFormatter', function() { var robot = new DummyRobot('dummy', null, false); it('should echo back for non-empty', function() { - var formatter = formatData.getFormatter(adapterName, robot); + var adapter = adapters.getAdapter(adapterName, robot); var str = '\nst2 list actions\n'; - var o = formatter.formatData(str, null); + var o = adapter.formatData(str); expect(o).to.be.an('string'); expect(o).to.equal('st2 list actions\n'); var str = 'st2 list actions\n\nrun remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"\n'; - var o = formatter.formatData(str, null); + var o = adapter.formatData(str); expect(o).to.be.an('string'); expect(o).to.equal('st2 list actions\n\nrun remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"\n'); }); it('should be an empty string for empty', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.formatData('', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData(''); expect(o).to.be.an('string'); expect(o).to.equal(''); }); - it('should correctly format recepient', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.formatRecepient('Estee'); + it('should correctly format recipient', function() { + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('Estee'); }); it('should normalize command by returning it', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.normalizeCommand('run remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"'); expect(o).to.be.an('string'); expect(o).to.equal('run remote \u201cuname -a" \u201dlocalhost, 127.0.0.1"'); }); it('should normalize command by returning it', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.normalizeCommand('run remote \u2018uname -a\' \u2019localhost, 127.0.0.1\''); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run remote \u2018uname -a\' \u2019localhost, 127.0.0.1\''); expect(o).to.be.an('string'); expect(o).to.equal('run remote \u2018uname -a\' \u2019localhost, 127.0.0.1\''); }); it('should normalize the addressee', function() { - var formatter = formatData.getFormatter(adapterName, robot); + var adapter = adapters.getAdapter(adapterName, robot); var msg = { message: { room: "MSTeamsRoomName", @@ -211,7 +219,7 @@ describe('MSTeamsFormatter', function() { } } }; - var o = formatter.normalizeAddressee(msg); + var o = adapter.normalizeAddressee(msg); expect(o.name).to.be.an('string'); expect(o.name).to.equal('MSTeamsUserName'); expect(o.room).to.be.an('string'); @@ -221,47 +229,48 @@ describe('MSTeamsFormatter', function() { describe('HipChatFormatter', function() { var adapterName = 'hipchat'; + var robot = new DummyRobot('dummy', null, false); it('should echo back for non-empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('DATA', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('DATA'); expect(o).to.be.an('string'); expect(o).to.equal('/code DATA'); }); it('should be an empty string for empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData(''); expect(o).to.be.an('string'); expect(o).to.equal(''); }); - it('should correctly format recepient', function() { - var formatter = formatData.getFormatter(adapterName, null); + it('should correctly format recipient', function() { + var adapter = adapters.getAdapter(adapterName, robot); env.HUBOT_HIPCHAT_JID = '234x_y234@conf.hipchat.com'; - var o = formatter.formatRecepient('Estee'); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('234x_Estee@conf.hipchat.com'); }); - it('should correctly format recepient with conf.btf.hipchat.com', function() { - var formatter = formatData.getFormatter(adapterName, null); + it('should correctly format recipient with conf.btf.hipchat.com', function() { + var adapter = adapters.getAdapter(adapterName, robot); env.HUBOT_HIPCHAT_JID = '234x_y234@conf.hipchat.com'; env.HUBOT_HIPCHAT_XMPP_DOMAIN = "btf.hipchat.com"; - var o = formatter.formatRecepient('Estee'); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('234x_Estee@conf.btf.hipchat.com'); }); it('should normalize command', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run local "uname -a"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run local "uname -a"'); expect(o).to.be.an('string'); expect(o).to.equal('run local "uname -a"'); }); it('should normalize the addressee', function() { - var formatter = formatData.getFormatter(adapterName, null); + var adapter = adapters.getAdapter(adapterName, robot); // HipChat packages the room and user name differently var msg = { message: { @@ -271,7 +280,7 @@ describe('HipChatFormatter', function() { } } }; - var o = formatter.normalizeAddressee(msg); + var o = adapter.normalizeAddressee(msg); expect(o.name).to.be.an('string'); expect(o.name).to.equal('HipChatUserName'); expect(o.room).to.be.an('string'); @@ -281,37 +290,38 @@ describe('HipChatFormatter', function() { describe('RocketChatFormatter', function() { var adapterName = 'rocketchat'; + var robot = new DummyRobot('dummy', null, false); it('should echo back for non-empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('DATA', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('DATA'); expect(o).to.be.an('string'); expect(o).to.equal('DATA'); }); it('should be an empty string for empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData(''); expect(o).to.be.an('string'); expect(o).to.equal(''); }); - it('should correctly format recepient', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatRecepient('Estee'); + it('should correctly format recipient', function() { + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('Estee'); }); it('should normalize command', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run local "uname -a"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run local "uname -a"'); expect(o).to.be.an('string'); expect(o).to.equal('run local "uname -a"'); }); it('should normalize the addressee', function() { - var formatter = formatData.getFormatter(adapterName, null); + var adapter = adapters.getAdapter(adapterName, robot); var msg = { message: { room: "RocketChatRoomName", @@ -320,7 +330,7 @@ describe('RocketChatFormatter', function() { } } }; - var o = formatter.normalizeAddressee(msg); + var o = adapter.normalizeAddressee(msg); expect(o.name).to.be.an('string'); expect(o.name).to.equal('RocketChatUserName'); expect(o.room).to.be.an('string'); @@ -330,17 +340,18 @@ describe('RocketChatFormatter', function() { describe('SparkFormatter', function() { var adapterName = 'spark'; + var robot = new DummyRobot('dummy', null, false); it('should echo back for non-empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('DATA'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('DATA'); expect(o).to.be.an('string'); expect(o).to.equal('DATA'); }); it('should be an empty string for empty', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData(''); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData(''); expect(o).to.be.an('string'); expect(o).to.equal(''); }); @@ -348,8 +359,8 @@ describe('SparkFormatter', function() { it('should truncate text more than a certain length', function() { var old_ST2_MAX_MESSAGE_LENGTH = env.ST2_MAX_MESSAGE_LENGTH; env.ST2_MAX_MESSAGE_LENGTH = 4; - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('asdfqwerty'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('asdfqwerty'); expect(o).to.be.an('string'); expect(o).to.equal('asdf...'); env.ST2_MAX_MESSAGE_LENGTH = old_ST2_MAX_MESSAGE_LENGTH; @@ -358,29 +369,29 @@ describe('SparkFormatter', function() { it('should not truncate text more than a certain length', function() { var old_ST2_MAX_MESSAGE_LENGTH = env.ST2_MAX_MESSAGE_LENGTH; env.ST2_MAX_MESSAGE_LENGTH = 0; - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatData('asdfqwerty'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('asdfqwerty'); expect(o).to.be.an('string'); expect(o).to.equal('asdfqwerty'); env.ST2_MAX_MESSAGE_LENGTH = old_ST2_MAX_MESSAGE_LENGTH; }); - it('should correctly format recepient', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.formatRecepient('Estee'); + it('should correctly format recipient', function() { + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('Estee'); }); it('should normalize command', function() { - var formatter = formatData.getFormatter(adapterName, null); - var o = formatter.normalizeCommand('run local "uname -a"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run local "uname -a"'); expect(o).to.be.an('string'); expect(o).to.equal('run local "uname -a"'); }); it('should normalize the addressee', function() { - var formatter = formatData.getFormatter(adapterName, null); + var adapter = adapters.getAdapter(adapterName, robot); var msg = { message: { user: { @@ -389,7 +400,7 @@ describe('SparkFormatter', function() { } } }; - var o = formatter.normalizeAddressee(msg); + var o = adapter.normalizeAddressee(msg); expect(o.name).to.be.an('string'); expect(o.name).to.equal('SparkUserName'); expect(o.room).to.be.an('string'); @@ -402,8 +413,8 @@ describe('DefaultFormatter', function() { var robot = new DummyRobot('dummy', null, false); it('should create a snippet for non-empty', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.formatData('DATA', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('DATA'); expect(o).to.be.an('string'); expect(o).to.equal('DATA'); }); @@ -411,8 +422,8 @@ describe('DefaultFormatter', function() { it('should truncate text more than a certain length', function() { var org_max_length = env.ST2_MAX_MESSAGE_LENGTH; env.ST2_MAX_MESSAGE_LENGTH = 10; - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.formatData('abcd efgh ijklm', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('abcd efgh ijklm'); env.ST2_MAX_MESSAGE_LENGTH = org_max_length; expect(o).to.be.an('string'); @@ -422,8 +433,8 @@ describe('DefaultFormatter', function() { it('should truncate text more than a certain length', function() { var org_max_length = env.ST2_MAX_MESSAGE_LENGTH; env.ST2_MAX_MESSAGE_LENGTH = 0; - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.formatData('abcd efgh ijklm', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData('abcd efgh ijklm'); env.ST2_MAX_MESSAGE_LENGTH = org_max_length; expect(o).to.be.an('string'); @@ -431,28 +442,28 @@ describe('DefaultFormatter', function() { }); it('should be an empty string for empty', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.formatData('', null); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatData(''); expect(o).to.be.an('string'); expect(o).to.equal(''); }); - it('should echo back recepient', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.formatRecepient('Estee'); + it('should echo back recipient', function() { + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.formatRecipient('Estee'); expect(o).to.be.an('string'); expect(o).to.equal('Estee'); }); it('should normalize command', function() { - var formatter = formatData.getFormatter(adapterName, robot); - var o = formatter.normalizeCommand('run local "uname -a"'); + var adapter = adapters.getAdapter(adapterName, robot); + var o = adapter.normalizeCommand('run local "uname -a"'); expect(o).to.be.an('string'); expect(o).to.equal('run local "uname -a"'); }); it('should normalize the addressee', function() { - var formatter = formatData.getFormatter(adapterName, robot); + var adapter = adapters.getAdapter(adapterName, robot); var msg = { message: { room: "DefaultRoomName", @@ -461,7 +472,7 @@ describe('DefaultFormatter', function() { } } }; - var o = formatter.normalizeAddressee(msg); + var o = adapter.normalizeAddressee(msg); expect(o.name).to.be.an('string'); expect(o.name).to.equal('DefaultUserName'); expect(o.room).to.be.an('string'); diff --git a/tests/test-postdata.js b/test/test-postdata.js similarity index 91% rename from tests/test-postdata.js rename to test/test-postdata.js index a275017..ce1ba21 100644 --- a/tests/test-postdata.js +++ b/test/test-postdata.js @@ -23,8 +23,7 @@ var chai = require("chai"), Robot = require('./dummy-robot.js'), dummyAdapters = require('./dummy-adapters.js'), Log = require('log'), - formatData = require('../lib/format_data.js'), - postData = require('../lib/post_data.js'), + adapters = require('../src/lib/adapters'), sinon = require('sinon'), sinonChai = require('sinon-chai'), util = require('util'); @@ -35,11 +34,9 @@ var MockBotFrameworkAdapter = dummyAdapters.MockBotFrameworkAdapter; chai.use(sinonChai); describe("slack post data", function() { - var robot, formatter, postDataHandler; var logger = new Log('info'); - robot = new Robot(false, new MockSlackAdapter(logger)); - formatter = formatData.getFormatter('slack', robot); - postDataHandler = postData.getDataPostHandler('slack', robot, formatter); + var robot = new Robot(false, new MockSlackAdapter(logger)); + var adapter = adapters.getAdapter('slack', robot); env.ST2_SLACK_SUCCESS_COLOR = 'dfdfdf'; env.ST2_SLACK_FAIL_COLOR = 'danger'; @@ -55,7 +52,7 @@ describe("slack post data", function() { '1'), whisper: false }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, @@ -83,7 +80,7 @@ describe("slack post data", function() { '1'), whisper: false }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": undefined }, @@ -109,7 +106,7 @@ describe("slack post data", function() { message: "NORMAL PRETEXT{~}normal boring text", whisper: false }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, @@ -135,7 +132,7 @@ describe("slack post data", function() { message: "NORMAL PRETEXT{~}", whisper: false }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, @@ -170,7 +167,7 @@ describe("slack post data", function() { } } }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, @@ -206,7 +203,7 @@ describe("slack post data", function() { } } }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, { icon_emoji: ":slack:", username: "SlackBot", attachments: [ @@ -246,7 +243,7 @@ describe("slack post data", function() { } } }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, @@ -265,7 +262,7 @@ describe("slack post data", function() { '1'), whisper: false }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, @@ -293,7 +290,7 @@ describe("slack post data", function() { whisper: false }; var chunks = input.message.match(/[\s\S]{1,7900}/g); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledWith( { "id": "#stackstorm", "room": "#stackstorm", "user": "stanley" }, { @@ -338,7 +335,7 @@ describe("slack post data", function() { '1'), whisper: true }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.adapter.client.send).to.have.been.calledOnce; expect(robot.adapter.client.send).to.have.been.calledWith( { "user": "stanley" }, @@ -358,11 +355,10 @@ describe("slack post data", function() { }); describe("msteams post data", function () { - var robot, formatter, logMessage, postDataHandler; + var logMessage; var logger = new Log('debug'); - robot = new Robot(false, new MockBotFrameworkAdapter(logger)); - formatter = formatData.getFormatter('botframework', robot); - postDataHandler = postData.getDataPostHandler('botframework', robot, formatter); + var robot = new Robot(false, new MockBotFrameworkAdapter(logger)); + var adapter = adapters.getAdapter('botframework', robot); it('should just send', function () { robot.adapter.send = sinon.spy(); @@ -373,7 +369,7 @@ describe("msteams post data", function () { message: "Hello world!" }; - postDataHandler.postData(data); + adapter.postData(data); expect(robot.adapter.send).to.have.been.calledOnce; expect(robot.adapter.send).to.have.been.calledWith(data.context, data.message); }); @@ -391,7 +387,7 @@ describe("msteams post data", function () { message: "Hello world with extra.botframework!" }; - postDataHandler.postData(data); + adapter.postData(data); expect(robot.logger.warning).to.have.been.calledOnce; logMessage = util.format('The extra.botframework attribute of aliases is not used yet.'); expect(robot.logger.warning).to.have.been.calledWith(logMessage); @@ -411,7 +407,7 @@ describe("msteams post data", function () { message: pretext + "{~}" + text }; - postDataHandler.postData(data); + adapter.postData(data); expect(robot.adapter.send).to.have.been.calledWith(data.context, pretext); this.clock.tick(500); expect(robot.adapter.send).to.have.been.calledWith(data.context, text); @@ -421,11 +417,9 @@ describe("msteams post data", function () { }); describe("mattermost post data", function() { - var robot, formatter, postDataHandler; var logger = new Log('info'); - robot = new Robot(false, new MockSlackAdapter(logger)); - formatter = formatData.getFormatter('mattermost', robot); - postDataHandler = postData.getDataPostHandler('mattermost', robot, formatter); + var robot = new Robot(false, new MockSlackAdapter(logger)); + var adapter = adapters.getAdapter('mattermost', robot); env.ST2_MATTERMOST_SUCCESS_COLOR = 'dfdfdf'; env.ST2_MATTERMOST_FAIL_COLOR = 'danger'; @@ -440,7 +434,7 @@ describe("mattermost post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledOnce; expect(robot.emit).to.have.been.calledWith( 'slack-attachment', @@ -466,7 +460,7 @@ describe("mattermost post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledOnce; expect(robot.emit).to.have.been.calledWith( 'slack-attachment', @@ -493,7 +487,7 @@ describe("mattermost post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( input.channel, @@ -514,7 +508,7 @@ describe("mattermost post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledOnce; expect(robot.emit).to.have.been.calledWith( 'slack-attachment', @@ -543,7 +537,7 @@ describe("mattermost post data", function() { var user = util.format('@%s: ', input.user), chunks = input.message.match(/[\s\S]{1,3800}/g); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledWith( 'slack-attachment', { @@ -594,7 +588,7 @@ describe("mattermost post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledOnce; expect(robot.emit).to.have.been.calledWith( 'slack-attachment', @@ -639,7 +633,7 @@ describe("mattermost post data", function() { } }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledOnce; expect(robot.emit).to.have.been.calledWith( 'slack-attachment', @@ -679,7 +673,7 @@ describe("mattermost post data", function() { } }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledOnce; expect(robot.emit).to.have.been.calledWith( 'slack-attachment', @@ -704,7 +698,7 @@ describe("mattermost post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.emit).to.have.been.calledOnce; expect(robot.emit).to.have.been.calledWith( 'slack-attachment', @@ -723,11 +717,9 @@ describe("mattermost post data", function() { }); describe("hipchat post data", function() { - var robot, formatter, postDataHandler; var logger = new Log('info'); - robot = new Robot(false, new MockSlackAdapter(logger)); - formatter = formatData.getFormatter('hipchat', robot); - postDataHandler = postData.getDataPostHandler('hipchat', robot, formatter); + var robot = new Robot(false, new MockSlackAdapter(logger)); + var adapter = adapters.getAdapter('hipchat', robot); it('should post to channel', function() { this.clock = sinon.useFakeTimers(); @@ -745,7 +737,7 @@ describe("hipchat post data", function() { jid = env.HUBOT_HIPCHAT_JID.split("_")[0], recipient = util.format('%s_%s@%s', jid, input.channel, 'conf.hipchat.com'); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledWith(recipient, '@stackstorm: '); this.clock.tick(500); expect(robot.messageRoom).to.have.been.calledWith(recipient, '/code Hello world!'); @@ -768,7 +760,7 @@ describe("hipchat post data", function() { jid = env.HUBOT_HIPCHAT_JID.split("_")[0], recipient = util.format('%s_%s@%s', jid, input.channel, 'conf.hipchat.com'); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.send).to.have.been.calledOnce; expect(robot.send).to.have.been.calledWith(input.channel, '/code Hello world!'); }); @@ -789,7 +781,7 @@ describe("hipchat post data", function() { jid = env.HUBOT_HIPCHAT_JID.split("_")[0], recipient = util.format('%s_%s@%s', jid, input.channel, 'conf.hipchat.com'); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledWith(input.channel, '@stackstorm: '); this.clock.tick(500); expect(robot.messageRoom).to.have.been.calledWith(input.channel, '/code Hello world!'); @@ -813,7 +805,7 @@ describe("hipchat post data", function() { jid = env.HUBOT_HIPCHAT_JID.split("_")[0], recipient = util.format('%s_%s@%s', jid, input.channel, 'conf.hipchat.com'); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledWith(recipient, '@stackstorm: PRETEXT HEADER'); this.clock.tick(500); expect(robot.messageRoom).to.have.been.calledWith(recipient, '/code Hello world!'); @@ -836,7 +828,7 @@ describe("hipchat post data", function() { jid = env.HUBOT_HIPCHAT_JID.split("_")[0], recipient = util.format('%s_%s@%s', jid, input.channel, 'conf.hipchat.com'); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith(recipient, '@stackstorm: PRETEXT HEADER'); }); @@ -857,7 +849,7 @@ describe("hipchat post data", function() { jid = env.HUBOT_HIPCHAT_JID.split("_")[0], recipient = util.format('%s_%s@%s', jid, input.channel, 'conf.hipchat.com'); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.send).to.have.been.calledWith(input.channel, 'PRETEXT HEADER'); this.clock.tick(500); expect(robot.send).to.have.been.calledWith(input.channel, '/code Hello world!'); @@ -867,11 +859,9 @@ describe("hipchat post data", function() { }); describe("spark post data", function() { - var robot, formatter, postDataHandler; var logger = new Log('info'); - robot = new Robot(false, new MockSlackAdapter(logger)); - formatter = formatData.getFormatter('spark', robot); - postDataHandler = postData.getDataPostHandler('spark', robot, formatter); + var robot = new Robot(false, new MockSlackAdapter(logger)); + var adapter = adapters.getAdapter('spark', robot); it('should post to room and mention a user', function() { robot.messageRoom = sinon.spy(); @@ -883,7 +873,7 @@ describe("spark post data", function() { }; var user = util.format('%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( { @@ -903,7 +893,7 @@ describe("spark post data", function() { }; var user = util.format('%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( { @@ -928,7 +918,7 @@ describe("spark post data", function() { }; var user = util.format('%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( { @@ -952,7 +942,7 @@ describe("spark post data", function() { }; var user = util.format('%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( { @@ -973,7 +963,7 @@ describe("spark post data", function() { }; var user = util.format('%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( { @@ -986,11 +976,9 @@ describe("spark post data", function() { }); describe("rocketchat post data", function() { - var robot, formatter, postDataHandler; var logger = new Log('info'); - robot = new Robot(false, new MockSlackAdapter(logger)); - formatter = formatData.getFormatter('rocketchat', robot); - postDataHandler = postData.getDataPostHandler('rocketchat', robot, formatter); + var robot = new Robot(false, new MockSlackAdapter(logger)); + var adapter = adapters.getAdapter('rocketchat', robot); env.ST2_ROCKETCHAT_SUCCESS_COLOR = 'success'; env.ST2_ROCKETCHAT_FAIL_COLOR = 'danger'; @@ -1005,7 +993,7 @@ describe("rocketchat post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( input.channel, @@ -1022,7 +1010,7 @@ describe("rocketchat post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( input.channel, @@ -1043,7 +1031,7 @@ describe("rocketchat post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', @@ -1071,7 +1059,7 @@ describe("rocketchat post data", function() { var user = util.format('@%s: ', input.user), chunks = input.message.match(/[\s\S]{1,7900}/g); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', { @@ -1118,7 +1106,7 @@ describe("rocketchat post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', @@ -1153,7 +1141,7 @@ describe("rocketchat post data", function() { } }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', @@ -1190,7 +1178,7 @@ describe("rocketchat post data", function() { } }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( 'stanley', @@ -1219,7 +1207,7 @@ describe("rocketchat post data", function() { }; var user = util.format('@%s: ', input.user); - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', @@ -1237,11 +1225,8 @@ describe("rocketchat post data", function() { }); describe("default post data", function() { - var robot, formatter, postDataHandler; - - robot = new Robot(false); - formatter = formatData.getFormatter('default', robot); - postDataHandler = postData.getDataPostHandler('default', robot, formatter); + var robot = new Robot(false); + var adapter = adapters.getAdapter('default', robot); it('should send proper args to robot.messageRoom', function() { robot.messageRoom = sinon.spy(); @@ -1254,7 +1239,7 @@ describe("default post data", function() { '1'), whisper: false }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', @@ -1270,7 +1255,7 @@ describe("default post data", function() { message: util.format('NORMAL PRETEXT{~}normal boring text'), whisper: false }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', @@ -1289,7 +1274,7 @@ describe("default post data", function() { '1'), whisper: true }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( 'stanley', @@ -1307,7 +1292,7 @@ describe("default post data", function() { '1'), whisper: true }; - postDataHandler.postData(input); + adapter.postData(input); expect(robot.messageRoom).to.have.been.calledOnce; expect(robot.messageRoom).to.have.been.calledWith( '#stackstorm', diff --git a/tests/test-slack-messages.js b/test/test-slack-messages.js similarity index 99% rename from tests/test-slack-messages.js rename to test/test-slack-messages.js index dd5d076..f8c258a 100644 --- a/tests/test-slack-messages.js +++ b/test/test-slack-messages.js @@ -19,8 +19,8 @@ var chai = require('chai'), assert = chai.assert, expect = chai.expect, - messages = require('../lib/slack-messages.js'), - utils = require('../lib/utils.js'); + messages = require('../src/lib/slack-messages.js'), + utils = require('../src/lib/utils.js'); describe('buildMessages', function() { diff --git a/tests/test-st2-invalid-auth.js b/test/test-st2-invalid-auth.js similarity index 78% rename from tests/test-st2-invalid-auth.js rename to test/test-st2-invalid-auth.js index d7df084..fd9a239 100644 --- a/tests/test-st2-invalid-auth.js +++ b/test/test-st2-invalid-auth.js @@ -1,19 +1,16 @@ -/* - 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. -*/ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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*/ @@ -53,7 +50,7 @@ describe("invalid st2 credential configuration", function() { 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")]; + delete require.cache[require.resolve("../src/stackstorm.js")]; }); it("should error out with missing auth URL", function(done) { @@ -65,7 +62,7 @@ describe("invalid st2 credential configuration", function() { }); // Load script under test - var stackstorm = require("../scripts/stackstorm.js"); + var stackstorm = require("../src/stackstorm.js"); try { stackstorm(robot); done(new Error("The previous code should have thrown an exception")) @@ -84,7 +81,7 @@ describe("invalid st2 credential configuration", function() { }); // Load script under test - var stackstorm = require("../scripts/stackstorm.js"); + var stackstorm = require("../src/stackstorm.js"); try { stackstorm(robot); done(new Error("The previous code should have thrown an exception")) @@ -104,7 +101,7 @@ describe("invalid st2 credential configuration", function() { }); // Load script under test - var stackstorm = require("../scripts/stackstorm.js"); + var stackstorm = require("../src/stackstorm.js"); try { stackstorm(robot); done(new Error("The previous code should have thrown an exception")) @@ -122,7 +119,7 @@ describe("invalid st2 credential configuration", function() { }); // Load script under test - var i, stackstorm = require("../scripts/stackstorm.js"); + var i, stackstorm = require("../src/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'); diff --git a/tests/test-st2-unauthorized.js b/test/test-st2-unauthorized.js similarity index 96% rename from tests/test-st2-unauthorized.js rename to test/test-st2-unauthorized.js index 6e259b8..85a4284 100644 --- a/tests/test-st2-unauthorized.js +++ b/test/test-st2-unauthorized.js @@ -61,7 +61,7 @@ describe("auth with invalid st2 API key", function() { //done(); }); - var stackstorm = require("../scripts/stackstorm.js"); + var stackstorm = require("../src/stackstorm.js"); stackstorm(robot).then(function (result) { stop = result; done(); @@ -75,7 +75,7 @@ describe("auth with invalid st2 API key", function() { robot.shutdown(); // 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")]; + delete require.cache[require.resolve("../src/stackstorm.js")]; }); // CAUTION: These tests are brittle - do not move them around, remove diff --git a/tests/test-st2bot-envvars.js b/test/test-st2bot-envvars.js similarity index 71% rename from tests/test-st2bot-envvars.js rename to test/test-st2bot-envvars.js index 45bff36..c2f258f 100644 --- a/tests/test-st2bot-envvars.js +++ b/test/test-st2bot-envvars.js @@ -1,19 +1,16 @@ -/* - 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. -*/ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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*/ @@ -45,7 +42,7 @@ describe("environment variable configuration", function () { debug_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")]; + delete require.cache[require.resolve("../src/stackstorm.js")]; if (robot) { robot.shutdown(); if (robot.server) { @@ -62,7 +59,7 @@ describe("environment variable configuration", function () { }); // Load script under test - var stackstorm = require("../scripts/stackstorm.js"); + var stackstorm = require("../src/stackstorm.js"); stackstorm(robot).then(function (stop) { expect(robot.logger.logs.warning).to.have.length.above(0); expect(robot.logger.logs.warning).to.contain( @@ -86,7 +83,7 @@ describe("environment variable configuration", function () { }); // Load script under test - var stackstorm = require("../scripts/stackstorm.js"); + var stackstorm = require("../src/stackstorm.js"); stackstorm(robot).then(function (stop) { expect(info_spy.args).length.to.be.above(1); expect(info_spy).to.have.been.calledWith('Two-factor auth is enabled'); diff --git a/tests/test-st2bot-setup.js b/test/test-st2bot-setup.js similarity index 98% rename from tests/test-st2bot-setup.js rename to test/test-st2bot-setup.js index 47595be..78af0ab 100644 --- a/tests/test-st2bot-setup.js +++ b/test/test-st2bot-setup.js @@ -54,7 +54,7 @@ describe("stanley the StackStorm bot", function() { robot.adapter.on("connected", function() { // Load script under test - st2bot = require("../scripts/stackstorm"); + st2bot = require("../src/stackstorm"); st2bot(robot).then(function(result) { stop = result; diff --git a/tests/test-st2bot-sigusr2.js b/test/test-st2bot-sigusr2.js similarity index 67% rename from tests/test-st2bot-sigusr2.js rename to test/test-st2bot-sigusr2.js index 386bbbb..d3164cd 100644 --- a/tests/test-st2bot-sigusr2.js +++ b/test/test-st2bot-sigusr2.js @@ -1,19 +1,16 @@ -/* - 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. -*/ +// Copyright 2019 Extreme Networks, Inc. +// +// Licensed 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*/ @@ -69,16 +66,17 @@ describe("SIGUSR2", function () { nock.cleanAll(); // 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")]; + delete require.cache[require.resolve("../src/stackstorm.js")]; }); it("should run loadCommands() after receiving signal", function (done) { // Load script under test - var stackstorm = require("../scripts/stackstorm.js"); + var stackstorm = require("../src/stackstorm.js"); stackstorm(robot).then(function (stop) { + expect(debug_spy).to.have.callCount(1); + expect(debug_spy).to.have.been.calledWith('Using default adapter'); process.emit('SIGUSR2'); expect(debug_spy).to.have.callCount(2); - expect(debug_spy).to.have.been.calledWith('Using default post data handler.'); expect(debug_spy).to.have.been.calledWith('Caught SIGUSR2, reloading commands'); expect(info_spy.args).to.have.length.above(1); expect(info_spy).to.have.been.calledWith('Loading commands....'); @@ -86,6 +84,10 @@ describe("SIGUSR2", function () { stop(); done(); + }).catch(function (err) { + console.log(err); + stop && stop(); + done(err); }); }); }); diff --git a/tests/test-twofactor.js b/test/test-twofactor.js similarity index 91% rename from tests/test-twofactor.js rename to test/test-twofactor.js index 94785a7..b8b9aa8 100644 --- a/tests/test-twofactor.js +++ b/test/test-twofactor.js @@ -25,11 +25,11 @@ var fs = require('fs'), path = require("path"), Robot = require("hubot/src/robot"), TextMessage = require("hubot/src/message").TextMessage, - CommandFactory = require('../lib/command_factory.js'), - formatCommand = require('../lib/format_command.js'), - utils = require('../lib/utils.js'); + CommandFactory = require('../src/lib/command_factory.js'), + formatCommand = require('../src/lib/format_command.js'), + utils = require('../src/lib/utils.js'); -var ALIAS_FIXTURES = fs.readFileSync('tests/fixtures/aliases.json'); +var ALIAS_FIXTURES = fs.readFileSync('test/fixtures/aliases.json'); ALIAS_FIXTURES = JSON.parse(ALIAS_FIXTURES); var disableLogger = true, @@ -59,7 +59,7 @@ describe("two-factor auth module", function() { robot.adapter.on("connected", function() { // Load script under test - st2bot = require("../scripts/stackstorm"); + st2bot = require("../src/stackstorm"); st2bot(robot).then(function(result) { stop = result; diff --git a/tests/test-utils.js b/test/test-utils.js similarity index 99% rename from tests/test-utils.js rename to test/test-utils.js index e858f6d..6665fb5 100644 --- a/tests/test-utils.js +++ b/test/test-utils.js @@ -19,7 +19,7 @@ var chai = require('chai'), assert = chai.assert, expect = chai.expect, - utils = require('../lib/utils.js'), + utils = require('../src/lib/utils.js'), env = process.env; var MOCK_MESSAGE = 'Action st2.sensors.list completed.\nstatus : succeeded \nexecution: 55701c8b0640fd53cdf4f08 \nresult :'; diff --git a/tests/dummy-logger.js b/tests/dummy-logger.js deleted file mode 100644 index 832b5be..0000000 --- a/tests/dummy-logger.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - 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;