From 74ed93a0765c7a3ac77c548ab7c8c0ccf2d84e9a Mon Sep 17 00:00:00 2001 From: Yami Date: Tue, 25 Jul 2017 19:17:15 +0000 Subject: [PATCH 1/3] test: add integration test --- package.json | 2 + src/mod_controller.js | 40 ++++++ test/integration/statsTests.js | 199 ++++++++++++++++++++++++++++++ test/unit/modControllerTest.js | 216 +++++++++++++++++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 test/integration/statsTests.js diff --git a/package.json b/package.json index 9cd6b26..203aa2a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ }, "homepage": "https://github.com/yamikuronue/sockMafia#readme", "dependencies": { + "axios": "^0.16.2", "bluebird": "^3.1.1", "debug": "^2.2.0", "fs-readfile-promise": "^3.0.0", @@ -46,6 +47,7 @@ "istanbul": "^0.4.1", "jsdoc-to-markdown": "^2.0.1", "mocha": "^2.3.4", + "nock": "^9.0.14", "publish-please": "2.1.4", "sinon": "^1.15.1", "sinon-as-promised": "^4.0.0", diff --git a/src/mod_controller.js b/src/mod_controller.js index 2cc4b90..df3e84a 100644 --- a/src/mod_controller.js +++ b/src/mod_controller.js @@ -11,6 +11,7 @@ const view = require('./view'); const Promise = require('bluebird'); const debug = require('debug')('sockbot:mafia:modController'); const Utils = require('./utils'); +const axios = require('axios'); exports.internals = {}; @@ -722,6 +723,45 @@ class MafiaModController { view.reportError(command, 'Error listing night actions: ', err); }); } + + endHandler(command) { + let game, mod; + let winner = command.args[0]; + + if (!winner) { + view.reportError(command, 'No winner set. Please use `!winner scum`, `!winner town`, or `!winner 3party` to declare a winner'); + return Promise.resolve(); + } + + winner = winner.toLowerCase(); + + if (['town', 'scum', '3party'].indexOf(winner) === -1) { + view.reportError(command, 'No winner set. Please use `!winner scum`, `!winner town`, or `!winner 3party` to declare a winner'); + return Promise.resolve(); + } + + + return Promise.all([this.getGame(command), command.getUser()]) + .then((responses) => { + game = responses[0]; + + debug('Ending game ' + game.id); + try { + mod = game.getModerator(responses[1].username); + } catch (_) { + return Promise.reject(new Error('You are not a moderator')); + } + return mod.isModerator ? Promise.resolve() : Promise.reject('You are not a moderator'); + }) + .then(() => game.setActive(false)) + .then(() => game.setValue('winner', winner)) + .then(() => debug('about to send data')) + .then(() => axios.post('http://mafia.sockdrawer.io:5984/games', game.toJSON())) + .catch((err) => { + logRecoveredError('Error ending game: ' + err); + view.reportError(command, 'Error ending game: ', err); + }); + } } module.exports = MafiaModController; diff --git a/test/integration/statsTests.js b/test/integration/statsTests.js new file mode 100644 index 0000000..47578fe --- /dev/null +++ b/test/integration/statsTests.js @@ -0,0 +1,199 @@ +'use strict'; +/*globals describe, it, beforeEach, afterEach, before*/ + +const chai = require('chai'), + sinon = require('sinon'), + nock = require('nock'); + +//do NOT hit real servers +nock.disableNetConnect(); + +//promise library plugins +require('sinon-as-promised'); +const chaiAsPromised = require('chai-as-promised'); +chai.use(chaiAsPromised); + +chai.should(); + +const PlayerController = require('../../src/player_controller'); +const ModController = require('../../src/mod_controller'); +const DAO = require('../../src/dao'); +const view = require('../../src/view.js'); + +const testConfig = { + db: ':memory:', +}; + +const mockForum = { + Chat: { + create: () => 1 + }, + User: { + getByName: (user) => { + return user; + } + }, + Post: { + reply: () => { + return Promise.resolve(); + } + }, + supports: (input) => { + return input === 'Chats' || input === 'Formatting.Markup.HTML' || input === 'Formatting.Multiline'; + }, + Format: { + urlForTopic: (topicId, slug, postId) => { + return '/t/' + slug + '/' + topicId + '/' + postId; + }, + urlForPost: (postId) => { + return '/p/' + postId; + }, + header2: (text) => `

${text}

`, + header3: (text) => `

${text}

