├── .DS_Store ├── .gitignore ├── .jshint ├── .travis.yml ├── API.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Procfile ├── README.md ├── app.js ├── app.json ├── images └── duel.gif ├── index.html ├── lib ├── api.js ├── app.js ├── hal.js ├── player_middleware.js ├── pong.js ├── routes.js └── url.js ├── models ├── Challenge.js └── Player.js ├── npm-shrinkwrap.json ├── package.json └── test ├── api_test.js ├── models └── Player_tests.js ├── player_middleware_tests.js ├── pong_tests.js ├── routes_test.js └── shared.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewvy/slack-pongbot/14627ce191a1374d7792a613d387e8d8fb4a4e85/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.jshint: -------------------------------------------------------------------------------- 1 | { 2 | "expr" : true 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | 6 | services: 7 | - mongodb 8 | 9 | script: 10 | - npm test 11 | - npm run lint 12 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | The Pongbot Hypermedia API provides access to players and challenges. Explore the API from the root. 4 | 5 | ```json 6 | { 7 | version: "0.9.0", 8 | _links: { 9 | self: { 10 | href: "http://localhost:3000/" 11 | }, 12 | players: { 13 | href: "http://localhost:3000/players" 14 | }, 15 | challenges: { 16 | href: "http://localhost:3000/challenges" 17 | } 18 | } 19 | } 20 | ``` 21 | 22 | ### Players 23 | 24 | Returns a collection of players. 25 | 26 | ### Challenges 27 | 28 | Returns a collection of challenges. 29 | 30 | ## Pagination 31 | 32 | Use `size` to limit the number of items returned and `anchor` to paginate through large collections. 33 | 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | * 2015/07/22: [#67](https://github.com/andrewvy/slack-pongbot/pull/67) - Quality-of-life improvements to Challenges, and a new Leaderboards Endpoint - [@geoffrey](https://github.com/geoffrey). 4 | * 2015/06/25: [#66](https://github.com/andrewvy/slack-pongbot/pull/66) - Any player can `chicken` out of an accepted challenge - [@101100](https://github.com/101100). 5 | * 2015/04/27: [#64](https://github.com/andrewvy/slack-pongbot/issues/64) - Fix: challenge created even when another challenge already exists - [@dblock](https://github.com/dblock). 6 | * 2015/04/26: [#54](https://github.com/andrewvy/slack-pongbot/issues/54) - Leaderboard Infinity is now case-insensitive - [@dblock](https://github.com/dblock). 7 | * 2015/04/26: [#56](https://github.com/andrewvy/slack-pongbot/issues/56) - Usernames are now case-insensitive - [@dblock](https://github.com/dblock). 8 | * 2015/04/26: [#37](https://github.com/andrewvy/slack-pongbot/issues/37) - Support @player - [@dblock](https://github.com/dblock). 9 | * 2015/04/25: [#40](https://github.com/andrewvy/slack-pongbot/issues/40) - Expose a Hypermedia API with pagination support - [@dblock](https://github.com/dblock). 10 | * 2015/04/24: [#55](https://github.com/andrewvy/slack-pongbot/pull/55) - API `matches` became `challenges` and `rankings` became `players` - [@dblock](https://github.com/dblock). 11 | * 2015/04/22: [#50](https://github.com/andrewvy/slack-pongbot/issues/33) - You can `chicken` out of your own challenge - [@dblock](https://github.com/dblock). 12 | * 2015/04/22: [#50](https://github.com/andrewvy/slack-pongbot/issues/50) - You can no longer accept or decline your own challenges - [@dblock](https://github.com/dblock). 13 | * 2015/04/21: [#49](https://github.com/andrewvy/slack-pongbot/pull/49) - Set LOG_LEVEL=debug to log the Slack hook data - [@dblock](https://github.com/dblock). 14 | * 2015/04/21: [#48](https://github.com/andrewvy/slack-pongbot/pull/48) - Use shrinkwrap and lock at node 10.x - [@dblock](https://github.com/dblock). 15 | * 2015/04/21: [#43](https://github.com/andrewvy/slack-pongbot/pull/43) - Unwrap callback hell with Q - [@dblock](https://github.com/dblock). 16 | * 2015/04/19: [#32](https://github.com/andrewvy/slack-pongbot/issues/32) - You can no longer challenge yourself - [@dblock](https://github.com/dblock). 17 | * 2015/04/19: [#29](https://github.com/andrewvy/slack-pongbot/pull/29) - Added `pongbot new_season ` to reset all user stats - [@dblock](https://github.com/dblock), [@ilyakava](https://github.com/ilyakava), [@jinpark](https://github.com/jinpark). 18 | * 2015/04/19: [#29](https://github.com/andrewvy/slack-pongbot/pull/29) - The `pongbot reset ` command now requires a secret - [@dblock](https://github.com/dblock), [@ilyakava](https://github.com/ilyakava). 19 | * 2015/04/19: [#29](https://github.com/andrewvy/slack-pongbot/pull/29) - Admin no-longer hard-coded, set `ADMIN_SECRET` - [@dblock](https://github.com/dblock), [@ilyakava](https://github.com/ilyakava), [@jinpark](https://github.com/jinpark). 20 | * 2015/04/19: [#28](https://github.com/andrewvy/slack-pongbot/pull/28) - Accept both challenge `single(s)` and `double(s)` - [@dblock](https://github.com/dblock), [@ilyakava](https://github.com/ilyakava), [@jinpark](https://github.com/jinpark). 21 | * 2015/04/17: [#19](https://github.com/andrewvy/slack-pongbot/pull/19) - Use jshint for code linting - [@dblock](https://github.com/dblock). 22 | * 2015/04/14: [#13](https://github.com/andrewvy/slack-pongbot/pull/13) - Fix: `undefined variable err` in `getEveryone` - [@dblock](https://github.com/dblock). 23 | * 2015/04/14: [#12](https://github.com/andrewvy/slack-pongbot/pull/12) - Added tests - [@dblock](https://github.com/dblock). 24 | * 2014/07/02: Initial public release - [@andrewvy](https://github.com/andrewvy). 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project is work of [many contributors](https://github.com/andrewvy/slack-pongbot/graphs/contributors). 4 | 5 | You're encouraged to submit [pull requests](https://github.com/andrewvy/slack-pongbot/pulls), [propose features and discuss issues](https://github.com/andrewvy/slack-pongbot/issues). 6 | 7 | In the examples below, substitute your Github username for `contributor` in URLs. 8 | 9 | ## Fork the Project 10 | 11 | Fork the [project on Github](https://github.com/andrewvy/slack-pongbot) and check out your copy. 12 | 13 | ``` 14 | git clone https://github.com/contributor/slack-pongbot.git 15 | cd slack-pongbot 16 | git remote add upstream https://github.com/andrewvy/slack-pongbot.git 17 | ``` 18 | 19 | ## Create a Topic Branch 20 | 21 | Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. 22 | 23 | ``` 24 | git checkout master 25 | git pull upstream master 26 | git checkout -b my-feature-branch 27 | ``` 28 | 29 | ## Bundle Install and Test 30 | 31 | Ensure that you can build the project and run tests and linter. 32 | 33 | ``` 34 | npm install 35 | npm test 36 | npm run lint 37 | ``` 38 | 39 | **Note**: You will need a copy of [MongoDB](https://www.mongodb.org/downloads) running on `localhost` in order to run the tests. You can learn how to install MongoDB and run it on `localhost` by opening the [Installation](http://docs.mongodb.org/master/installation/) section of the manual and then choosing “Install on [your OS]” from the sidebar at the left. 40 | 41 | ## Write Tests 42 | 43 | Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. 44 | 45 | We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. 46 | 47 | ## Write Code 48 | 49 | Implement your feature or bug fix. 50 | 51 | Make sure that `npm test` completes without errors. 52 | 53 | ## Write Documentation 54 | 55 | Document any external behavior in the [README](README.md). 56 | 57 | ## Update Changelog 58 | 59 | Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. 60 | Make it look like every other line, including your name and link to your Github account. 61 | 62 | ## Commit Changes 63 | 64 | Make sure git knows your name and email address: 65 | 66 | ``` 67 | git config --global user.name "Your Name" 68 | git config --global user.email "contributor@example.com" 69 | ``` 70 | 71 | Writing good commit logs is important. A commit log should describe what changed and why. 72 | 73 | ``` 74 | git add ... 75 | git commit 76 | ``` 77 | 78 | ## Push 79 | 80 | ``` 81 | git push origin my-feature-branch 82 | ``` 83 | 84 | ## Make a Pull Request 85 | 86 | Go to https://github.com/contributor/slack-pongbot and select your feature branch. 87 | Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. 88 | 89 | ## Rebase 90 | 91 | If you've been working on a change for a while, rebase with upstream/master. 92 | 93 | ``` 94 | git fetch upstream 95 | git rebase upstream/master 96 | git push origin my-feature-branch -f 97 | ``` 98 | 99 | ## Update CHANGELOG Again 100 | 101 | Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows. 102 | 103 | ``` 104 | * [#123](https://github.com/andrewvy/slack-pongbot/pull/123): Reticulated splines - [@contributor](https://github.com/contributor). 105 | ``` 106 | 107 | Amend your previous commit and force push the changes. 108 | 109 | ``` 110 | git commit --amend 111 | git push origin my-feature-branch -f 112 | ``` 113 | 114 | ## Check on Your Pull Request 115 | 116 | Go back to your pull request after a few minutes and see whether it passed with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. 117 | 118 | ## Be Patient 119 | 120 | It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! 121 | 122 | ## Thank You 123 | 124 | Please do know that we really appreciate and value your time and work. We love you, really. 125 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015, Andrew Vy and Contributors 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | 7 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node app.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | This repo was never meant to be public, I just happened to throw code together in a span of a day to have something fun. Unfortunately, the code quality is terrible and not fit for open source contributions. 4 | 5 | I highly recommend checking out one of these alternatives: 6 | 7 | ## Open source alternatives 8 | 9 | - [slack-gamebot](https://github.com/dblock/slack-gamebot) 10 | 11 | ## Hosted alternatives 12 | 13 | - [Playplay.io](http://playplay.io) 14 | - [Pinpon](https://pinpon.co) 15 | 16 | # Pongbot 17 | 18 | Slack Bot for Ping Pong tracking. 19 | 20 | [![Build Status](https://travis-ci.org/andrewvy/slack-pongbot.svg?branch=master)](https://travis-ci.org/andrewvy/slack-pongbot) 21 | 22 | [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) 23 | 24 | ![](images/duel.gif) 25 | 26 | # Installation 27 | 28 | Deploy with your preferred solution, eg. with [Heroku](https://devcenter.heroku.com/articles/getting-started-with-nodejs). 29 | 30 | Administrative commands require the `ADMIN_SECRET` environment to be set. 31 | 32 | ``` 33 | heroku config:add ADMIN_SECRET=secret 34 | ``` 35 | 36 | Visit `https://yourteamname.slack.com/services/new` and choose "Outgoing WebHooks." Choose which channels you would like pongbot active in, a trigger word with `pongbot`, and the url that you deployed to. 37 | 38 | # Using Pongbot from Slack 39 | 40 | Make sure you're registered with pongbot. 41 | 42 | ``` 43 | pongbot register 44 | ``` 45 | 46 | Challenge someone, or a team. 47 | 48 | Singles: 49 | 50 | 51 | ``` 52 | pongbot challenge singles 53 | ``` 54 | 55 | Doubles: 56 | 57 | ``` 58 | pongbot challenge doubles against 59 | ``` 60 | 61 | Let them run this, to accept the challenge. Only one other person (teammate or opponent) needs to accept to confirm the challenge. 62 | 63 | ``` 64 | pongbot accept 65 | ``` 66 | 67 | If you can't play now, `pongbot decline`. 68 | 69 | Game On! 70 | 71 | Record the match. Only the person/team that lost can record, it'll automatically change everyone's scores/rankings. 72 | 73 | ``` 74 | pongbot lost 75 | ``` 76 | 77 | 78 | # Other Commands 79 | 80 | * `pongbot decline` - Decline's any proposed match. 81 | * `pongbot chicken` - Chicken out of your own challenge before it is accepted, or out of an accepted challenge (as the challenger or challenged). 82 | * `pongbot leaderboard <1-infinity>` - Shows the top players, sorted by Elo. 83 | * `pongbot rank ` - Gets that person's stats. If none given, it will return your own stats. 84 | * `pongbot source` - Get's Pongbot's Github repository. 85 | * `pongbot reset ` - Admin-only command that reset's a person's stats. 86 | * `pongbot new_season ` - Admin-only command that reset's all stats and begins a new season. 87 | 88 | # API 89 | 90 | See [API Documentation](API.md). 91 | 92 | # License & Copyright 93 | 94 | Copyright (c) 2014-2015, Andrew Vy and Contributors 95 | 96 | ISC License, see [LICENSE](LICENSE) for details. 97 | 98 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // _____ _____ _ 2 | // | _ |___ ___ ___| __ |___| | 3 | // | __| . | | . | __ -| . | _| 4 | // |__| |___|_|_|_ |_____|___|_| 5 | // |___| 6 | 7 | 'use strict'; 8 | 9 | var mongoose = require('mongoose'); 10 | var mongoUri = process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || 'mongodb://localhost/pingpong'; 11 | mongoose.connect(mongoUri); 12 | 13 | var app = require('./lib/app').instance(); 14 | 15 | var port = process.env.PORT || 3000; 16 | app.listen(port); 17 | console.log('Listening on port', port); 18 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pongbot for Slack", 3 | "description": "A ping-pong Slack integration.", 4 | "respository": "https://github.com/andrewvy/slack-pongbot", 5 | "keywords": ["node", "pingpong", "slack", "integration"], 6 | "addons": [ 7 | "mongolab" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /images/duel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewvy/slack-pongbot/14627ce191a1374d7792a613d387e8d8fb4a4e85/images/duel.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Ping Pong Leaderboard 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 20 |

Leaderboard

21 |

No one has registered yet!

22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
RankPlayer NameGames PlayedWinsLossELO
{{ ($index + 1) }}{{ player.user_name }}{{ player.wins + player.losses }}{{ player.wins }}{{ player.losses }}{{ player.elo }}
40 | 41 |

How to participate?

42 |
43 |
Register first!
44 |
45 | Go to your ping-pong Slack channel and type pongbot register 46 |
47 |
48 | 49 |
50 |
Challenge someone!
51 |
52 | pongbot (challenge|vs) "player"or 53 |
54 | pongbot (challenge|vs) doubles "teammate's name" (against|vs) "opponent_1" "opponent_2" 55 |
56 |
57 | 58 |
59 |
Accept or decline the challenge...
60 |
61 | pongbot (ok|accept) 62 |
63 | pongbot (no|decline) 64 |
65 |
66 | 67 |
68 |
Results?
69 |
70 | Only the loser can finish a game by typongbot pongbot lost 71 |
72 |
73 | 74 |
75 |
See who is leading!
76 |
77 | pongbot leaderboard 78 |
79 |
80 | 81 |
82 |
What else?
83 |
84 | pongbot chicken Chicken out of your own challenge before it is accepted, or out of an accepted challenge (as the challenger or challenged). 85 |
86 | pongbot rank "playername" Gets that person's stats. If none given, it will return your own stats. 87 |
88 |
89 |
90 | 91 | 92 | 93 | 94 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /lib/api.js: -------------------------------------------------------------------------------- 1 | var Player = require('../models/Player'); 2 | var Challenge = require('../models/Challenge'); 3 | var pjson = require('../package.json'); 4 | var mongoose = require('mongoose'); 5 | var Q = require('q'); 6 | 7 | module.exports = { 8 | root: function (req, res) { 9 | res.hal({ 10 | data: { 11 | version: pjson.version 12 | }, 13 | links: { 14 | self: req.rootUrl() + '/', 15 | players: req.rootUrl() + '/players', 16 | challenges: req.rootUrl() + '/challenges', 17 | leaderboard: req.rootUrl() + '/leaderboard', 18 | } 19 | }); 20 | }, 21 | 22 | players: function (req, res) { 23 | res.halWithPagination(Player, req, res, function(players, req) { 24 | return { 25 | players: players.map(function(player) { 26 | return player.halJSON(req); 27 | }) 28 | }; 29 | }); 30 | }, 31 | 32 | leaderboard: function (req, res) { 33 | Player.find({ 34 | "$or" : [ 35 | { 'wins' : { '$ne' : 0 } }, 36 | { 'losses' : { '$ne' : 0 } 37 | }] 38 | }) 39 | .sort({ 'elo' : 'descending', 'wins' : 'descending' }) 40 | .find() 41 | .then(function (players) { 42 | res.hal({ 43 | data: { 44 | totalPages: 1 45 | }, 46 | embeds: { 47 | players: players.map(function(player) { 48 | return player.halJSON(req); 49 | }), 50 | }, 51 | links: { 52 | self: req.fullPath(), 53 | } 54 | }); 55 | }, 56 | function (err) { 57 | res.json({ text: "Error: " + err.message }); 58 | }); 59 | }, 60 | 61 | player: function (req, res) { 62 | var promises = []; 63 | if (mongoose.Types.ObjectId.isValid(req.params.id)) { 64 | promises.push(Player.findById(req.params.id)); 65 | } 66 | promises.push(Player.findOne({ user_name: req.params.id })); 67 | Q.any(promises).then( 68 | function (player) { 69 | if (player && player._id.toString() !== req.params.id) { 70 | res.redirect(302, req.rootUrl() + '/players/' + player._id); 71 | } else if (player) { 72 | res.hal(player.halJSON(req)); 73 | } else { 74 | res.status(404).send('Not Found'); 75 | } 76 | }, 77 | function (err) { 78 | return res.status(500).send(err); 79 | } 80 | ); 81 | }, 82 | 83 | challenges: function (req, res) { 84 | res.halWithPagination(Challenge, req, res, function(challenges, req) { 85 | return { 86 | challenges: challenges.map(function(challenge) { 87 | return challenge.halJSON(req); 88 | }) 89 | }; 90 | }); 91 | }, 92 | 93 | challenge: function (req, res) { 94 | Challenge.findById(req.params.id).then( 95 | function (challenge) { 96 | if (challenge) { 97 | res.hal(challenge.halJSON(req)); 98 | } else { 99 | res.status(404).send('Not Found'); 100 | } 101 | }, 102 | function (err) { 103 | return res.status(500).send(err); 104 | } 105 | ); 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /lib/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var hal = require("express-hal"); 3 | var bodyParser = require('body-parser'); 4 | var request = require('request'); 5 | 6 | var pong = require('./pong'); 7 | var pong_routes = require('./routes'); 8 | var api_routes = require('./api'); 9 | 10 | var Player = require('../models/Player'); 11 | var Challenge = require('../models/Challenge'); 12 | 13 | module.exports.instance = function () { 14 | var app = express(); 15 | app.use(bodyParser.urlencoded({ extended: false })); 16 | app.use(bodyParser.json()); 17 | app.use(hal.middleware); 18 | 19 | app.use(require('./url').middleware); 20 | app.use(require('./hal').middleware); 21 | app.use(require('./player_middleware').middleware); 22 | 23 | app.post('/', pong_routes.index); 24 | 25 | app.get('/', api_routes.root); 26 | app.get('/players', api_routes.players); 27 | app.get('/players/:id', api_routes.player); 28 | app.get('/challenges', api_routes.challenges); 29 | app.get('/challenges/:id', api_routes.challenge); 30 | app.get('/leaderboard', api_routes.leaderboard); 31 | 32 | app.get('/info', pong_routes.leaderboard); 33 | 34 | 35 | return app; 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /lib/hal.js: -------------------------------------------------------------------------------- 1 | module.exports.middleware = function(req, res, next) { 2 | res.halWithPagination = function (model, req, res, embeds) { 3 | var size = req.query.size || 10; 4 | model.findPaginated({}, null, { sort: { _id: 'ascending' }}, function (err, result) { 5 | if (err) return res.status(500).send(err); 6 | res.hal({ 7 | data: { 8 | totalPages: result.totalPages 9 | }, 10 | embeds: embeds(result.documents, req), 11 | links: { 12 | self: req.fullPath() + '?size=' + size + (req.query.anchor ? '&anchor=' + req.query.anchor : ''), 13 | next: result.nextAnchorId ? req.fullPath() + '?size=' + size + '&anchor=' + result.nextAnchorId : null 14 | } 15 | }); 16 | }, size, req.query.anchor); 17 | }; 18 | return next(); 19 | }; 20 | -------------------------------------------------------------------------------- /lib/player_middleware.js: -------------------------------------------------------------------------------- 1 | var Player = require('../models/Player'); 2 | var Challenge = require('../models/Challenge'); 3 | var pong = require('./pong'); 4 | var Q = require('q'); 5 | 6 | // a middlware that keeps player IDs and names in sync 7 | module.exports.middleware = function(req, res, next) { 8 | var hook = req.body; 9 | if (hook && hook.user_name && hook.user_id) { 10 | Player.where({ 11 | '$or' : [ 12 | { user_name: hook.user_name }, 13 | { user_id: hook.user_id } 14 | ]}).findOne().then( 15 | function (player) { 16 | if (player && ((player.user_id !== hook.user_id) || (player.user_name !== hook.user_name))) { 17 | 18 | if (process.env.LOG_LEVEL === 'debug') { 19 | console.log("Updating player '" + player.user_name + "' (" + player.user_id + ').'); 20 | } 21 | 22 | player.user_id = hook.user_id; 23 | player.user_name = hook.user_name; 24 | player.save().then( 25 | function () { 26 | return next(); 27 | }, 28 | function (err) { 29 | return next(); 30 | } 31 | ); 32 | } else { 33 | return next(); 34 | } 35 | }, 36 | function (err) { 37 | return next(); 38 | } 39 | ); 40 | } else { 41 | return next(); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /lib/pong.js: -------------------------------------------------------------------------------- 1 | var Player = require('../models/Player'); 2 | var Challenge = require('../models/Challenge'); 3 | var pluralize = require('pluralize'); 4 | var Q = require('q'); 5 | var deltaTau = 0.94; 6 | 7 | var pong = { 8 | 9 | registerPlayer: function (player_name, options) { 10 | var data = { 11 | user_name: player_name, 12 | wins: 0, 13 | losses: 0, 14 | elo: 0, 15 | tau: 0 16 | }; 17 | 18 | if (options) { 19 | for (var attrname in options) { 20 | data[attrname] = options[attrname]; 21 | } 22 | } 23 | 24 | return new Player(data).save(); 25 | }, 26 | 27 | registerPlayers: function (player_names) { 28 | return Q.all(player_names.map(pong.registerPlayer)); 29 | }, 30 | 31 | findPlayer: function (player_name) { 32 | var q = null; 33 | if (player_name[0] === '<' && player_name[1] === '@' && player_name[player_name.length - 1] === '>') { 34 | q = { user_id: player_name.substr(2, player_name.length - 3) }; 35 | } else { 36 | var re = new RegExp('^' + player_name + '$', 'i'); 37 | q = { user_name: { $regex: re } }; 38 | } 39 | return Player.where(q).findOne() 40 | .then(function (player) { 41 | var deferred = Q.defer(); 42 | if (player) { 43 | deferred.resolve(player); 44 | } else { 45 | deferred.reject(new Error("Player '" + player_name + "' does not exist.")); 46 | } 47 | return deferred.promise; 48 | }); 49 | }, 50 | 51 | findPlayers: function(player_names) { 52 | return Q.all(player_names.map(pong.findPlayer)); 53 | }, 54 | 55 | updateWins: function (player_names) { 56 | return pong.findPlayers(player_names).then(function(players) { 57 | return Q.all(players.map(function (player) { 58 | player.wins++; 59 | return player.save(); 60 | })); 61 | }); 62 | }, 63 | 64 | updateLosses: function (player_names) { 65 | return pong.findPlayers(player_names).then(function(players) { 66 | return Q.all(players.map(function (player) { 67 | player.losses++; 68 | return player.save(); 69 | })); 70 | }); 71 | }, 72 | 73 | checkChallenge: function (player_names) { 74 | return pong.findPlayers(player_names).then(function(players) { 75 | return Q.all(players.map(function (player) { 76 | var deferred = Q.defer(); 77 | Challenge.findOne({ _id: player.currentChallenge }).then(function (challenge) { 78 | if (challenge) { 79 | deferred.reject(new Error("There's already an active challenge between " + challenge.challenger.join(' and ') + ' and ' + challenge.challenged.join(' and ') + '.')); 80 | } else { 81 | deferred.resolve(player); 82 | } 83 | }, function(err) { 84 | deferred.reject(err); 85 | }); 86 | return deferred.promise; 87 | })); 88 | }); 89 | }, 90 | 91 | setChallenge: function (player_names, challenge_id) { 92 | return pong.findPlayers(player_names).then(function(players) { 93 | return Q.all(players.map(function (player) { 94 | player.currentChallenge = challenge_id; 95 | return player.save(); 96 | })); 97 | }); 98 | }, 99 | 100 | ensureUniquePlayers: function(players) { 101 | return pong.findPlayers(players).then(function(players) { 102 | var playerCounts = players.reduce(function (acc, curr) { 103 | curr = curr.user_name; 104 | if (typeof acc[curr] == 'undefined') { 105 | acc[curr] = 1; 106 | } else { 107 | acc[curr] += 1; 108 | } 109 | return acc; 110 | }, {}); 111 | 112 | return Q.all(Object.keys(playerCounts).map(function (key) { 113 | var deferred = Q.defer(); 114 | var count = playerCounts[key]; 115 | if (count > 1) { 116 | deferred.reject(new Error('Does ' + key + ' have ' + 2 * count + ' hands?')); 117 | } else { 118 | deferred.resolve(key); 119 | } 120 | return deferred.promise; 121 | })); 122 | }); 123 | }, 124 | 125 | createSingleChallenge: function (c1, c2) { 126 | return pong.ensureUniquePlayers([c1, c2]).then().spread(function (c1, c2) { 127 | return pong.checkChallenge([c1, c2]).then(function () { 128 | return new Challenge({ 129 | state: 'Proposed', 130 | type: 'Singles', 131 | date: Date.now(), 132 | challenger: [c1], 133 | challenged: [c2] 134 | }).save().then(function (challenge) { 135 | return pong.setChallenge([c1, c2], challenge._id).then(function () { 136 | var deferred = Q.defer(); 137 | deferred.resolve({ message: c1 + ' has challenged ' + c2 + ' to a ping pong match!', challenge: challenge }); 138 | return deferred.promise; 139 | }); 140 | }); 141 | }); 142 | }); 143 | }, 144 | 145 | createDoubleChallenge: function (c1, c2, c3, c4) { 146 | return pong.ensureUniquePlayers([c1, c2, c3, c4]).then().spread(function (c1, c2, c3, c4) { 147 | return pong.checkChallenge([c1, c2, c3, c4]).then(function () { 148 | return new Challenge({ 149 | state: 'Proposed', 150 | type: 'Doubles', 151 | date: Date.now(), 152 | challenger: [c1, c2], 153 | challenged: [c3, c4] 154 | }).save().then(function (challenge) { 155 | return pong.setChallenge([c1, c2, c3, c4], challenge._id).then(function () { 156 | var deferred = Q.defer(); 157 | deferred.resolve({ message: c1 + ' and ' + c2 + ' have challenged ' + c3 + ' and ' + c4 + ' to a ping pong match!', challenge: challenge }); 158 | return deferred.promise; 159 | }); 160 | }); 161 | }); 162 | }); 163 | }, 164 | 165 | acceptChallenge: function (player_name) { 166 | return findChallengeForPlayer(player_name).then(function (challenge) { 167 | var deferred = Q.defer(); 168 | if (challenge && challenge.state == 'Proposed') { 169 | if (player_name == challenge.challenger[0] || (challenge.challenger[1] && player_name === challenge.challenger[1])) { 170 | deferred.reject(new Error('Please wait for ' + challenge.challenged.join(' or ') + " to accept your challenge.")); 171 | } else { 172 | challenge.state = 'Accepted'; 173 | Q.when(challenge.save(), function (challenge) { 174 | deferred.resolve({ message: player_name + ' accepted ' + challenge.challenger.join(' and ') + "'s challenge.", challenge: challenge }); 175 | }); 176 | } 177 | } else if (challenge && challenge.state == 'Accepted') { 178 | deferred.reject(new Error('You have already accepted ' + challenge.challenger.join(' and ') + "'s challenge.")); 179 | } else { 180 | deferred.reject(new Error("No challenge to accept.")); 181 | } 182 | return deferred.promise; 183 | }); 184 | }, 185 | 186 | declineChallenge: function (player_name) { 187 | return findChallengeForPlayer(player_name).then(function (challenge) { 188 | var deferred = Q.defer(); 189 | if (challenge && challenge.state == 'Proposed') { 190 | if (player_name == challenge.challenger[0]) { 191 | deferred.reject(new Error('Please wait for ' + challenge.challenged.join(' or ') + " to accept or decline your challenge.")); 192 | } else { 193 | challenge.state = 'Declined'; 194 | saveChallengeAndUpdatePlayer(challenge).then(function () { 195 | if (challenge.challenger[1] && player_name === challenge.challenger[1]) { 196 | deferred.resolve({ message: player_name + ' declined ' + challenge.challenger[0] + "'s challenge against " + challenge.challenged.join(' and ') + '.', challenge: challenge }); 197 | } else { 198 | deferred.resolve({ message: player_name + ' declined ' + challenge.challenger.join(' and ') + "'s challenge.", challenge: challenge }); 199 | } 200 | }); 201 | } 202 | } else { 203 | deferred.reject(new Error("No challenge to decline.")); 204 | } 205 | return deferred.promise; 206 | }); 207 | }, 208 | 209 | chickenChallenge: function (player_name) { 210 | function chickenedMessageText(player_name, who_challenge_is_against) { 211 | return player_name + ' chickened out of the challenge against ' + who_challenge_is_against.join(' and ') + "."; 212 | } 213 | 214 | return findChallengeForPlayer(player_name).then(function (challenge) { 215 | var deferred = Q.defer(); 216 | if (challenge && challenge.state == 'Proposed') { 217 | if (player_name == challenge.challenger[0]) { 218 | challenge.state = 'Chickened'; 219 | saveChallengeAndUpdatePlayer(challenge).then(function () { 220 | deferred.resolve({ message: chickenedMessageText(player_name, challenge.challenged), challenge: challenge }); 221 | }); 222 | } else { 223 | deferred.reject(new Error('Only ' + challenge.challenger[0] + ' can do that.')); 224 | } 225 | } else if (challenge && challenge.state == 'Accepted') { 226 | challenge.state = 'Chickened'; 227 | saveChallengeAndUpdatePlayer(challenge).then(function () { 228 | var who_challenge_is_against = 229 | (player_name == challenge.challenger[0] || player_name == challenge.challenger[1]) ? 230 | challenge.challenged : challenge.challenger; 231 | deferred.resolve({ message: chickenedMessageText(player_name, who_challenge_is_against), challenge: challenge }); 232 | }); 233 | } else { 234 | deferred.reject(new Error("First, challenge someone!")); 235 | } 236 | return deferred.promise; 237 | }); 238 | }, 239 | 240 | calculateTeamElo: function (p1, p2) { 241 | return pong.findPlayers([p1, p2]).then().spread(function(p1, p2) { 242 | var deferred = Q.defer(); 243 | deferred.resolve((p1.elo + p2.elo) / 2); 244 | return deferred.promise; 245 | }); 246 | }, 247 | 248 | eloSinglesChange: function (winner_name, loser_name) { 249 | return pong.findPlayers([winner_name, loser_name]).then(function(players) { 250 | var winner = players[0]; 251 | var loser = players[1]; 252 | var e = 100 - Math.round(1 / (1 + Math.pow(10, (loser.elo - winner.elo) / 400)) * 100); 253 | winner.tau = winner.tau || 0; 254 | winner.tau = winner.tau + 0.5; 255 | winner.elo = winner.elo + Math.round(e * Math.pow(deltaTau, winner.tau)); 256 | loser.tau = loser.tau || 0; 257 | loser.tau = loser.tau + 0.5; 258 | loser.elo = loser.elo - Math.round(e * Math.pow(deltaTau, loser.tau)); 259 | return Q.all([winner.save(), loser.save()]); 260 | }); 261 | }, 262 | 263 | eloDoublesChange: function (p1, p2, p3, p4) { 264 | return pong.calculateTeamElo(p1, p2).then(function (t1) { 265 | return pong.calculateTeamElo(p3, p4).then(function (t2) { 266 | return pong.findPlayers([p1, p2, p3, p4]).then(function(players) { 267 | 268 | var u1 = players[0]; 269 | var u2 = players[1]; 270 | var u3 = players[2]; 271 | var u4 = players[3]; 272 | 273 | var e = 100 - Math.round(1 / (1 + Math.pow(10, (t2 - u1.elo) / 400)) * 100); 274 | var e2 = 100 - Math.round(1 / (1 + Math.pow(10, (t2 - u2.elo) / 400)) * 100); 275 | var e3 = 100 - Math.round(1 / (1 + Math.pow(10, (u3.elo - t1) / 400)) * 100); 276 | var e4 = 100 - Math.round(1 / (1 + Math.pow(10, (u4.elo - t1) / 400)) * 100); 277 | 278 | u1.tau = u1.tau || 0; 279 | u1.tau = u1.tau + 0.5; 280 | u1.elo = u1.elo + Math.round(e * Math.pow(deltaTau, u1.tau)); 281 | u2.tau = u2.tau || 0; 282 | u2.tau = u2.tau + 0.5; 283 | u2.elo = u2.elo + Math.round(e2 * Math.pow(deltaTau, u2.tau)); 284 | u3.tau = u3.tau || 0; 285 | u3.tau = u3.tau + 0.5; 286 | u3.elo = u3.elo - Math.round(e3 * Math.pow(deltaTau, u3.tau)); 287 | u4.tau = u4.tau || 0; 288 | u4.tau = u4.tau + 0.5; 289 | u4.elo = u4.elo - Math.round(e4 * Math.pow(deltaTau, u4.tau)); 290 | 291 | return Q.all([u1.save(), u2.save(), u3.save(), u4.save()]); 292 | }); 293 | }); 294 | }); 295 | }, 296 | 297 | win: function (player_name) { 298 | return findChallengeForPlayer(player_name).then(function (challenge) { 299 | var deferred = Q.defer(); 300 | if (challenge && challenge.state == 'Proposed') { 301 | deferred.reject(new Error("Challenge needs to be accepted before recording match.")); 302 | } else if (challenge && challenge.state == 'Accepted') { 303 | challenge.state = 'Finished'; 304 | saveChallengeAndUpdatePlayer(challenge).then(function () { 305 | var winners, losers; 306 | if (player_name === challenge.challenger[0] || (challenge.challenger[1] && player_name === challenge.challenger[1])) { 307 | winners = challenge.challenger; 308 | losers = challenge.challenged; 309 | } else { 310 | losers = challenge.challenger; 311 | winners = challenge.challenged; 312 | } 313 | return (challenge.type == 'Singles' ? pong.eloSinglesChange(winners[0], losers[0]) : pong.eloDoublesChange(winners[0], winners[1], losers[0], losers[1])).then(function () { 314 | return pong.updateWins(winners).then(function () { 315 | return pong.updateLosses(losers).then(function () { 316 | deferred.resolve({ message: 'Match has been recorded, ' + winners.join(' and ') + ' defeated ' + losers.join(' and ') + '.', challenge: challenge }); 317 | }); 318 | }); 319 | }); 320 | }); 321 | } else { 322 | deferred.reject(new Error("No challenge to record.")); 323 | } 324 | return deferred.promise; 325 | }); 326 | }, 327 | 328 | lose: function (player_name) { 329 | return findChallengeForPlayer(player_name).then(function (challenge) { 330 | var deferred = Q.defer(); 331 | if (challenge && ((player_name === challenge.challenged[0]) || (challenge.challenged[1] && player_name === challenge.challenged[1]))) { 332 | return pong.win(challenge.challenger[0]); 333 | } else if (challenge) { 334 | return pong.win(challenge.challenged[0]); 335 | } else { 336 | deferred.reject(new Error("No challenge to record.")); 337 | } 338 | return deferred.promise; 339 | }); 340 | }, 341 | 342 | resetAll: function() { 343 | return Player.update( 344 | {}, 345 | { currentChallenge: null, wins: 0, losses: 0, elo: 0, tau: 1 }, 346 | { multi: true } 347 | ); 348 | }, 349 | 350 | reset: function (player_name) { 351 | return pong.findPlayer(player_name).then(function (player) { 352 | player.wins = 0; 353 | player.losses = 0; 354 | player.elo = 0; 355 | player.tau = 1; 356 | return player.save(); 357 | }); 358 | }, 359 | 360 | getDuelGif: function (cb) { 361 | var gifs = [ 362 | 'http://i235.photobucket.com/albums/ee210/f4nt0mh43d/BadDuel.gif', 363 | 'http://31.media.tumblr.com/99b8b1af381990801020079ae223a526/tumblr_mrbe6wQqR91sdds6qo1_500.gif', 364 | 'http://stream1.gifsoup.com/view3/1147041/duel-dollars-ending-o.gif', 365 | 'https://i.chzbgr.com/maxW500/5233508864/hC54C768C/', 366 | 'http://global3.memecdn.com/it-amp-039-s-time-to-duel_o_1532701.jpg', 367 | 'http://iambrony.dget.cc/mlp/gif/172595__UNOPT__safe_animated_trixie_spoiler-s03e05_magic-duel.gif', 368 | 'https://i.chzbgr.com/maxW500/2148438784/h7857A12F/', 369 | 'https://i.chzbgr.com/maxW500/3841869568/h2814E598/', 370 | 'http://24.media.tumblr.com/4e71f3df088eefed3d08ce4ce34e8d62/tumblr_mhyjqdJZ1g1s3r24zo1_500.gif', 371 | 'http://i.imgur.com/P5LVOmg.gif', 372 | 'http://i.imgur.com/SqF0q3h.gif', 373 | 'http://i.imgur.com/CvUl6jj.gif', 374 | 'http://i.imgur.com/FH4GErU.gif', 375 | 'http://i.imgur.com/Y9i4axg.gif', 376 | 'http://i.imgur.com/ib7O7VW.gif', 377 | 'http://i.imgur.com/rABWS5B.gif', 378 | 'http://i.imgur.com/r6JqmcQ.gif', 379 | 'http://i.imgur.com/rbZimEm.gif', 380 | 'http://i.imgur.com/st4lx7J.gif', 381 | 'http://i.imgur.com/xVHUe.gif', 382 | 'http://i.imgur.com/NDCUDvZ.gif', 383 | 'http://i.imgur.com/849OAOZ.gif', 384 | 'http://i.imgur.com/uAKLmb8.gif', 385 | 'http://i.imgur.com/VMZhdeW.gif', 386 | 'http://i.imgur.com/IfFqf.gif', 387 | 'http://i.imgur.com/ICuvQ92.gif', 388 | 'http://i.imgur.com/UiDlw3K.gif', 389 | 'http://i.imgur.com/9HwUmYY.gif' 390 | ]; 391 | var deferred = Q.defer(); 392 | deferred.resolve(gifs[Math.floor(Math.random() * gifs.length)]); 393 | return deferred.promise; 394 | }, 395 | }; 396 | 397 | function findChallengeForPlayer(player_name) { 398 | return pong.findPlayer(player_name).then(function(player) { 399 | return Challenge.findOne({ _id: player.currentChallenge }); 400 | }); 401 | } 402 | 403 | function saveChallengeAndUpdatePlayer(challenge) { 404 | return Q.when(challenge.save(), function (challenge) { 405 | Player.update({ currentChallenge: challenge._id }, { currentChallenge: null }, { multi: true }); 406 | }); 407 | } 408 | 409 | module.exports = pong; 410 | -------------------------------------------------------------------------------- /lib/routes.js: -------------------------------------------------------------------------------- 1 | var Player = require('../models/Player'); 2 | var Challenge = require('../models/Challenge'); 3 | var pong = require('./pong'); 4 | var Q = require('q'); 5 | var _ = require('underscore'); 6 | var path = require('path'); 7 | 8 | 9 | module.exports.leaderboard = function (req, res) { 10 | res.sendFile(path.join(__dirname + '/../index.html')); 11 | }; 12 | 13 | module.exports.index = function (req, res) { 14 | var hook = req.body; 15 | 16 | if (hook) { 17 | 18 | if (process.env.LOG_LEVEL === 'debug') { 19 | console.log(hook); 20 | } 21 | 22 | var params = _.compact(hook.text.split(' ')); 23 | var command = params[1].toLowerCase(); 24 | 25 | switch (command) { 26 | case 'register': 27 | if (params.length != 2) { 28 | res.json({ text: "Invalid params, use _pongbot register_." }); 29 | } else { 30 | var promises = []; 31 | promises.push(pong.findPlayer(hook.user_name)); 32 | if (hook.user_id) { 33 | promises.push(pong.findPlayer('<@' + hook.user_id + '>')); 34 | } 35 | Q.any(promises).then( 36 | function (player) { 37 | res.json({ text: "You've already registered!" }); 38 | }, 39 | function (err) { 40 | pong.registerPlayer(hook.user_name, { user_id: hook.user_id }).then(function (player) { 41 | res.json({ text: 'Successfully registered! Welcome to the system, ' + hook.user_name + '.' }); 42 | }, function (err) { 43 | res.json({ text: err.toString() }); 44 | }); 45 | } 46 | ); 47 | } 48 | break; 49 | 50 | case 'vs': 51 | case 'challenge': 52 | pong.findPlayer(hook.user_name).then( 53 | function (player) { 54 | if ((params[2] == 'double' || params[2] == 'doubles') && (params[4] == 'against' || params[4] == 'vs') && (params.length == 7)) { 55 | return pong.createDoubleChallenge(hook.user_name, params[3], params[5], params[6]).then( 56 | function (result) { 57 | return pong.getDuelGif().then(function (url) { 58 | res.json({ text: result.message + ' ' + url }); 59 | }); 60 | }, 61 | function (err) { 62 | return res.json({ text: err.toString() }); 63 | } 64 | ); 65 | } else if ((params[2] == 'single' || params[2] == 'singles') && (params.length == 4)) { 66 | return pong.createSingleChallenge(hook.user_name, params[3]).then( 67 | function (result) { 68 | return pong.getDuelGif().then(function (url) { 69 | res.json({ text: result.message + ' ' + url }); 70 | }); 71 | }, 72 | function (err) { 73 | return res.json({ text: err.toString() }); 74 | } 75 | ); 76 | } else if (params.length == 3) { 77 | return pong.createSingleChallenge(hook.user_name, params[2]).then( 78 | function (result) { 79 | return pong.getDuelGif().then(function (url) { 80 | res.json({ text: result.message + ' ' + url }); 81 | }); 82 | }, 83 | function (err) { 84 | return res.json({ text: err.toString() }); 85 | } 86 | ); 87 | } else { 88 | res.json({ text: "Invalid params, use _pongbot challenge against _." }); 89 | } 90 | }, 91 | function (err) { 92 | res.json({ text: err.message + " Are you registered? Use _pongbot register_ first." }); 93 | } 94 | ); 95 | break; 96 | 97 | case 'ok': 98 | case 'accept': 99 | if (params.length != 2) { 100 | res.json({ text: "Invalid params, use _pongbot accept_." }); 101 | } else { 102 | pong.acceptChallenge(hook.user_name).then( 103 | function (result) { 104 | res.json({ text: result.message }); 105 | }, 106 | function (err) { 107 | res.json({ text: err.message }); 108 | } 109 | ); 110 | } 111 | break; 112 | 113 | case 'no': 114 | case 'decline': 115 | if (params.length != 2) { 116 | res.json({ text: "Invalid params, use _pongbot decline_." }); 117 | } else { 118 | pong.declineChallenge(hook.user_name).then( 119 | function (result) { 120 | res.json({ text: result.message }); 121 | }, 122 | function (err) { 123 | res.json({ text: err.message }); 124 | } 125 | ); 126 | } 127 | break; 128 | 129 | case 'chicken': 130 | if (params.length != 2) { 131 | res.json({ text: "Invalid params, use _pongbot chicken_." }); 132 | } else { 133 | pong.chickenChallenge(hook.user_name).then( 134 | function (result) { 135 | res.json({ text: result.message }); 136 | }, 137 | function (err) { 138 | res.json({ text: err.message }); 139 | } 140 | ); 141 | } 142 | break; 143 | 144 | case 'lost': 145 | if (params.length != 2) { 146 | res.json({ text: "Invalid params, use _pongbot lost_." }); 147 | } else { 148 | pong.lose(hook.user_name).then( 149 | function (result) { 150 | res.json({ text: result.message }); 151 | }, 152 | function (err) { 153 | res.json({ text: err.message }); 154 | } 155 | ); 156 | } 157 | break; 158 | 159 | case 'won': 160 | res.json({ text: 'Only the player/team that lost can record the game.' }); 161 | break; 162 | 163 | case 'rank': 164 | if ((params.length != 2) && (params.length != 3)) { 165 | res.json({ text: "Invalid params, use _pongbot rank _." }); 166 | } else { 167 | pong.findPlayer(params[2] || hook.user_name).then( 168 | function (player) { 169 | res.json({ text: player.toString() }); 170 | }, 171 | function (err) { 172 | res.json({ text: err.message }); 173 | } 174 | ); 175 | } 176 | break; 177 | 178 | case 'leaderboard': 179 | var topN = null; 180 | 181 | if (params[2] && params[2].toLowerCase() === 'infinity') { 182 | topN = Infinity; 183 | } else if (params[2] && !isNaN(parseFloat(params[2])) && isFinite(params[2])) { 184 | topN = +params[2]; 185 | } else { 186 | topN = 5; 187 | } 188 | 189 | if (topN <= 0) { 190 | res.json({ text: "Invalid params, use _pongbot leaderboard <1-Infinity>_." }); 191 | } else { 192 | Player.find({ 193 | "$or" : [ 194 | { 'wins' : { '$ne' : 0 } }, 195 | { 'losses' : { '$ne' : 0 } 196 | }] 197 | }) 198 | .sort({ 'elo' : 'descending', 'wins' : 'descending' }) 199 | .limit(topN) 200 | .find().then( 201 | function (players) { 202 | res.json({ text: Player.toString(players) }); 203 | }, 204 | function (err) { 205 | res.json({ text: "Error: " + err.message }); 206 | } 207 | ); 208 | } 209 | break; 210 | 211 | case 'reset': 212 | if (!process.env.ADMIN_SECRET) { 213 | res.json({ text: 'Error: ADMIN_SECRET not set.' }); 214 | } else if (process.env.ADMIN_SECRET !== params[3]) { 215 | res.json({ text: "Invalid secret. Use _pongbot reset _." }); 216 | } else { 217 | pong.reset(params[2]).then( 218 | function (result) { 219 | res.json({ text: params[2] + "'s stats have been reset." }); 220 | }, 221 | function (err) { 222 | res.json({ text: err.message }); 223 | } 224 | ); 225 | } 226 | break; 227 | 228 | case 'new_season': 229 | if (!process.env.ADMIN_SECRET) { 230 | res.json({ text: 'Error: ADMIN_SECRET not set.' }); 231 | } else if (process.env.ADMIN_SECRET !== params[2]) { 232 | res.json({ text: "Invalid secret. Use _pongbot new_season _." }); 233 | } else { 234 | pong.resetAll().then( 235 | function (result) { 236 | res.json({ text: 'Welcome to the new season!' }); 237 | }, 238 | function (err) { 239 | res.json({ text: err.message }); 240 | } 241 | ); 242 | } 243 | break; 244 | 245 | case 'source': 246 | res.json({ text: 'https://github.com/andrewvy/slack-pongbot' }); 247 | break; 248 | 249 | case 'help': 250 | res.json({ text: 'https://github.com/andrewvy/slack-pongbot' }); 251 | break; 252 | 253 | case 'hug': 254 | res.json({ text: 'No.' }); 255 | break; 256 | 257 | case 'sucks': 258 | res.json({ text: 'No, you suck.' }); 259 | break; 260 | 261 | default: 262 | res.json({ text: "I couldn't understand that command. Use _pongbot help_ to get a list of available commands." }); 263 | break; 264 | } 265 | } 266 | }; 267 | -------------------------------------------------------------------------------- /lib/url.js: -------------------------------------------------------------------------------- 1 | module.exports.middleware = function(req, res, next) { 2 | req.rootUrl = function() { 3 | var port = req.app.settings.port; 4 | res.locals.requested_url = req.protocol + '://' + req.hostname + (port == 80 || port == 443 ? '' : ':' + port); 5 | return req.protocol + "://" + req.get('host'); 6 | }; 7 | req.fullPath = function() { 8 | return req.rootUrl() + req.path; 9 | }; 10 | return next(); 11 | }; 12 | -------------------------------------------------------------------------------- /models/Challenge.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var mongoosePages = require('mongoose-pages'); 3 | var Schema = mongoose.Schema; 4 | 5 | var ChallengeSchema = new Schema({ 6 | state: String, 7 | type: String, 8 | date: Date, 9 | challenger: Array, 10 | challenged: Array 11 | }); 12 | 13 | mongoosePages.anchor(ChallengeSchema); 14 | 15 | ChallengeSchema.methods = { 16 | halJSON: function (req) { 17 | return { 18 | data: { 19 | type: this.type, 20 | state: this.state, 21 | date: this.date 22 | }, 23 | links: { 24 | self: req.rootUrl() + '/challenges/' + this._id, 25 | challengers: this.challenger.map(function(player) { 26 | return { href: req.rootUrl() + '/players/' + player }; 27 | }), 28 | challenged: this.challenged.map(function(player) { 29 | return { href: req.rootUrl() + '/players/' + player }; 30 | }) 31 | } 32 | }; 33 | } 34 | }; 35 | 36 | var Challenge = mongoose.model('Challenge', ChallengeSchema); 37 | module.exports = Challenge; 38 | -------------------------------------------------------------------------------- /models/Player.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var mongoosePages = require('mongoose-pages'); 3 | var Schema = mongoose.Schema; 4 | var pluralize = require('pluralize'); 5 | 6 | var playerSchema = new Schema({ 7 | user_id: String, 8 | user_name: { type: String, index: { unique: true }, required: true }, 9 | wins: Number, 10 | losses: Number, 11 | elo: Number, 12 | tau: Number, 13 | currentChallenge: { type: Schema.Types.ObjectId, ref: 'Challenge' } 14 | }); 15 | 16 | playerSchema.methods = { 17 | halJSON: function (req) { 18 | return { 19 | data: { 20 | user_id: this.user_id, 21 | user_name: this.user_name, 22 | wins: this.wins, 23 | losses: this.losses, 24 | elo: this.elo 25 | }, 26 | links: { 27 | self: req.rootUrl() + '/players/' + this._id, 28 | } 29 | }; 30 | }, 31 | 32 | toString: function() { 33 | return this.user_name + ": " + pluralize('win', this.wins, true) + " " + pluralize('loss', this.losses, true) + " (elo: " + this.elo + ")"; 34 | } 35 | }; 36 | 37 | playerSchema.statics = { 38 | toString: function(players) { 39 | var rank = 1; 40 | var out = ''; 41 | players.forEach(function(player, i) { 42 | if (players[i - 1] && players[i - 1].elo != player.elo) { 43 | rank = i + 1; 44 | } 45 | out += rank + ". " + player.toString() + "\n"; 46 | }); 47 | return out; 48 | } 49 | }; 50 | 51 | mongoosePages.anchor(playerSchema); 52 | 53 | var Player = mongoose.model('Player', playerSchema); 54 | module.exports = Player; 55 | -------------------------------------------------------------------------------- /npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pongbot", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "body-parser": { 6 | "version": "1.12.3", 7 | "from": "body-parser@", 8 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.12.3.tgz", 9 | "dependencies": { 10 | "bytes": { 11 | "version": "1.0.0", 12 | "from": "bytes@1.0.0", 13 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz" 14 | }, 15 | "content-type": { 16 | "version": "1.0.1", 17 | "from": "content-type@~1.0.1", 18 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" 19 | }, 20 | "debug": { 21 | "version": "2.1.3", 22 | "from": "debug@~2.1.3", 23 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.1.3.tgz", 24 | "dependencies": { 25 | "ms": { 26 | "version": "0.7.0", 27 | "from": "ms@0.7.0", 28 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.0.tgz" 29 | } 30 | } 31 | }, 32 | "depd": { 33 | "version": "1.0.1", 34 | "from": "depd@~1.0.0", 35 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz" 36 | }, 37 | "iconv-lite": { 38 | "version": "0.4.8", 39 | "from": "iconv-lite@0.4.8", 40 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.8.tgz" 41 | }, 42 | "on-finished": { 43 | "version": "2.2.0", 44 | "from": "on-finished@~2.2.0", 45 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.0.tgz", 46 | "dependencies": { 47 | "ee-first": { 48 | "version": "1.1.0", 49 | "from": "ee-first@1.1.0", 50 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" 51 | } 52 | } 53 | }, 54 | "qs": { 55 | "version": "2.4.1", 56 | "from": "qs@2.4.1", 57 | "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.1.tgz" 58 | }, 59 | "raw-body": { 60 | "version": "1.3.4", 61 | "from": "raw-body@1.3.4", 62 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.4.tgz" 63 | }, 64 | "type-is": { 65 | "version": "1.6.1", 66 | "from": "type-is@~1.6.1", 67 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.1.tgz", 68 | "dependencies": { 69 | "media-typer": { 70 | "version": "0.3.0", 71 | "from": "media-typer@0.3.0", 72 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" 73 | }, 74 | "mime-types": { 75 | "version": "2.0.10", 76 | "from": "mime-types@~2.0.10", 77 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.10.tgz", 78 | "dependencies": { 79 | "mime-db": { 80 | "version": "1.8.0", 81 | "from": "mime-db@~1.8.0", 82 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.8.0.tgz" 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | }, 90 | "express": { 91 | "version": "4.12.3", 92 | "from": "express@^4.10.6", 93 | "resolved": "https://registry.npmjs.org/express/-/express-4.12.3.tgz", 94 | "dependencies": { 95 | "accepts": { 96 | "version": "1.2.5", 97 | "from": "accepts@~1.2.5", 98 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.5.tgz", 99 | "dependencies": { 100 | "mime-types": { 101 | "version": "2.0.10", 102 | "from": "mime-types@~2.0.10", 103 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.10.tgz", 104 | "dependencies": { 105 | "mime-db": { 106 | "version": "1.8.0", 107 | "from": "mime-db@~1.8.0", 108 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.8.0.tgz" 109 | } 110 | } 111 | }, 112 | "negotiator": { 113 | "version": "0.5.1", 114 | "from": "negotiator@0.5.1", 115 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.1.tgz" 116 | } 117 | } 118 | }, 119 | "content-disposition": { 120 | "version": "0.5.0", 121 | "from": "content-disposition@0.5.0", 122 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz" 123 | }, 124 | "content-type": { 125 | "version": "1.0.1", 126 | "from": "content-type@~1.0.1", 127 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" 128 | }, 129 | "cookie": { 130 | "version": "0.1.2", 131 | "from": "cookie@0.1.2", 132 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz" 133 | }, 134 | "cookie-signature": { 135 | "version": "1.0.6", 136 | "from": "cookie-signature@1.0.6", 137 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" 138 | }, 139 | "debug": { 140 | "version": "2.1.3", 141 | "from": "debug@~2.1.3", 142 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.1.3.tgz", 143 | "dependencies": { 144 | "ms": { 145 | "version": "0.7.0", 146 | "from": "ms@0.7.0", 147 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.0.tgz" 148 | } 149 | } 150 | }, 151 | "depd": { 152 | "version": "1.0.1", 153 | "from": "depd@~1.0.0", 154 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz" 155 | }, 156 | "escape-html": { 157 | "version": "1.0.1", 158 | "from": "escape-html@1.0.1", 159 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz" 160 | }, 161 | "etag": { 162 | "version": "1.5.1", 163 | "from": "etag@~1.5.1", 164 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.5.1.tgz", 165 | "dependencies": { 166 | "crc": { 167 | "version": "3.2.1", 168 | "from": "crc@3.2.1", 169 | "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz" 170 | } 171 | } 172 | }, 173 | "finalhandler": { 174 | "version": "0.3.4", 175 | "from": "finalhandler@0.3.4", 176 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.3.4.tgz" 177 | }, 178 | "fresh": { 179 | "version": "0.2.4", 180 | "from": "fresh@0.2.4", 181 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.4.tgz" 182 | }, 183 | "merge-descriptors": { 184 | "version": "1.0.0", 185 | "from": "merge-descriptors@1.0.0", 186 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.0.tgz" 187 | }, 188 | "methods": { 189 | "version": "1.1.1", 190 | "from": "methods@~1.1.1", 191 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.1.tgz" 192 | }, 193 | "on-finished": { 194 | "version": "2.2.0", 195 | "from": "on-finished@~2.2.0", 196 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.0.tgz", 197 | "dependencies": { 198 | "ee-first": { 199 | "version": "1.1.0", 200 | "from": "ee-first@1.1.0", 201 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" 202 | } 203 | } 204 | }, 205 | "parseurl": { 206 | "version": "1.3.0", 207 | "from": "parseurl@~1.3.0", 208 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz" 209 | }, 210 | "path-to-regexp": { 211 | "version": "0.1.3", 212 | "from": "path-to-regexp@0.1.3", 213 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.3.tgz" 214 | }, 215 | "proxy-addr": { 216 | "version": "1.0.7", 217 | "from": "proxy-addr@~1.0.7", 218 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.7.tgz", 219 | "dependencies": { 220 | "forwarded": { 221 | "version": "0.1.0", 222 | "from": "forwarded@~0.1.0", 223 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz" 224 | }, 225 | "ipaddr.js": { 226 | "version": "0.1.9", 227 | "from": "ipaddr.js@0.1.9", 228 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.9.tgz" 229 | } 230 | } 231 | }, 232 | "qs": { 233 | "version": "2.4.1", 234 | "from": "qs@2.4.1", 235 | "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.1.tgz" 236 | }, 237 | "range-parser": { 238 | "version": "1.0.2", 239 | "from": "range-parser@~1.0.2", 240 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.2.tgz" 241 | }, 242 | "send": { 243 | "version": "0.12.2", 244 | "from": "send@0.12.2", 245 | "resolved": "https://registry.npmjs.org/send/-/send-0.12.2.tgz", 246 | "dependencies": { 247 | "destroy": { 248 | "version": "1.0.3", 249 | "from": "destroy@1.0.3", 250 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz" 251 | }, 252 | "mime": { 253 | "version": "1.3.4", 254 | "from": "mime@1.3.4", 255 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" 256 | }, 257 | "ms": { 258 | "version": "0.7.0", 259 | "from": "ms@0.7.0", 260 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.0.tgz" 261 | } 262 | } 263 | }, 264 | "serve-static": { 265 | "version": "1.9.2", 266 | "from": "serve-static@~1.9.2", 267 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.9.2.tgz" 268 | }, 269 | "type-is": { 270 | "version": "1.6.1", 271 | "from": "type-is@~1.6.1", 272 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.1.tgz", 273 | "dependencies": { 274 | "media-typer": { 275 | "version": "0.3.0", 276 | "from": "media-typer@0.3.0", 277 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" 278 | }, 279 | "mime-types": { 280 | "version": "2.0.10", 281 | "from": "mime-types@~2.0.10", 282 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.10.tgz", 283 | "dependencies": { 284 | "mime-db": { 285 | "version": "1.8.0", 286 | "from": "mime-db@~1.8.0", 287 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.8.0.tgz" 288 | } 289 | } 290 | } 291 | } 292 | }, 293 | "vary": { 294 | "version": "1.0.0", 295 | "from": "vary@~1.0.0", 296 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.0.tgz" 297 | }, 298 | "utils-merge": { 299 | "version": "1.0.0", 300 | "from": "utils-merge@1.0.0", 301 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" 302 | } 303 | } 304 | }, 305 | "express-hal": { 306 | "version": "0.0.1", 307 | "from": "express-hal@", 308 | "resolved": "https://registry.npmjs.org/express-hal/-/express-hal-0.0.1.tgz", 309 | "dependencies": { 310 | "lodash": { 311 | "version": "0.8.2", 312 | "from": "lodash@~0.8.2", 313 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.8.2.tgz" 314 | } 315 | } 316 | }, 317 | "mongoose": { 318 | "version": "4.0.1", 319 | "from": "mongoose@", 320 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.0.1.tgz", 321 | "dependencies": { 322 | "hooks-fixed": { 323 | "version": "1.0.1", 324 | "from": "hooks-fixed@1.0.1", 325 | "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-1.0.1.tgz" 326 | }, 327 | "mongodb": { 328 | "version": "2.0.24", 329 | "from": "mongodb@2.0.24", 330 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.0.24.tgz", 331 | "dependencies": { 332 | "mongodb-core": { 333 | "version": "1.1.20", 334 | "from": "mongodb-core@1.1.20", 335 | "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-1.1.20.tgz", 336 | "dependencies": { 337 | "bson": { 338 | "version": "0.2.21", 339 | "from": "bson@~0.2", 340 | "resolved": "https://registry.npmjs.org/bson/-/bson-0.2.21.tgz", 341 | "dependencies": { 342 | "nan": { 343 | "version": "1.7.0", 344 | "from": "nan@1.7.0", 345 | "resolved": "https://registry.npmjs.org/nan/-/nan-1.7.0.tgz" 346 | } 347 | } 348 | }, 349 | "mkdirp": { 350 | "version": "0.5.0", 351 | "from": "mkdirp@0.5.0", 352 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", 353 | "dependencies": { 354 | "minimist": { 355 | "version": "0.0.8", 356 | "from": "minimist@0.0.8", 357 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" 358 | } 359 | } 360 | }, 361 | "rimraf": { 362 | "version": "2.2.6", 363 | "from": "rimraf@2.2.6", 364 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz" 365 | }, 366 | "kerberos": { 367 | "version": "0.0.10", 368 | "from": "kerberos@~0.0", 369 | "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-0.0.10.tgz", 370 | "dependencies": { 371 | "nan": { 372 | "version": "1.7.0", 373 | "from": "nan@1.7.0", 374 | "resolved": "https://registry.npmjs.org/nan/-/nan-1.7.0.tgz" 375 | } 376 | } 377 | } 378 | } 379 | }, 380 | "readable-stream": { 381 | "version": "1.0.31", 382 | "from": "readable-stream@1.0.31", 383 | "dependencies": { 384 | "core-util-is": { 385 | "version": "1.0.1", 386 | "from": "core-util-is@~1.0.0", 387 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" 388 | }, 389 | "isarray": { 390 | "version": "0.0.1", 391 | "from": "isarray@0.0.1", 392 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" 393 | }, 394 | "string_decoder": { 395 | "version": "0.10.31", 396 | "from": "string_decoder@~0.10.x", 397 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" 398 | }, 399 | "inherits": { 400 | "version": "2.0.1", 401 | "from": "inherits@~2.0.1", 402 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" 403 | } 404 | } 405 | } 406 | } 407 | }, 408 | "ms": { 409 | "version": "0.1.0", 410 | "from": "ms@0.1.0" 411 | }, 412 | "sliced": { 413 | "version": "0.0.5", 414 | "from": "sliced@0.0.5", 415 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz" 416 | }, 417 | "muri": { 418 | "version": "1.0.0", 419 | "from": "muri@1.0.0", 420 | "resolved": "https://registry.npmjs.org/muri/-/muri-1.0.0.tgz" 421 | }, 422 | "mpromise": { 423 | "version": "0.5.4", 424 | "from": "mpromise@0.5.4", 425 | "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.4.tgz" 426 | }, 427 | "mpath": { 428 | "version": "0.1.1", 429 | "from": "mpath@0.1.1", 430 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.1.1.tgz" 431 | }, 432 | "kareem": { 433 | "version": "1.0.0", 434 | "from": "kareem@1.0.0", 435 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.0.0.tgz" 436 | }, 437 | "regexp-clone": { 438 | "version": "0.0.1", 439 | "from": "regexp-clone@0.0.1", 440 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz" 441 | }, 442 | "mquery": { 443 | "version": "1.4.0", 444 | "from": "mquery@1.4.0", 445 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-1.4.0.tgz", 446 | "dependencies": { 447 | "bluebird": { 448 | "version": "2.3.2", 449 | "from": "bluebird@2.3.2", 450 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.3.2.tgz" 451 | }, 452 | "debug": { 453 | "version": "0.7.4", 454 | "from": "debug@0.7.4", 455 | "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" 456 | } 457 | } 458 | }, 459 | "async": { 460 | "version": "0.9.0", 461 | "from": "async@0.9.0", 462 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" 463 | } 464 | } 465 | }, 466 | "mongoose-pages": { 467 | "version": "0.0.3", 468 | "from": "mongoose-pages@", 469 | "resolved": "https://registry.npmjs.org/mongoose-pages/-/mongoose-pages-0.0.3.tgz" 470 | }, 471 | "node-slack": { 472 | "version": "0.0.5", 473 | "from": "node-slack@^0.0.5", 474 | "resolved": "https://registry.npmjs.org/node-slack/-/node-slack-0.0.5.tgz", 475 | "dependencies": { 476 | "deferred": { 477 | "version": "0.7.1", 478 | "from": "deferred@0.7.1", 479 | "resolved": "https://registry.npmjs.org/deferred/-/deferred-0.7.1.tgz", 480 | "dependencies": { 481 | "es5-ext": { 482 | "version": "0.10.6", 483 | "from": "es5-ext@~0.10.2", 484 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.6.tgz", 485 | "dependencies": { 486 | "es6-iterator": { 487 | "version": "0.1.3", 488 | "from": "es6-iterator@~0.1.3", 489 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz" 490 | }, 491 | "es6-symbol": { 492 | "version": "2.0.1", 493 | "from": "es6-symbol@~2.0.1", 494 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz" 495 | } 496 | } 497 | }, 498 | "event-emitter": { 499 | "version": "0.3.3", 500 | "from": "event-emitter@~0.3.1", 501 | "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.3.tgz" 502 | }, 503 | "d": { 504 | "version": "0.1.1", 505 | "from": "d@~0.1.1", 506 | "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz" 507 | }, 508 | "next-tick": { 509 | "version": "0.2.2", 510 | "from": "next-tick@~0.2.2", 511 | "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz" 512 | } 513 | } 514 | } 515 | } 516 | }, 517 | "pluralize": { 518 | "version": "1.1.2", 519 | "from": "pluralize@", 520 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.1.2.tgz" 521 | }, 522 | "q": { 523 | "version": "1.2.0", 524 | "from": "q@", 525 | "resolved": "https://registry.npmjs.org/q/-/q-1.2.0.tgz" 526 | }, 527 | "request": { 528 | "version": "2.55.0", 529 | "from": "request@", 530 | "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz", 531 | "dependencies": { 532 | "bl": { 533 | "version": "0.9.4", 534 | "from": "bl@~0.9.0", 535 | "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.4.tgz", 536 | "dependencies": { 537 | "readable-stream": { 538 | "version": "1.0.33", 539 | "from": "readable-stream@~1.0.26", 540 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", 541 | "dependencies": { 542 | "core-util-is": { 543 | "version": "1.0.1", 544 | "from": "core-util-is@~1.0.0", 545 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" 546 | }, 547 | "isarray": { 548 | "version": "0.0.1", 549 | "from": "isarray@0.0.1", 550 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" 551 | }, 552 | "string_decoder": { 553 | "version": "0.10.31", 554 | "from": "string_decoder@~0.10.x", 555 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" 556 | }, 557 | "inherits": { 558 | "version": "2.0.1", 559 | "from": "inherits@~2.0.1", 560 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" 561 | } 562 | } 563 | } 564 | } 565 | }, 566 | "caseless": { 567 | "version": "0.9.0", 568 | "from": "caseless@~0.9.0", 569 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz" 570 | }, 571 | "forever-agent": { 572 | "version": "0.6.1", 573 | "from": "forever-agent@~0.6.0", 574 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" 575 | }, 576 | "form-data": { 577 | "version": "0.2.0", 578 | "from": "form-data@~0.2.0", 579 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", 580 | "dependencies": { 581 | "async": { 582 | "version": "0.9.0", 583 | "from": "async@~0.9.0", 584 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" 585 | } 586 | } 587 | }, 588 | "json-stringify-safe": { 589 | "version": "5.0.0", 590 | "from": "json-stringify-safe@~5.0.0" 591 | }, 592 | "mime-types": { 593 | "version": "2.0.10", 594 | "from": "mime-types@~2.0.10", 595 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.10.tgz", 596 | "dependencies": { 597 | "mime-db": { 598 | "version": "1.8.0", 599 | "from": "mime-db@~1.8.0", 600 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.8.0.tgz" 601 | } 602 | } 603 | }, 604 | "node-uuid": { 605 | "version": "1.4.3", 606 | "from": "node-uuid@~1.4.0", 607 | "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz" 608 | }, 609 | "qs": { 610 | "version": "2.4.1", 611 | "from": "qs@~2.4.0", 612 | "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.1.tgz" 613 | }, 614 | "tunnel-agent": { 615 | "version": "0.4.0", 616 | "from": "tunnel-agent@~0.4.0" 617 | }, 618 | "tough-cookie": { 619 | "version": "0.12.1", 620 | "from": "tough-cookie@>=0.12.0", 621 | "dependencies": { 622 | "punycode": { 623 | "version": "1.3.2", 624 | "from": "punycode@>=0.2.0", 625 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" 626 | } 627 | } 628 | }, 629 | "http-signature": { 630 | "version": "0.10.1", 631 | "from": "http-signature@~0.10.0", 632 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", 633 | "dependencies": { 634 | "assert-plus": { 635 | "version": "0.1.5", 636 | "from": "assert-plus@^0.1.5", 637 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" 638 | }, 639 | "asn1": { 640 | "version": "0.1.11", 641 | "from": "asn1@0.1.11", 642 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" 643 | }, 644 | "ctype": { 645 | "version": "0.5.3", 646 | "from": "ctype@0.5.3", 647 | "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz" 648 | } 649 | } 650 | }, 651 | "oauth-sign": { 652 | "version": "0.6.0", 653 | "from": "oauth-sign@~0.6.0", 654 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz" 655 | }, 656 | "hawk": { 657 | "version": "2.3.1", 658 | "from": "hawk@~2.3.0", 659 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", 660 | "dependencies": { 661 | "hoek": { 662 | "version": "2.12.0", 663 | "from": "hoek@2.x.x", 664 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.12.0.tgz" 665 | }, 666 | "boom": { 667 | "version": "2.7.1", 668 | "from": "boom@2.x.x", 669 | "resolved": "https://registry.npmjs.org/boom/-/boom-2.7.1.tgz" 670 | }, 671 | "cryptiles": { 672 | "version": "2.0.4", 673 | "from": "cryptiles@2.x.x", 674 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.4.tgz" 675 | }, 676 | "sntp": { 677 | "version": "1.0.9", 678 | "from": "sntp@1.x.x", 679 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" 680 | } 681 | } 682 | }, 683 | "aws-sign2": { 684 | "version": "0.5.0", 685 | "from": "aws-sign2@~0.5.0" 686 | }, 687 | "stringstream": { 688 | "version": "0.0.4", 689 | "from": "stringstream@~0.0.4" 690 | }, 691 | "combined-stream": { 692 | "version": "0.0.7", 693 | "from": "combined-stream@~0.0.5", 694 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", 695 | "dependencies": { 696 | "delayed-stream": { 697 | "version": "0.0.5", 698 | "from": "delayed-stream@0.0.5", 699 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz" 700 | } 701 | } 702 | }, 703 | "isstream": { 704 | "version": "0.1.2", 705 | "from": "isstream@~0.1.1", 706 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" 707 | }, 708 | "har-validator": { 709 | "version": "1.6.1", 710 | "from": "har-validator@^1.4.0", 711 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.6.1.tgz", 712 | "dependencies": { 713 | "bluebird": { 714 | "version": "2.9.24", 715 | "from": "bluebird@^2.9.21", 716 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.24.tgz" 717 | }, 718 | "chalk": { 719 | "version": "1.0.0", 720 | "from": "chalk@^1.0.0", 721 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.0.0.tgz", 722 | "dependencies": { 723 | "ansi-styles": { 724 | "version": "2.0.1", 725 | "from": "ansi-styles@^2.0.1", 726 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.0.1.tgz" 727 | }, 728 | "escape-string-regexp": { 729 | "version": "1.0.3", 730 | "from": "escape-string-regexp@^1.0.2", 731 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz" 732 | }, 733 | "has-ansi": { 734 | "version": "1.0.3", 735 | "from": "has-ansi@^1.0.3", 736 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-1.0.3.tgz", 737 | "dependencies": { 738 | "ansi-regex": { 739 | "version": "1.1.1", 740 | "from": "ansi-regex@^1.1.0", 741 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" 742 | }, 743 | "get-stdin": { 744 | "version": "4.0.1", 745 | "from": "get-stdin@^4.0.1", 746 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" 747 | } 748 | } 749 | }, 750 | "strip-ansi": { 751 | "version": "2.0.1", 752 | "from": "strip-ansi@^2.0.1", 753 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", 754 | "dependencies": { 755 | "ansi-regex": { 756 | "version": "1.1.1", 757 | "from": "ansi-regex@^1.1.0", 758 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" 759 | } 760 | } 761 | }, 762 | "supports-color": { 763 | "version": "1.3.1", 764 | "from": "supports-color@^1.3.0", 765 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.3.1.tgz" 766 | } 767 | } 768 | }, 769 | "commander": { 770 | "version": "2.8.0", 771 | "from": "commander@^2.7.1", 772 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.0.tgz", 773 | "dependencies": { 774 | "graceful-readlink": { 775 | "version": "1.0.1", 776 | "from": "graceful-readlink@>= 1.0.0", 777 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" 778 | } 779 | } 780 | }, 781 | "is-my-json-valid": { 782 | "version": "2.10.1", 783 | "from": "is-my-json-valid@^2.10.0", 784 | "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.10.1.tgz", 785 | "dependencies": { 786 | "generate-function": { 787 | "version": "2.0.0", 788 | "from": "generate-function@^2.0.0", 789 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" 790 | }, 791 | "generate-object-property": { 792 | "version": "1.1.1", 793 | "from": "generate-object-property@^1.1.0", 794 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.1.1.tgz", 795 | "dependencies": { 796 | "is-property": { 797 | "version": "1.0.2", 798 | "from": "is-property@^1.0.0", 799 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" 800 | } 801 | } 802 | }, 803 | "jsonpointer": { 804 | "version": "1.1.0", 805 | "from": "jsonpointer@^1.1.0", 806 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-1.1.0.tgz" 807 | }, 808 | "xtend": { 809 | "version": "4.0.0", 810 | "from": "xtend@^4.0.0", 811 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz" 812 | } 813 | } 814 | } 815 | } 816 | } 817 | } 818 | }, 819 | "underscore": { 820 | "version": "1.8.3", 821 | "from": "underscore@", 822 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz" 823 | } 824 | } 825 | } 826 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pongbot", 3 | "version": "1.0.0", 4 | "description": "Pongbot for Slack.", 5 | "main": "app.js", 6 | "dependencies": { 7 | "express": "^4.10.6", 8 | "express-hal": "", 9 | "node-slack": "^0.0.5", 10 | "body-parser": "", 11 | "mongoose": "", 12 | "mongoose-pages": "", 13 | "request": "", 14 | "pluralize": "", 15 | "q": "", 16 | "underscore": "" 17 | }, 18 | "engines": { 19 | "node": "0.10.x", 20 | "npm": ">=1.1.2" 21 | }, 22 | "devDependencies": { 23 | "mocha": "", 24 | "chai": "", 25 | "chai-string": "", 26 | "sinon": "", 27 | "jshint": "", 28 | "supertest": "" 29 | }, 30 | "scripts": { 31 | "test": "./node_modules/mocha/bin/mocha --timeout=10s", 32 | "lint": "./node_modules/jshint/bin/jshint lib test -c .jshint", 33 | "start": "node app.js" 34 | }, 35 | "author": "Andrew Vy", 36 | "license": "ISC", 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/andrewvy/slack-pongbot.git" 40 | }, 41 | "bugs": { 42 | "url": "https://github.com/andrewvy/slack-pongbot/issues" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/api_test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | chai.use(require('chai-string')); 3 | var expect = chai.expect; 4 | var app = require('../lib/app').instance(); 5 | var pong = require('../lib/pong'); 6 | var request = require('supertest'); 7 | 8 | describe('Routes', function () { 9 | require('./shared').setup(); 10 | 11 | describe('root', function () { 12 | it('returns links', function (done) { 13 | request(app) 14 | .get('/') 15 | .expect(200) 16 | .end(function(err, res) { 17 | if (err) throw err; 18 | expect(res.body._links.self.href).to.endsWith('/'); 19 | expect(res.body._links.players.href).to.endsWith('/players'); 20 | expect(res.body._links.challenges.href).to.endsWith('/challenges'); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | 26 | describe('challenges', function() { 27 | describe('with a challenge', function () { 28 | var playerWangHoe = null; 29 | var playerZhangJike = null; 30 | var challengeBetweenWangHoeAndZhangJike = null; 31 | 32 | beforeEach(function (done) { 33 | pong.registerPlayers(['WangHao', 'ZhangJike']).then().spread(function (p1, p2) { 34 | playerWangHoe = p1; 35 | playerZhangJike = p2; 36 | pong.createSingleChallenge('WangHao', 'ZhangJike').then(function (result) { 37 | challengeBetweenWangHoeAndZhangJike = result.challenge; 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | it('returns challenges', function (done) { 44 | request(app) 45 | .get('/challenges') 46 | .expect(200) 47 | .end(function(err, res) { 48 | if (err) throw err; 49 | var challenges = res.body._embedded.challenges; 50 | expect(challenges.length).to.eq(1); 51 | var challenge = challenges[0]; 52 | expect(challenge.state).to.eq('Proposed'); 53 | expect(challenge.type).to.eq('Singles'); 54 | expect(challenge._links.challengers[0].href).to.endsWith('/players/WangHao'); 55 | expect(challenge._links.challenged[0].href).to.endsWith('/players/ZhangJike'); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('returns a challenge by id', function (done) { 61 | request(app) 62 | .get('/challenges/' + challengeBetweenWangHoeAndZhangJike._id) 63 | .expect(200) 64 | .end(function(err, res) { 65 | if (err) throw err; 66 | var challenge = res.body; 67 | expect(challenge.state).to.eq('Proposed'); 68 | expect(challenge.type).to.eq('Singles'); 69 | expect(challenge._links.challengers[0].href).to.endsWith('/players/WangHao'); 70 | expect(challenge._links.challenged[0].href).to.endsWith('/players/ZhangJike'); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | }); 76 | 77 | describe('players', function() { 78 | describe('with 3 players', function () { 79 | var playerZhangJike = null; 80 | var playerDengYaping = null; 81 | var playerChenQi = null; 82 | beforeEach(function (done) { 83 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi']).then().spread(function (p1, p2, p3) { 84 | playerZhangJike = p1; 85 | playerDengYaping = p2; 86 | playerWangHoe = p3; 87 | done(); 88 | }); 89 | }); 90 | 91 | it('paginates players on page 1', function (done) { 92 | request(app) 93 | .get('/players?size=1') 94 | .expect(200) 95 | .end(function(err, res) { 96 | if (err) throw err; 97 | var players = res.body._embedded.players; 98 | expect(players.length).to.eq(1); 99 | var player = players[0]; 100 | expect(player.user_name).to.eq('ZhangJike'); 101 | expect(res.body._links.next.href).to.endsWith('/players?size=1&anchor=' + playerZhangJike._id); 102 | done(); 103 | }); 104 | }); 105 | 106 | it('paginates players on page 2', function (done) { 107 | request(app) 108 | .get('/players?size=1&anchor=' + playerZhangJike._id) 109 | .expect(200) 110 | .end(function(err, res) { 111 | if (err) throw err; 112 | var players = res.body._embedded.players; 113 | expect(players.length).to.eq(1); 114 | var player = players[0]; 115 | expect(player.user_name).to.eq('DengYaping'); 116 | expect(res.body._links.next.href).to.endsWith('/players?size=1&anchor=' + playerDengYaping._id); 117 | done(); 118 | }); 119 | }); 120 | 121 | it('paginates players on page 3', function (done) { 122 | request(app) 123 | .get('/players?size=1&anchor=' + playerDengYaping._id) 124 | .expect(200) 125 | .end(function(err, res) { 126 | if (err) throw err; 127 | var players = res.body._embedded.players; 128 | expect(players.length).to.eq(1); 129 | var player = players[0]; 130 | expect(player.user_name).to.eq('ChenQi'); 131 | expect(res.body._links.next.href).to.be.null; 132 | done(); 133 | }); 134 | }); 135 | }); 136 | 137 | describe('with a player', function () { 138 | var playerWangHoe = null; 139 | beforeEach(function (done) { 140 | pong.registerPlayer('WangHao').then(function (player) { 141 | playerWangHoe = player; 142 | done(); 143 | }); 144 | }); 145 | 146 | it('returns players', function (done) { 147 | request(app) 148 | .get('/players') 149 | .expect(200) 150 | .end(function(err, res) { 151 | if (err) throw err; 152 | var players = res.body._embedded.players; 153 | expect(players.length).to.eq(1); 154 | var player = players[0]; 155 | expect(player.user_name).to.eq('WangHao'); 156 | done(); 157 | }); 158 | }); 159 | 160 | it('returns a player by id', function (done) { 161 | request(app) 162 | .get('/players/' + playerWangHoe._id) 163 | .expect(200) 164 | .end(function(err, res) { 165 | if (err) throw err; 166 | var player = res.body; 167 | expect(player.user_name).to.eq('WangHao'); 168 | done(); 169 | }); 170 | }); 171 | 172 | it('redirects to a player by name', function (done) { 173 | request(app) 174 | .get('/players/WangHao') 175 | .expect(302) 176 | .end(function(err, res) { 177 | if (err) throw err; 178 | expect(res.headers.location).to.endsWith('/players/' + playerWangHoe._id); 179 | done(); 180 | }); 181 | }); 182 | }); 183 | }); 184 | 185 | describe('leaderboard', function () { 186 | describe('with 3 players', function () { 187 | beforeEach(function (done) { 188 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi']).then().spread(function (p1, p2, p3) { 189 | done(); 190 | }); 191 | }); 192 | 193 | describe('without challenge', function () { 194 | it('returns no player', function (done) { 195 | request(app) 196 | .get('/leaderboard') 197 | .expect(200) 198 | .end(function(err, res) { 199 | if (err) throw err; 200 | var players = res.body._embedded.players; 201 | expect(players.length).to.eq(0); 202 | done(); 203 | }); 204 | }); 205 | }); 206 | 207 | describe('with 1 challenge', function () { 208 | beforeEach(function (done) { 209 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function () { 210 | pong.acceptChallenge('DengYaping').then(function () { 211 | pong.lose('DengYaping').then(function () { 212 | done(); 213 | }); 214 | }); 215 | }); 216 | }); 217 | 218 | it('returns 2 players', function (done) { 219 | request(app) 220 | .get('/leaderboard') 221 | .expect(200) 222 | .end(function(err, res) { 223 | if (err) throw err; 224 | var players = res.body._embedded.players; 225 | expect(players.length).to.eq(2); 226 | var player = players[0]; 227 | expect(player.user_name).to.eq('ZhangJike'); 228 | done(); 229 | }); 230 | }); 231 | }); 232 | }); 233 | }); 234 | }); 235 | -------------------------------------------------------------------------------- /test/models/Player_tests.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Player = require('../../models/Player'); 3 | 4 | describe('Player', function () { 5 | describe("toString", function(){ 6 | var currentPlayer = new Player({ 7 | user_name: 'ZhangJike', 8 | wins: 1, 9 | losses: 2, 10 | elo: 56 11 | }); 12 | 13 | it("includes wins, losses and elo", function () { 14 | expect(currentPlayer.toString()).to.eql("ZhangJike: 1 win 2 losses (elo: 56)"); 15 | }); 16 | }); 17 | 18 | describe("toString(Array)", function() { 19 | var player1 = new Player({ 20 | user_name: 'ZhangJike', 21 | wins: 1, 22 | losses: 2, 23 | elo: 56 24 | }); 25 | 26 | var player2 = new Player({ 27 | user_name: 'DengYaping', 28 | wins: 1, 29 | losses: 3, 30 | elo: 42 31 | }); 32 | 33 | it("includes wins, losses and elo", function () { 34 | expect(Player.toString([player1, player2])).to.eql( 35 | "1. ZhangJike: 1 win 2 losses (elo: 56)\n" + 36 | "2. DengYaping: 1 win 3 losses (elo: 42)\n" 37 | ); 38 | }); 39 | 40 | it("ranks players with the same elo equally", function () { 41 | var player3 = new Player({ 42 | user_name: 'WangHoe', 43 | wins: 1, 44 | losses: 3, 45 | elo: 56 46 | }); 47 | 48 | expect(Player.toString([player1, player3, player2])).to.eql( 49 | "1. ZhangJike: 1 win 2 losses (elo: 56)\n" + 50 | "1. WangHoe: 1 win 3 losses (elo: 56)\n" + 51 | "3. DengYaping: 1 win 3 losses (elo: 42)\n" 52 | ); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/player_middleware_tests.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | chai.use(require('chai-string')); 3 | var expect = chai.expect; 4 | var pong = require('../lib/pong'); 5 | var Player = require('../models/Player'); 6 | var Challenge = require('../models/Challenge'); 7 | var sinon = require('sinon'); 8 | 9 | var request = require('supertest'); 10 | var routes = require('../lib/routes'); 11 | var app = require('../lib/app').instance(); 12 | 13 | describe('Player Middleware', function () { 14 | require('./shared').setup(); 15 | 16 | describe('with a pre-registered player with username', function () { 17 | beforeEach(function (done) { 18 | pong.registerPlayer('WangHao').then(function() { 19 | done(); 20 | }); 21 | }); 22 | 23 | it('automatically updates ID', function (done) { 24 | request(app) 25 | .post('/') 26 | .send({ text: 'pongbot foobar', user_id: 'U02BEFY4U', user_name: 'WangHao' }) 27 | .expect(200) 28 | .end(function(err, res){ 29 | if (err) throw err; 30 | pong.findPlayer('<@U02BEFY4U>').then(function (player) { 31 | expect(player.user_id).to.eq('U02BEFY4U'); 32 | expect(player.user_name).to.eq('WangHao'); 33 | done(); 34 | }); 35 | }); 36 | }); 37 | 38 | it('does not update with a different ID', function (done) { 39 | request(app) 40 | .post('/') 41 | .send({ text: 'pongbot foobar', user_id: 'ZZZZZZZZ', user_name: 'NotWangHao' }) 42 | .expect(200) 43 | .end(function(err, res){ 44 | if (err) throw err; 45 | pong.findPlayer('WangHao').then(function (player) { 46 | expect(player.user_id).to.be.undefined; 47 | expect(player.user_name).to.eq('WangHao'); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | }); 53 | 54 | describe('with a pre-registered player with username and ID', function () { 55 | beforeEach(function (done) { 56 | pong.registerPlayer('WangHao', { user_id: 'U02BEFY4U' }).then(function() { 57 | done(); 58 | }); 59 | }); 60 | 61 | it('automatically keeps name in sync', function (done) { 62 | request(app) 63 | .post('/') 64 | .send({ text: 'pongbot foobar', user_id: 'U02BEFY4U', user_name: 'WangHaoWasRenamed' }) 65 | .expect(200) 66 | .end(function(err, res){ 67 | if (err) throw err; 68 | pong.findPlayer('<@U02BEFY4U>').then(function (player) { 69 | expect(player.user_id).to.eq('U02BEFY4U'); 70 | expect(player.user_name).to.eq('WangHaoWasRenamed'); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | }); 76 | }); 77 | 78 | 79 | -------------------------------------------------------------------------------- /test/pong_tests.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | chai.use(require('chai-string')); 3 | var expect = chai.expect; 4 | var pong = require('../lib/pong.js'); 5 | var Player = require('../models/Player'); 6 | var Challenge = require('../models/Challenge'); 7 | var sinon = require('sinon'); 8 | 9 | describe('Pong', function () { 10 | require('./shared').setup(); 11 | 12 | describe('#registerPlayer', function () { 13 | beforeEach(function (done) { 14 | pong.registerPlayer('ZhangJike').then(function () { 15 | done(); 16 | }); 17 | }); 18 | 19 | it('creates a player record', function (done) { 20 | Player.where({ user_name: 'ZhangJike' }).findOne(function (err, player) { 21 | expect(player).not.to.be.null; 22 | expect(player.user_name).to.eq('ZhangJike'); 23 | expect(player.wins).to.eq(0); 24 | expect(player.losses).to.eq(0); 25 | expect(player.elo).to.eq(0); 26 | expect(player.tau).to.eq(0); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('does not create a duplicate player', function (done) { 32 | pong.registerPlayer('ZhangJike').then(null).then(undefined, function (err) { 33 | expect(err).to.not.be.undefined; 34 | expect(err.code).to.eq(11000); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | 40 | describe('#findPlayer', function () { 41 | describe('with a player', function () { 42 | beforeEach(function (done) { 43 | pong.registerPlayer('ZhangJike', { user_id: 'U02BEFY4U' }).then(function () { 44 | done(); 45 | }); 46 | }); 47 | 48 | it('finds a player by name', function (done) { 49 | pong.findPlayer('ZhangJike').then(function (player) { 50 | expect(player).not.to.be.null; 51 | expect(player.user_id).to.eq('U02BEFY4U'); 52 | expect(player.user_name).to.eq('ZhangJike'); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('finds a player by lowercase name', function (done) { 58 | pong.findPlayer('zhangjike').then(function (player) { 59 | expect(player).not.to.be.null; 60 | expect(player.user_id).to.eq('U02BEFY4U'); 61 | expect(player.user_name).to.eq('ZhangJike'); 62 | done(); 63 | }); 64 | }); 65 | 66 | it('finds a player by ID', function (done) { 67 | pong.findPlayer('<@U02BEFY4U>').then(function (player) { 68 | expect(player).not.to.be.null; 69 | expect(player.user_id).to.eq('U02BEFY4U'); 70 | expect(player.user_name).to.eq('ZhangJike'); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | 76 | describe('without a player', function () { 77 | it("doesn't find player", function (done) { 78 | pong.findPlayer('ZhangJike').then(undefined, function(err) { 79 | expect(err).to.not.be.null; 80 | expect(err.message).to.eq("Player 'ZhangJike' does not exist."); 81 | done(); 82 | }); 83 | }); 84 | }); 85 | }); 86 | 87 | describe('#findPlayers', function () { 88 | describe('with two players', function () { 89 | beforeEach(function (done) { 90 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function () { 91 | done(); 92 | }); 93 | }); 94 | 95 | it('finds both players', function (done) { 96 | pong.findPlayers(['ZhangJike', 'DengYaping']).then(function (players) { 97 | expect(players.length).to.eq(2); 98 | expect(players[0].user_name).to.eq('ZhangJike'); 99 | expect(players[1].user_name).to.eq('DengYaping'); 100 | done(); 101 | }); 102 | }); 103 | }); 104 | 105 | describe('without a player', function () { 106 | it("doesn't find player", function (done) { 107 | pong.findPlayer('ZhangJike').then(undefined).then(undefined, function (err) { 108 | expect(err).to.not.be.null; 109 | expect(err.message).to.eq("Player 'ZhangJike' does not exist."); 110 | done(); 111 | }); 112 | }); 113 | }); 114 | }); 115 | 116 | describe('updateWins', function () { 117 | it('returns an error when a user cannot be found', function (done) { 118 | pong.updateWins(['ZhangJike']).then(undefined, function (err) { 119 | expect(err).not.to.be.null; 120 | expect(err.message).to.eq("Player 'ZhangJike' does not exist."); 121 | done(); 122 | }); 123 | }); 124 | 125 | describe('with a player', function () { 126 | beforeEach(function (done) { 127 | pong.registerPlayer('ZhangJike').then(function () { 128 | done(); 129 | }); 130 | }); 131 | 132 | it('increments the number of wins', function (done) { 133 | pong.updateWins(['ZhangJike']).then(function (player) { 134 | pong.findPlayer('ZhangJike').then(function (player) { 135 | expect(player.wins).to.eq(1); 136 | done(); 137 | }); 138 | }); 139 | }); 140 | 141 | it('increments the number of wins twice', function (done) { 142 | pong.updateWins(['ZhangJike']).then(function (player) { 143 | pong.updateWins(['ZhangJike']).then(function (player) { 144 | pong.findPlayer('ZhangJike').then(function (player) { 145 | expect(player.wins).to.eq(2); 146 | done(); 147 | }); 148 | }); 149 | }); 150 | }); 151 | }); 152 | 153 | describe('with two players', function () { 154 | beforeEach(function (done) { 155 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function () { 156 | done(); 157 | }); 158 | }); 159 | 160 | it('increments the number of wins', function (done) { 161 | pong.updateWins(['ZhangJike', 'DengYaping']).then(function (players) { 162 | pong.findPlayers(['ZhangJike', 'DengYaping']).then(function(players) { 163 | expect(players[0].wins).to.eq(1); 164 | expect(players[1].wins).to.eq(1); 165 | }).then(function () { 166 | done(); 167 | }); 168 | }); 169 | }); 170 | }); 171 | 172 | }); 173 | 174 | describe('updateLosses', function () { 175 | it('returns an error when a player cannot be found', function (done) { 176 | pong.updateLosses(['ZhangJike']).then(undefined, function (err) { 177 | expect(err).not.to.be.null; 178 | expect(err.message).to.eq("Player 'ZhangJike' does not exist."); 179 | done(); 180 | }); 181 | }); 182 | 183 | describe('with a player', function () { 184 | beforeEach(function (done) { 185 | pong.registerPlayer('ZhangJike').then(function () { 186 | done(); 187 | }); 188 | }); 189 | 190 | it('increments the number of loss', function (done) { 191 | pong.updateLosses(['ZhangJike']).then(function (player) { 192 | pong.findPlayer('ZhangJike').then(function (player) { 193 | expect(player.losses).to.eq(1); 194 | done(); 195 | }); 196 | }); 197 | }); 198 | 199 | it('increments the number of loss twice', function (done) { 200 | pong.updateLosses(['ZhangJike']).then(function (player) { 201 | pong.updateLosses(['ZhangJike']).then(function (player) { 202 | pong.findPlayer('ZhangJike').then(function (player) { 203 | expect(player.losses).to.eq(2); 204 | done(); 205 | }); 206 | }); 207 | }); 208 | }); 209 | 210 | describe('with another player', function () { 211 | beforeEach(function (done) { 212 | pong.registerPlayer('DengYaping').then(function () { 213 | done(); 214 | }); 215 | }); 216 | 217 | it('increments the number of losses for multiple players', function (done) { 218 | pong.updateLosses(['ZhangJike', 'DengYaping']).then(function () { 219 | pong.findPlayers(['ZhangJike', 'DengYaping']).then(function(players) { 220 | expect(players[0].losses).to.eq(1); 221 | expect(players[1].losses).to.eq(1); 222 | }).then(function () { 223 | done(); 224 | }); 225 | }); 226 | }); 227 | }); 228 | 229 | }); 230 | }); 231 | 232 | describe('setChallenge', function () { 233 | it('returns an error when a player cannot be found', function (done) { 234 | pong.setChallenge(['ZhangJike'], null).then(undefined, function(err) { 235 | expect(err).not.to.be.null; 236 | expect(err.message).to.eq("Player 'ZhangJike' does not exist."); 237 | done(); 238 | }); 239 | }); 240 | 241 | describe('with a player', function () { 242 | beforeEach(function (done) { 243 | pong.registerPlayer('ZhangJike').then(function() { 244 | done(); 245 | }); 246 | }); 247 | 248 | it('sets challenge', function(done) { 249 | new Challenge({ 250 | state: 'Proposed', 251 | type: 'Singles', 252 | date: Date.now(), 253 | challenger: ['ZhangJike'], 254 | challenged: ['DengYaping'] 255 | }).save().then(function(challenge) { 256 | pong.setChallenge(['ZhangJike'], challenge._id).then(function () { 257 | pong.findPlayer('ZhangJike').then(function (player) { 258 | expect(player.currentChallenge.equals(challenge._id)).to.be.true; 259 | done(); 260 | }); 261 | }); 262 | }); 263 | }); 264 | }); 265 | 266 | describe('with two players', function () { 267 | var challenge = null; 268 | beforeEach(function (done) { 269 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function() { 270 | challenge = new Challenge({ 271 | state: 'Proposed', 272 | type: 'Singles', 273 | date: Date.now(), 274 | challenger: ['ZhangJike'], 275 | challenged: ['DengYaping'] 276 | }); 277 | challenge.save().then(function () { 278 | done(); 279 | }); 280 | }); 281 | }); 282 | 283 | it('sets challenge', function(done) { 284 | pong.setChallenge(['ZhangJike', 'DengYaping'], challenge._id).then(function () { 285 | pong.findPlayers(['ZhangJike', 'DengYaping']).then(function(players) { 286 | expect(players[0].currentChallenge.equals(challenge._id)).to.be.true; 287 | expect(players[1].currentChallenge.equals(challenge._id)).to.be.true; 288 | }).then(function () { 289 | done(); 290 | }); 291 | }); 292 | }); 293 | }); 294 | }); 295 | 296 | describe('checkChallenge', function () { 297 | describe('with a challenge already set', function () { 298 | beforeEach(function (done) { 299 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function(players) { 300 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function (challenge) { 301 | done(); 302 | }); 303 | }); 304 | }); 305 | 306 | it('fails on check challenge from challenger', function(done) { 307 | pong.checkChallenge(['ZhangJike', 'DengYaping']).then(undefined, function (err) { 308 | expect(err.message).to.eq("There's already an active challenge between ZhangJike and DengYaping."); 309 | done(); 310 | }); 311 | }); 312 | 313 | it('fails on check challenge from chellenged', function(done) { 314 | pong.checkChallenge(['DengYaping', 'ZhangJike']).then(undefined, function (err) { 315 | expect(err.message).to.eq("There's already an active challenge between ZhangJike and DengYaping."); 316 | done(); 317 | }); 318 | }); 319 | }); 320 | }); 321 | 322 | describe('ensureUniquePlayers', function () { 323 | beforeEach(function (done) { 324 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function () { 325 | done(); 326 | }); 327 | }); 328 | 329 | it('fails with a duplicate', function (done) { 330 | pong.ensureUniquePlayers(['ZhangJike', 'ZhangJike', 'ZhangJike', 'ChenQi']).then(undefined, function (err) { 331 | expect(err).to.not.be.null; 332 | expect(err.message).to.eq('Does ZhangJike have 6 hands?'); 333 | done(); 334 | }); 335 | }); 336 | 337 | it('succeeds without a duplicate', function (done) { 338 | pong.ensureUniquePlayers(['ZhangJike', 'ViktorBarna']).then(function (players) { 339 | expect(players).to.eql(['ZhangJike','ViktorBarna']); 340 | done(); 341 | }); 342 | }); 343 | }); 344 | 345 | describe('createSingleChallenge', function () { 346 | it('returns an error when the challenger cannot be found', function (done) { 347 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(undefined, function (err) { 348 | expect(err).not.to.be.null; 349 | expect(err.message).to.eq("Player 'ZhangJike' does not exist."); 350 | done(); 351 | }); 352 | }); 353 | 354 | describe('with a challenger', function () { 355 | beforeEach(function (done) { 356 | pong.registerPlayer('ZhangJike').then(function () { 357 | done(); 358 | }); 359 | }); 360 | 361 | it('requires all players to be unique', function (done) { 362 | pong.createSingleChallenge('ZhangJike', 'ZhangJike').then(undefined, function (err) { 363 | expect(err).to.not.be.null; 364 | expect(err.message).to.eq("Does ZhangJike have 4 hands?"); 365 | done(); 366 | }); 367 | }); 368 | 369 | it('returns an error when the challenged cannot be found', function (done) { 370 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(undefined, function (err) { 371 | expect(err).not.to.be.null; 372 | expect(err.message).to.eq("Player 'DengYaping' does not exist."); 373 | done(); 374 | }); 375 | }); 376 | 377 | describe('with a challenged', function () { 378 | beforeEach(function (done) { 379 | pong.registerPlayer('DengYaping').then(function () { 380 | done(); 381 | }); 382 | }); 383 | 384 | it('creates a challenge', function (done) { 385 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function (result) { 386 | expect(result.message).to.eq("ZhangJike has challenged DengYaping to a ping pong match!"); 387 | expect(result.challenge).to.not.be.null; 388 | pong.findPlayers(['ZhangJike', 'DengYaping']).then(function(players) { 389 | var challenger = players[0]; 390 | var challenged = players[1]; 391 | expect(challenger.currentChallenge).to.not.be.undefined; 392 | expect(result.challenge._id.equals(challenger.currentChallenge)).to.be.true; 393 | expect(challenged.currentChallenge).to.not.be.undefined; 394 | expect(challenged.currentChallenge.equals(challenger.currentChallenge)).to.be.true; 395 | done(); 396 | }); 397 | }); 398 | }); 399 | 400 | describe('with an existing challenge', function (done) { 401 | beforeEach(function (done) { 402 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function() { 403 | done(); 404 | }); 405 | }); 406 | 407 | it('fails to create a challenge', function (done) { 408 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(undefined, function (err) { 409 | expect(err).to.not.be.null; 410 | expect(err.message).to.eq("There's already an active challenge between ZhangJike and DengYaping."); 411 | done(); 412 | }); 413 | }); 414 | }); 415 | }); 416 | }); 417 | }); 418 | 419 | describe('createDoubleChallenge', function () { 420 | describe('with 4 players', function () { 421 | beforeEach(function (done) { 422 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function () { 423 | done(); 424 | }); 425 | }); 426 | 427 | it('creates a challenge', function (done) { 428 | pong.createDoubleChallenge('ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna').then(function (result) { 429 | expect(result).to.not.be.null; 430 | expect(result.message).to.eq("ZhangJike and DengYaping have challenged ChenQi and ViktorBarna to a ping pong match!"); 431 | expect(result.challenge).to.not.be.null; 432 | pong.findPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function (players) { 433 | expect(players[0].currentChallenge.equals(result.challenge._id)).to.be.true; 434 | expect(players[1].currentChallenge.equals(result.challenge._id)).to.be.true; 435 | expect(players[2].currentChallenge.equals(result.challenge._id)).to.be.true; 436 | expect(players[3].currentChallenge.equals(result.challenge._id)).to.be.true; 437 | }).then(function () { 438 | done(); 439 | }); 440 | }); 441 | }); 442 | 443 | it('with an existing challenge between two of the players', function (done) { 444 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function (challenge) { 445 | pong.createDoubleChallenge('ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna').then(undefined, function (err) { 446 | expect(err).to.not.be.null; 447 | expect(err.message).to.eq("There's already an active challenge between ZhangJike and DengYaping."); 448 | done(); 449 | }); 450 | }); 451 | }); 452 | 453 | it('requires all players to be unique', function (done) { 454 | pong.createDoubleChallenge('ZhangJike', 'ZhangJike', 'ChenQi', 'ViktorBarna').then(undefined, function (err) { 455 | expect(err).to.not.be.null; 456 | expect(err.message).to.eq("Does ZhangJike have 4 hands?"); 457 | done(); 458 | }); 459 | }); 460 | }); 461 | }); 462 | 463 | describe('acceptChallenge', function () { 464 | describe('with a singles challenge', function () { 465 | beforeEach(function (done) { 466 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function () { 467 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function () { 468 | done(); 469 | }); 470 | }); 471 | }); 472 | 473 | it('accepts challenge', function (done) { 474 | pong.acceptChallenge('DengYaping').then(function (result) { 475 | expect(result.message).to.eq("DengYaping accepted ZhangJike's challenge."); 476 | expect(result.challenge.state).to.eq('Accepted'); 477 | done(); 478 | }); 479 | }); 480 | 481 | it("can't accept a challenge twice", function (done) { 482 | pong.acceptChallenge('DengYaping').then(function (challenge) { 483 | pong.acceptChallenge('DengYaping').then(undefined, function (err) { 484 | expect(err.message).to.eq("You have already accepted ZhangJike's challenge."); 485 | done(); 486 | }); 487 | }); 488 | }); 489 | 490 | it("can't accept your own challenge", function (done) { 491 | pong.acceptChallenge('ZhangJike').then(undefined, function (err) { 492 | expect(err.message).to.eq('Please wait for DengYaping to accept your challenge.'); 493 | done(); 494 | }); 495 | }); 496 | 497 | it("can't accept a challenge that doesn't exist", function (done) { 498 | pong.registerPlayer('ChenQi').then(function () { 499 | pong.acceptChallenge('DengYaping').then(function (challenge) { 500 | pong.acceptChallenge('ChenQi').then(undefined, function (err) { 501 | expect(err.message).to.eq('No challenge to accept.'); 502 | done(); 503 | }); 504 | }); 505 | }); 506 | }); 507 | }); 508 | 509 | describe('with a doubles challenge', function () { 510 | beforeEach(function (done) { 511 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function () { 512 | pong.createDoubleChallenge('ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna').then(function () { 513 | done(); 514 | }); 515 | }); 516 | }); 517 | 518 | it('accepts challenge with opponent one', function (done) { 519 | pong.acceptChallenge('ChenQi').then(function (result) { 520 | expect(result.message).to.eq("ChenQi accepted ZhangJike and DengYaping's challenge."); 521 | expect(result.challenge.state).to.eq('Accepted'); 522 | done(); 523 | }); 524 | }); 525 | 526 | it('accepts challenge with opponent two', function (done) { 527 | pong.acceptChallenge('ViktorBarna').then(function (result) { 528 | expect(result.message).to.eq("ViktorBarna accepted ZhangJike and DengYaping's challenge."); 529 | expect(result.challenge.state).to.eq('Accepted'); 530 | done(); 531 | }); 532 | }); 533 | 534 | it("can't accept your own challenge with player one", function (done) { 535 | pong.acceptChallenge('ZhangJike').then(undefined, function (err) { 536 | expect(err.message).to.eq('Please wait for ChenQi or ViktorBarna to accept your challenge.'); 537 | done(); 538 | }); 539 | }); 540 | 541 | it("can't accept your own challenge with player two", function (done) { 542 | pong.acceptChallenge('DengYaping').then(undefined, function (err) { 543 | expect(err.message).to.eq('Please wait for ChenQi or ViktorBarna to accept your challenge.'); 544 | done(); 545 | }); 546 | }); 547 | }); 548 | }); 549 | 550 | describe('declineChallenge', function () { 551 | describe('with a singles challenge', function () { 552 | beforeEach(function (done) { 553 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function () { 554 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function () { 555 | done(); 556 | }); 557 | }); 558 | }); 559 | 560 | it('declines challenge', function (done) { 561 | pong.declineChallenge('DengYaping').then(function (result) { 562 | expect(result.message).to.eq("DengYaping declined ZhangJike's challenge."); 563 | expect(result.challenge.state).to.eq('Declined'); 564 | done(); 565 | }); 566 | }); 567 | 568 | it("can't decline your own challenge", function (done) { 569 | pong.declineChallenge('ZhangJike').then(undefined, function (err) { 570 | expect(err.message).to.eq("Please wait for DengYaping to accept or decline your challenge."); 571 | done(); 572 | }); 573 | }); 574 | 575 | it("can't decline a challenge twice", function (done) { 576 | pong.declineChallenge('DengYaping').then(function (challenge) { 577 | pong.declineChallenge('DengYaping').then(undefined, function (err) { 578 | expect(err.message).to.eq("No challenge to decline."); 579 | done(); 580 | }); 581 | }); 582 | }); 583 | }); 584 | 585 | describe('with a doubles challenge', function () { 586 | beforeEach(function (done) { 587 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function () { 588 | pong.createDoubleChallenge('ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna').then(function () { 589 | done(); 590 | }); 591 | }); 592 | }); 593 | 594 | it('declines challenge with opponent one', function (done) { 595 | pong.declineChallenge('ChenQi').then(function (result) { 596 | expect(result.message).to.eq("ChenQi declined ZhangJike and DengYaping's challenge."); 597 | expect(result.challenge.state).to.eq('Declined'); 598 | done(); 599 | }); 600 | }); 601 | 602 | it('declines challenge with opponent two', function (done) { 603 | pong.declineChallenge('ViktorBarna').then(function (result) { 604 | expect(result.message).to.eq("ViktorBarna declined ZhangJike and DengYaping's challenge."); 605 | expect(result.challenge.state).to.eq('Declined'); 606 | done(); 607 | }); 608 | }); 609 | 610 | it("can't decline your own challenge with player one", function (done) { 611 | pong.declineChallenge('ZhangJike').then(undefined, function (err) { 612 | expect(err.message).to.eq('Please wait for ChenQi or ViktorBarna to accept or decline your challenge.'); 613 | done(); 614 | }); 615 | }); 616 | 617 | it("can decline your own challenge with player two", function (done) { 618 | pong.declineChallenge('DengYaping').then(function (result) { 619 | expect(result.message).to.eq("DengYaping declined ZhangJike's challenge against ChenQi and ViktorBarna."); 620 | expect(result.challenge.state).to.eq('Declined'); 621 | done(); 622 | }); 623 | }); 624 | }); 625 | }); 626 | 627 | describe('chickenChallenge', function () { 628 | describe('with a singles challenge', function () { 629 | beforeEach(function (done) { 630 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function () { 631 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function () { 632 | done(); 633 | }); 634 | }); 635 | }); 636 | 637 | it('chickens out of the challenge', function (done) { 638 | pong.chickenChallenge('ZhangJike').then(function (result) { 639 | expect(result.message).to.eq("ZhangJike chickened out of the challenge against DengYaping."); 640 | expect(result.challenge.state).to.eq('Chickened'); 641 | done(); 642 | }); 643 | }); 644 | 645 | it("can't chicken out of the challenge twice", function (done) { 646 | pong.chickenChallenge('ZhangJike').then(function (result) { 647 | pong.chickenChallenge('ZhangJike').then(undefined, function (err) { 648 | expect(err.message).to.eq("First, challenge someone!"); 649 | done(); 650 | }); 651 | }); 652 | }); 653 | 654 | it("can't chicken out of someone else's challenge", function (done) { 655 | pong.chickenChallenge('DengYaping').then(undefined, function (err) { 656 | expect(err.message).to.eq("Only ZhangJike can do that."); 657 | done(); 658 | }); 659 | }); 660 | 661 | describe('after it is accepted', function () { 662 | beforeEach(function (done) { 663 | pong.acceptChallenge('DengYaping').then(function (result) { 664 | done(); 665 | }); 666 | }); 667 | 668 | it('chickens out of the challenge for the challenger', function (done) { 669 | pong.chickenChallenge('ZhangJike').then(function (result) { 670 | expect(result.message).to.eq("ZhangJike chickened out of the challenge against DengYaping."); 671 | expect(result.challenge.state).to.eq('Chickened'); 672 | done(); 673 | }); 674 | }); 675 | 676 | it('chickens out of the challenge for the challenged', function (done) { 677 | pong.chickenChallenge('DengYaping').then(function (result) { 678 | expect(result.message).to.eq("DengYaping chickened out of the challenge against ZhangJike."); 679 | expect(result.challenge.state).to.eq('Chickened'); 680 | done(); 681 | }); 682 | }); 683 | }); 684 | }); 685 | 686 | describe('with a doubles challenge', function () { 687 | beforeEach(function (done) { 688 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function () { 689 | pong.createDoubleChallenge('ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna').then(function () { 690 | done(); 691 | }); 692 | }); 693 | }); 694 | 695 | it('chickens out of the challenge with opponent one', function (done) { 696 | pong.chickenChallenge('ZhangJike').then(function (result) { 697 | expect(result.message).to.eq("ZhangJike chickened out of the challenge against ChenQi and ViktorBarna."); 698 | expect(result.challenge.state).to.eq('Chickened'); 699 | done(); 700 | }); 701 | }); 702 | 703 | it("can't chicken out of the the challenge with player two", function (done) { 704 | pong.chickenChallenge('DengYaping').then(undefined, function (err) { 705 | expect(err.message).to.eq('Only ZhangJike can do that.'); 706 | done(); 707 | }); 708 | }); 709 | 710 | it("can't chicken out of the the challenge with player three", function (done) { 711 | pong.chickenChallenge('ChenQi').then(undefined, function (err) { 712 | expect(err.message).to.eq('Only ZhangJike can do that.'); 713 | done(); 714 | }); 715 | }); 716 | 717 | it("can't chicken out of the the challenge with player four", function (done) { 718 | pong.chickenChallenge('ViktorBarna').then(undefined, function (err) { 719 | expect(err.message).to.eq('Only ZhangJike can do that.'); 720 | done(); 721 | }); 722 | }); 723 | 724 | describe('after it is accepted', function () { 725 | beforeEach(function (done) { 726 | pong.acceptChallenge('ChenQi').then(function (result) { 727 | done(); 728 | }); 729 | }); 730 | 731 | it('chickens out of the challenge for the challenger', function (done) { 732 | pong.chickenChallenge('ZhangJike').then(function (result) { 733 | expect(result.message).to.eq("ZhangJike chickened out of the challenge against ChenQi and ViktorBarna."); 734 | expect(result.challenge.state).to.eq('Chickened'); 735 | done(); 736 | }); 737 | }); 738 | 739 | it('chickens out of the challenge for player two', function (done) { 740 | pong.chickenChallenge('DengYaping').then(function (result) { 741 | expect(result.message).to.eq("DengYaping chickened out of the challenge against ChenQi and ViktorBarna."); 742 | expect(result.challenge.state).to.eq('Chickened'); 743 | done(); 744 | }); 745 | }); 746 | 747 | it('chickens out of the challenge for player three', function (done) { 748 | pong.chickenChallenge('ChenQi').then(function (result) { 749 | expect(result.message).to.eq("ChenQi chickened out of the challenge against ZhangJike and DengYaping."); 750 | expect(result.challenge.state).to.eq('Chickened'); 751 | done(); 752 | }); 753 | }); 754 | 755 | it('chickens out of the challenge for player four', function (done) { 756 | pong.chickenChallenge('ViktorBarna').then(function (result) { 757 | expect(result.message).to.eq("ViktorBarna chickened out of the challenge against ZhangJike and DengYaping."); 758 | expect(result.challenge.state).to.eq('Chickened'); 759 | done(); 760 | }); 761 | }); 762 | }); 763 | }); 764 | }); 765 | 766 | describe('calculateTeamElo', function () { 767 | beforeEach(function (done) { 768 | pong.registerPlayer('ZhangJike', { elo: 4 }).then(function () { 769 | pong.registerPlayer('DengYaping', { elo: 2 }).then(function () { 770 | done(); 771 | }); 772 | }); 773 | }); 774 | 775 | it('returns average of elo', function (done) { 776 | pong.calculateTeamElo('ZhangJike', 'DengYaping').then(function (elo) { 777 | expect(elo).to.eq(3); 778 | done(); 779 | }); 780 | }); 781 | }); 782 | 783 | describe('eloSinglesChange', function () { 784 | beforeEach(function (done) { 785 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function () { 786 | done(); 787 | }); 788 | }); 789 | 790 | it('updates elo after a challenge', function (done) { 791 | pong.eloSinglesChange('ZhangJike', 'DengYaping').then().spread(function (winner, loser) { 792 | expect(winner.elo).to.eq(48); 793 | expect(winner.tau).to.eq(0.5); 794 | expect(loser.elo).to.eq(-48); 795 | expect(loser.tau).to.eq(0.5); 796 | done(); 797 | }); 798 | }); 799 | }); 800 | 801 | describe('eloDoublesChange', function () { 802 | beforeEach(function (done) { 803 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function () { 804 | done(); 805 | }); 806 | }); 807 | 808 | it('updates elo after a challenge', function (done) { 809 | pong.eloDoublesChange('ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna').then().spread(function (p1, p2, p3, p4) { 810 | expect(p1.elo).to.eq(48); 811 | expect(p1.tau).to.eq(0.5); 812 | expect(p2.elo).to.eq(48); 813 | expect(p2.tau).to.eq(0.5); 814 | expect(p3.elo).to.eq(-48); 815 | expect(p3.tau).to.eq(0.5); 816 | expect(p4.elo).to.eq(-48); 817 | expect(p4.tau).to.eq(0.5); 818 | done(); 819 | }); 820 | }); 821 | }); 822 | 823 | describe('win and lose', function () { 824 | describe('with a single challenge', function () { 825 | beforeEach(function (done) { 826 | pong.registerPlayers(['ZhangJike', 'DengYaping']).then(function () { 827 | pong.createSingleChallenge('ZhangJike', 'DengYaping').then(function () { 828 | done(); 829 | }); 830 | }); 831 | }); 832 | 833 | it('challenge must be accepted', function (done) { 834 | pong.win('ZhangJike').then(undefined, function (err) { 835 | expect(err).not.to.be.null; 836 | expect(err.message).to.eq("Challenge needs to be accepted before recording match."); 837 | done(); 838 | }); 839 | }); 840 | 841 | describe('challenge accepted', function () { 842 | beforeEach(function (done) { 843 | pong.acceptChallenge('DengYaping').then(function () { 844 | done(); 845 | }); 846 | }); 847 | 848 | it('player one wins', function (done) { 849 | pong.win('ZhangJike').then(function (result) { 850 | expect(result.message).to.eq("Match has been recorded, ZhangJike defeated DengYaping."); 851 | pong.findPlayers(['ZhangJike', 'DengYaping']).then().spread(function (p1, p2) { 852 | expect(p1.wins).to.eq(1); 853 | expect(p1.tau).to.eq(0.5); 854 | expect(p1.elo).to.eq(48); 855 | expect(p1.losses).to.eq(0); 856 | expect(p2.wins).to.eq(0); 857 | expect(p2.tau).to.eq(0.5); 858 | expect(p2.elo).to.eq(-48); 859 | expect(p2.losses).to.eq(1); 860 | done(); 861 | }); 862 | }); 863 | }); 864 | 865 | it('player two wins', function (done) { 866 | pong.win('DengYaping').then(function (result) { 867 | expect(result.message).to.eq("Match has been recorded, DengYaping defeated ZhangJike."); 868 | pong.findPlayers(['DengYaping', 'ZhangJike']).then().spread(function (p1, p2) { 869 | expect(p1.wins).to.eq(1); 870 | expect(p1.tau).to.eq(0.5); 871 | expect(p1.elo).to.eq(48); 872 | expect(p1.losses).to.eq(0); 873 | expect(p2.wins).to.eq(0); 874 | expect(p2.tau).to.eq(0.5); 875 | expect(p2.elo).to.eq(-48); 876 | expect(p2.losses).to.eq(1); 877 | done(); 878 | }); 879 | }); 880 | }); 881 | 882 | it('player one loses', function (done) { 883 | pong.lose('ZhangJike').then(function (result) { 884 | expect(result.message).to.eq("Match has been recorded, DengYaping defeated ZhangJike."); 885 | pong.findPlayers(['DengYaping', 'ZhangJike']).then().spread(function (p1, p2) { 886 | expect(p1.wins).to.eq(1); 887 | expect(p1.tau).to.eq(0.5); 888 | expect(p1.elo).to.eq(48); 889 | expect(p1.losses).to.eq(0); 890 | expect(p2.wins).to.eq(0); 891 | expect(p2.tau).to.eq(0.5); 892 | expect(p2.elo).to.eq(-48); 893 | expect(p2.losses).to.eq(1); 894 | done(); 895 | }); 896 | }); 897 | }); 898 | 899 | it('player two loses', function (done) { 900 | pong.lose('DengYaping').then(function (result) { 901 | expect(result.message).to.eq("Match has been recorded, ZhangJike defeated DengYaping."); 902 | pong.findPlayers(['ZhangJike', 'DengYaping']).then().spread(function (p1, p2) { 903 | expect(p1.wins).to.eq(1); 904 | expect(p1.tau).to.eq(0.5); 905 | expect(p1.elo).to.eq(48); 906 | expect(p1.losses).to.eq(0); 907 | expect(p2.wins).to.eq(0); 908 | expect(p2.tau).to.eq(0.5); 909 | expect(p2.elo).to.eq(-48); 910 | expect(p2.losses).to.eq(1); 911 | done(); 912 | }); 913 | }); 914 | }); 915 | }); 916 | }); 917 | 918 | describe('with an accepted doubles challenge', function () { 919 | beforeEach(function (done) { 920 | pong.registerPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then(function () { 921 | pong.createDoubleChallenge('ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna').then(function (challenge) { 922 | pong.acceptChallenge('ChenQi').then(function () { 923 | done(); 924 | }); 925 | }); 926 | }); 927 | }); 928 | 929 | it('player one wins', function (done) { 930 | pong.win('ZhangJike').then(function (result) { 931 | expect(result).not.to.be.null; 932 | expect(result.message).to.eq("Match has been recorded, ZhangJike and DengYaping defeated ChenQi and ViktorBarna."); 933 | pong.findPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then().spread(function (p1, p2, p3, p4) { 934 | expect(p1.wins).to.eq(1); 935 | expect(p1.tau).to.eq(0.5); 936 | expect(p1.elo).to.eq(48); 937 | expect(p1.losses).to.eq(0); 938 | expect(p2.wins).to.eq(1); 939 | expect(p2.tau).to.eq(0.5); 940 | expect(p2.elo).to.eq(48); 941 | expect(p2.losses).to.eq(0); 942 | expect(p3.wins).to.eq(0); 943 | expect(p3.tau).to.eq(0.5); 944 | expect(p3.elo).to.eq(-48); 945 | expect(p3.losses).to.eq(1); 946 | expect(p4.wins).to.eq(0); 947 | expect(p4.tau).to.eq(0.5); 948 | expect(p4.elo).to.eq(-48); 949 | expect(p4.losses).to.eq(1); 950 | done(); 951 | }); 952 | }); 953 | }); 954 | 955 | it('player two wins', function (done) { 956 | pong.win('DengYaping').then(function (result) { 957 | expect(result).not.to.be.null; 958 | expect(result.message).to.eq("Match has been recorded, ZhangJike and DengYaping defeated ChenQi and ViktorBarna."); 959 | pong.findPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then().spread(function (p1, p2, p3, p4) { 960 | expect(p1.wins).to.eq(1); 961 | expect(p1.tau).to.eq(0.5); 962 | expect(p1.elo).to.eq(48); 963 | expect(p1.losses).to.eq(0); 964 | expect(p2.wins).to.eq(1); 965 | expect(p2.tau).to.eq(0.5); 966 | expect(p2.elo).to.eq(48); 967 | expect(p2.losses).to.eq(0); 968 | expect(p3.wins).to.eq(0); 969 | expect(p3.tau).to.eq(0.5); 970 | expect(p3.elo).to.eq(-48); 971 | expect(p3.losses).to.eq(1); 972 | expect(p4.wins).to.eq(0); 973 | expect(p4.tau).to.eq(0.5); 974 | expect(p4.elo).to.eq(-48); 975 | expect(p4.losses).to.eq(1); 976 | done(); 977 | }); 978 | }); 979 | }); 980 | 981 | it('player three wins', function (done) { 982 | pong.win('ChenQi').then(function (result) { 983 | expect(result).not.to.be.null; 984 | expect(result.message).to.eq("Match has been recorded, ChenQi and ViktorBarna defeated ZhangJike and DengYaping."); 985 | pong.findPlayers(['ChenQi', 'ViktorBarna', 'ZhangJike', 'DengYaping']).then().spread(function (p1, p2, p3, p4) { 986 | expect(p1.wins).to.eq(1); 987 | expect(p1.tau).to.eq(0.5); 988 | expect(p1.elo).to.eq(48); 989 | expect(p1.losses).to.eq(0); 990 | expect(p2.wins).to.eq(1); 991 | expect(p2.tau).to.eq(0.5); 992 | expect(p2.elo).to.eq(48); 993 | expect(p2.losses).to.eq(0); 994 | expect(p3.wins).to.eq(0); 995 | expect(p3.tau).to.eq(0.5); 996 | expect(p3.elo).to.eq(-48); 997 | expect(p3.losses).to.eq(1); 998 | expect(p4.wins).to.eq(0); 999 | expect(p4.tau).to.eq(0.5); 1000 | expect(p4.elo).to.eq(-48); 1001 | expect(p4.losses).to.eq(1); 1002 | done(); 1003 | }); 1004 | }); 1005 | }); 1006 | 1007 | it('player four wins', function (done) { 1008 | pong.win('ViktorBarna').then(function (result) { 1009 | expect(result).not.to.be.null; 1010 | expect(result.message).to.eq("Match has been recorded, ChenQi and ViktorBarna defeated ZhangJike and DengYaping."); 1011 | pong.findPlayers(['ChenQi', 'ViktorBarna', 'ZhangJike', 'DengYaping']).then().spread(function (p1, p2, p3, p4) { 1012 | expect(p1.wins).to.eq(1); 1013 | expect(p1.tau).to.eq(0.5); 1014 | expect(p1.elo).to.eq(48); 1015 | expect(p1.losses).to.eq(0); 1016 | expect(p2.wins).to.eq(1); 1017 | expect(p2.tau).to.eq(0.5); 1018 | expect(p2.elo).to.eq(48); 1019 | expect(p2.losses).to.eq(0); 1020 | expect(p3.wins).to.eq(0); 1021 | expect(p3.tau).to.eq(0.5); 1022 | expect(p3.elo).to.eq(-48); 1023 | expect(p3.losses).to.eq(1); 1024 | expect(p4.wins).to.eq(0); 1025 | expect(p4.tau).to.eq(0.5); 1026 | expect(p4.elo).to.eq(-48); 1027 | expect(p4.losses).to.eq(1); 1028 | done(); 1029 | }); 1030 | }); 1031 | }); 1032 | 1033 | it('player one loses', function (done) { 1034 | pong.lose('ZhangJike').then(function (result) { 1035 | expect(result).not.to.be.null; 1036 | expect(result.message).to.eq("Match has been recorded, ChenQi and ViktorBarna defeated ZhangJike and DengYaping."); 1037 | pong.findPlayers(['ChenQi', 'ViktorBarna', 'ZhangJike', 'DengYaping']).then().spread(function (p1, p2, p3, p4) { 1038 | expect(p1.wins).to.eq(1); 1039 | expect(p1.tau).to.eq(0.5); 1040 | expect(p1.elo).to.eq(48); 1041 | expect(p1.losses).to.eq(0); 1042 | expect(p2.wins).to.eq(1); 1043 | expect(p2.tau).to.eq(0.5); 1044 | expect(p2.elo).to.eq(48); 1045 | expect(p2.losses).to.eq(0); 1046 | expect(p3.wins).to.eq(0); 1047 | expect(p3.tau).to.eq(0.5); 1048 | expect(p3.elo).to.eq(-48); 1049 | expect(p3.losses).to.eq(1); 1050 | expect(p4.wins).to.eq(0); 1051 | expect(p4.tau).to.eq(0.5); 1052 | expect(p4.elo).to.eq(-48); 1053 | expect(p4.losses).to.eq(1); 1054 | done(); 1055 | }); 1056 | }); 1057 | }); 1058 | 1059 | it('player two loses', function (done) { 1060 | pong.lose('DengYaping').then(function (result) { 1061 | expect(result).not.to.be.null; 1062 | expect(result.message).to.eq("Match has been recorded, ChenQi and ViktorBarna defeated ZhangJike and DengYaping."); 1063 | pong.findPlayers(['ChenQi', 'ViktorBarna', 'ZhangJike', 'DengYaping']).then().spread(function (p1, p2, p3, p4) { 1064 | expect(p1.wins).to.eq(1); 1065 | expect(p1.tau).to.eq(0.5); 1066 | expect(p1.elo).to.eq(48); 1067 | expect(p1.losses).to.eq(0); 1068 | expect(p2.wins).to.eq(1); 1069 | expect(p2.tau).to.eq(0.5); 1070 | expect(p2.elo).to.eq(48); 1071 | expect(p2.losses).to.eq(0); 1072 | expect(p3.wins).to.eq(0); 1073 | expect(p3.tau).to.eq(0.5); 1074 | expect(p3.elo).to.eq(-48); 1075 | expect(p3.losses).to.eq(1); 1076 | expect(p4.wins).to.eq(0); 1077 | expect(p4.tau).to.eq(0.5); 1078 | expect(p4.elo).to.eq(-48); 1079 | expect(p4.losses).to.eq(1); 1080 | done(); 1081 | }); 1082 | }); 1083 | }); 1084 | 1085 | it('player three loses', function (done) { 1086 | pong.lose('ChenQi').then(function (result) { 1087 | expect(result).not.to.be.null; 1088 | expect(result.message).to.eq("Match has been recorded, ZhangJike and DengYaping defeated ChenQi and ViktorBarna."); 1089 | pong.findPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then().spread(function (p1, p2, p3, p4) { 1090 | expect(p1.wins).to.eq(1); 1091 | expect(p1.tau).to.eq(0.5); 1092 | expect(p1.elo).to.eq(48); 1093 | expect(p1.losses).to.eq(0); 1094 | expect(p2.wins).to.eq(1); 1095 | expect(p2.tau).to.eq(0.5); 1096 | expect(p2.elo).to.eq(48); 1097 | expect(p2.losses).to.eq(0); 1098 | expect(p3.wins).to.eq(0); 1099 | expect(p3.tau).to.eq(0.5); 1100 | expect(p3.elo).to.eq(-48); 1101 | expect(p3.losses).to.eq(1); 1102 | expect(p4.wins).to.eq(0); 1103 | expect(p4.tau).to.eq(0.5); 1104 | expect(p4.elo).to.eq(-48); 1105 | expect(p4.losses).to.eq(1); 1106 | done(); 1107 | }); 1108 | }); 1109 | }); 1110 | 1111 | it('player four loses', function (done) { 1112 | pong.lose('ViktorBarna').then(function (result) { 1113 | expect(result).not.to.be.null; 1114 | expect(result.message).to.eq("Match has been recorded, ZhangJike and DengYaping defeated ChenQi and ViktorBarna."); 1115 | pong.findPlayers(['ZhangJike', 'DengYaping', 'ChenQi', 'ViktorBarna']).then().spread(function (p1, p2, p3, p4) { 1116 | expect(p1.wins).to.eq(1); 1117 | expect(p1.tau).to.eq(0.5); 1118 | expect(p1.elo).to.eq(48); 1119 | expect(p1.losses).to.eq(0); 1120 | expect(p2.wins).to.eq(1); 1121 | expect(p2.tau).to.eq(0.5); 1122 | expect(p2.elo).to.eq(48); 1123 | expect(p2.losses).to.eq(0); 1124 | expect(p3.wins).to.eq(0); 1125 | expect(p3.tau).to.eq(0.5); 1126 | expect(p3.elo).to.eq(-48); 1127 | expect(p3.losses).to.eq(1); 1128 | expect(p4.wins).to.eq(0); 1129 | expect(p4.tau).to.eq(0.5); 1130 | expect(p4.elo).to.eq(-48); 1131 | expect(p4.losses).to.eq(1); 1132 | done(); 1133 | }); 1134 | }); 1135 | }); 1136 | }); 1137 | }); 1138 | 1139 | describe('reset', function () { 1140 | it('returns an error when a player cannot be found', function (done) { 1141 | pong.reset('ZhangJike').then(undefined, function (err) { 1142 | expect(err).not.to.be.null; 1143 | expect(err.message).to.eq("Player 'ZhangJike' does not exist."); 1144 | done(); 1145 | }); 1146 | }); 1147 | 1148 | describe('with a player', function () { 1149 | beforeEach(function (done) { 1150 | pong.registerPlayer('ZhangJike', { wins: 42, losses: 24, tau: 3, elo: 158 }).then(function (player) { 1151 | done(); 1152 | }); 1153 | }); 1154 | 1155 | it('resets player fields', function (done) { 1156 | pong.reset('ZhangJike').then(function () { 1157 | pong.findPlayer('ZhangJike').then(function (player) { 1158 | expect(player.wins).to.eq(0); 1159 | expect(player.tau).to.eq(1); 1160 | expect(player.elo).to.eq(0); 1161 | expect(player.losses).to.eq(0); 1162 | done(); 1163 | }); 1164 | }); 1165 | }); 1166 | }); 1167 | }); 1168 | 1169 | describe('resetAll', function () { 1170 | describe('with two players', function () { 1171 | beforeEach(function (done) { 1172 | pong.registerPlayer('ZhangJike', { wins: 42, losses: 24, tau: 3, elo: 158 }).then(function (player) { 1173 | pong.registerPlayer('ViktorBarna', { wins: 4, losses: 4, tau: 3, elo: 18 }).then(function (player) { 1174 | done(); 1175 | }); 1176 | }); 1177 | }); 1178 | 1179 | it('resets all players', function (done) { 1180 | pong.resetAll().then(function () { 1181 | pong.findPlayers(['ZhangJike', 'ViktorBarna']).then().spread(function (p1, p2) { 1182 | expect(p1.wins).to.eq(0); 1183 | expect(p1.tau).to.eq(1); 1184 | expect(p1.elo).to.eq(0); 1185 | expect(p1.losses).to.eq(0); 1186 | expect(p2.wins).to.eq(0); 1187 | expect(p2.tau).to.eq(1); 1188 | expect(p2.elo).to.eq(0); 1189 | expect(p2.losses).to.eq(0); 1190 | done(); 1191 | }); 1192 | }); 1193 | }); 1194 | }); 1195 | }); 1196 | 1197 | describe('getDuelGif', function () { 1198 | it('returns a gif', function (done) { 1199 | pong.getDuelGif().then(function (gif) { 1200 | expect(gif).to.startsWith('http'); 1201 | done(); 1202 | }); 1203 | }); 1204 | }); 1205 | }); 1206 | -------------------------------------------------------------------------------- /test/routes_test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | chai.use(require('chai-string')); 3 | var expect = chai.expect; 4 | var pong = require('../lib/pong'); 5 | var Player = require('../models/Player'); 6 | var Challenge = require('../models/Challenge'); 7 | var sinon = require('sinon'); 8 | 9 | var request = require('supertest'); 10 | var routes = require('../lib/routes'); 11 | var app = require('../lib/app').instance(); 12 | 13 | describe('Routes', function () { 14 | require('./shared').setup(); 15 | 16 | it('unknown command', function () { 17 | request(app) 18 | .post('/') 19 | .send({ text: 'pongbot foobar' }) 20 | .expect(200) 21 | .end(function(err, res){ 22 | if (err) throw err; 23 | expect(res.body.text).to.eq("I couldn't understand that command. Use _pongbot help_ to get a list of available commands."); 24 | }); 25 | }); 26 | 27 | describe('register', function() { 28 | it('registers a new player', function (done) { 29 | request(app) 30 | .post('/') 31 | .send({ text: 'pongbot register', user_name: 'WangHao' }) 32 | .expect(200) 33 | .end(function(err, res){ 34 | if (err) throw err; 35 | expect(res.body.text).to.eq("Successfully registered! Welcome to the system, WangHao."); 36 | done(); 37 | }); 38 | }); 39 | 40 | describe('with a pre-registered player', function () { 41 | beforeEach(function (done) { 42 | pong.registerPlayer('WangHao', { user_id: 'U02BEFY4U' }).then(function() { 43 | done(); 44 | }); 45 | }); 46 | 47 | it('does not register twice by name', function (done) { 48 | request(app) 49 | .post('/') 50 | .send({ text: 'pongbot register', user_name: 'WangHao' }) 51 | .expect(200) 52 | .end(function(err, res) { 53 | expect(res.body.text).to.eq("You've already registered!"); 54 | done(); 55 | }); 56 | }); 57 | 58 | it('does not register twice by ID', function (done) { 59 | request(app) 60 | .post('/') 61 | .send({ text: 'pongbot register', user_name: 'WangHaoWasRenamed', user_id: 'U02BEFY4U' }) 62 | .expect(200) 63 | .end(function(err, res) { 64 | expect(res.body.text).to.eq("You've already registered!"); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | }); 70 | 71 | describe('challenge', function() { 72 | describe('not registered', function () { 73 | it('advises to register', function (done) { 74 | request(app) 75 | .post('/') 76 | .send({ text: 'pongbot challenge singles ZhangJike', user_name: 'WangHao' }) 77 | .expect(200) 78 | .end(function(err, res) { 79 | expect(res.body.text).to.eq("Player 'WangHao' does not exist. Are you registered? Use _pongbot register_ first."); 80 | done(); 81 | }); 82 | }); 83 | }); 84 | 85 | describe('with two players', function () { 86 | beforeEach(function (done) { 87 | pong.registerPlayers(['WangHao', 'ZhangJike']).then(function () { 88 | done(); 89 | }); 90 | }); 91 | 92 | it('creates a challenge', function (done) { 93 | request(app) 94 | .post('/') 95 | .send({ text: 'pongbot challenge singles ZhangJike', user_name: 'WangHao' }) 96 | .expect(200) 97 | .end(function(err, res) { 98 | expect(res.body.text).to.startsWith("WangHao has challenged ZhangJike to a ping pong match!"); 99 | done(); 100 | }); 101 | }); 102 | 103 | it('creates a challenge with case insensitive usernames', function (done) { 104 | request(app) 105 | .post('/') 106 | .send({ text: 'pongbot challenge singles zhangjike', user_name: 'wanghao' }) 107 | .expect(200) 108 | .end(function(err, res) { 109 | expect(res.body.text).to.startsWith("WangHao has challenged ZhangJike to a ping pong match!"); 110 | done(); 111 | }); 112 | }); 113 | }); 114 | 115 | describe('with four players', function () { 116 | beforeEach(function (done) { 117 | pong.registerPlayers(['WangHao', 'ZhangJike', 'ChenQi', 'ViktorBarna']).then(function () { 118 | done(); 119 | }); 120 | }); 121 | 122 | it('yields an error when user does not exist', function (done) { 123 | request(app) 124 | .post('/') 125 | .send({ text: 'pongbot challenge doubles ChenQi against ZhangJike GuoYue', user_name: 'WangHao' }) 126 | .expect(200) 127 | .end(function(err, res) { 128 | expect(res.body.text).to.eq("Error: Player 'GuoYue' does not exist."); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('creates a challenge', function (done) { 134 | request(app) 135 | .post('/') 136 | .send({ text: 'pongbot challenge doubles ChenQi against ZhangJike ViktorBarna', user_name: 'WangHao' }) 137 | .expect(200) 138 | .end(function(err, res) { 139 | expect(res.body.text).to.startsWith("WangHao and ChenQi have challenged ZhangJike and ViktorBarna to a ping pong match!"); 140 | done(); 141 | }); 142 | }); 143 | 144 | it('creates a challenge with case-insensitive usernames', function (done) { 145 | request(app) 146 | .post('/') 147 | .send({ text: 'pongbot challenge doubles chenQi against zhangJike viktorBarna', user_name: 'wangHao' }) 148 | .expect(200) 149 | .end(function(err, res) { 150 | expect(res.body.text).to.startsWith("WangHao and ChenQi have challenged ZhangJike and ViktorBarna to a ping pong match!"); 151 | done(); 152 | }); 153 | }); 154 | }); 155 | }); 156 | 157 | describe('accept and decline', function() { 158 | describe('with a challenge', function () { 159 | beforeEach(function (done) { 160 | pong.registerPlayers(['WangHao', 'ZhangJike']).then(function () { 161 | pong.createSingleChallenge('WangHao', 'ZhangJike').then(function () { 162 | done(); 163 | }); 164 | }); 165 | }); 166 | 167 | it('accepts a challenge', function (done) { 168 | request(app) 169 | .post('/') 170 | .send({ text: 'pongbot accept', user_name: 'ZhangJike' }) 171 | .expect(200) 172 | .end(function(err, res) { 173 | expect(res.body.text).to.eq("ZhangJike accepted WangHao's challenge."); 174 | done(); 175 | }); 176 | }); 177 | 178 | it('declines a challenge', function (done) { 179 | request(app) 180 | .post('/') 181 | .send({ text: 'pongbot decline', user_name: 'ZhangJike' }) 182 | .expect(200) 183 | .end(function(err, res) { 184 | expect(res.body.text).to.eq("ZhangJike declined WangHao's challenge."); 185 | done(); 186 | }); 187 | }); 188 | 189 | it('chickens out of a challenge', function (done) { 190 | request(app) 191 | .post('/') 192 | .send({ text: 'pongbot chicken', user_name: 'WangHao' }) 193 | .expect(200) 194 | .end(function(err, res) { 195 | expect(res.body.text).to.eq("WangHao chickened out of the challenge against ZhangJike."); 196 | done(); 197 | }); 198 | }); 199 | 200 | it('cannot create an opposite challenge', function (done) { 201 | request(app) 202 | .post('/') 203 | .send({ text: 'pongbot challenge singles WangHao', user_name: 'ZhangJike' }) 204 | .expect(200) 205 | .end(function(err, res) { 206 | expect(res.body.text).to.eq("Error: There's already an active challenge between WangHao and ZhangJike."); 207 | Challenge.count().then(function (count) { 208 | expect(count).to.eq(1); 209 | done(); 210 | }); 211 | }); 212 | }); 213 | }); 214 | }); 215 | 216 | describe('won and lost', function() { 217 | describe('with an accepted challenge', function () { 218 | beforeEach(function (done) { 219 | pong.registerPlayers(['WangHao', 'ZhangJike']).then(function () { 220 | pong.createSingleChallenge('WangHao', 'ZhangJike').then(function () { 221 | pong.acceptChallenge('ZhangJike').then(function () { 222 | done(); 223 | }); 224 | }); 225 | }); 226 | }); 227 | 228 | it('won', function (done) { 229 | request(app) 230 | .post('/') 231 | .send({ text: 'pongbot won', user_name: 'ZhangJike' }) 232 | .expect(200) 233 | .end(function(err, res) { 234 | expect(res.body.text).to.eq("Only the player/team that lost can record the game."); 235 | done(); 236 | }); 237 | }); 238 | 239 | it('lost', function (done) { 240 | request(app) 241 | .post('/') 242 | .send({ text: 'pongbot lost', user_name: 'ZhangJike' }) 243 | .expect(200) 244 | .end(function(err, res) { 245 | expect(res.body.text).to.eq("Match has been recorded, WangHao defeated ZhangJike."); 246 | done(); 247 | }); 248 | }); 249 | }); 250 | }); 251 | 252 | describe('rank', function() { 253 | describe('with a pre-registered player', function () { 254 | beforeEach(function (done) { 255 | pong.registerPlayer('WangHao').then(function() { 256 | done(); 257 | }); 258 | }); 259 | 260 | it('returns elo', function (done) { 261 | request(app) 262 | .post('/') 263 | .send({ text: 'pongbot rank', user_name: 'WangHao' }) 264 | .expect(200) 265 | .end(function(err, res) { 266 | expect(res.body.text).to.eq("WangHao: 0 wins 0 losses (elo: 0)"); 267 | done(); 268 | }); 269 | }); 270 | 271 | it('tolerates lots of spaces', function (done) { 272 | request(app) 273 | .post('/') 274 | .send({ text: 'pongbot rank WangHao ' }) 275 | .expect(200) 276 | .end(function(err, res) { 277 | expect(res.body.text).to.eq("WangHao: 0 wins 0 losses (elo: 0)"); 278 | done(); 279 | }); 280 | }); 281 | }); 282 | }); 283 | 284 | describe('leaderboard', function() { 285 | beforeEach(function (done) { 286 | pong.registerPlayer('WangHao', { wins: 4, losses: 3, tau: 3, elo: 58 }).then(function (player) { 287 | pong.registerPlayer('ZhangJike', { wins: 42, losses: 24, tau: 3, elo: 158 }).then(function (player) { 288 | pong.registerPlayer('A', { wins: 20, losses: 3, tau: 3, elo: 57 }).then(function (player) { 289 | pong.registerPlayer('B', { wins: 19, losses: 3, tau: 3, elo: 56 }).then(function (player) { 290 | pong.registerPlayer('C', { wins: 18, losses: 3, tau: 3, elo: 55 }).then(function (player) { 291 | pong.registerPlayer('D', { wins: 17, losses: 3, tau: 3, elo: 54 }).then(function (player) { 292 | done(); 293 | }); 294 | }); 295 | }); 296 | }); 297 | }); 298 | }); 299 | }); 300 | 301 | it('infinity', function (done) { 302 | request(app) 303 | .post('/') 304 | .send({ text: 'pongbot leaderboard infinity', user_name: 'WangHao' }) 305 | .expect(200) 306 | .end(function(err, res) { 307 | expect(res.body.text).to.eq( 308 | "1. ZhangJike: 42 wins 24 losses (elo: 158)\n" + 309 | "2. WangHao: 4 wins 3 losses (elo: 58)\n" + 310 | "3. A: 20 wins 3 losses (elo: 57)\n" + 311 | "4. B: 19 wins 3 losses (elo: 56)\n" + 312 | "5. C: 18 wins 3 losses (elo: 55)\n" + 313 | "6. D: 17 wins 3 losses (elo: 54)\n" 314 | ); 315 | done(); 316 | }); 317 | }); 318 | 319 | it('Infinity', function (done) { 320 | request(app) 321 | .post('/') 322 | .send({ text: 'pongbot leaderboard Infinity', user_name: 'WangHao' }) 323 | .expect(200) 324 | .end(function(err, res) { 325 | expect(res.body.text).to.eq( 326 | "1. ZhangJike: 42 wins 24 losses (elo: 158)\n" + 327 | "2. WangHao: 4 wins 3 losses (elo: 58)\n" + 328 | "3. A: 20 wins 3 losses (elo: 57)\n" + 329 | "4. B: 19 wins 3 losses (elo: 56)\n" + 330 | "5. C: 18 wins 3 losses (elo: 55)\n" + 331 | "6. D: 17 wins 3 losses (elo: 54)\n" 332 | ); 333 | done(); 334 | }); 335 | }); 336 | 337 | it('99', function (done) { 338 | request(app) 339 | .post('/') 340 | .send({ text: 'pongbot leaderboard infinity', user_name: 'WangHao' }) 341 | .expect(200) 342 | .end(function(err, res) { 343 | expect(res.body.text).to.eq( 344 | "1. ZhangJike: 42 wins 24 losses (elo: 158)\n" + 345 | "2. WangHao: 4 wins 3 losses (elo: 58)\n" + 346 | "3. A: 20 wins 3 losses (elo: 57)\n" + 347 | "4. B: 19 wins 3 losses (elo: 56)\n" + 348 | "5. C: 18 wins 3 losses (elo: 55)\n" + 349 | "6. D: 17 wins 3 losses (elo: 54)\n" 350 | ); 351 | done(); 352 | }); 353 | }); 354 | 355 | it('without arguments', function (done) { 356 | request(app) 357 | .post('/') 358 | .send({ text: 'pongbot leaderboard', user_name: 'WangHao' }) 359 | .expect(200) 360 | .end(function(err, res) { 361 | expect(res.body.text).to.eq( 362 | "1. ZhangJike: 42 wins 24 losses (elo: 158)\n" + 363 | "2. WangHao: 4 wins 3 losses (elo: 58)\n" + 364 | "3. A: 20 wins 3 losses (elo: 57)\n" + 365 | "4. B: 19 wins 3 losses (elo: 56)\n" + 366 | "5. C: 18 wins 3 losses (elo: 55)\n" 367 | ); 368 | done(); 369 | }); 370 | }); 371 | 372 | it('eLiTe', function (done) { 373 | request(app) 374 | .post('/') 375 | .send({ text: 'pongbot LeADeRBoaRD', user_name: 'WangHao' }) 376 | .expect(200) 377 | .end(function(err, res) { 378 | expect(res.body.text).to.eq( 379 | "1. ZhangJike: 42 wins 24 losses (elo: 158)\n" + 380 | "2. WangHao: 4 wins 3 losses (elo: 58)\n" + 381 | "3. A: 20 wins 3 losses (elo: 57)\n" + 382 | "4. B: 19 wins 3 losses (elo: 56)\n" + 383 | "5. C: 18 wins 3 losses (elo: 55)\n" 384 | ); 385 | done(); 386 | }); 387 | }); 388 | it('2', function (done) { 389 | request(app) 390 | .post('/') 391 | .send({ text: 'pongbot leaderboard 2', user_name: 'WangHao' }) 392 | .expect(200) 393 | .end(function(err, res) { 394 | expect(res.body.text).to.eq( 395 | "1. ZhangJike: 42 wins 24 losses (elo: 158)\n" + 396 | "2. WangHao: 4 wins 3 losses (elo: 58)\n" 397 | ); 398 | done(); 399 | }); 400 | }); 401 | 402 | }); 403 | 404 | describe('reset', function() { 405 | describe('with a pre-registered player', function () { 406 | beforeEach(function (done) { 407 | process.env.ADMIN_SECRET = 'admin_secret'; 408 | pong.registerPlayer('WangHao').then(function() { 409 | done(); 410 | }); 411 | }); 412 | 413 | afterEach(function () { 414 | delete process.env.ADMIN_SECRET; 415 | }); 416 | 417 | it('with the wrong admin secret', function (done) { 418 | request(app) 419 | .post('/') 420 | .send({ text: 'pongbot reset WangHao invalid', user_name: 'WangHao' }) 421 | .expect(200) 422 | .end(function(err, res) { 423 | expect(res.body.text).to.eq("Invalid secret. Use _pongbot reset _."); 424 | done(); 425 | }); 426 | }); 427 | 428 | it('with the correct admin secret', function (done) { 429 | request(app) 430 | .post('/') 431 | .send({ text: 'pongbot reset WangHao admin_secret', user_name: 'vy' }) 432 | .expect(200) 433 | .end(function(err, res) { 434 | expect(res.body.text).to.eq("WangHao's stats have been reset."); 435 | done(); 436 | }); 437 | }); 438 | }); 439 | }); 440 | 441 | describe('new_season', function() { 442 | beforeEach(function () { 443 | process.env.ADMIN_SECRET = 'admin_secret'; 444 | }); 445 | 446 | afterEach(function () { 447 | delete process.env.ADMIN_SECRET; 448 | }); 449 | 450 | it('with the wrong admin secret', function (done) { 451 | request(app) 452 | .post('/') 453 | .send({ text: 'pongbot new_season invalid', user_name: 'WangHao' }) 454 | .expect(200) 455 | .end(function(err, res) { 456 | expect(res.body.text).to.eq("Invalid secret. Use _pongbot new_season _."); 457 | done(); 458 | }); 459 | }); 460 | 461 | describe('with two players', function () { 462 | beforeEach(function (done) { 463 | pong.registerPlayer('ZhangJike', { wins: 42, losses: 24, tau: 3, elo: 158 }).then(function (player) { 464 | pong.registerPlayer('ViktorBarna', { wins: 4, losses: 4, tau: 3, elo: 18 }).then(function (player) { 465 | done(); 466 | }); 467 | }); 468 | }); 469 | 470 | it('with the correct admin secret', function (done) { 471 | request(app) 472 | .post('/') 473 | .send({ text: 'pongbot new_season admin_secret', user_name: 'WangHao' }) 474 | .expect(200) 475 | .end(function(err, res) { 476 | expect(res.body.text).to.eq("Welcome to the new season!"); 477 | pong.findPlayers(['ZhangJike', 'ViktorBarna']).then().spread(function (p1, p2) { 478 | expect(p1.wins).to.eq(0); 479 | expect(p2.wins).to.eq(0); 480 | done(); 481 | }); 482 | }); 483 | }); 484 | }); 485 | }); 486 | 487 | describe('source', function() { 488 | it('is helpful', function () { 489 | request(app) 490 | .post('/') 491 | .send({ text: 'pongbot source' }) 492 | .expect(200) 493 | .end(function(err, res){ 494 | if (err) throw err; 495 | expect(res.body.text).to.eq("https://github.com/andrewvy/slack-pongbot"); 496 | }); 497 | }); 498 | }); 499 | 500 | describe('help', function() { 501 | it('is helpful', function () { 502 | request(app) 503 | .post('/') 504 | .send({ text: 'pongbot help' }) 505 | .expect(200) 506 | .end(function(err, res){ 507 | if (err) throw err; 508 | expect(res.body.text).to.eq("https://github.com/andrewvy/slack-pongbot"); 509 | }); 510 | }); 511 | }); 512 | 513 | describe('LOG_LEVEL', function() { 514 | beforeEach(function () { 515 | sinon.spy(console, 'log'); 516 | }); 517 | 518 | afterEach(function () { 519 | console.log.restore(); 520 | }); 521 | 522 | it("doesn't log", function (done) { 523 | request(app) 524 | .post('/') 525 | .send({ text: 'pongbot help' }) 526 | .expect(200) 527 | .end(function(err, res){ 528 | if (err) throw err; 529 | expect(console.log.calledOnce).to.be.false; 530 | done(); 531 | }); 532 | }); 533 | 534 | describe('with LOG_LEVEL=debug', function () { 535 | beforeEach(function () { 536 | process.env.LOG_LEVEL = 'debug'; 537 | }); 538 | 539 | afterEach(function () { 540 | delete(process.env.LOG_LEVEL); 541 | }); 542 | 543 | it("logs hook", function (done) { 544 | request(app) 545 | .post('/') 546 | .send({ text: 'pongbot help' }) 547 | .expect(200) 548 | .end(function(err, res){ 549 | if (err) throw err; 550 | expect(console.log.calledOnce).to.be.true; 551 | done(); 552 | }); 553 | }); 554 | }); 555 | }); 556 | 557 | describe('hug', function() { 558 | it('mean pongbot', function () { 559 | request(app) 560 | .post('/') 561 | .send({ text: 'pongbot hug' }) 562 | .expect(200) 563 | .end(function(err, res){ 564 | if (err) throw err; 565 | expect(res.body.text).to.eq("No."); 566 | }); 567 | }); 568 | }); 569 | 570 | describe('sucks', function() { 571 | it('no, you suck', function () { 572 | request(app) 573 | .post('/') 574 | .send({ text: 'pongbot sucks' }) 575 | .expect(200) 576 | .end(function(err, res){ 577 | if (err) throw err; 578 | expect(res.body.text).to.eq("No, you suck."); 579 | }); 580 | }); 581 | }); 582 | }); 583 | -------------------------------------------------------------------------------- /test/shared.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | var Player = require('../models/Player'); 4 | var Challenge = require('../models/Challenge'); 5 | 6 | exports.setup = function(){ 7 | before(function (done) { 8 | mongoose.connect('mongodb://localhost/pingpong_test', done); 9 | }); 10 | 11 | after(function (done) { 12 | mongoose.disconnect(done); 13 | }); 14 | 15 | beforeEach(function (done) { 16 | Player.remove().then(function () { 17 | Challenge.remove().then(function () { 18 | done(); 19 | }); 20 | }); 21 | }); 22 | }; 23 | --------------------------------------------------------------------------------