diff --git a/.vscode/launch.json b/.vscode/launch.json index 111e462..2e8265b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,6 +23,21 @@ "request": "launch", "name": "Launch Program", "program": "${workspaceFolder}/index.js" + }, + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/test" + ], + "internalConsoleOptions": "openOnSessionStart" } ] } \ No newline at end of file diff --git a/bot/index.js b/bot/index.js index b53295b..d85cdb8 100644 --- a/bot/index.js +++ b/bot/index.js @@ -7,7 +7,8 @@ const config = require('../config') const prices = require('./prices') const github = require('./github') -const contractAddressString = 'Contract address:' +const winnerString = 'Winner:' +const contractAddressString = 'Contract address: ' const logger = winston.createLogger({ level: 'info', @@ -26,23 +27,31 @@ const logger = winston.createLogger({ }) function needsFunding (req) { + if (req.headers['x-github-event'] !== 'issue_comment') { + return false + } if (req.body.action !== 'edited' || !req.body.hasOwnProperty('comment')) { return false } else if (req.body.comment.user.login !== config.githubUsername) { return false } else if (!hasAddress(req)) { return false + } else if (hasWinner(req)) { + return false } return true } +function hasWinner (req) { + return req.body.comment.body.search(winnerString) !== -1 +} function hasAddress (req) { return req.body.comment.body.search(contractAddressString) !== -1 } function getAddress (req) { const commentBody = req.body.comment.body - const index = commentBody.search(contractAddressString) + 19 + const index = commentBody.search(contractAddressString) + contractAddressString.length return commentBody.substring(index, index + 42) } @@ -85,7 +94,7 @@ function info (msg) { } function error (errorMessage) { - logger.error(`[ERROR] Request processing failed: ${errorMessage}`) + logger.error(`Request processing failed: ${errorMessage}`) } async function sendTransaction (to, amount, gasPrice) { @@ -169,6 +178,7 @@ module.exports = { getAddress: getAddress, getAmount: getAmount, getGasPrice: prices.getGasPrice, + getTokenPrice: prices.getTokenPrice, sendTransaction: sendTransaction, info: info, logTransaction: logTransaction, diff --git a/test/test_bot.js b/test/test_bot.js index 6215361..bb7f903 100644 --- a/test/test_bot.js +++ b/test/test_bot.js @@ -1,19 +1,20 @@ const chai = require('chai') -const expect = require('chai').expect -const assert = require('chai').assert -const should = require('chai').should +const { expect, assert, should } = chai const config = require('../config') const bot = require('../bot') // status-open-bounty comment from https://github.com/status-im/autobounty/issues/1 const sobComment = 'Current balance: 0.000000 ETH\nTokens: SNT: 2500.00 ANT: 25.00\nContract address: 0x3645fe42b1a744ad98cc032c22472388806f86f9\nNetwork: Mainnet\n To claim this bounty sign up at https://openbounty.status.im and make sure to update your Ethereum address in My Payment Details so that the bounty is correctly allocated.\nTo fund it, send ETH or ERC20/ERC223 tokens to the contract address.' +const sobCommentWithWinner = 'Balance: 0.000000 ETH\nContract address: [0xe02fbffb3422ddb8e2227c3495f710ba4f8e0c10](https://etherscan.io/address/0xe02fbffb3422ddb8e2227c3495f710ba4f8e0c10)\nNetwork: Mainnet\nStatus: Pending maintainer confirmation\nWinner: foopang\nVisit [https://openbounty.status.im](https://openbounty.status.im) to learn more.' // Fake requests const requests = [ - { body: { action: 'created', comment: { body: 'Creating my first comment', user: { login: 'randomUser' } } } }, - { body: { action: 'edited', comment: { body: 'Editing my comment', user: { login: 'RandomUser' } } } }, - { body: { action: 'edited', comment: { body: sobComment, user: { login: 'status-open-bounty' } } } }, - { body: { action: 'created', issue: { labels: ['bounty', 'bounty-s'] }, comment: { body: sobComment, user: { login: 'status-open-bounty' } } } } + { headers: {'x-github-event': 'issue_comment'}, body: { action: 'created', comment: { body: 'Creating my first comment', user: { login: 'randomUser' } } } }, + { headers: {'x-github-event': 'issue_comment'}, body: { action: 'edited', comment: { body: 'Editing my comment', user: { login: 'RandomUser' } } } }, + { headers: {'x-github-event': 'issue_comment'}, body: { action: 'created', comment: { body: sobComment, user: { login: 'status-open-bounty' } } } }, + { headers: {'x-github-event': 'issue_comment'}, body: { action: 'edited', repository: { owner: { login: 'status-im' }, name: 'autobounty' }, issue: { labels: ['bounty', 'bounty-xl'], number: 1 }, comment: { body: sobComment, user: { login: 'status-open-bounty' } } } }, + { headers: {'x-github-event': 'issue_comment'}, body: { action: 'edited', repository: { owner: { login: 'status-im' }, name: 'autobounty' }, issue: { labels: ['bounty', 'bounty-xl'], number: 1 }, comment: { body: sobCommentWithWinner, user: { login: 'status-open-bounty' } } } }, + { headers: {'x-github-event': 'labels'}, body: { action: 'created' } } ] describe('Bot behavior', function () { @@ -30,6 +31,12 @@ describe('Bot behavior', function () { it('should return true, it is all right and we should fund', function () { assert.isTrue(bot.needsFunding(requests[3])) }) + it('should return false because issue already has a winner', function () { + assert.isFalse(bot.needsFunding(requests[4])) + }) + it('should return false because the action is not related to issue comments', function () { + assert.isFalse(bot.needsFunding(requests[5])) + }) }) describe('#getAddress', function () { @@ -39,17 +46,29 @@ describe('Bot behavior', function () { }) describe('#getAmount', function () { - it('should return the amount for the issue given the price per hour and the bounty label for this issue', (done) => { - bot.getAmount(requests[3]) - .then(function (amount) { - const label = 'bounty-s' - const tokenPrice = 0.35 - const priceInDollars = config.priceHour * config.bountyLabels[label] - expected_amount = priceInDollars / tokenPrice - assert.equal(amount, expected_amount) - done() - }) - .catch(() => { console.log('error'), done() }) + it('should return the amount for the issue given the price per hour and the bounty label for this issue', async () => { + try { + const amount = await bot.getAmount(requests[3]) + const label = 'bounty-xl' + const tokenPrice = await bot.getTokenPrice(config.token) + const priceInDollars = config.priceHour * config.bountyLabels[label] + const expectedAmount = priceInDollars / tokenPrice + assert.equal(amount, expectedAmount) + } catch (err) { + console.log(err) + } + }) + it('should return the amount for the issue given the price per hour and the bounty label for this issue, even when label issue has different case', async () => { + try { + const amount = await bot.getAmount(requests[3]) + const label = 'bounty-XL' + const tokenPrice = await bot.getTokenPrice(config.token) + const priceInDollars = config.priceHour * config.bountyLabels[label.toLowerCase()] + const expectedAmount = priceInDollars / tokenPrice + assert.equal(amount, expectedAmount) + } catch (err) { + console.log(err) + } }) }) })