`, + bold: (text) => `${text}`, + link: (url, text) => `${text}`, + } +}; + +describe('MafiaStats', function () { + let mockCalls = []; + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + mockCalls = []; + }); + + afterEach(() => { + nock.cleanAll(); + sandbox.restore(); + }); + + describe('Basic game stats', () => { + let dao, playerController, modController, game; + + before(() => { + //Set up the database + dao = new DAO(':memory:'); + playerController = new PlayerController(dao, testConfig); + playerController.formatter = { + urlForPost: () => '', + quoteText: (input) => input + }; + modController = new ModController(dao, testConfig); + + + view.activate(mockForum); + + return dao.createGame(1, 'Game 1') + .then((g) => { + game = g; + sinon.stub(dao, 'getGameByTopicId').resolves(game); + return game.addPlayer('yamikuronue'); + }) + .then(() => game.addPlayer('accalia')) + .then(() => game.addPlayer('dreikin')) + .then(() => game.addPlayer('tehninja')) + .then(() => game.addModerator('moddyMcModFace')) + .then(() => game.newDay()); + }); + + it('should record basic stats', () => { + let command = { + args: ['@accalia'], + input: '!vote @accalia', + reply: sandbox.stub(), + getTopic: () => Promise.resolve({id: 2}), + getPost: () => Promise.resolve({id: 1}), + getUser: () => Promise.resolve({username: 'yamikuronue'}), + parent: { + ids: { + topic: 1 + } + } + }; + + mockCalls.push(nock('http://mafia.sockdrawer.io:5984') + .post('/games') + .reply(201, JSON.stringify({ok: true}))); + + //First, register a vote + + return playerController.voteHandler(command).then(() => { + command = { + args: [], + input: '!unvote', + reply: sandbox.stub(), + getTopic: () => Promise.resolve({id: 2}), + getPost: () => Promise.resolve({id: 2}), + getUser: () => Promise.resolve({username: 'yamikuronue'}), + parent: { + ids: { + topic: 2 + } + } + }; + + //Then, unvote + return playerController.unvoteHandler(command); + }).then(() => { + command.reply.lastCall.args[0].should.include('@yamikuronue unvoted'); + + command = { + args: ['@accalia'], + input: '!vote @accalia', + reply: sandbox.stub(), + getTopic: () => Promise.resolve({id: 2}), + getPost: () => Promise.resolve({id: 3}), + getUser: () => Promise.resolve({username: 'yamikuronue'}), + parent: { + ids: { + topic: 2 + } + } + }; + + //Vote for the same person again + return playerController.voteHandler(command); + }).then(() => { + + command = { + args: [], + input: '!unvote', + reply: sandbox.stub(), + getTopic: () => Promise.resolve({id: 2}), + getPost: () => Promise.resolve({id: 4}), + getUser: () => Promise.resolve({username: 'yamikuronue'}), + parent: { + ids: { + topic: 2 + } + } + }; + + //Then unvote + return playerController.unvoteHandler(command); + }).then(() => { + + command = { + args: ['scum'], + input: '!endGame', + reply: sandbox.stub(), + getTopic: () => Promise.resolve({id: 2}), + getPost: () => Promise.resolve({id: 5}), + getUser: () => Promise.resolve({username: 'moddyMcModFace'}), + parent: { + ids: { + topic: 2 + } + } + }; + + return modController.endHandler(command); + }).then(() => { + mockCalls[0].isDone().should.be.true; + game.getValue('winner').should.equal('scum'); + }); + }); + }); +}); diff --git a/test/unit/modControllerTest.js b/test/unit/modControllerTest.js index 978a660..0d43fe8 100644 --- a/test/unit/modControllerTest.js +++ b/test/unit/modControllerTest.js @@ -1260,6 +1260,7 @@ describe('mod controller', () => { }); }); }); + describe('set()', () => { let mockGame, mockUser, mockTarget, mockdao, modController; @@ -2258,4 +2259,219 @@ describe('mod controller', () => { }); }); }); + + + describe('endGame()', () => { + let mockGame, mockUser, mockdao, modController, mockRequest; + + beforeEach(() => { + + mockUser = { + username: 'God', + getPlayerProperty: () => [], + isModerator: true + }; + + + mockGame = { + isActive: true, + name: 'testMafia', + getAllPlayers: () => ['Rachel', 'Ross', 'Joey', 'Chandler', 'Phoebe', 'Monica'], + livePlayers: [mockUser, mockUser, mockUser], + killPlayer: () => Promise.resolve(), + nextPhase: () => Promise.resolve(), + newDay: () => Promise.resolve(), + setActive: () => Promise.resolve(), + toJSON: () => 1, + getActions: () => 1, + getPlayer: () => mockUser, + getModerator: () => mockUser, + topicId: 12, + day: 1, + setValue: () => Promise.resolve(), + phase: 'night' + }; + + mockdao = { + getGameByTopicId: () => Promise.resolve(mockGame), + getGameByChatId: () => Promise.resolve(mockGame) + }; + + modController = new ModController(mockdao); + + mockRequest = sandbox.stub(require('axios'), 'post'); + }); + + it('Should reject non-mods', () => { + const command = { + getTopic: () => Promise.resolve({ + id: 12345 + }), + getUser: () => Promise.resolve({ + username: 'tehNinja' + }), + args: ['scum'], + parent: { + ids: { + topic: 12345 + } + }, + }; + mockUser.isModerator = false; + + return modController.endHandler(command).then(() => { + //Output back to mod + view.reportError.calledWith(command).should.be.true; + const modOutput = view.reportError.getCall(0).args[2].toString(); + modOutput.should.include('You are not a moderator'); + }); + }); + + it('Should reject lack of winner', () => { + const command = { + getTopic: () => Promise.resolve({ + id: 12345 + }), + getUser: () => Promise.resolve({ + username: 'tehNinja' + }), + args: [], + parent: { + ids: { + topic: 12345 + } + }, + }; + + return modController.endHandler(command).then(() => { + //Output back to mod + view.reportError.calledWith(command).should.be.true; + }); + }); + + it('Should reject bad winner', () => { + const command = { + getTopic: () => Promise.resolve({ + id: 12345 + }), + getUser: () => Promise.resolve({ + username: 'tehNinja' + }), + args: ['banana'], + parent: { + ids: { + topic: 12345 + } + }, + }; + + return modController.endHandler(command).then(() => { + //Output back to mod + view.reportError.calledWith(command).should.be.true; + }); + }); + + it('Should reject non-existant game', () => { + const command = { + getTopic: () => Promise.resolve({ + id: 12345 + }), + getUser: () => Promise.resolve({ + username: 'tehNinja' + }), + args: ['scum'], + parent: { + ids: { + topic: 12345 + } + }, + }; + + sandbox.stub(mockdao, 'getGameByTopicId').rejects('No such game'); + sandbox.spy(mockGame, 'nextPhase'); + + return modController.endHandler(command).then(() => { + //Output back to mod + view.reportError.calledWith(command).should.be.true; + const modOutput = view.reportError.getCall(0).args[2]; + modOutput.should.be.an('Error'); + modOutput.toString().should.include('Error: No such game'); + }); + }); + + it('Should set the game inactive', () => { + const command = { + getTopic: () => Promise.resolve({ + id: 12345 + }), + getUser: () => Promise.resolve({ + username: 'tehNinja' + }), + args: ['scum'], + parent: { + ids: { + topic: 12345 + } + }, + }; + + sandbox.spy(mockGame, 'setActive'); + + return modController.endHandler(command).then(() => { + mockGame.setActive.should.have.been.calledWith(false); + }); + }); + + it('Should set the winner', () => { + const command = { + getTopic: () => Promise.resolve({ + id: 12345 + }), + getUser: () => Promise.resolve({ + username: 'tehNinja' + }), + args: ['scum'], + parent: { + ids: { + topic: 12345 + } + }, + }; + + sandbox.spy(mockGame, 'setValue'); + + return modController.endHandler(command).then(() => { + mockGame.setValue.should.have.been.calledWith('winner', 'scum'); + }); + }); + + it('Should send stats', () => { + const command = { + getTopic: () => Promise.resolve({ + id: 12345 + }), + getUser: () => Promise.resolve({ + username: 'tehNinja' + }), + args: ['scum'], + parent: { + ids: { + topic: 12345 + } + }, + }; + + const fakeData = { + some: 'keys', + are: 'included' + }; + + sandbox.stub(mockGame, 'toJSON').returns(fakeData); + + return modController.endHandler(command).then(() => { + mockGame.toJSON.called.should.be.true; + mockRequest.should.have.been.calledWith('http://mafia.sockdrawer.io:5984/games', fakeData); + }); + }); + }); }); From e63255b3c6a2bbfbca0bbfbb4b35914e12fc9a04 Mon Sep 17 00:00:00 2001 From: Yami Date: Wed, 26 Jul 2017 12:20:45 +0000 Subject: [PATCH 2/3] add output --- src/mod_controller.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mod_controller.js b/src/mod_controller.js index df3e84a..3fa3976 100644 --- a/src/mod_controller.js +++ b/src/mod_controller.js @@ -180,6 +180,10 @@ class MafiaModController { 'You are a **cop**! Each night you can investigate one person using `!target playerName in TargetGame`.\n' + '!send-rolecard TargetUsername in TargetGame\n' + '```\n'); + + forum.Commands.add('endGame', 'End the game and declare the winner (mod only)', this.endHandler.bind(this)); + forum.Commands.addExtendedHelp('endGame', 'End the game\n\n' + + 'Usage: `!endGame [winner]`'); } @@ -755,8 +759,10 @@ class MafiaModController { }) .then(() => game.setActive(false)) .then(() => game.setValue('winner', winner)) + .then(() => view.respondInThread(game.topicId, `The game is over! ${winner} has won!`)) .then(() => debug('about to send data')) .then(() => axios.post('http://mafia.sockdrawer.io:5984/games', game.toJSON())) + .then(() => debug('data sent!')) .catch((err) => { logRecoveredError('Error ending game: ' + err); view.reportError(command, 'Error ending game: ', err); From e800f8b12b4a5c781f4ee2c3c842476dac571a81 Mon Sep 17 00:00:00 2001 From: Yami Date: Wed, 26 Jul 2017 12:22:13 +0000 Subject: [PATCH 3/3] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 203aa2a..def5640 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sockmafia", - "version": "4.0.3", + "version": "4.1.3", "description": "Mafia plugin for sockbot", "main": "src/mafiabot.js", "scripts": {