├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── index.js ├── lib ├── Persisters │ ├── filepersister.js │ ├── mongopersister.js │ ├── postgrespersister.js │ └── redispersister.js ├── alarms.js ├── auth.js ├── bot.js ├── ec2.js ├── index.js └── sqs.js ├── package.json ├── publish.sh ├── samples └── sample.js └── test ├── alarms-test.js ├── ec2-test.js ├── filepersister-test.js ├── mongopersister-test.js ├── postgrespersister-test.js ├── redispersister-test.js ├── sqs-test.js ├── test-auth.js └── test-bot.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "smarttabs" : true, 4 | "bitwise" : true, 5 | "curly" : true, 6 | "eqeqeq" : true, 7 | "es3" : false, 8 | "forin" : false, 9 | "immed" : true, 10 | "indent" : 4, 11 | "latedef" : true, 12 | "noarg" : true, 13 | "noempty" : true, 14 | "nonew" : false, 15 | "plusplus" : true, 16 | "quotmark" : "single", 17 | "undef" : true, 18 | "strict" : "implied", 19 | "trailing" : true, 20 | "maxparams" : 5, 21 | "maxdepth" : 3, 22 | "maxstatements" : false, 23 | "maxlen" : 90, 24 | "node" : true, 25 | "force" : false, 26 | "ignores" : [ "node_modules/**/*.js" ], 27 | "expr" : true, 28 | "globals" : { 29 | "describe" : false, 30 | "it" : false, 31 | "before" : false, 32 | "beforeEach" : false, 33 | "after" : false, 34 | "afterEach" : false, 35 | "unescape" : false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - chmod 0777 ./node_modules/.bin/mocha 3 | script: 4 | - grunt default 5 | - grunt doc 6 | after_success: 7 | - chmod 0777 ./publish.sh 8 | - ./publish.sh 9 | language: node_js 10 | node_js: 11 | - 6.11.1 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | #Contributing To Opkit 2 | 3 | First of all, thank you for helping to contribute to this repository! Here are just a few things to keep in mind as you are doing so. 4 | 5 | ##Code of Conduct 6 | 7 | This project adheres to the Contributor Covenant code of conduct: http://contributor-covenant.org/version/1/2/0/. By participating, 8 | you are expected to uphold this code. 9 | 10 | ##Reporting Bugs 11 | 12 | For reporting a bug with existing code, open a new issue. If you are unsure how to do this, visit: https://help.github.com/articles/creating-an-issue/. 13 | 14 | Be sure to keep your issue titles and descriptions as concise as possible. 15 | 16 | ##New Features 17 | 18 | Keep in mind that Opkit is a devops bot framework for Slack. If the feature you want to include does not fall into this category, it will likely not be accepted. That being said, if you are planning to work on a substantial feature, be sure to contact us first (preferrably through a github issue) and let us know that you are planning on doing so -- this way you won't waste time working on a feature that may never get approved. 19 | 20 | Once you're done coding up a new feature, simply submit a new pull request. Again, be sure to keep these descriptive and concise. 21 | Travis will automatically trigger a build and let you know whether you've broken anything. We enforce 100 percent code coverage 22 | for this repository -- that means that every line of code that you have written is being tested appropriately. Note that we will not 23 | accept any pull requests which break the build on Travis. 24 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt){ 2 | grunt.initConfig({ 3 | mocha_istanbul: { 4 | coverage: { 5 | src: 'test', // a folder works nicely 6 | } 7 | }, 8 | istanbul_check_coverage: { 9 | default: { 10 | options: { 11 | coverage : true, 12 | reporter: 'spec', 13 | coverageFolder: 'coverage', // will check both coverage folders and merge the coverage results 14 | check: { 15 | lines: 100, 16 | branches: 100, 17 | statements: 100 18 | } 19 | } 20 | } 21 | }, 22 | jsdoc : { 23 | default : { 24 | src: ['lib/*.js'], 25 | options: { 26 | destination: 'out' 27 | } 28 | } 29 | }, 30 | jshint: { 31 | default : { 32 | src: ['lib/*.js'], 33 | options : { 34 | "smarttabs" : true, 35 | "bitwise" : true, 36 | "curly" : true, 37 | "eqeqeq" : true, 38 | "es3" : false, 39 | "forin" : false, 40 | "immed" : true, 41 | "indent" : 4, 42 | "noarg" : true, 43 | "noempty" : true, 44 | "nonew" : false, 45 | "trailing" : true, 46 | "maxparams" : 5, 47 | "maxdepth" : 4, 48 | "maxstatements" : false, 49 | "maxlen" : 120, 50 | "node" : true, 51 | "quotmark" : false, 52 | "force" : false, 53 | "ignores" : [ "node_modules/**/*.js" ], 54 | "expr" : true, 55 | "globals" : { 56 | "describe" : false, 57 | "it" : false, 58 | "before" : false, 59 | "beforeEach" : false, 60 | "after" : false, 61 | "afterEach" : false, 62 | "unescape" : false 63 | } 64 | } 65 | } 66 | }, 67 | }); 68 | grunt.loadNpmTasks('grunt-mocha-istanbul'); 69 | grunt.loadNpmTasks('grunt-jsdoc'); 70 | grunt.loadNpmTasks('grunt-contrib-jshint'); 71 | grunt.registerTask('default', ['jshint', 'mocha_istanbul:coverage', 'istanbul_check_coverage']); 72 | grunt.registerTask('doc', ['jsdoc']); 73 | }; 74 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright Bandwidth.com, Inc. (c) 2016 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/bandwidthcom/opkit.svg?branch=master)](https://travis-ci.org/bandwidthcom/opkit) 2 | [![Known Vulnerabilities](https://snyk.io/test/npm/opkit/badge.svg)](https://snyk.io/test/npm/opkit) 3 | # Opkit 4 | Part of [Bandwidth Open Source](http://bandwidth.com/?utm_medium=social&utm_source=github&utm_campaign=dtolb&utm_content=opkit) 5 | 6 | ## Introduction 7 | 8 | Opkit is a devops bot framework for Slack. It aims to provide an easy-to-use and resource-light way to set up a bot, optimized for rapid deployment onto a cloud hosting provider. We've found that simple bots made using this framework run comfortably on even very small instances (like Heroku's hobby tier). It's optimized for use with AWS, and includes helpful methods that provide information on things like SQS queue sizes and Cloudwatch alarms. 9 | 10 | 11 | It has many features which are useful for configuring the bot to control mission-critical interfaces, including sophisticated access control and automatic persistence to disk, MongoDB, Redis, and Postgres. 12 | 13 | ## Usage 14 | 15 | ### Installation 16 | 17 | Opkit is an npm module. To install: 18 | 19 | `npm install opkit (--save)` 20 | 21 | You may need to run this command as root. 22 | 23 | There is an example bot that includes several useful commands at https://github.com/BandwidthExamples/opkit-example which features one-click Heroku deploy. Consider cloning this to get a better idea of how Opkit is used. 24 | 25 | ### Getting Started 26 | 27 | There is an example script at examples/example.js in the repository. You'll need to set an environment variable for your slack token, which can be obtained through the Custom Integrations panel on Slack. (The example script expects the environment variable to be named `token`. Run it with `node example.js`. 28 | 29 | ### Configuration 30 | 31 | The fundamental object that Opkit operates on is the command. Opkit expects an array of command objects to be passed in to the constructor. Each command object represents a script that ought to be run when a particular pattern is encountered and matched. By default, opkit reads each message coming in each channel it is invited to (or direct messages it recieves), so long as that message is prefixed with the bot's name (say, 'examplebot'). 32 | 33 | Suppose that you wanted to write a command to have Opkit respond each time it is greeted with an 'examplebot hello' message. That command would look like this: 34 | 35 | ```javascript 36 | 37 | var sayHello = { 38 | name : 'hello', 39 | script : 'hello', 40 | syntax : ['hello', ['say', 'hello']], 41 | command : function(message, bot, auth, brain){ 42 | bot.sendMessage("Hello to you, too!", message.channel); 43 | } 44 | } 45 | 46 | ``` 47 | 48 | This command would cause the bot to respond to each message that says 'examplebot hello' or 'examplebot say hello'. The `name` field allows you to access the command at `bot.commands.hello`, if need be. `syntax` provides an array of possible syntaxes that can be used to call the command. Here, the bot will respond to both `hello` and `say hello`. 49 | 50 | The actual logic is contained in the function at 'command'. That function ought to take four arguments: 51 | 52 | `message` has the contents of the message as provided by Slack; three useful fields are `message.text`, which contains a string of message text, `message.channel`, which is the channel ID of the incoming message, and `message.user`, which is the user ID of the user sending the message. `message.args` contains the arguments provided to the command from the message; that is, it is an array of words that came after the command; it is useful if a command has multiple `syntax`es of varying length. 53 | 54 | `bot` has the actual bot that called the script. In cases where there are multiple bots listening in the same channel, each will have different `sendMessage` commands (as well as different access to the Slack RTM API methods, all of which are accessible at bot.rtm.) 55 | 56 | `auth` is used with the optional access control. The bot constructor takes an optional `authFunction` parameter, which allows access control to be implemented. `authFunction` should return a promise that resolves to an array of strings; each string should be a role that is assigned to that particular user. A local access file or database can be queried; then, the command can check the `auth` argument to see if a user can has authorization to run that command. (Alternatively, providing a `roles` field in the command object will cause Opkit to restrict access to that command to only those users who have at least one of those roles.) 57 | 58 | `brain` provides a place to store state variables. These variables are 'scoped' to the script; that is, all commands with the same script share a brain. If you put all of your new commands into the same script, they will all be able to view each others' contributions to the state, while presenting no risk of interfering with commands from other scripts. The brain is populated from the persister (the local filesystem, MongoDB, PostgreSQL, or Redis) before each command is run, and saved to the persister after each command is run. The maximum size of `brain` is 16MB on the Mongo persister or 512MB on the Redis persister; try a local FS or Postgres if you'd like to store more. 59 | 60 | Once you've put your commands into an array, the next choice is that of your persister. Opkit currently offers four complete persisters, while also offering you the ability to write your own. `Opkit.FilePersister` saves to a folder on the filesystem in a human-readable JSON format, `Opkit.MongoPersister` saves to a MongoDB database, `Opkit.RedisPersister` saves to Redis, and `Opkit.PostgresPersister` saves to Postgres. The constructors for each persister take a folder, a Mongo URI, a Redis URL, or a Postgres URL, respectively. After building a persister, simply run the constructor and start the bot: 61 | 62 | ```javascript 63 | 64 | var commands = [sayHello]; 65 | var Opkit = require('opkit'); 66 | var Persiter = new Opkit.FilePersister('~/bot-state'); 67 | var myBot = new Opkit.bot('mybot', commands, Persister); 68 | myBot.start(); 69 | ``` 70 | This bot will listen to all messages that begin with its name, so if you taught it the command above, messaging it `mybot hello` will elicit a response! 71 | 72 | You'll need to ensure that your Slack API token is stored at the environment variable named 'token', though; for example: 73 | 74 | `token=$YOUR_API_TOKEN_HERE node server.js`. 75 | 76 | There is more thorough documentation available at the Javadoc page; documentation is automagically generated and posted to http://bandwidthcom.github.io/opkit/index.html with each update. Look at our example scripts, too! 77 | 78 | ##Running Tests Locally 79 | 80 | Simply run `npm test`. 81 | 82 | ##Contributing 83 | 84 | See our [contributing guidelines](CONTRIBUTING.md). 85 | 86 | ## Contacts 87 | 88 | Illirik Smirnov (ismirnov@bandwidth.com) 89 | 90 | Ramkumar Rao (rrao@bandwidth.com) 91 | 92 | ## License 93 | 94 | *The MIT License (MIT)* 95 | 96 | Copyright (c) 2016 Bandwidth, Inc. 97 | 98 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 99 | 100 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 101 | 102 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 103 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = require('./lib'); -------------------------------------------------------------------------------- /lib/Persisters/filepersister.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | var fsp = require('fs-promise'); 3 | 4 | /** 5 | * File persister factory method. Returns a persister object. 6 | * @param {Schema} filepath - Directory to store the data 7 | * @param {Object} persisterPluginObject - For users who have written their own persister object. 8 | */ 9 | 10 | function filePersister(filepath) { 11 | 12 | var self = this; 13 | this.initialized = false; 14 | this.filepath = filepath; 15 | 16 | /** 17 | * Check to see if the specified file path is available. 18 | * @returns A promise appropriately resolving or rejecting. 19 | */ 20 | this.start = function() { 21 | if (!this.initialized) { 22 | return fsp.emptyDir(this.filepath) 23 | .then(function() { 24 | self.initialized = true; 25 | return Promise.resolve('User has permissions to write to that file.'); 26 | }) 27 | .catch(function(err) { 28 | return Promise.reject('User does not have permissions to write to that folder.'); 29 | }); 30 | } 31 | return Promise.reject('Error: Persister already initialized.'); 32 | }; 33 | 34 | /** 35 | * Save the passed state to the file. 36 | * @param {Object} passedState - State to be saved in the file. 37 | * @returns A promise resolving to an appropriate success message or an error message. 38 | */ 39 | this.save = function(brain, package) { 40 | var filepath = this.filepath; 41 | if (this.initialized) { 42 | return fsp.remove(filepath + '/' + package + '.txt') 43 | .then(function(){ 44 | return fsp.writeFile(filepath + '/' + package + '.txt', JSON.stringify(brain)); 45 | }) 46 | .then(function(){ 47 | return Promise.resolve('Saved.'); 48 | }) 49 | } 50 | return Promise.reject('Error: Persister not initialized.'); 51 | }; 52 | 53 | /** 54 | * Retrieve data from the file. 55 | * @returns The most recent entry to the file, as a JavaScript object. 56 | */ 57 | this.recover = function(package) { 58 | if (this.initialized) { 59 | var filepath = this.filepath 60 | return fsp.ensureFile(filepath +'/' + package + '.txt') 61 | .then(function(){ 62 | return fsp.readFile(filepath + '/' + package + '.txt', 'utf8') 63 | }) 64 | .then(function(data) { 65 | if (data === ''){ 66 | return Promise.resolve({}); 67 | } else { 68 | return Promise.resolve(JSON.parse(data)); 69 | } 70 | }); 71 | } 72 | return Promise.reject('Error: Persister not initialized.'); 73 | }; 74 | 75 | } 76 | 77 | module.exports = filePersister; 78 | -------------------------------------------------------------------------------- /lib/Persisters/mongopersister.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | var MongoDB = require('mongodb-bluebird'); 3 | 4 | function mongoPersister(MONGODB_URI) { 5 | 6 | this.initialized = false; 7 | var self = this; 8 | 9 | /** 10 | * Initialize a connection to the MongoDB. 11 | * @returns A promise if the connection is successfully established. 12 | */ 13 | this.start = function() { 14 | if (!this.initialized) { 15 | return MongoDB.connect(MONGODB_URI) 16 | .then(function(returnedDB) { 17 | self.initialized = true; 18 | self.db = returnedDB; 19 | return Promise.resolve('Connection established.'); 20 | }); 21 | } 22 | return Promise.reject('Error: Persister already initialized.'); 23 | }; 24 | 25 | /** 26 | * Save the passed state to the DB. 27 | * @param {Object} brain - State to be saved in the DB. 28 | * @param {String} script - Name of collection to save to in DB. 29 | * @returns A promise resolving to an appropriate success message or an error message. 30 | */ 31 | this.save = function(brain, script) { 32 | if (this.initialized) { 33 | var collection = this.db.collection(script); 34 | return collection.remove({}) 35 | .then(function() { 36 | return collection.insert(brain); 37 | }); 38 | } 39 | return Promise.reject('Error: Persister not initialized.'); 40 | }; 41 | 42 | /** 43 | * Retrieve data from the DB. 44 | * @param {String} script - Name of collection to retrieve from in DB. 45 | * @returns The most recent entry to the DB, as a JavaScript object. 46 | */ 47 | this.recover = function(script) { 48 | if (this.initialized) { 49 | var collection = this.db.collection(script); 50 | return collection.find({}) 51 | .then(function(data) { 52 | data = data[0]; 53 | if (data === undefined) { 54 | data = {}; 55 | } 56 | return Promise.resolve(data); 57 | }); 58 | } else { 59 | return Promise.reject('Error: Persister not initialized.'); 60 | } 61 | }; 62 | } 63 | 64 | module.exports = mongoPersister; -------------------------------------------------------------------------------- /lib/Persisters/postgrespersister.js: -------------------------------------------------------------------------------- 1 | var pgp = require('pg-promise')(); 2 | var parse = require('url-parse'); 3 | var _ = require('lodash'); 4 | 5 | function postgresPersister(POSTGRES_URL) { 6 | this.initialized = false; 7 | var self = this; 8 | 9 | /** 10 | * Initialize a connection to Postgres. 11 | * @returns A promise if the connection is successfully established. 12 | */ 13 | this.start = function() { 14 | if (!this.initialized) { 15 | var url = parse(POSTGRES_URL, true); 16 | var cn = { 17 | user : url.username, 18 | password : url.password, 19 | host : url.hostname, 20 | port : url.port, 21 | database : _.trim(url.pathname, '/'), 22 | ssl : true 23 | }; 24 | this.db = pgp(cn); 25 | this.initialized = true; 26 | return Promise.resolve('Connected.'); 27 | } 28 | return Promise.reject('Error: Persister already initialized.'); 29 | }; 30 | 31 | /** 32 | * Save the passed state to Postgres. 33 | * @param {Object} brain - State to be saved in Postgres. 34 | * @param {String} script - Name of collection to save in Postgres. 35 | * @returns A promise resolving to an appropriate success message or an error message. 36 | */ 37 | this.save = function(brain, script) { 38 | var saveToDB = function() { 39 | return self.db.result("delete from " + script) 40 | .then(function() { 41 | return self.db.none("insert into " + script + "(info) values($1)", [JSON.stringify(brain)]); 42 | }); 43 | }; 44 | 45 | if (this.initialized) { 46 | return saveToDB() 47 | .catch(function(err) { 48 | if (err.code === '42P01') { //Table does not exist 49 | return self.db.none("create table " + script + "(info text)") 50 | .then(function() { 51 | return saveToDB(); 52 | }); 53 | } else { 54 | return Promise.reject(err); 55 | } 56 | }); 57 | } 58 | return Promise.reject('Error: Persister not initialized.'); 59 | }; 60 | 61 | /** 62 | * Retrieve data from Postgres. 63 | * @param {String} script - Name of collection to retrieve from in Postgres. 64 | * @returns The most recent entry to Postgres, as a JavaScript object. 65 | */ 66 | this.recover = function(script) { 67 | var recoverFromDB = function() { 68 | return self.db.any("select * from " + script) 69 | .then(function(data) { 70 | if (data[0]) { 71 | return Promise.resolve(JSON.parse(data[0].info)); 72 | } else { 73 | return Promise.resolve({}); 74 | } 75 | }); 76 | }; 77 | 78 | if (this.initialized) { 79 | return recoverFromDB() 80 | .catch(function(err) { 81 | if (err.code === '42P01') { //Table does not exist 82 | return self.db.none("create table " + script + "(info text)") 83 | .then(function() { 84 | return recoverFromDB(); 85 | }); 86 | } else { 87 | return Promise.reject(err); 88 | } 89 | }); 90 | } 91 | return Promise.reject('Error: Persister not initialized.'); 92 | }; 93 | } 94 | 95 | module.exports = postgresPersister; -------------------------------------------------------------------------------- /lib/Persisters/redispersister.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | var redis = Promise.promisifyAll(require('redis')); 3 | 4 | function redisPersister(REDIS_URL) { 5 | 6 | this.initialized = false; 7 | var self = this; 8 | 9 | /** 10 | * Initialize a connection to Redis. 11 | * @returns A promise if the connection is successfully established. 12 | */ 13 | this.start = function() { 14 | if (!this.initialized) { 15 | this.client = redis.createClient(REDIS_URL); 16 | this.client.onAsync("error") 17 | .catch(function(err) { 18 | self.client.end(false); 19 | }); 20 | this.initialized = true; 21 | return Promise.resolve('Connected.'); 22 | } 23 | return Promise.reject('Error: Persister already initialized.'); 24 | }; 25 | 26 | /** 27 | * Save the passed state to Redis. 28 | * @param {Object} brain - State to be saved in Redis. 29 | * @param {String} script - Name of key to save to in Redis. 30 | * @returns A promise resolving to an appropriate success message or an error message. 31 | */ 32 | this.save = function(brain, script) { 33 | if (this.initialized) { 34 | return this.client.delAsync(script) 35 | .then(function() { 36 | return self.client.hsetAsync(script, JSON.stringify(brain), redis.print); 37 | }); 38 | } 39 | return Promise.reject('Error: Persister not initialized.'); 40 | }; 41 | 42 | /** 43 | * Retrieve data from Redis. 44 | * @param {String} script - Name of key to retrieve data from in Redis. 45 | * @returns The most recent entry to Redis, as a JavaScript object. 46 | */ 47 | this.recover = function(script) { 48 | if (this.initialized) { 49 | return this.client.hkeysAsync(script) 50 | .then(function(data) { 51 | if (data[0]) { 52 | data = JSON.parse(data[0]); 53 | return Promise.resolve(data); 54 | } 55 | return Promise.resolve({}); 56 | }); 57 | } 58 | return Promise.reject('Error: Persister not initialized.'); 59 | }; 60 | } 61 | 62 | module.exports = redisPersister; -------------------------------------------------------------------------------- /lib/alarms.js: -------------------------------------------------------------------------------- 1 | /**@namespace Alarms */ 2 | 3 | var AWS = require('aws-promised'); 4 | var Promise = require('bluebird'); 5 | var _ = require('lodash'); 6 | function Alarms(){ 7 | } 8 | 9 | /** 10 | * Retrive all CloudWatch Alarms. 11 | * Returns a Promise containing a JS object with all configured alarms. 12 | * @param {Auth} auth - An auth object made using the keys and region you'd like to use. 13 | * @param {Object} params - Additional AWS Parameters for describe-alarms. 14 | * @param {Function} filterPredicate - A predicate that receives a MetricAlarm map and 15 | * returns true to include the alarm and false to exclude it. 16 | */ 17 | Alarms.prototype.getAllAlarms = function(auth, params, filterPredicate, prevResults){ 18 | var self = this; 19 | params = params || {}; 20 | 21 | var cloudwatch = new AWS.cloudWatch(auth.props); 22 | return cloudwatch.describeAlarmsPromised(params) 23 | .then(function(data) { 24 | if (prevResults) { 25 | data.MetricAlarms = data.MetricAlarms.concat(prevResults.MetricAlarms); 26 | } 27 | if (data.NextToken) { 28 | params.NextToken = data.NextToken; 29 | return self.getAllAlarms(auth, params, filterPredicate, data); 30 | } else { 31 | if (filterPredicate) { 32 | data.MetricAlarms = _.filter(data.MetricAlarms, filterPredicate); 33 | } 34 | return Promise.resolve(data); 35 | } 36 | }); 37 | }; 38 | /** 39 | * Queries Cloudwatch alarms that are currently in a particular state. 40 | * Returns a Promise containing a JS object with all of the alarms currently in that state. 41 | * @param {string} state - A string containing the state you'd like to query for (e.g. 'ALARM') 42 | * @param {Auth} auth - An auth object made using the keys and region you'd like to use. 43 | * @param {Function} filterPredicate - A predicate that receives a MetricAlarm object and 44 | * returns true to include the alarm and false to exclude it. 45 | */ 46 | 47 | Alarms.prototype.queryAlarmsByState = function(state, auth, filterPredicate){ 48 | return this.getAllAlarms(auth, {StateValue : state}, filterPredicate); 49 | }; 50 | /** 51 | * Queries Cloudwatch alarms that are currently in a particular state. 52 | * Returns a Promise that resolves to a string containing information about each alarm in the queried state. 53 | * @param {string} state - A string containing the state you'd like to query for (e.g. 'ALARM') 54 | * @param {Auth} auth - An auth object made using the keys and region you'd like to use. 55 | * @param {Function} filterPredicate - A predicate that receives a MetricAlarm object and 56 | * returns true to include the alarm and false to exclude it. 57 | */ 58 | 59 | Alarms.prototype.queryAlarmsByStateReadably = function(state, auth, filterPredicate){ 60 | return this.queryAlarmsByState(state, auth, filterPredicate) 61 | .then(function(data){ 62 | var returnMe = ''; 63 | var alarms = data.MetricAlarms; 64 | for (var k=0; k record){ 211 | record = distance; 212 | currentBestSyntax = syntax; 213 | } 214 | }); 215 | var reply; 216 | if (record > HELPFUL_SUGGESTION_COEFFICIENT){ 217 | reply = "Unfortunately, that command was not found. Did you mean " + bot.name; 218 | for (n=0; n 1) { 223 | return Promise.reject('More than one instance specified.'); 224 | } else { 225 | return Promise.reject('No instances available.'); 226 | } 227 | }) 228 | .then(function(output) { 229 | var buf = new Buffer(output.Output, 'base64'); 230 | return Promise.resolve('Timestamp: ' + output.Timestamp + '\n' + buf.toString()); 231 | }); 232 | }; 233 | 234 | /** 235 | * Reboots EC2 instances with the specified tag. 236 | * @param {string} tagName - Name tag (Examples: Name, Environment, etc). 237 | * @param {string} value - Actual value of tag (Example: opkit-test-johndoe) 238 | * @param {Auth} auth - The Auth object to be used for the AWS query. 239 | */ 240 | EC2.prototype.reboot = function(tagName, value, auth) { 241 | 242 | var ec2 = new AWS.ec2(auth.props); 243 | var instancesArray; 244 | 245 | return this.getInstanceID(tagName, value, auth) 246 | .then(function(data) { 247 | instancesArray = data; 248 | return ec2.stopInstancesPromised({InstanceIds: data}); 249 | }) 250 | .then(function(data) { 251 | return ec2.waitForPromised('instanceStopped', {InstanceIds: instancesArray}); 252 | }) 253 | .then(function(data) { 254 | return ec2.startInstancesPromised({InstanceIds: instancesArray}); 255 | }) 256 | .then(function(data) { 257 | return ec2.waitForPromised('instanceRunning', {InstanceIds: instancesArray}); 258 | }); 259 | }; 260 | 261 | /** 262 | * Helper function to format data returned by AWS EC2 queries. 263 | */ 264 | function dataFormatter(data) { 265 | var instanceInfoArray = []; 266 | var instanceInfoObject = {}; 267 | 268 | var reservations = data.Reservations; 269 | for (var reservation in reservations) { 270 | var instances = reservations[reservation].Instances; 271 | for (var instance in instances) { 272 | var tags = instances[instance].Tags; 273 | for (var tag in tags) { 274 | if (tags[tag].Key === 'Name') { 275 | instanceInfoObject = _.assign(instanceInfoObject, 276 | {Name: tags[tag].Value}); 277 | } 278 | } 279 | instanceInfoObject = _.assign(instanceInfoObject, 280 | {State: instances[instance].State.Name}, 281 | {id: instances[instance].InstanceId}); 282 | instanceInfoArray.push(instanceInfoObject); 283 | instanceInfoObject = {}; 284 | } 285 | } 286 | return Promise.resolve(instanceInfoArray); 287 | } 288 | 289 | module.exports = EC2; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var Opkit = {}; 2 | Opkit.Alarms = require('./alarms'); 3 | Opkit.Auth = require('./auth'); 4 | Opkit.SQS = require('./sqs'); 5 | Opkit.Bot = require('./bot'); 6 | Opkit.EC2 = require('./ec2'); 7 | Opkit.FilePersister = require('./Persisters/filepersister'); 8 | Opkit.MongoPersister = require('./Persisters/mongopersister'); 9 | Opkit.RedisPersister = require('./Persisters/redispersister'); 10 | Opkit.PostgresPersister = require('./Persisters/postgrespersister'); 11 | 12 | module.exports = Opkit; 13 | -------------------------------------------------------------------------------- /lib/sqs.js: -------------------------------------------------------------------------------- 1 | /** @namespace SQS 2 | */ 3 | var AWS = require('aws-promised'); 4 | var Promise = require('bluebird'); 5 | 6 | function SQS(){ 7 | 8 | } 9 | 10 | /** 11 | * Queries SQS queues. Returns a promise containing number of messages in queue. 12 | * @param {string} name - Name of SQS queue on AWS. 13 | * @param {Auth} auth - The Auth object to be used for the AWS query. 14 | */ 15 | 16 | SQS.prototype.getSQSQueueSizeInt = function(name, auth) { 17 | var sqs = new AWS.sqs(auth.props); 18 | return sqs.getQueueUrlPromised({QueueName: name}) 19 | .then(function(data) { 20 | return sqs.getQueueAttributesPromised({ 21 | QueueUrl: data.QueueUrl, 22 | AttributeNames: [ 23 | 'ApproximateNumberOfMessages', 24 | ] 25 | }); 26 | }) 27 | .then(function(data) { 28 | return Promise.resolve(data.Attributes.ApproximateNumberOfMessages); 29 | }); 30 | }; 31 | 32 | /** 33 | * Queries SQS queues. Returns a promise containing number of messages which have been taken off of the queue, 34 | but have not finished processing. 35 | * @param {string} name - Name of SQS queue on AWS. 36 | * @param {Auth} auth - The Auth object to be used for the AWS query. 37 | */ 38 | SQS.prototype.getSQSQueueSizeNotVisibleInt = function(name, auth) { 39 | var sqs = new AWS.sqs(auth.props); 40 | return sqs.getQueueUrlPromised({QueueName: name}) 41 | .then(function(data) { 42 | return sqs.getQueueAttributesPromised({ 43 | QueueUrl: data.QueueUrl, 44 | AttributeNames: [ 45 | 'ApproximateNumberOfMessagesNotVisible', 46 | ] 47 | }); 48 | }) 49 | .then(function (data) { 50 | return Promise.resolve(data.Attributes.ApproximateNumberOfMessagesNotVisible); 51 | }); 52 | }; 53 | 54 | /** 55 | * Returns a promise containing a list of SQS URLs with an associated account given a prefix. 56 | * @param {string} queuePrefix - The prefix of the SQS Queues desired. "" for all queues. 57 | * @param {Auth} auth = The Auth object to be used for the AWS query. 58 | */ 59 | 60 | SQS.prototype.listQueues = function(queuePrefix, auth) { 61 | var sqs = new AWS.sqs(auth.props); 62 | return sqs.listQueuesPromised({QueueNamePrefix: queuePrefix}) 63 | .then(function(data) { 64 | return Promise.resolve(data.QueueUrls); 65 | }); 66 | }; 67 | 68 | module.exports = SQS; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opkit", 3 | "version": "1.7.1", 4 | "description": "An Erector Set For Devops Bots", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "grunt" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/bandwidthcom/opkit.git" 12 | }, 13 | "keywords": [ 14 | "botkit" 15 | ], 16 | "author": "Bandwidth", 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/bandwidthcom/opkit/blob/master/LICENSE.md" 21 | } 22 | ], 23 | "bugs": { 24 | "url": "https://github.com/bandwidthcom/opkit/issues" 25 | }, 26 | "homepage": "https://github.com/bandwidthcom/opkit#readme", 27 | "devDependencies": { 28 | "chai": "^3.5.0", 29 | "fs-extra": "^0.30.0", 30 | "fs-extra-promise": "^0.4.0", 31 | "grunt": "^1.0.1", 32 | "grunt-contrib-jshint": "^1.0.0", 33 | "grunt-jsdoc": "^2.0.0", 34 | "grunt-mocha-istanbul": "^4.0.2", 35 | "istanbul": "^0.4.3", 36 | "mocha": "^2.4.5", 37 | "mock-require": "^1.3.0", 38 | "sinon": "^1.17.4", 39 | "sinon-as-promised": "^4.0.0" 40 | }, 41 | "dependencies": { 42 | "aws-promised": "^2.13.0", 43 | "aws-sdk": "^2.3.10", 44 | "bluebird": "^3.4.0", 45 | "cron": "^1.1.0", 46 | "fs-promise": "^0.5.0", 47 | "lodash": "^3.10.1", 48 | "mongodb-bluebird": "^0.1.1", 49 | "natural": "^0.5.0", 50 | "pg-promise": "^4.8.1", 51 | "redis": "^2.6.2", 52 | "slack-client": "^2.0.6", 53 | "url-parse": "^1.1.1", 54 | "winston": "^2.2.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then 5 | cp -Rf out/ $HOME/ 6 | cd $HOME 7 | git config --global user.email "travis@travis-ci.org" 8 | git config --global user.name "travis-ci" 9 | git clone --quiet --branch=gh-pages git@github.com:bandwidthcom/opkit.git 10 | 11 | cd opkit 12 | git rm -rf . 13 | cp -Rf $HOME/out/* . 14 | git add -f . 15 | git commit -m "Generated docs from Travis." 16 | git push -fq origin gh-pages > /dev/null 17 | fi 18 | -------------------------------------------------------------------------------- /samples/sample.js: -------------------------------------------------------------------------------- 1 | var Opkit = new require('../index'); 2 | var Bot = Opkit.Bot; 3 | var Alamrs = Opkit.Alarms; 4 | var BOT_NAME = 'samplebot'; 5 | var persister = new Opkit.Persister('./state'); 6 | var commands = []; 7 | 8 | var health = { 9 | name : 'health', 10 | script : 'sample', 11 | syntax: ['status', 'health'], 12 | command : function(message, bot, auth, brain){ 13 | brain.numberOfMessages = brain.numberOfMessages + 1 || 1; 14 | if (process.env.amazonId && process.env.amazonSecret){ 15 | return Alarms.healthReportByState( 16 | new Opkit.Auth(process.env.amazonId, 17 | process.env.amazonSecret, 'us-east-1')) 18 | .then(function(data){ 19 | bot.sendMessage(data.replace(/(.*)([:])/mg, 20 | function(match) { return '*' + match + '*'; }), message.channel); 21 | return Promise.resolve('User ' + bot.dataStore.getUserById(message.user).name + 22 | ' queried for a health report.'); 23 | }); 24 | } else { 25 | bot.sendMessage("You haven't got AWS keys defined as environment variables!\n"+ 26 | "Ensure that your AWS keys are in the following places:\n"+ 27 | "amazonId : Access Key ID\n"+ 28 | "amazonSecret : Secret Access Key\n"+ 29 | "amazonRegion : Region (e.g. 'us-east-1').", message.channel); 30 | return Promise.resolve('User ' + bot.dataStore.getUserById(message.user).name + 31 | ' queried for a health report.'); 32 | } 33 | } 34 | } 35 | 36 | var joke = { 37 | name : 'joke', 38 | script : 'sample', 39 | syntax : ['joke'], 40 | command : function(message, bot, auth, brain){ 41 | bot.sendMessage("A burrito is a sleeping bag for ground beef.", message.channel); 42 | } 43 | } 44 | 45 | var count = { 46 | name : 'count', 47 | script : 'count', 48 | syntax : ['count'], 49 | command : function(message, bot, auth, brain){ 50 | brain.numberOfMessages = brain.numberOfMessages + 1 || 1; 51 | bot.sendMessage(brain.numberOfMessages + '!', 52 | message.channel); 53 | } 54 | }; 55 | 56 | commands.push(health); 57 | commands.push(joke); 58 | commands.push(count); 59 | bot = new Bot(BOT_NAME, commands, persister); 60 | bot.start(); -------------------------------------------------------------------------------- /test/alarms-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var opkit = require('../index'); 3 | var alarms = new opkit.Alarms(); 4 | var sinon = require('sinon'); 5 | require('sinon-as-promised'); 6 | var Promise = require('bluebird'); 7 | var auth1 = new opkit.Auth(); 8 | var AWS = require('aws-promised'); 9 | 10 | var result; 11 | 12 | var defaultResult = { 13 | MetricAlarms: [{ 14 | StateValue : 'OK', 15 | MetricName : 'MetricName', 16 | AlarmDescription: 'AlarmDescription', 17 | Namespace : 'Namespace', 18 | AlarmName : 'AlarmName' 19 | }] 20 | }; 21 | 22 | describe('Alarms', function(){ 23 | 24 | before(function() { 25 | auth1.updateRegion('narnia-1'); 26 | auth1.updateAuthKeys('shiny gold one', 'old rusty one'); 27 | sinon.stub(AWS, 'cloudWatch', function(auth) { 28 | this.describeAlarmsPromised = function(params) { 29 | return Promise.resolve(defaultResult); 30 | } 31 | }); 32 | }); 33 | 34 | after(function() { 35 | AWS.cloudWatch.restore(); 36 | }); 37 | 38 | afterEach(function() { 39 | result = undefined; 40 | }); 41 | 42 | describe('#queryAlarmsByState()', function(){ 43 | before(function() { 44 | return alarms.queryAlarmsByState('OK', auth1) 45 | .then(function (data){ 46 | result = data.MetricAlarms[0].StateValue; 47 | }); 48 | }); 49 | it('Should result in an object with StateValue same as state given', function () { 50 | assert.equal(result, 'OK'); 51 | }); 52 | }); 53 | describe('#queryAlarmsByStateReadably', function(){ 54 | before(function () { 55 | return alarms.queryAlarmsByStateReadably('OK', auth1) 56 | .then(function (data){ 57 | result = data; 58 | }); 59 | }); 60 | it('Should result in the correct human-readable string', function () { 61 | assert.isOk(result); 62 | }); 63 | }); 64 | describe('#countAlarmsByState', function(){ 65 | before(function () { 66 | return alarms.countAlarmsByState('OK', auth1) 67 | .then(function (data){ 68 | result = data; 69 | }); 70 | }); 71 | it('Should result in the number of alarms in the particular search', function () { 72 | assert.equal(result, 1); 73 | }); 74 | }); 75 | describe('#queryAlarmsByWatchlist()', function(){ 76 | before(function() { 77 | return alarms.queryAlarmsByWatchlist(['AlarmName'], auth1) 78 | .then(function (data){ 79 | result = data.MetricAlarms[0].AlarmName; 80 | }); 81 | }); 82 | it('Should result in an object with AlarmName on the watchlist', function () { 83 | assert.equal(result, 'AlarmName'); 84 | }); 85 | }); 86 | describe('#queryAlarmsByWatchlistReadably()', function(){ 87 | before(function() { 88 | return alarms.queryAlarmsByWatchlistReadably(['AlarmName'], auth1) 89 | .then(function (data){ 90 | result = data; 91 | }); 92 | }); 93 | it('Should result in a neat string with the correct AlarmName', function () { 94 | assert.equal(result, '*OK*: AlarmName\n'); 95 | }); 96 | }); 97 | describe('#queryAlarmsByPrefix()', function(){ 98 | before(function() { 99 | return alarms.queryAlarmsByPrefix('Alarm', auth1) 100 | .then(function (data){ 101 | result = data.MetricAlarms[0].AlarmName; 102 | }); 103 | }); 104 | it('Should result in an object with AlarmName that starts with prefix', function () { 105 | assert.equal(result, 'AlarmName'); 106 | }); 107 | }); 108 | describe('#queryAlarmsByPrefixReadably()', function(){ 109 | before(function() { 110 | return alarms.queryAlarmsByPrefixReadably('Alarm', auth1) 111 | .then(function (data){ 112 | result = data; 113 | }); 114 | }); 115 | it('Should result in a neat string with the correct AlarmName', function () { 116 | assert.equal(result, '*OK*: AlarmName\n'); 117 | }); 118 | }); 119 | describe('#getAllAlarms with a filter', function(){ 120 | before(function() { 121 | filterByName = function(alarm) { 122 | return alarm.AlarmName !== 'AlarmName'; 123 | } 124 | 125 | return alarms.getAllAlarms(auth1, {}, filterByName) 126 | .then(function (data){ 127 | result = data; 128 | }) 129 | }); 130 | it('Should retrieve no results', function () { 131 | assert.deepEqual(result, { MetricAlarms: [] }); 132 | }); 133 | }); 134 | describe('#getAllAlarms filter a non-existent alarm', function(){ 135 | before(function() { 136 | filterByName = function(alarm) { 137 | return alarm.AlarmName !== 'SomeOtherAlarm'; 138 | } 139 | 140 | return alarms.getAllAlarms(auth1, {}, filterByName) 141 | .then(function (data){ 142 | result = data; 143 | }); 144 | }); 145 | it('Should retrieve no results', function () { 146 | assert.deepEqual(result, defaultResult); 147 | }); 148 | }); 149 | describe('#healthReportByState', function(){ 150 | 151 | before(function () { 152 | AWS.cloudWatch.restore(); 153 | sinon.stub(AWS, 'cloudWatch', function(auth) { 154 | this.describeAlarmsPromised = function(params) { 155 | return Promise.resolve({ 156 | MetricAlarms: [{ 157 | StateValue : 'OK', 158 | MetricName : 'MetricName', 159 | AlarmDescription: 'AlarmDescription', 160 | Namespace : 'Namespace', 161 | AlarmName : 'AlarmNamey' 162 | } 163 | , 164 | { 165 | StateValue : 'INSUFFICIENT_DATA', 166 | MetricName : 'MetricName', 167 | AlarmDescription: 'AlarmDescription', 168 | Namespace : 'Namespace', 169 | AlarmName : 'AlarmName' 170 | } 171 | , 172 | { 173 | StateValue : 'ALARM', 174 | MetricName : 'MetricName', 175 | AlarmDescription: 'AlarmDescription', 176 | Namespace : 'Namespace', 177 | AlarmName : 'AlarmName' 178 | }] 179 | }); 180 | } 181 | }); 182 | return alarms.healthReportByState(auth1) 183 | .then(function (data){ 184 | result = data; 185 | }); 186 | }); 187 | it('Should result in a correct health report', function () { 188 | assert.equal(result, "*Number Of Alarms, By State:* \n"+ 189 | "OK: *"+'1'+"*\n"+ 190 | "Alarm: *"+'1'+ "*\n"+ 191 | "Insufficient Data: *"+'1'+"*"); 192 | }); 193 | }); 194 | describe('Alarms Paginated Response', function() { 195 | 196 | before(function() { 197 | AWS.cloudWatch.restore(); 198 | sinon.stub(AWS, 'cloudWatch', function(auth) { 199 | this.describeAlarmsPromised = function(params) { 200 | if (!params.NextToken) { 201 | return Promise.resolve({ 202 | MetricAlarms: [{ 203 | StateValue : 'OK', 204 | MetricName : 'MetricName', 205 | AlarmDescription: 'AlarmDescription', 206 | Namespace : 'Namespace', 207 | AlarmName : 'AlarmName' 208 | }], 209 | NextToken : 'next' 210 | }); 211 | } else { 212 | return Promise.resolve({ 213 | MetricAlarms: [{ 214 | StateValue : 'OK', 215 | MetricName : 'MetricName2', 216 | AlarmDescription: 'AlarmDescription2', 217 | Namespace : 'Namespace2', 218 | AlarmName : 'AlarmName2' 219 | }], 220 | }); 221 | } 222 | } 223 | }); 224 | return alarms.getAllAlarms(auth1) 225 | .then(function(data) { 226 | result = data; 227 | }); 228 | }); 229 | 230 | it('Should properly retrieve the alarms', function() { 231 | assert.isOk(result); 232 | }); 233 | }); 234 | describe('Alarms getMetricStatistics', function() { 235 | before(function() { 236 | AWS.cloudWatch.restore(); 237 | sinon.stub(AWS, 'cloudWatch', function(auth) { 238 | this.getMetricStatisticsPromised = function(params) { 239 | return Promise.resolve([{Timestamp : 'time', Sum : 2, Unit : 'none'}]) 240 | } 241 | }); 242 | return alarms.getMetricStatistics(auth1, {Namespace : 'Namespace', 243 | MetricName : 'Metric', 244 | Period : 60, 245 | Statistics : ['Statistics']}, 10, 1) 246 | .then(function(data) { 247 | result = data; 248 | }); 249 | }); 250 | 251 | it('Should properly retrieve statistics', function() { 252 | assert.isOk(result); 253 | }); 254 | }); 255 | describe('Alarms getMetricStatisticsSingle', function() { 256 | before(function() { 257 | AWS.cloudWatch.restore(); 258 | sinon.stub(AWS, 'cloudWatch', function(auth) { 259 | this.getMetricStatisticsPromised = function(params) { 260 | return Promise.resolve([{Timestamp : 'time', Sum : 2, Unit : 'none'}]) 261 | } 262 | }); 263 | return alarms.getMetricStatisticsSingle(auth1, {Namespace : 'Namespace', 264 | MetricName : 'Metric', 265 | Period : 60, 266 | Statistics : ['Statistics']}) 267 | .then(function(data) { 268 | result = data; 269 | }); 270 | }); 271 | 272 | it('Should properly retrieve statistics', function() { 273 | assert.isOk(result); 274 | }); 275 | }); 276 | describe('Alarms getMetricStatisticsForDay', function() { 277 | before(function() { 278 | AWS.cloudWatch.restore(); 279 | sinon.stub(AWS, 'cloudWatch', function(auth) { 280 | this.getMetricStatisticsPromised = function(params) { 281 | return Promise.resolve([{Timestamp : 'time', Sum : 2, Unit : 'none'}]) 282 | } 283 | }); 284 | return alarms.getMetricStatisticsForDay(auth1, {Namespace : 'Namespace', 285 | MetricName : 'Metric', 286 | Period : 60, 287 | Statistics : ['Statistics']}, 7) 288 | .then(function(data) { 289 | result = data; 290 | }); 291 | }); 292 | 293 | it('Should properly retrieve statistics', function() { 294 | assert.isOk(result); 295 | }); 296 | }); 297 | }); 298 | -------------------------------------------------------------------------------- /test/ec2-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var opkit = require('../index'); 3 | var ecInstance = new opkit.EC2(); 4 | var sinon = require('sinon'); 5 | require('sinon-as-promised'); 6 | var AWS = require('aws-promised'); 7 | 8 | describe('EC2', function() { 9 | 10 | var auth1; 11 | var result; 12 | var spy; 13 | 14 | before(function() { 15 | auth1 = new opkit.Auth(); 16 | auth1.updateRegion('narnia-1'); 17 | auth1.updateAuthKeys('shiny gold one', 'old rusty one'); 18 | 19 | sinon.stub(AWS, 'ec2', function(auth) { 20 | this.startInstancesPromised = function(params) { 21 | return Promise.resolve(true); 22 | }; 23 | this.stopInstancesPromised = function(params) { 24 | return Promise.resolve(true); 25 | }; 26 | this.describeInstancesPromised = function(params) { 27 | var data = {}; 28 | data.Reservations = [{Instances : [{InstanceId : 'ExampleId', 29 | Tags : [{Key : 'Name', Value : 'Tag'}], State : {Name : 'Testing'}}]}]; 30 | return Promise.resolve(data); 31 | }; 32 | this.waitForPromised = function(params) { 33 | return Promise.resolve("Success"); 34 | }; 35 | }); 36 | }); 37 | 38 | afterEach(function() { 39 | result = undefined; 40 | spy = undefined; 41 | }); 42 | 43 | after(function() { 44 | AWS.ec2.restore(); 45 | }); 46 | 47 | describe("starting and stopping instaces", function() { 48 | describe("start", function() { 49 | before(function() { 50 | return ecInstance.start('Name', 'My-EC2', auth1) 51 | .then(function(data) { 52 | result = data; 53 | }); 54 | }); 55 | 56 | it("starting an EC2 instance works", function() { 57 | assert.equal(result, "Success"); 58 | }); 59 | }); 60 | 61 | describe("startByName", function() { 62 | before(function() { 63 | spy = sinon.spy(ecInstance, "start"); 64 | return ecInstance.startByName('My-EC2', auth1) 65 | .then(function(data) { 66 | result = data; 67 | }); 68 | }); 69 | 70 | it("starting an EC2 instance by name works", function() { 71 | assert.equal(spy.calledWithExactly('Name', 'My-EC2', auth1),true); 72 | }); 73 | }); 74 | 75 | describe("stop", function() { 76 | before(function() { 77 | return ecInstance.stop('Name', 'My-EC2', auth1) 78 | .then(function(data) { 79 | result = data; 80 | }); 81 | }); 82 | 83 | it("stopping an EC2 instance works", function() { 84 | assert.equal(result, "Success"); 85 | }); 86 | }); 87 | 88 | describe("stopByName", function() { 89 | before(function() { 90 | spy = sinon.spy(ecInstance, 'stop'); 91 | return ecInstance.stopByName('My-EC2', auth1) 92 | .then(function(data) { 93 | result = data; 94 | }); 95 | }); 96 | 97 | it("stopping an EC2 instance by name works", function() { 98 | assert.equal(spy.calledWithExactly('Name', 'My-EC2', auth1),true); 99 | }); 100 | }); 101 | }); 102 | 103 | describe('Identifying EC2 instances', function() { 104 | describe("getTagsByID", function() { 105 | before(function() { 106 | AWS.ec2.restore(); 107 | sinon.stub(AWS, 'ec2', function(auth) { 108 | this.describeInstancesPromised = function(params) { 109 | var data = {}; 110 | data.Reservations = [{Instances : [{Tags : [{Key : 'Name', Value: 'Tag'}, 111 | {Key: 'OtherName', Value: 'OtherTag'}]}]}]; 112 | return Promise.resolve(data); 113 | } 114 | }); 115 | return ecInstance.getTagsByID('a-bcdefg', auth1) 116 | .then(function(data) { 117 | result = data; 118 | }); 119 | }); 120 | 121 | it("getting the tags of a specified EC2 instance works", function() { 122 | assert.isOk(result); 123 | }); 124 | }); 125 | 126 | describe("getInstanceID", function() { 127 | before(function() { 128 | AWS.ec2.restore(); 129 | sinon.stub(AWS, 'ec2', function(auth) { 130 | this.describeInstancesPromised = function(params) { 131 | var data = {}; 132 | data.Reservations = [{Instances : [{InstanceId : 'ExampleId', 133 | Tags : [{Key : 'Name', Value : 'Tag'}], State : {Name : 'Testing'}}]}]; 134 | return Promise.resolve(data); 135 | }; 136 | }); 137 | return ecInstance.getInstanceID('tag', 'My-EC2', auth1) 138 | .then(function(data) { 139 | result = data; 140 | }); 141 | }); 142 | 143 | it("getting the instance ID of a specified EC2 instance works", function() { 144 | assert.equal(result[0], 'ExampleId'); 145 | }); 146 | }); 147 | 148 | describe("listInstances", function() { 149 | before(function() { 150 | return ecInstance.listInstances('tag', 'My-EC2', auth1) 151 | .then(function(data) { 152 | result = data; 153 | }); 154 | }); 155 | 156 | it("listing instances works", function() { 157 | assert.equal(JSON.stringify(result[0]), 158 | '{"Name":"Tag","State":"Testing","id":"ExampleId"}'); 159 | }); 160 | }); 161 | 162 | describe("listInstances without a name tag", function() { 163 | before(function() { 164 | AWS.ec2.restore(); 165 | sinon.stub(AWS, 'ec2', function(auth) { 166 | this.describeInstancesPromised = function(params) { 167 | var data = {}; 168 | data.Reservations = [{Instances : [{InstanceId : 'ExampleId', 169 | Tags : [{Key : 'NotName', Value : 'Tag'}], State : {Name : 'Testing'}}]}]; 170 | return Promise.resolve(data); 171 | }; 172 | }); 173 | return ecInstance.listInstances('tag', 'My-EC2', auth1) 174 | .then(function(data) { 175 | result = data; 176 | }); 177 | }); 178 | 179 | it("listing instances without a name tag works", function() { 180 | assert.equal(JSON.stringify(result[0]), 181 | '{"State":"Testing","id":"ExampleId"}'); 182 | }); 183 | }); 184 | }); 185 | 186 | describe('Identifying instances by status', function() { 187 | describe("getInstancesStatus", function() { 188 | before(function() { 189 | AWS.ec2.restore(); 190 | sinon.stub(AWS, 'ec2', function(auth) { 191 | this.describeInstancesPromised = function(params) { 192 | var data = {}; 193 | data.Reservations = [{Instances : [{InstanceId : 'ExampleId', 194 | Tags : [{Key : 'NotName', Value : 'Tag'}], State : {Name : 'Testing'}}]}]; 195 | return Promise.resolve(data); 196 | }; 197 | }); 198 | return ecInstance.getInstancesStatus('tag', 'My-EC2', 'running', auth1) 199 | .then(function(data) { 200 | result = data; 201 | }); 202 | }); 203 | 204 | it("getting data about instances based on state works", function() { 205 | assert.equal(JSON.stringify(result[0]), '{"State":"Testing","id":"ExampleId"}'); 206 | }); 207 | }); 208 | 209 | describe("getInstancesStopped", function() { 210 | before(function() { 211 | spy = sinon.spy(ecInstance, 'getInstancesStatus'); 212 | return ecInstance.getInstancesStopped('tag', 'My-EC2', auth1); 213 | }); 214 | 215 | after(function() { 216 | ecInstance.getInstancesStatus.restore(); 217 | }); 218 | 219 | it("getting data about stopped instances works", function() { 220 | assert.equal(spy.calledWith('tag', 'My-EC2', 'stopped', auth1), true); 221 | }); 222 | }); 223 | 224 | describe("getInstancesStarted", function() { 225 | before(function() { 226 | spy = sinon.spy(ecInstance, 'getInstancesStatus'); 227 | return ecInstance.getInstancesStarted('tag', 'My-EC2', auth1); 228 | }); 229 | 230 | it("getting data about started instances works", function() { 231 | assert.equal(spy.calledWith('tag', 'My-EC2', 'running', auth1), true); 232 | }); 233 | }); 234 | }); 235 | 236 | describe("Retrieving EC2 Logs", function() { 237 | describe("getLogs", function() { 238 | before(function() { 239 | AWS.ec2.restore(); 240 | sinon.stub(AWS, 'ec2', function(auth) { 241 | this.getConsoleOutputPromised = function(params) { 242 | return Promise.resolve({Timestamp : 1, Output : 'dGVzdA=='}); 243 | }; 244 | this.describeInstancesPromised = function(params) { 245 | var data = {}; 246 | data.Reservations = [{Instances : [{InstanceId : 'ExampleId', 247 | Tags : [{Key : 'NotName', Value : 'Tag'}], State : {Name : 'Testing'}}]}]; 248 | return Promise.resolve(data); 249 | }; 250 | }); 251 | return ecInstance.getLogs('tag', 'My-EC2', auth1) 252 | .then(function(data) { 253 | result = data; 254 | }); 255 | }); 256 | 257 | it("getting logs about an instance works", function() { 258 | assert.equal(result, 'Timestamp: 1\ntest'); 259 | }); 260 | }); 261 | 262 | describe("getLogs with multiple instances", function() { 263 | before(function() { 264 | AWS.ec2.restore(); 265 | sinon.stub(AWS, 'ec2', function(auth) { 266 | this.describeInstancesPromised = function(params) { 267 | var data = {}; 268 | data.Reservations = [{Instances : [{InstanceId : 'ExampleId', 269 | Tags : [{Key : 'NotName', Value : 'Tag'}], State : {Name : 'Testing'}}, 270 | {InstanceId : 'OtherId', 271 | Tags : [{Key : 'OtherName', Value : 'Tag'}], State : {Name : 'Testing'}}]}]; 272 | return Promise.resolve(data); 273 | }; 274 | }); 275 | return ecInstance.getLogs('tag', 'My-EC2', auth1) 276 | .catch(function(err) { 277 | result = err; 278 | }); 279 | }); 280 | 281 | it("does not retrieve logs if more than one instance is specified", function() { 282 | assert.equal(result, 'More than one instance specified.'); 283 | }); 284 | }); 285 | 286 | describe("getLogs with no instances", function() { 287 | before(function() { 288 | AWS.ec2.restore(); 289 | sinon.stub(AWS, 'ec2', function(auth) { 290 | this.describeInstancesPromised = function(params) { 291 | return Promise.resolve({}); 292 | }; 293 | }); 294 | return ecInstance.getLogs('tag', 'My-EC2', auth1) 295 | .catch(function(err) { 296 | result = err; 297 | }); 298 | }); 299 | 300 | it("does not retrieve logs if no instances are specified", function() { 301 | assert.equal(result, 'No instances available.'); 302 | }); 303 | }); 304 | }); 305 | 306 | describe("reboot", function() { 307 | before(function() { 308 | AWS.ec2.restore(); 309 | sinon.stub(AWS, 'ec2', function(auth) { 310 | this.startInstancesPromised = function(params) { 311 | return Promise.resolve(true); 312 | }; 313 | this.stopInstancesPromised = function(params) { 314 | return Promise.resolve(true); 315 | }; 316 | this.describeInstancesPromised = function(params) { 317 | return Promise.resolve({}); 318 | }; 319 | this.waitForPromised = function(params) { 320 | return Promise.resolve("Success"); 321 | }; 322 | }); 323 | return ecInstance.reboot('tag', 'My-EC2', auth1) 324 | .then(function(data) { 325 | result = data; 326 | }); 327 | }); 328 | 329 | it("rebooting instances works", function() { 330 | assert.equal(result, 'Success'); 331 | }); 332 | }); 333 | }); -------------------------------------------------------------------------------- /test/filepersister-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var sinon = require('sinon'); 3 | var fsp = require('fs-promise'); 4 | var Promise = require('bluebird'); 5 | 6 | describe('File Persister', function() { 7 | 8 | var persister; 9 | var result; 10 | var filePersisterFunc; 11 | var readStub; 12 | 13 | before(function() { 14 | var writeStub = sinon.stub(fsp, 'writeFile', function(path, data) { 15 | return Promise.resolve('Saved.'); 16 | }); 17 | 18 | var ensureStub = sinon.stub(fsp, 'ensureFile', function(path) { 19 | return Promise.resolve(true); 20 | }); 21 | 22 | readStub = sinon.stub(fsp, 'readFile', function(path, encoding) { 23 | return Promise.resolve('{"data":1}'); 24 | }); 25 | 26 | var validateStub = sinon.stub(fsp, 'emptyDir', function(path, encoding) { 27 | if (path !== './inaccessiblePath') { 28 | return Promise.resolve(true); 29 | } 30 | return Promise.reject(false); 31 | }); 32 | 33 | var opkit = require('../index'); 34 | filePersisterFunc = opkit.FilePersister; 35 | }); 36 | 37 | afterEach(function() { 38 | result = undefined; 39 | }); 40 | 41 | describe('Constructor', function() { 42 | before(function() { 43 | persister = new filePersisterFunc('./folderpath'); 44 | }); 45 | 46 | it('Successfully creates a persister object', function() { 47 | assert.isOk(persister); 48 | }); 49 | }); 50 | 51 | describe('Persister not started', function() { 52 | describe('Saving', function() { 53 | before(function() { 54 | return persister.save({obj : 2}) 55 | .catch(function(err) { 56 | result = err; 57 | }); 58 | }); 59 | 60 | it('Does not let the user save if the persister has not been started', function() { 61 | assert.equal(result, 'Error: Persister not initialized.'); 62 | }); 63 | }); 64 | 65 | describe('Recovering', function() { 66 | before(function() { 67 | return persister.recover() 68 | .catch(function(err) { 69 | result = err; 70 | }); 71 | }); 72 | 73 | it('Does not let the user recover if the persister has not been started', function() { 74 | assert.equal(result, 'Error: Persister not initialized.'); 75 | }); 76 | }); 77 | }); 78 | 79 | describe('Initialization', function() { 80 | before(function() { 81 | var otherPersister = new filePersisterFunc('./inaccessiblePath'); 82 | return otherPersister.start() 83 | .catch(function(err) { 84 | result = err; 85 | }); 86 | }); 87 | 88 | it('Does not let the persister initialize if the user does not have sufficient permissions', function() { 89 | assert.equal(result, 'User does not have permissions to write to that folder.'); 90 | }); 91 | }); 92 | 93 | describe('Successful initialization', function() { 94 | before(function() { 95 | return persister.start() 96 | .then(function(data) { 97 | result = data; 98 | }); 99 | }); 100 | 101 | it('The persister successfully initializes', function() { 102 | assert.equal(result, 'User has permissions to write to that file.'); 103 | }); 104 | }); 105 | 106 | describe('The persister cannot be started multiple times', function() { 107 | before(function() { 108 | return persister.start() 109 | .catch(function(err) { 110 | result = err; 111 | }); 112 | }); 113 | 114 | it('Does not allow the persister to re-initialize', function() { 115 | assert.equal(result, 'Error: Persister already initialized.'); 116 | }); 117 | }); 118 | 119 | describe('Save', function() { 120 | before(function() { 121 | return persister.save({obj: 1}) 122 | .then(function(data) { 123 | result = data; 124 | }); 125 | }); 126 | 127 | it('Successfully attempts to save data', function() { 128 | assert.equal(result, 'Saved.'); 129 | }); 130 | }); 131 | 132 | describe('Recover', function() { 133 | before(function() { 134 | return persister.recover() 135 | .then(function(data) { 136 | result = data; 137 | }); 138 | }); 139 | 140 | it('Successfully attempts to recover data', function() { 141 | assert.isOk(result); 142 | }); 143 | }); 144 | 145 | describe('Recover from a blank file', function() { 146 | before(function() { 147 | readStub.restore(); 148 | readStub = sinon.stub(fsp, 'readFile', function(path, encoding) { 149 | return Promise.resolve(''); 150 | }); 151 | return persister.recover() 152 | .then(function(data) { 153 | result = data; 154 | }); 155 | }); 156 | 157 | it('Successfully handles reading a blank file', function() { 158 | assert.isOk(result); 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/mongopersister-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var sinon = require('sinon'); 3 | var MongoDB = require('mongodb-bluebird'); 4 | var Promise = require('bluebird'); 5 | 6 | describe('Mongo Persister', function() { 7 | 8 | var mongoPersisterFunc; 9 | var result; 10 | 11 | before(function() { 12 | var mongoStub = sinon.stub(MongoDB, 'connect', function() { 13 | return Promise.resolve({ 14 | collection : function() { 15 | return { 16 | remove : function() { 17 | return Promise.resolve('Removed.'); 18 | }, 19 | insert : function() { 20 | return Promise.resolve('Inserted.'); 21 | }, 22 | find : function(args) { 23 | return Promise.resolve([1]); 24 | } 25 | }; 26 | } 27 | }); 28 | }); 29 | 30 | var opkit = require('../index'); 31 | mongoPersisterFunc = opkit.MongoPersister; 32 | }); 33 | 34 | afterEach(function() { 35 | result = undefined; 36 | }); 37 | 38 | var persister; 39 | 40 | describe('Calling the Constructor', function() { 41 | before(function() { 42 | persister = new mongoPersisterFunc('notauri'); 43 | }); 44 | 45 | it('Successfully returns a persister object', function() { 46 | assert.isOk(persister); 47 | }); 48 | }); 49 | 50 | describe('Persister not started', function() { 51 | 52 | describe('Saving', function() { 53 | before(function() { 54 | return persister.save({bool : 32}, 'somecollection') 55 | .catch(function(err) { 56 | result = err; 57 | }); 58 | }); 59 | 60 | it('Does not let the user save if the persister has not been started', function() { 61 | assert.equal(result, 'Error: Persister not initialized.'); 62 | }); 63 | }); 64 | 65 | describe('Recovering', function() { 66 | before(function() { 67 | return persister.recover() 68 | .catch(function(err) { 69 | result = err; 70 | }); 71 | }); 72 | 73 | it('Does not let the user recover if the persister has not been started', function() { 74 | assert.equal(result, 'Error: Persister not initialized.'); 75 | }); 76 | }); 77 | }); 78 | 79 | describe('Saving Data', function() { 80 | before(function() { 81 | return persister.start() 82 | .then(function() { 83 | return persister.save({bool : 1}) 84 | }) 85 | .then(function(data) { 86 | result = data; 87 | }); 88 | }); 89 | 90 | it('Successfully attempts to save data', function() { 91 | assert.equal(result, 'Inserted.'); 92 | }); 93 | }); 94 | 95 | describe('Retrieving Data', function() { 96 | before(function() { 97 | return persister.recover() 98 | .then(function(data) { 99 | result = data; 100 | }); 101 | }); 102 | 103 | it('Successfully recovers data', function() { 104 | assert.equal(result, 1); 105 | }); 106 | }); 107 | 108 | describe('The persister cannot be started multiple times', function() { 109 | before(function() { 110 | return persister.start() 111 | .catch(function(err) { 112 | result = err; 113 | }); 114 | }); 115 | 116 | it('Does not allow the user to initialize the persister twice', function() { 117 | assert.equal(result, 'Error: Persister already initialized.'); 118 | }); 119 | }); 120 | 121 | describe('If no data is available an empty object is returned', function() { 122 | before(function() { 123 | persister.db.collection = function() { 124 | return { 125 | find : function() { 126 | return Promise.resolve([]); 127 | } 128 | }; 129 | }; 130 | return persister.recover('no collection') 131 | .then(function(data) { 132 | result = data; 133 | }); 134 | }); 135 | 136 | it('Returns an empty JavaScript object', function() { 137 | assert.isOk(result); 138 | }); 139 | }); 140 | }); -------------------------------------------------------------------------------- /test/postgrespersister-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var sinon = require('sinon'); 3 | var Promise = require('bluebird'); 4 | var mock = require('mock-require'); 5 | 6 | describe('Postgres Persister', function() { 7 | var persister; 8 | var result; 9 | 10 | before (function() { 11 | mock('pg-promise', function() { 12 | return function pgp(connection) { 13 | return { 14 | result : function(str) { 15 | return Promise.resolve('Deleted.'); 16 | }, 17 | none : function(str) { 18 | return Promise.resolve('Query Complete.'); 19 | }, 20 | any : function(str) { 21 | return Promise.resolve([{info : "1"}]); 22 | }, 23 | config : connection 24 | }; 25 | }; 26 | }); 27 | 28 | require.searchCache = function (moduleName, callback) { 29 | var mod = require.resolve(moduleName); 30 | 31 | if (mod && ((mod = require.cache[mod]) !== undefined)) { 32 | (function run(mod) { 33 | mod.children.forEach(function (child) { 34 | run(child); 35 | }); 36 | callback(mod); 37 | })(mod); 38 | } 39 | }; 40 | 41 | require.uncache = function (moduleName) { 42 | require.searchCache(moduleName, function (mod) { 43 | delete require.cache[mod.id]; 44 | }); 45 | 46 | Object.keys(module.constructor._pathCache).forEach(function(cacheKey) { 47 | if (cacheKey.indexOf(moduleName)>0) { 48 | delete module.constructor._pathCache[cacheKey]; 49 | } 50 | }); 51 | }; 52 | 53 | require.uncache('../lib/Persisters/postgrespersister.js'); 54 | var postgresPersisterFunc = require('../lib/Persisters/postgrespersister.js'); 55 | 56 | persister = new postgresPersisterFunc('postgres://username:password@host:5432/database'); 57 | }); 58 | 59 | afterEach(function() { 60 | result = undefined; 61 | }); 62 | 63 | describe('Persister not started', function() { 64 | 65 | describe('Saving', function() { 66 | before(function() { 67 | return persister.save({bool : 13}, 'collec') 68 | .catch(function(err) { 69 | result = err; 70 | }); 71 | }); 72 | 73 | it('Does not let the user save', function() { 74 | assert.equal(result, 'Error: Persister not initialized.'); 75 | }); 76 | }); 77 | 78 | describe('Recovering', function() { 79 | before(function() { 80 | return persister.recover('collec') 81 | .catch(function(err) { 82 | result = err; 83 | }); 84 | }); 85 | 86 | it('Does not let the user retrieve data', function() { 87 | assert.equal(result, 'Error: Persister not initialized.'); 88 | }); 89 | }); 90 | }); 91 | 92 | describe('Saving Data', function() { 93 | 94 | describe('Save', function() { 95 | before(function() { 96 | return persister.start() 97 | .then(function() { 98 | return persister.save({bool : 21}, 'collec') 99 | }) 100 | .then(function(data) { 101 | result = data; 102 | }); 103 | }); 104 | 105 | it('Properly saves data', function() { 106 | assert.equal(result, 'Query Complete.'); 107 | }); 108 | }); 109 | 110 | describe('Save to non-existent table', function() { 111 | before(function() { 112 | persister.db.result = function(str) { 113 | return Promise.reject({code : '42P01'}); 114 | }; 115 | persister.db.none = function(str) { 116 | this.result = function(str) { 117 | return Promise.resolve('Deleted.'); 118 | }; 119 | return Promise.resolve('Query Complete.'); 120 | }; 121 | return persister.save({bool : 21}, 'collec') 122 | .then(function(data) { 123 | result = data; 124 | }); 125 | }); 126 | 127 | it('Properly saves data when table is not created', function() { 128 | assert.equal(result, 'Query Complete.'); 129 | }); 130 | }); 131 | 132 | describe('Save on an unknown error', function() { 133 | before(function() { 134 | persister.db.result = function(str) { 135 | return Promise.reject({code : 'somethingelse'}); 136 | }; 137 | return persister.save({bool : 15}, 'collec') 138 | .catch(function(err) { 139 | result = err; 140 | }); 141 | }); 142 | 143 | it('Does not save data on an unkown error', function() { 144 | assert.equal(result.code, 'somethingelse'); 145 | }); 146 | }); 147 | }); 148 | 149 | describe('Retrieving Data', function() { 150 | 151 | describe('Recover', function() { 152 | before(function() { 153 | return persister.recover() 154 | .then(function(data) { 155 | result = data; 156 | }); 157 | }); 158 | 159 | it('Properly retrieves data', function() { 160 | assert.equal(result, 1); 161 | }); 162 | }); 163 | 164 | describe('Recover with no data available', function() { 165 | before(function() { 166 | persister.db.any = function(str) { 167 | return Promise.resolve([]); 168 | }; 169 | return persister.recover() 170 | .then(function(data) { 171 | result = data; 172 | }); 173 | }); 174 | 175 | it('Returns an empty object if there is no data', function() { 176 | assert.isOk(result); 177 | }); 178 | }); 179 | 180 | describe('Recover from non-existent table', function() { 181 | before(function() { 182 | persister.db.any = function(str) { 183 | return Promise.reject({code : '42P01'}); 184 | }; 185 | persister.db.none = function(str) { 186 | this.any = function(str) { 187 | return Promise.resolve([]); 188 | }; 189 | return Promise.resolve('Query Complete.'); 190 | }; 191 | return persister.recover() 192 | .then(function(data) { 193 | result = data; 194 | }); 195 | }); 196 | 197 | it('Still works if the table has not been created', function() { 198 | assert.isOk(result); 199 | }); 200 | }); 201 | 202 | describe('Recover with an unknown error', function() { 203 | before(function() { 204 | persister.db.any = function(str) { 205 | return Promise.reject({code : 'somethingelse'}); 206 | }; 207 | persister.db.none = function(str) { 208 | return Promise.resolve('Query Complete.'); 209 | }; 210 | return persister.recover() 211 | .catch(function(err) { 212 | result = err; 213 | }); 214 | }); 215 | 216 | it('Does not recover data on an unkown error', function() { 217 | assert.equal(result.code, 'somethingelse'); 218 | }); 219 | }); 220 | }); 221 | 222 | describe('Does not let the user start the persister twice', function() { 223 | before(function() { 224 | return persister.start() 225 | .catch(function(err) { 226 | result = err; 227 | }); 228 | }); 229 | 230 | it('Returns an error message', function() { 231 | assert.equal(result, 'Error: Persister already initialized.'); 232 | }); 233 | }); 234 | 235 | describe('Configuration', function() { 236 | it('The URL was properly parsed', function() { 237 | assert.equal(persister.db.config.user, 'username'); 238 | assert.equal(persister.db.config.password, 'password'); 239 | assert.equal(persister.db.config.host, 'host'); 240 | assert.equal(persister.db.config.port, '5432'); 241 | assert.equal(persister.db.config.database, 'database'); 242 | }); 243 | }); 244 | }); -------------------------------------------------------------------------------- /test/redispersister-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var sinon = require('sinon'); 3 | var redis = require('redis'); 4 | var Promise = require('bluebird'); 5 | 6 | describe('Redis Persister', function() { 7 | 8 | var persister; 9 | var result; 10 | var redisPersisterFunc; 11 | 12 | before(function() { 13 | var redisStub = sinon.stub(redis, 'createClient', function() { 14 | return { 15 | delAsync : function() { 16 | return Promise.resolve('Deleted.'); 17 | }, 18 | hsetAsync : function() { 19 | return Promise.resolve('Saved.'); 20 | }, 21 | hkeysAsync : function() { 22 | return Promise.resolve([1]); 23 | }, 24 | end : function() { 25 | return Promise.resolve('Connection exited.'); 26 | }, 27 | onAsync : function(str) { 28 | if (str === 'error') { 29 | return Promise.reject('Error.'); 30 | } 31 | return Promise.resolve(); 32 | } 33 | }; 34 | }); 35 | 36 | var opkit = require('../index'); 37 | redisPersisterFunc = opkit.RedisPersister; 38 | }); 39 | 40 | afterEach(function() { 41 | result = undefined; 42 | }); 43 | 44 | describe('Calling the Constructor', function() { 45 | before(function() { 46 | persister = new redisPersisterFunc('notaurl'); 47 | }) 48 | 49 | it('Successfully returns a new persister object', function() { 50 | assert.isOk(persister); 51 | }); 52 | }); 53 | 54 | describe('Persister not started', function() { 55 | 56 | describe('Saving', function() { 57 | before(function() { 58 | return persister.save({bool: 14}, 'somecollection') 59 | .catch(function(err) { 60 | result = err; 61 | }); 62 | }); 63 | 64 | it('Should not let the user save data', function() { 65 | assert.equal(result, 'Error: Persister not initialized.'); 66 | }); 67 | }); 68 | 69 | describe('Recovering', function() { 70 | before(function() { 71 | return persister.recover() 72 | .catch(function(err) { 73 | result = err; 74 | }); 75 | }); 76 | 77 | it('Should not let the user retrieve data', function() { 78 | assert.equal(result, 'Error: Persister not initialized.'); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('Saving Data', function() { 84 | before(function() { 85 | return persister.start() 86 | .then(function() { 87 | return persister.save({bool: 12}, 'collec') 88 | }) 89 | .then(function(data) { 90 | result = data; 91 | }); 92 | }); 93 | 94 | it('Successfully attempts to save data', function() { 95 | assert.equal(result, 'Saved.'); 96 | }); 97 | }); 98 | 99 | describe('Retrieving Data', function() { 100 | before(function() { 101 | return persister.recover('collec') 102 | .then(function(data) { 103 | result = data; 104 | }); 105 | }); 106 | 107 | it('Successfully attempts to retrieve data', function() { 108 | assert.equal(result, 1); 109 | }); 110 | }); 111 | 112 | describe('The persister cannot be started multiple times', function() { 113 | before(function() { 114 | return persister.start() 115 | .catch(function(err) { 116 | result = err; 117 | }); 118 | }); 119 | 120 | it('Does not allow the user to initialize the persister twice', function() { 121 | assert.equal(result, 'Error: Persister already initialized.'); 122 | }); 123 | }); 124 | 125 | describe('If no data is available an empty object is returned', function() { 126 | before(function() { 127 | persister.client.hkeysAsync = function() { 128 | return Promise.resolve([]); 129 | }; 130 | return persister.recover('not a collection') 131 | .then(function(data) { 132 | result = data; 133 | }); 134 | }); 135 | 136 | it('Returns an empty JavaScript Object', function() { 137 | assert.isOk(result); 138 | }); 139 | }); 140 | 141 | describe('The client quits on an error', function() { 142 | before(function() { 143 | return persister.client.onAsync('error') 144 | .catch(function(err) { 145 | result = err; 146 | }); 147 | }); 148 | 149 | it('Exits on an error', function() { 150 | assert.equal(result, 'Error.'); 151 | }); 152 | }); 153 | }); -------------------------------------------------------------------------------- /test/sqs-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var opkit = require('../index'); 3 | var sqsqueue = new opkit.SQS(); 4 | var sinon = require('sinon'); 5 | require('sinon-as-promised'); 6 | var AWS = require('aws-promised'); 7 | 8 | describe('SQS', function() { 9 | 10 | var auth1; 11 | var result; 12 | 13 | before(function() { 14 | auth1 = new opkit.Auth(); 15 | auth1.updateRegion('narnia-1'); 16 | auth1.updateAuthKeys('shiny gold one', 'old rusty one'); 17 | 18 | sinon.stub(AWS, 'sqs', function(auth) { 19 | this.getQueueAttributesPromised = function(params) { 20 | var data = {}; 21 | var qualities = {}; 22 | qualities.ApproximateNumberOfMessages = 2; 23 | qualities.ApproximateNumberOfMessagesNotVisible = 0; 24 | data.Attributes = qualities; 25 | return Promise.resolve(data); 26 | }; 27 | this.listQueuesPromised = function(params) { 28 | var data = {}; 29 | var urls = ['www.example.com']; 30 | data.QueueUrls = urls; 31 | return Promise.resolve(data); 32 | }; 33 | this.getQueueUrlPromised = function(params) { 34 | return Promise.resolve('www.example.com'); 35 | } 36 | }); 37 | }); 38 | 39 | afterEach(function() { 40 | result = undefined; 41 | }); 42 | 43 | after(function() { 44 | AWS.sqs.restore(); 45 | }); 46 | 47 | describe('SQSQueueSizeInt', function() { 48 | before(function() { 49 | return sqsqueue.getSQSQueueSizeInt("https://sqs", auth1) 50 | .then(function (data) { 51 | result = data; 52 | }); 53 | }); 54 | 55 | it("getSQSQueueSizeInt successfully returns a promise with specified parameters", function() { 56 | assert.equal(result, 2); 57 | }); 58 | }); 59 | describe('SQSQueueSizeNotVisibleInt', function() { 60 | before(function() { 61 | return sqsqueue.getSQSQueueSizeNotVisibleInt("Example", auth1) 62 | .then(function (data) { 63 | result = data; 64 | }); 65 | }); 66 | 67 | it("getSQSQueueSizeNotVisibleInt successfully returns a promise with specified parameters", function() { 68 | assert.equal(result,0); 69 | }); 70 | }); 71 | describe('ListQueues', function() { 72 | before(function() { 73 | return sqsqueue.listQueues("prefix", auth1) 74 | .then(function (data) { 75 | result = data; 76 | }); 77 | }); 78 | 79 | it("listQueues successfully returns a promise with an array of queue urls", function() { 80 | assert.equal(result.toString(), ['www.example.com'].toString()); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/test-auth.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var opkit = require('../index'); 3 | 4 | describe('Auth', function() { 5 | 6 | var auth1; 7 | 8 | afterEach(function() { 9 | auth1 = undefined; 10 | }); 11 | 12 | describe('#updateRegion()', function () { 13 | before(function() { 14 | auth1 = new opkit.Auth(); 15 | auth1.updateRegion("us-east-1"); 16 | }); 17 | 18 | it('should update the region in that particular object', function () { 19 | assert.equal("us-east-1", auth1.props.region); 20 | }); 21 | }); 22 | 23 | describe('#updateAuthKeys()', function () { 24 | before(function() { 25 | auth1 = new opkit.Auth(); 26 | auth1.updateAuthKeys("skeleton", "other_skeleton"); 27 | }); 28 | 29 | it('should update the keys in that particular object', function () { 30 | assert.equal("skeleton", auth1.props.accessKeyId); 31 | assert.equal("other_skeleton", auth1.props.secretAccessKey); 32 | }); 33 | }); 34 | 35 | describe('#updateAccessKeyId()', function () { 36 | before(function() { 37 | auth1 = new opkit.Auth(); 38 | auth1.updateAccessKeyId("skeleton"); 39 | }); 40 | 41 | it('should update the access key ID', function () { 42 | assert.equal("skeleton", auth1.props.accessKeyId); 43 | }); 44 | }); 45 | 46 | describe('#updateSecretAccessKey()', function () { 47 | before(function() { 48 | auth1 = new opkit.Auth(); 49 | auth1.updateSecretAccessKey("skeleton"); 50 | }); 51 | 52 | it('should update the secret access key in that particular object', function () { 53 | assert.equal("skeleton", auth1.props.secretAccessKey); 54 | }); 55 | }); 56 | 57 | describe('#updateShortName', function () { 58 | before(function() { 59 | auth1 = new opkit.Auth(); 60 | auth1.updateShortName("skeleton"); 61 | }); 62 | 63 | it('should update the short name in that particular object', function () { 64 | assert.equal("skeleton", auth1.shortName); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/test-bot.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var Opkit = require('../index'); 3 | var sinon = require('sinon'); 4 | var Promise = require('bluebird'); 5 | var _ = require('lodash'); 6 | require('sinon-as-promised')(Promise); 7 | 8 | describe('Bot', function(){ 9 | 10 | var pattern = function(message, bot, auths){ 11 | return (message.text === 'boogie'); 12 | }; 13 | 14 | var logic = function(message, bot, auths){ 15 | bot.sendMessage('You know I can boogie down', message.channel); 16 | if (message.user === 'user') { 17 | bot.sendMessage('Specially for you', message.channel); 18 | } else { 19 | return Promise.reject(); 20 | } 21 | return Promise.resolve(); 22 | }; 23 | 24 | var otherPattern = function(message, bot, auths){ 25 | return (message.text === 'opkit twelve'); 26 | }; 27 | var otherLogic = function(message, bot, auths){ 28 | bot.sendMessage('Twelve, from the special handler', message.channel); 29 | if (message.user === 'user') { 30 | return Promise.resolve(); 31 | } else { 32 | return Promise.reject(); 33 | } 34 | }; 35 | 36 | var sendsTwelve = function(message, bot, auth){ 37 | bot.sendMessage('12', message.channel); 38 | console.log("Sends twelve worked"); 39 | return Promise.resolve("Bot successfully sent the string literal '12'."); 40 | }; 41 | 42 | var authorizationFunction = function(message, bot, auth){ 43 | return Promise.resolve(['A', 'B', 'C']); 44 | }; 45 | 46 | var sendsTwelveObject = { 47 | command : sendsTwelve, 48 | name : 'twelve', 49 | syntax : [ ['give', 'me', 'twelve'], 50 | ['send', 'me', 'twelve'], 51 | ['send', 'twelve'], 52 | ['give', 'me', 'a', 'number'], 53 | 'twelve'], 54 | package : 'numbers' 55 | }; 56 | 57 | var otherObject = { 58 | command : sendsTwelve, 59 | name : 'totesnotTwelve', 60 | syntax : ['give', 'me', 'totesnotTwelve'], 61 | package : 'numbers' 62 | }; 63 | 64 | var mockPersister = { 65 | save : function(brain, package){ 66 | return Promise.resolve(true); 67 | }, 68 | recover : function(package){ 69 | return Promise.resolve({ 70 | data : 'some_data' 71 | }); 72 | }, 73 | start : function(){ 74 | return Promise.resolve(true); 75 | } 76 | }; 77 | 78 | var failsToSendTwelve = function(message, bot, auth){ 79 | return Promise.reject("Bot couldn't send '12' for some reason."); 80 | }; 81 | 82 | var bot; 83 | var result; 84 | 85 | var clock = sinon.useFakeTimers(); 86 | 87 | afterEach(function() { 88 | result = undefined; 89 | }); 90 | 91 | describe('#Constructor without params', function(){ 92 | before(function(){ 93 | bot = new Opkit.Bot( 94 | 'opkit', 95 | [sendsTwelveObject, otherObject], 96 | mockPersister 97 | ); 98 | }); 99 | it('should result in a properly initialized name', function(){ 100 | assert.equal(bot.name, 'opkit'); 101 | }); 102 | it('should result in a properly initialized command', function(){ 103 | assert.isOk(bot.commands.twelve); 104 | }); 105 | it('should provide a default authorization function', function(){ 106 | assert.isOk(bot.authorizationFunction); 107 | }); 108 | it('should provide the correct default authorization function', function(){ 109 | return bot.authorizationFunction('user') 110 | .then(function(data){ 111 | assert.equal(data.length, 0); 112 | }); 113 | }); 114 | it('should expose the recover method', function(){ 115 | return bot.recover('package') 116 | .then(function(data){ 117 | assert.isOk(data); 118 | }); 119 | }); 120 | }); 121 | describe('#Constructor with authorization function', function(){ 122 | before(function(){ 123 | bot = new Opkit.Bot( 124 | 'opkit', 125 | [sendsTwelveObject], 126 | mockPersister,{ 127 | authFunction : authorizationFunction 128 | } 129 | ); 130 | }); 131 | it('should not override the provided authorizationFunction', function(){ 132 | bot.authorizationFunction('user') 133 | .then(function(data){ 134 | assert.equal(data.length, 3); 135 | }); 136 | }); 137 | }); 138 | describe('#Constructor with log level DEBUG', function(){ 139 | before(function(){ 140 | bot = new Opkit.Bot( 141 | 'opkit', 142 | [sendsTwelveObject], 143 | mockPersister,{ 144 | logLevel : 'DEBUG' 145 | } 146 | ); 147 | }); 148 | it('should set the correct log level', function(){ 149 | assert.equal(bot.logLevel, 'DEBUG'); 150 | }); 151 | }); 152 | describe('#Constructor with log level PROD', function(){ 153 | before(function(){ 154 | bot = new Opkit.Bot( 155 | 'opkit', 156 | [sendsTwelveObject], 157 | mockPersister,{ 158 | logLevel : 'PROD' 159 | } 160 | ); 161 | }); 162 | it('should set the correct log level', function(){ 163 | assert.equal(bot.logLevel, 'PROD'); 164 | }); 165 | }); 166 | describe('#Constructor with log level TEST', function(){ 167 | before(function(){ 168 | bot = new Opkit.Bot( 169 | 'opkit', 170 | [sendsTwelveObject], 171 | mockPersister,{ 172 | logLevel : 'TEST' 173 | } 174 | ); 175 | }); 176 | it('should set the correct log level', function(){ 177 | assert.equal(bot.logLevel, 'TEST'); 178 | }); 179 | }); 180 | 181 | describe('#Adding handlers', function(){ 182 | var stubCallback; 183 | var calledHandlerCallback; 184 | before(function(){ 185 | bot = new Opkit.Bot( 186 | 'opkit', 187 | [sendsTwelveObject], 188 | mockPersister, 189 | { 190 | authFunction : authorizationFunction, 191 | logLevel : 'TEST' 192 | } 193 | ); 194 | stubCallback = sinon.stub(); 195 | calledHandlerCallback = sinon.stub(); 196 | // These handlers should survive the 2,500 msec. 197 | bot.addHandler(pattern, logic); 198 | bot.addOneOffHandler(pattern, logic); 199 | // These handlers should not. After 2,500 msec, stubCallback should have been called twice. 200 | bot.addHandler(pattern, logic, 2, stubCallback); 201 | bot.addOneOffHandler(pattern, logic, 2, stubCallback); 202 | bot.addHandler(pattern, logic, 2); 203 | bot.addOneOffHandler(pattern, logic, 2); 204 | // These handlers are no longer stored in their arrays, so they shouldn't time out. 205 | // calledHandlerCallback should not be called. 206 | var calledHandler = bot.addHandler(pattern, logic, 2, calledHandlerCallback); 207 | var otherCalledHandler = bot.addOneOffHandler(pattern, logic, 2, calledHandlerCallback); 208 | _.pull(bot.handlers, calledHandler); 209 | _.pull(bot.oneoffHandlers, otherCalledHandler); 210 | }); 211 | it('should successfully add recurring handlers', function(){ 212 | assert.equal(bot.handlers.length, 3); 213 | }); 214 | it('should successfully add oneoff handlers', function(){ 215 | assert.equal(bot.oneoffHandlers.length, 3); 216 | }); 217 | it('should timeout recurring handlers', function(){ 218 | clock.tick(2500); 219 | assert.equal(bot.handlers.length, 1); 220 | }); 221 | it('should timeout oneoff handlers', function(){ 222 | assert.equal(bot.oneoffHandlers.length, 1); 223 | }); 224 | it('should call the onTimeout callbacks', function(){ 225 | assert(stubCallback.calledTwice); 226 | }); 227 | it('should not call the onTimeout callbacks of pulled handlers', function(){ 228 | assert(!calledHandlerCallback.called); 229 | }); 230 | }); 231 | describe("addCron", function(){ 232 | var spy; 233 | before(function(){ 234 | bot = new Opkit.Bot( 235 | 'opkit', 236 | [sendsTwelveObject], 237 | mockPersister, 238 | { 239 | authFunction : authorizationFunction, 240 | logLevel : 'TEST' 241 | } 242 | ); 243 | var clock = sinon.useFakeTimers(); 244 | bot.addCron('* * * * * *', 'opkit twelve', 'user', 'America/New_York'); 245 | spy = sinon.spy(bot, 'sendMessage'); 246 | clock.tick(2500); 247 | }); 248 | it('should run the job', function(){ 249 | assert.equal(spy.called, true); 250 | }); 251 | }); 252 | describe('#sendMessage', function(){ 253 | before(function(){ 254 | bot = new Opkit.Bot( 255 | 'opkit', 256 | [sendsTwelveObject], 257 | mockPersister, 258 | { 259 | authFunction : authorizationFunction, 260 | logLevel : 'TEST' 261 | } 262 | ); 263 | bot.rtm.sendMessage = sinon.mock().once(); 264 | bot.sendMessage('foo', 'bar'); 265 | }); 266 | it('it should call bot.rtm.sendMessage', function(){ 267 | bot.rtm.sendMessage.verify(); 268 | }); 269 | }); 270 | describe('#start', function(){ 271 | before(function(){ 272 | bot = new Opkit.Bot( 273 | 'opkit', 274 | [sendsTwelveObject], 275 | mockPersister, 276 | { 277 | authFunction : authorizationFunction, 278 | logLevel : 'TEST' 279 | } 280 | ); 281 | bot.rtm.start = sinon.mock().once(); 282 | bot.start(); 283 | }); 284 | it('it should call bot.rtm.start', function(){ 285 | bot.rtm.start.verify(); 286 | }); 287 | }); 288 | describe('#message parser', function(){ 289 | beforeEach(function(){ 290 | bot = new Opkit.Bot( 291 | 'opkit', 292 | [sendsTwelveObject], 293 | mockPersister, 294 | { 295 | authFunction : authorizationFunction, 296 | logLevel : 'TEST' 297 | } 298 | ); 299 | }); 300 | describe('#case of match without params', function () { 301 | before(function(){ 302 | return bot.messageParser(['testbot', 'send', 'me', 'twelve'], bot) 303 | .then(function(data){ 304 | result = data; 305 | }); 306 | }); 307 | 308 | it('should return a match', function(){ 309 | assert.equal(result.command, sendsTwelveObject); 310 | }); 311 | }); 312 | describe('#case of match with params', function () { 313 | before(function(){ 314 | return bot.messageParser(['testbot', 'send', 'me', 'twelve' ,'please'], bot) 315 | .then(function(data){ 316 | result = data; 317 | }); 318 | }); 319 | 320 | it('should return a match', function(){ 321 | assert.equal(result.command, sendsTwelveObject); 322 | assert.deepEqual(result.args, ['please']); 323 | }); 324 | }); 325 | describe('#case with no match (JW distance high; likely typo)', function(){ 326 | before(function(){ 327 | return bot.messageParser(['testbot', 'send', 'me', 'twilve'], bot) 328 | .then(function(){ 329 | result = 'Match'; 330 | }) 331 | .catch(function(){ 332 | result = 'No match'; 333 | }); 334 | }); 335 | 336 | it('should not return a match when there is no match', function(){ 337 | assert.equal(result, 'No match'); 338 | }); 339 | }); 340 | describe('#case with no match (JW distance low; unlikely typo)', function(){ 341 | before(function(){ 342 | return bot.messageParser(['testbot', 'fdasfdsfsdaf', 'fdsfdsdf', 'fdafdfdfdfdfdfdfdfdfdfdfd'], bot) 343 | .then(function(){ 344 | result = 'Match'; 345 | }) 346 | .catch(function(){ 347 | result = 'No match'; 348 | }); 349 | }); 350 | 351 | it('should not return a match when there is no match', function(){ 352 | assert.equal(result, 'No match'); 353 | }); 354 | }); 355 | }); 356 | 357 | describe('#onEventsMessage - uncontrolled functions', function(){ 358 | before(function(){ 359 | bot = new Opkit.Bot( 360 | 'opkit', 361 | [sendsTwelveObject], 362 | mockPersister, 363 | { 364 | authFunction : authorizationFunction, 365 | logLevel : 'TEST' 366 | } 367 | ); 368 | }); 369 | describe('#case where message is not addressed to Opkit', function(){ 370 | before(function(){ 371 | bot.sendMessage = sinon.mock().never(); 372 | bot.onEventsMessage({ 373 | text : "I really enjoyed that Nickelback concert-show.", 374 | user : "That guy we all know" 375 | }); 376 | }); 377 | it("shouldn't respond to messages not addressed to it", function(){ 378 | bot.sendMessage.verify(); 379 | }) 380 | }); 381 | describe("#case where Opkit doesn't have that command", function(){ 382 | var success; 383 | before(function(){ 384 | bot.sendMessage = sinon.mock().once(); 385 | bot.messageParser = sinon.mock().rejects(); 386 | return bot.onEventsMessage({ 387 | text : "opkit joke", 388 | user : "user" 389 | }); 390 | }); 391 | it("shouldn't respond to messages not addressed to it", function(){ 392 | bot.sendMessage.verify(); 393 | }); 394 | }); 395 | describe("#case where Opkit has that command", function(){ 396 | before(function(){ 397 | bot.sendMessage = sinon.mock().once(); 398 | bot.messageParser = sinon.mock().resolves({ 399 | command :sendsTwelveObject 400 | }); 401 | return bot.onEventsMessage({ 402 | text : "opkit twelve", 403 | user : "user" 404 | }); 405 | }); 406 | it("shouldn't respond to messages not addressed to it", function(){ 407 | bot.sendMessage.verify(); 408 | }); 409 | }); 410 | describe('#case where there is some special handler', function(){ 411 | var handlerLogic, otherHandlerLogic; 412 | before(function(){ 413 | handlerLogic = sinon.stub().resolves(); 414 | otherHandlerLogic = sinon.stub().resolves(); 415 | bot.addHandler(sinon.stub().returns(false), otherHandlerLogic); 416 | bot.addHandler(sinon.stub().returns(true), handlerLogic); 417 | return bot.onEventsMessage({ 418 | text : "boogie", 419 | user : "user" 420 | }); 421 | }); 422 | it("should call the handler that matches", function(){ 423 | assert(handlerLogic.calledOnce); 424 | }); 425 | it("shouldn't call a handler that doesn't match", function(){ 426 | assert(!otherHandlerLogic.called); 427 | }); 428 | }); 429 | describe('#case where there is some oneoff handler', function(){ 430 | var handlerLogic, otherHandlerLogic; 431 | before(function(){ 432 | bot.handlers = []; 433 | handlerLogic = sinon.stub().resolves(); 434 | otherHandlerLogic = sinon.stub().resolves(); 435 | bot.addOneOffHandler(sinon.stub().returns(false), otherHandlerLogic); 436 | bot.addOneOffHandler(sinon.stub().returns(true), handlerLogic); 437 | return bot.onEventsMessage({ 438 | text : "boogie", 439 | user : "user" 440 | }); 441 | }); 442 | it("should call the handler that matches once", function(){ 443 | assert(handlerLogic.calledOnce); 444 | }); 445 | it("shouldn't call a handler that doesn't match", function(){ 446 | assert(!otherHandlerLogic.called); 447 | }); 448 | }); 449 | describe("#case where just the bot's name is sent", function(){ 450 | before(function(){ 451 | bot.oneoffHandlers = []; 452 | bot.sendMessage = sinon.mock().once(); 453 | return bot.onEventsMessage({ 454 | text : "opkit", 455 | user : "user" 456 | }); 457 | }); 458 | it('should send a hello message if just the name of the bot is entered', function() { 459 | bot.sendMessage.verify(); 460 | }); 461 | }); 462 | }); 463 | 464 | describe('#onEventsMessage - controlled functions', function(){ 465 | before(function(){ 466 | sendsTwelveObject = { 467 | command : sendsTwelve, 468 | name : 'twelve', 469 | roles : [['A', 'B'], 'D'] 470 | } 471 | bot = new Opkit.Bot( 472 | 'opkit', 473 | [sendsTwelveObject], 474 | mockPersister, 475 | { 476 | authFunction : authorizationFunction, 477 | logLevel : 'TEST' 478 | } 479 | ); 480 | }); 481 | describe('#case where a user has one role', function(){ 482 | before(function(){ 483 | bot.messageParser = sinon.mock().resolves({ 484 | command : sendsTwelveObject, 485 | args : [] 486 | }); 487 | return bot.onEventsMessage({ 488 | text : "opkit twelve", 489 | user : "user" 490 | }) 491 | .then(function(){ 492 | result = true; 493 | }) 494 | .catch(function(){ 495 | result = false; 496 | }); 497 | }); 498 | it('should match with one role', function(){ 499 | assert(result); 500 | }); 501 | }); 502 | describe('#case where a user has multiple roles', function(){ 503 | before(function(){ 504 | bot.messageParser = sinon.mock().resolves({ 505 | command : sendsTwelveObject, 506 | args : [] 507 | }); 508 | bot.authorizationFunction = function(message, bot, auth){ 509 | return Promise.resolve(['A', 'C', 'D']); 510 | }; 511 | return bot.onEventsMessage({ 512 | text : "opkit twelve", 513 | user : "user" 514 | }) 515 | .then(function(){ 516 | result = true; 517 | }) 518 | .catch(function(){ 519 | result = false; 520 | }); 521 | }); 522 | it('should match', function(){ 523 | assert(result); 524 | }); 525 | }); 526 | describe('#case where access is denied', function(){ 527 | before(function(){ 528 | bot.messageParser = sinon.mock().resolves({ 529 | command : sendsTwelveObject, 530 | args : [] 531 | }); 532 | bot.authorizationFunction = function(message, bot, auth){ 533 | return Promise.resolve(['A', 'E', 'F']); 534 | }; 535 | return bot.onEventsMessage({ 536 | text : "opkit twelve", 537 | user : "user" 538 | }) 539 | .then(function(){ 540 | result = true; 541 | }) 542 | .catch(function(){ 543 | result = false; 544 | }); 545 | }); 546 | it('should still resolve', function(){ 547 | assert(result); 548 | }); 549 | }); 550 | describe("#case where the command rejects promise", function(){ 551 | before(function(){ 552 | bot.messageParser = sinon.mock().resolves({ 553 | command : sendsTwelveObject, 554 | args : [] 555 | }); 556 | bot.authorizationFunction = function(message, bot, auth){ 557 | return Promise.resolve(['A', 'C', 'D']); 558 | }; 559 | bot.commands.twelve.command = function(message, bot, auth){ 560 | return Promise.reject("Bot failed to send the string literal '12'."); 561 | }; 562 | return bot.onEventsMessage({ 563 | text : "opkit twelve", 564 | user : "user" 565 | }).then(function(){ 566 | result = true; 567 | }) 568 | .catch(function(){ 569 | result = false; 570 | }); 571 | }); 572 | it('should still resolve', function(){ 573 | assert(result); 574 | }); 575 | }); 576 | }); 577 | }); 578 | --------------------------------------------------------------------------------